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 | |
| parent | 2c77f00f1a7ecb6c8192f9c16d3b2001b254a107 (diff) | |
| download | xmake-docs-26105034da4fcce7ac883c899d781f016559310d.tar.gz xmake-docs-26105034da4fcce7ac883c899d781f016559310d.zip | |
switch to vuepress
Diffstat (limited to 'node_modules/vue/src')
190 files changed, 16753 insertions, 0 deletions
diff --git a/node_modules/vue/src/compiler/codegen/events.js b/node_modules/vue/src/compiler/codegen/events.js new file mode 100644 index 00000000..4f922246 --- /dev/null +++ b/node_modules/vue/src/compiler/codegen/events.js @@ -0,0 +1,167 @@ +/* @flow */ + +const fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/ +const simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/ + +// KeyboardEvent.keyCode aliases +const keyCodes: { [key: string]: number | Array<number> } = { + esc: 27, + tab: 9, + enter: 13, + space: 32, + up: 38, + left: 37, + right: 39, + down: 40, + 'delete': [8, 46] +} + +// KeyboardEvent.key aliases +const keyNames: { [key: string]: string | Array<string> } = { + esc: 'Escape', + tab: 'Tab', + enter: 'Enter', + space: ' ', + // #7806: IE11 uses key names without `Arrow` prefix for arrow keys. + up: ['Up', 'ArrowUp'], + left: ['Left', 'ArrowLeft'], + right: ['Right', 'ArrowRight'], + down: ['Down', 'ArrowDown'], + 'delete': ['Backspace', 'Delete'] +} + +// #4868: modifiers that prevent the execution of the listener +// need to explicitly return null so that we can determine whether to remove +// the listener for .once +const genGuard = condition => `if(${condition})return null;` + +const modifierCode: { [key: string]: string } = { + stop: '$event.stopPropagation();', + prevent: '$event.preventDefault();', + self: genGuard(`$event.target !== $event.currentTarget`), + ctrl: genGuard(`!$event.ctrlKey`), + shift: genGuard(`!$event.shiftKey`), + alt: genGuard(`!$event.altKey`), + meta: genGuard(`!$event.metaKey`), + left: genGuard(`'button' in $event && $event.button !== 0`), + middle: genGuard(`'button' in $event && $event.button !== 1`), + right: genGuard(`'button' in $event && $event.button !== 2`) +} + +export function genHandlers ( + events: ASTElementHandlers, + isNative: boolean, + warn: Function +): string { + let res = isNative ? 'nativeOn:{' : 'on:{' + for (const name in events) { + res += `"${name}":${genHandler(name, events[name])},` + } + return res.slice(0, -1) + '}' +} + +// Generate handler code with binding params on Weex +/* istanbul ignore next */ +function genWeexHandler (params: Array<any>, handlerCode: string) { + let innerHandlerCode = handlerCode + const exps = params.filter(exp => simplePathRE.test(exp) && exp !== '$event') + const bindings = exps.map(exp => ({ '@binding': exp })) + const args = exps.map((exp, i) => { + const key = `$_${i + 1}` + innerHandlerCode = innerHandlerCode.replace(exp, key) + return key + }) + args.push('$event') + return '{\n' + + `handler:function(${args.join(',')}){${innerHandlerCode}},\n` + + `params:${JSON.stringify(bindings)}\n` + + '}' +} + +function genHandler ( + name: string, + handler: ASTElementHandler | Array<ASTElementHandler> +): string { + if (!handler) { + return 'function(){}' + } + + if (Array.isArray(handler)) { + return `[${handler.map(handler => genHandler(name, handler)).join(',')}]` + } + + const isMethodPath = simplePathRE.test(handler.value) + const isFunctionExpression = fnExpRE.test(handler.value) + + if (!handler.modifiers) { + if (isMethodPath || isFunctionExpression) { + return handler.value + } + /* istanbul ignore if */ + if (__WEEX__ && handler.params) { + return genWeexHandler(handler.params, handler.value) + } + return `function($event){${handler.value}}` // inline statement + } else { + let code = '' + let genModifierCode = '' + const keys = [] + for (const key in handler.modifiers) { + if (modifierCode[key]) { + genModifierCode += modifierCode[key] + // left/right + if (keyCodes[key]) { + keys.push(key) + } + } else if (key === 'exact') { + const modifiers: ASTModifiers = (handler.modifiers: any) + genModifierCode += genGuard( + ['ctrl', 'shift', 'alt', 'meta'] + .filter(keyModifier => !modifiers[keyModifier]) + .map(keyModifier => `$event.${keyModifier}Key`) + .join('||') + ) + } else { + keys.push(key) + } + } + if (keys.length) { + code += genKeyFilter(keys) + } + // Make sure modifiers like prevent and stop get executed after key filtering + if (genModifierCode) { + code += genModifierCode + } + const handlerCode = isMethodPath + ? `return ${handler.value}($event)` + : isFunctionExpression + ? `return (${handler.value})($event)` + : handler.value + /* istanbul ignore if */ + if (__WEEX__ && handler.params) { + return genWeexHandler(handler.params, code + handlerCode) + } + return `function($event){${code}${handlerCode}}` + } +} + +function genKeyFilter (keys: Array<string>): string { + return `if(!('button' in $event)&&${keys.map(genFilterCode).join('&&')})return null;` +} + +function genFilterCode (key: string): string { + const keyVal = parseInt(key, 10) + if (keyVal) { + return `$event.keyCode!==${keyVal}` + } + const keyCode = keyCodes[key] + const keyName = keyNames[key] + return ( + `_k($event.keyCode,` + + `${JSON.stringify(key)},` + + `${JSON.stringify(keyCode)},` + + `$event.key,` + + `${JSON.stringify(keyName)}` + + `)` + ) +} diff --git a/node_modules/vue/src/compiler/codegen/index.js b/node_modules/vue/src/compiler/codegen/index.js new file mode 100644 index 00000000..1c912c4d --- /dev/null +++ b/node_modules/vue/src/compiler/codegen/index.js @@ -0,0 +1,513 @@ +/* @flow */ + +import { genHandlers } from './events' +import baseDirectives from '../directives/index' +import { camelize, no, extend } from 'shared/util' +import { baseWarn, pluckModuleFunction } from '../helpers' + +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>; + + 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) => !isReservedTag(el.tag) + this.onceId = 0 + this.staticRenderFns = [] + } +} + +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.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) { + 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 { + const data = el.plain ? undefined : 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 + state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`) + 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. ` + ) + 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.`, + 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, state.warn)},` + } + if (el.nativeEvents) { + data += `${genHandlers(el.nativeEvents, true, state.warn)},` + } + // slot target + // only for non-scoped slots + if (el.slotTarget && !el.slotScope) { + data += `slot:${el.slotTarget},` + } + // scoped slots + if (el.scopedSlots) { + data += `${genScopedSlots(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 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.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.') + } + if (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 ( + slots: { [key: string]: ASTElement }, + state: CodegenState +): string { + return `scopedSlots:_u([${ + Object.keys(slots).map(key => { + return genScopedSlot(key, slots[key], state) + }).join(',') + }])` +} + +function genScopedSlot ( + key: string, + el: ASTElement, + state: CodegenState +): string { + if (el.for && !el.forProcessed) { + return genForScopedSlot(key, el, state) + } + const fn = `function(${String(el.slotScope)}){` + + `return ${el.tag === 'template' + ? el.if + ? `${el.if}?${genChildren(el, state) || 'undefined'}:undefined` + : genChildren(el, state) || 'undefined' + : genElement(el, state) + }}` + return `{key:${key},fn:${fn}}` +} + +function genForScopedSlot ( + key: string, + el: any, + state: CodegenState +): string { + const exp = el.for + const alias = el.alias + const iterator1 = el.iterator1 ? `,${el.iterator1}` : '' + const iterator2 = el.iterator2 ? `,${el.iterator2}` : '' + el.forProcessed = true // avoid recursion + return `_l((${exp}),` + + `function(${alias}${iterator1}${iterator2}){` + + `return ${genScopedSlot(key, el, state)}` + + '})' +} + +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' + ) { + return (altGenElement || genElement)(el, state) + } + 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) + } 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.attrs.map(a => `${camelize(a.name)}:${a.value}`).join(',')}}` + 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<{ name: string, value: any }>): string { + let res = '' + for (let i = 0; i < props.length; i++) { + const prop = props[i] + /* istanbul ignore if */ + if (__WEEX__) { + res += `"${prop.name}":${generateValue(prop.value)},` + } else { + res += `"${prop.name}":${transformSpecialNewlines(prop.value)},` + } + } + return res.slice(0, -1) +} + +/* 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') +} diff --git a/node_modules/vue/src/compiler/create-compiler.js b/node_modules/vue/src/compiler/create-compiler.js new file mode 100644 index 00000000..6671dcfe --- /dev/null +++ b/node_modules/vue/src/compiler/create-compiler.js @@ -0,0 +1,55 @@ +/* @flow */ + +import { extend } from 'shared/util' +import { detectErrors } from './error-detector' +import { createCompileToFunctionFn } from './to-function' + +export function createCompilerCreator (baseCompile: Function): Function { + return function createCompiler (baseOptions: CompilerOptions) { + function compile ( + template: string, + options?: CompilerOptions + ): CompiledResult { + const finalOptions = Object.create(baseOptions) + const errors = [] + const tips = [] + finalOptions.warn = (msg, tip) => { + (tip ? tips : errors).push(msg) + } + + if (options) { + // merge custom modules + if (options.modules) { + finalOptions.modules = + (baseOptions.modules || []).concat(options.modules) + } + // merge custom directives + if (options.directives) { + finalOptions.directives = extend( + Object.create(baseOptions.directives || null), + options.directives + ) + } + // copy other options + for (const key in options) { + if (key !== 'modules' && key !== 'directives') { + finalOptions[key] = options[key] + } + } + } + + const compiled = baseCompile(template, finalOptions) + if (process.env.NODE_ENV !== 'production') { + errors.push.apply(errors, detectErrors(compiled.ast)) + } + compiled.errors = errors + compiled.tips = tips + return compiled + } + + return { + compile, + compileToFunctions: createCompileToFunctionFn(compile) + } + } +} diff --git a/node_modules/vue/src/compiler/directives/bind.js b/node_modules/vue/src/compiler/directives/bind.js new file mode 100644 index 00000000..e1035727 --- /dev/null +++ b/node_modules/vue/src/compiler/directives/bind.js @@ -0,0 +1,11 @@ +/* @flow */ + +export default function bind (el: ASTElement, dir: ASTDirective) { + el.wrapData = (code: string) => { + return `_b(${code},'${el.tag}',${dir.value},${ + dir.modifiers && dir.modifiers.prop ? 'true' : 'false' + }${ + dir.modifiers && dir.modifiers.sync ? ',true' : '' + })` + } +} diff --git a/node_modules/vue/src/compiler/directives/index.js b/node_modules/vue/src/compiler/directives/index.js new file mode 100644 index 00000000..8f4342c0 --- /dev/null +++ b/node_modules/vue/src/compiler/directives/index.js @@ -0,0 +1,11 @@ +/* @flow */ + +import on from './on' +import bind from './bind' +import { noop } from 'shared/util' + +export default { + on, + bind, + cloak: noop +} diff --git a/node_modules/vue/src/compiler/directives/model.js b/node_modules/vue/src/compiler/directives/model.js new file mode 100644 index 00000000..39bdbfda --- /dev/null +++ b/node_modules/vue/src/compiler/directives/model.js @@ -0,0 +1,148 @@ +/* @flow */ + +/** + * Cross-platform code generation for component v-model + */ +export function genComponentModel ( + el: ASTElement, + value: string, + modifiers: ?ASTModifiers +): ?boolean { + const { number, trim } = modifiers || {} + + const baseValueExpression = '$$v' + let valueExpression = baseValueExpression + if (trim) { + valueExpression = + `(typeof ${baseValueExpression} === 'string'` + + `? ${baseValueExpression}.trim()` + + `: ${baseValueExpression})` + } + if (number) { + valueExpression = `_n(${valueExpression})` + } + const assignment = genAssignmentCode(value, valueExpression) + + el.model = { + value: `(${value})`, + expression: `"${value}"`, + callback: `function (${baseValueExpression}) {${assignment}}` + } +} + +/** + * Cross-platform codegen helper for generating v-model value assignment code. + */ +export function genAssignmentCode ( + value: string, + assignment: string +): string { + const res = parseModel(value) + if (res.key === null) { + return `${value}=${assignment}` + } else { + return `$set(${res.exp}, ${res.key}, ${assignment})` + } +} + +/** + * Parse a v-model expression into a base path and a final key segment. + * Handles both dot-path and possible square brackets. + * + * Possible cases: + * + * - test + * - test[key] + * - test[test1[key]] + * - test["a"][key] + * - xxx.test[a[a].test1[key]] + * - test.xxx.a["asa"][test1[key]] + * + */ + +let len, str, chr, index, expressionPos, expressionEndPos + +type ModelParseResult = { + exp: string, + key: string | null +} + +export function parseModel (val: string): ModelParseResult { + // Fix https://github.com/vuejs/vue/pull/7730 + // allow v-model="obj.val " (trailing whitespace) + val = val.trim() + len = val.length + + if (val.indexOf('[') < 0 || val.lastIndexOf(']') < len - 1) { + index = val.lastIndexOf('.') + if (index > -1) { + return { + exp: val.slice(0, index), + key: '"' + val.slice(index + 1) + '"' + } + } else { + return { + exp: val, + key: null + } + } + } + + str = val + index = expressionPos = expressionEndPos = 0 + + while (!eof()) { + chr = next() + /* istanbul ignore if */ + if (isStringStart(chr)) { + parseString(chr) + } else if (chr === 0x5B) { + parseBracket(chr) + } + } + + return { + exp: val.slice(0, expressionPos), + key: val.slice(expressionPos + 1, expressionEndPos) + } +} + +function next (): number { + return str.charCodeAt(++index) +} + +function eof (): boolean { + return index >= len +} + +function isStringStart (chr: number): boolean { + return chr === 0x22 || chr === 0x27 +} + +function parseBracket (chr: number): void { + let inBracket = 1 + expressionPos = index + while (!eof()) { + chr = next() + if (isStringStart(chr)) { + parseString(chr) + continue + } + if (chr === 0x5B) inBracket++ + if (chr === 0x5D) inBracket-- + if (inBracket === 0) { + expressionEndPos = index + break + } + } +} + +function parseString (chr: number): void { + const stringQuote = chr + while (!eof()) { + chr = next() + if (chr === stringQuote) { + break + } + } +} diff --git a/node_modules/vue/src/compiler/directives/on.js b/node_modules/vue/src/compiler/directives/on.js new file mode 100644 index 00000000..893a678e --- /dev/null +++ b/node_modules/vue/src/compiler/directives/on.js @@ -0,0 +1,10 @@ +/* @flow */ + +import { warn } from 'core/util/index' + +export default function on (el: ASTElement, dir: ASTDirective) { + if (process.env.NODE_ENV !== 'production' && dir.modifiers) { + warn(`v-on without argument does not support modifiers.`) + } + el.wrapListeners = (code: string) => `_g(${code},${dir.value})` +} diff --git a/node_modules/vue/src/compiler/error-detector.js b/node_modules/vue/src/compiler/error-detector.js new file mode 100644 index 00000000..e729c499 --- /dev/null +++ b/node_modules/vue/src/compiler/error-detector.js @@ -0,0 +1,108 @@ +/* @flow */ + +import { dirRE, onRE } from './parser/index' + +// these keywords should not appear inside expressions, but operators like +// typeof, instanceof and in are allowed +const prohibitedKeywordRE = new RegExp('\\b' + ( + 'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' + + 'super,throw,while,yield,delete,export,import,return,switch,default,' + + 'extends,finally,continue,debugger,function,arguments' +).split(',').join('\\b|\\b') + '\\b') + +// these unary operators should not be used as property/method names +const unaryOperatorsRE = new RegExp('\\b' + ( + 'delete,typeof,void' +).split(',').join('\\s*\\([^\\)]*\\)|\\b') + '\\s*\\([^\\)]*\\)') + +// strip strings in expressions +const stripStringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g + +// detect problematic expressions in a template +export function detectErrors (ast: ?ASTNode): Array<string> { + const errors: Array<string> = [] + if (ast) { + checkNode(ast, errors) + } + return errors +} + +function checkNode (node: ASTNode, errors: Array<string>) { + if (node.type === 1) { + for (const name in node.attrsMap) { + if (dirRE.test(name)) { + const value = node.attrsMap[name] + if (value) { + if (name === 'v-for') { + checkFor(node, `v-for="${value}"`, errors) + } else if (onRE.test(name)) { + checkEvent(value, `${name}="${value}"`, errors) + } else { + checkExpression(value, `${name}="${value}"`, errors) + } + } + } + } + if (node.children) { + for (let i = 0; i < node.children.length; i++) { + checkNode(node.children[i], errors) + } + } + } else if (node.type === 2) { + checkExpression(node.expression, node.text, errors) + } +} + +function checkEvent (exp: string, text: string, errors: Array<string>) { + const stipped = exp.replace(stripStringRE, '') + const keywordMatch: any = stipped.match(unaryOperatorsRE) + if (keywordMatch && stipped.charAt(keywordMatch.index - 1) !== '$') { + errors.push( + `avoid using JavaScript unary operator as property name: ` + + `"${keywordMatch[0]}" in expression ${text.trim()}` + ) + } + checkExpression(exp, text, errors) +} + +function checkFor (node: ASTElement, text: string, errors: Array<string>) { + checkExpression(node.for || '', text, errors) + checkIdentifier(node.alias, 'v-for alias', text, errors) + checkIdentifier(node.iterator1, 'v-for iterator', text, errors) + checkIdentifier(node.iterator2, 'v-for iterator', text, errors) +} + +function checkIdentifier ( + ident: ?string, + type: string, + text: string, + errors: Array<string> +) { + if (typeof ident === 'string') { + try { + new Function(`var ${ident}=_`) + } catch (e) { + errors.push(`invalid ${type} "${ident}" in expression: ${text.trim()}`) + } + } +} + +function checkExpression (exp: string, text: string, errors: Array<string>) { + try { + new Function(`return ${exp}`) + } catch (e) { + const keywordMatch = exp.replace(stripStringRE, '').match(prohibitedKeywordRE) + if (keywordMatch) { + errors.push( + `avoid using JavaScript keyword as property name: ` + + `"${keywordMatch[0]}"\n Raw expression: ${text.trim()}` + ) + } else { + errors.push( + `invalid expression: ${e.message} in\n\n` + + ` ${exp}\n\n` + + ` Raw expression: ${text.trim()}\n` + ) + } + } +} diff --git a/node_modules/vue/src/compiler/helpers.js b/node_modules/vue/src/compiler/helpers.js new file mode 100644 index 00000000..6a7375a3 --- /dev/null +++ b/node_modules/vue/src/compiler/helpers.js @@ -0,0 +1,164 @@ +/* @flow */ + +import { emptyObject } from 'shared/util' +import { parseFilters } from './parser/filter-parser' + +export function baseWarn (msg: string) { + console.error(`[Vue compiler]: ${msg}`) +} + +export function pluckModuleFunction<F: Function> ( + modules: ?Array<Object>, + key: string +): Array<F> { + return modules + ? modules.map(m => m[key]).filter(_ => _) + : [] +} + +export function addProp (el: ASTElement, name: string, value: string) { + (el.props || (el.props = [])).push({ name, value }) + el.plain = false +} + +export function addAttr (el: ASTElement, name: string, value: any) { + (el.attrs || (el.attrs = [])).push({ name, value }) + el.plain = false +} + +// add a raw attr (use this in preTransforms) +export function addRawAttr (el: ASTElement, name: string, value: any) { + el.attrsMap[name] = value + el.attrsList.push({ name, value }) +} + +export function addDirective ( + el: ASTElement, + name: string, + rawName: string, + value: string, + arg: ?string, + modifiers: ?ASTModifiers +) { + (el.directives || (el.directives = [])).push({ name, rawName, value, arg, modifiers }) + el.plain = false +} + +export function addHandler ( + el: ASTElement, + name: string, + value: string, + modifiers: ?ASTModifiers, + important?: boolean, + warn?: Function +) { + modifiers = modifiers || emptyObject + // warn prevent and passive modifier + /* istanbul ignore if */ + if ( + process.env.NODE_ENV !== 'production' && warn && + modifiers.prevent && modifiers.passive + ) { + warn( + 'passive and prevent can\'t be used together. ' + + 'Passive handler can\'t prevent default event.' + ) + } + + // check capture modifier + if (modifiers.capture) { + delete modifiers.capture + name = '!' + name // mark the event as captured + } + if (modifiers.once) { + delete modifiers.once + name = '~' + name // mark the event as once + } + /* istanbul ignore if */ + if (modifiers.passive) { + delete modifiers.passive + name = '&' + name // mark the event as passive + } + + // normalize click.right and click.middle since they don't actually fire + // this is technically browser-specific, but at least for now browsers are + // the only target envs that have right/middle clicks. + if (name === 'click') { + if (modifiers.right) { + name = 'contextmenu' + delete modifiers.right + } else if (modifiers.middle) { + name = 'mouseup' + } + } + + let events + if (modifiers.native) { + delete modifiers.native + events = el.nativeEvents || (el.nativeEvents = {}) + } else { + events = el.events || (el.events = {}) + } + + const newHandler: any = { + value: value.trim() + } + if (modifiers !== emptyObject) { + newHandler.modifiers = modifiers + } + + const handlers = events[name] + /* istanbul ignore if */ + if (Array.isArray(handlers)) { + important ? handlers.unshift(newHandler) : handlers.push(newHandler) + } else if (handlers) { + events[name] = important ? [newHandler, handlers] : [handlers, newHandler] + } else { + events[name] = newHandler + } + + el.plain = false +} + +export function getBindingAttr ( + el: ASTElement, + name: string, + getStatic?: boolean +): ?string { + const dynamicValue = + getAndRemoveAttr(el, ':' + name) || + getAndRemoveAttr(el, 'v-bind:' + name) + if (dynamicValue != null) { + return parseFilters(dynamicValue) + } else if (getStatic !== false) { + const staticValue = getAndRemoveAttr(el, name) + if (staticValue != null) { + return JSON.stringify(staticValue) + } + } +} + +// note: this only removes the attr from the Array (attrsList) so that it +// doesn't get processed by processAttrs. +// By default it does NOT remove it from the map (attrsMap) because the map is +// needed during codegen. +export function getAndRemoveAttr ( + el: ASTElement, + name: string, + removeFromMap?: boolean +): ?string { + let val + if ((val = el.attrsMap[name]) != null) { + const list = el.attrsList + for (let i = 0, l = list.length; i < l; i++) { + if (list[i].name === name) { + list.splice(i, 1) + break + } + } + } + if (removeFromMap) { + delete el.attrsMap[name] + } + return val +} diff --git a/node_modules/vue/src/compiler/index.js b/node_modules/vue/src/compiler/index.js new file mode 100644 index 00000000..56b54fd3 --- /dev/null +++ b/node_modules/vue/src/compiler/index.js @@ -0,0 +1,25 @@ +/* @flow */ + +import { parse } from './parser/index' +import { optimize } from './optimizer' +import { generate } from './codegen/index' +import { createCompilerCreator } from './create-compiler' + +// `createCompilerCreator` allows creating compilers that use alternative +// parser/optimizer/codegen, e.g the SSR optimizing compiler. +// Here we just export a default compiler using the default parts. +export const createCompiler = createCompilerCreator(function baseCompile ( + template: string, + options: CompilerOptions +): CompiledResult { + const ast = parse(template.trim(), options) + if (options.optimize !== false) { + optimize(ast, options) + } + const code = generate(ast, options) + return { + ast, + render: code.render, + staticRenderFns: code.staticRenderFns + } +}) diff --git a/node_modules/vue/src/compiler/optimizer.js b/node_modules/vue/src/compiler/optimizer.js new file mode 100644 index 00000000..ce432035 --- /dev/null +++ b/node_modules/vue/src/compiler/optimizer.js @@ -0,0 +1,128 @@ +/* @flow */ + +import { makeMap, isBuiltInTag, cached, no } from 'shared/util' + +let isStaticKey +let isPlatformReservedTag + +const genStaticKeysCached = cached(genStaticKeys) + +/** + * Goal of the optimizer: walk the generated template AST tree + * and detect sub-trees that are purely static, i.e. parts of + * the DOM that never needs to change. + * + * Once we detect these sub-trees, we can: + * + * 1. Hoist them into constants, so that we no longer need to + * create fresh nodes for them on each re-render; + * 2. Completely skip them in the patching process. + */ +export function optimize (root: ?ASTElement, options: CompilerOptions) { + if (!root) return + isStaticKey = genStaticKeysCached(options.staticKeys || '') + isPlatformReservedTag = options.isReservedTag || no + // first pass: mark all non-static nodes. + markStatic(root) + // second pass: mark static roots. + markStaticRoots(root, false) +} + +function genStaticKeys (keys: string): Function { + return makeMap( + 'type,tag,attrsList,attrsMap,plain,parent,children,attrs' + + (keys ? ',' + keys : '') + ) +} + +function markStatic (node: ASTNode) { + node.static = isStatic(node) + if (node.type === 1) { + // do not make component slot content static. this avoids + // 1. components not able to mutate slot nodes + // 2. static slot content fails for hot-reloading + 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] + markStatic(child) + if (!child.static) { + node.static = false + } + } + if (node.ifConditions) { + for (let i = 1, l = node.ifConditions.length; i < l; i++) { + const block = node.ifConditions[i].block + markStatic(block) + if (!block.static) { + node.static = false + } + } + } + } +} + +function markStaticRoots (node: ASTNode, isInFor: boolean) { + if (node.type === 1) { + if (node.static || node.once) { + node.staticInFor = isInFor + } + // For a node to qualify as a static root, it should have children that + // are not just static text. Otherwise the cost of hoisting out will + // outweigh the benefits and it's better off to just always render it fresh. + if (node.static && node.children.length && !( + node.children.length === 1 && + node.children[0].type === 3 + )) { + node.staticRoot = true + return + } else { + node.staticRoot = false + } + if (node.children) { + for (let i = 0, l = node.children.length; i < l; i++) { + markStaticRoots(node.children[i], isInFor || !!node.for) + } + } + if (node.ifConditions) { + for (let i = 1, l = node.ifConditions.length; i < l; i++) { + markStaticRoots(node.ifConditions[i].block, isInFor) + } + } + } +} + +function isStatic (node: ASTNode): boolean { + if (node.type === 2) { // expression + return false + } + if (node.type === 3) { // text + return true + } + return !!(node.pre || ( + !node.hasBindings && // no dynamic bindings + !node.if && !node.for && // not v-if or v-for or v-else + !isBuiltInTag(node.tag) && // not a built-in + isPlatformReservedTag(node.tag) && // not a component + !isDirectChildOfTemplateFor(node) && + Object.keys(node).every(isStaticKey) + )) +} + +function isDirectChildOfTemplateFor (node: ASTElement): boolean { + while (node.parent) { + node = node.parent + if (node.tag !== 'template') { + return false + } + if (node.for) { + return true + } + } + return false +} diff --git a/node_modules/vue/src/compiler/parser/entity-decoder.js b/node_modules/vue/src/compiler/parser/entity-decoder.js new file mode 100644 index 00000000..2836294f --- /dev/null +++ b/node_modules/vue/src/compiler/parser/entity-decoder.js @@ -0,0 +1,11 @@ +/* @flow */ + +let decoder + +export default { + decode (html: string): string { + decoder = decoder || document.createElement('div') + decoder.innerHTML = html + return decoder.textContent + } +} diff --git a/node_modules/vue/src/compiler/parser/filter-parser.js b/node_modules/vue/src/compiler/parser/filter-parser.js new file mode 100644 index 00000000..10939d3f --- /dev/null +++ b/node_modules/vue/src/compiler/parser/filter-parser.js @@ -0,0 +1,97 @@ +/* @flow */ + +const validDivisionCharRE = /[\w).+\-_$\]]/ + +export function parseFilters (exp: string): string { + let inSingle = false + let inDouble = false + let inTemplateString = false + let inRegex = false + let curly = 0 + let square = 0 + let paren = 0 + let lastFilterIndex = 0 + let c, prev, i, expression, filters + + for (i = 0; i < exp.length; i++) { + prev = c + c = exp.charCodeAt(i) + if (inSingle) { + if (c === 0x27 && prev !== 0x5C) inSingle = false + } else if (inDouble) { + if (c === 0x22 && prev !== 0x5C) inDouble = false + } else if (inTemplateString) { + if (c === 0x60 && prev !== 0x5C) inTemplateString = false + } else if (inRegex) { + if (c === 0x2f && prev !== 0x5C) inRegex = false + } else if ( + c === 0x7C && // pipe + exp.charCodeAt(i + 1) !== 0x7C && + exp.charCodeAt(i - 1) !== 0x7C && + !curly && !square && !paren + ) { + if (expression === undefined) { + // first filter, end of expression + lastFilterIndex = i + 1 + expression = exp.slice(0, i).trim() + } else { + pushFilter() + } + } else { + switch (c) { + case 0x22: inDouble = true; break // " + case 0x27: inSingle = true; break // ' + case 0x60: inTemplateString = true; break // ` + case 0x28: paren++; break // ( + case 0x29: paren--; break // ) + case 0x5B: square++; break // [ + case 0x5D: square--; break // ] + case 0x7B: curly++; break // { + case 0x7D: curly--; break // } + } + if (c === 0x2f) { // / + let j = i - 1 + let p + // find first non-whitespace prev char + for (; j >= 0; j--) { + p = exp.charAt(j) + if (p !== ' ') break + } + if (!p || !validDivisionCharRE.test(p)) { + inRegex = true + } + } + } + } + + if (expression === undefined) { + expression = exp.slice(0, i).trim() + } else if (lastFilterIndex !== 0) { + pushFilter() + } + + function pushFilter () { + (filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim()) + lastFilterIndex = i + 1 + } + + if (filters) { + for (i = 0; i < filters.length; i++) { + expression = wrapFilter(expression, filters[i]) + } + } + + return expression +} + +function wrapFilter (exp: string, filter: string): string { + const i = filter.indexOf('(') + if (i < 0) { + // _f: resolveFilter + return `_f("${filter}")(${exp})` + } else { + const name = filter.slice(0, i) + const args = filter.slice(i + 1) + return `_f("${name}")(${exp}${args !== ')' ? ',' + args : args}` + } +} diff --git a/node_modules/vue/src/compiler/parser/html-parser.js b/node_modules/vue/src/compiler/parser/html-parser.js new file mode 100644 index 00000000..e2203613 --- /dev/null +++ b/node_modules/vue/src/compiler/parser/html-parser.js @@ -0,0 +1,310 @@ +/** + * Not type-checking this file because it's mostly vendor code. + */ + +/*! + * HTML Parser By John Resig (ejohn.org) + * Modified by Juriy "kangax" Zaytsev + * Original code by Erik Arvidsson, Mozilla Public License + * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js + */ + +import { makeMap, no } from 'shared/util' +import { isNonPhrasingTag } from 'web/compiler/util' + +// Regular Expressions for parsing tags and attributes +const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/ +// could use https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName +// but for Vue templates we can enforce a simple charset +const ncname = '[a-zA-Z_][\\w\\-\\.]*' +const qnameCapture = `((?:${ncname}\\:)?${ncname})` +const startTagOpen = new RegExp(`^<${qnameCapture}`) +const startTagClose = /^\s*(\/?)>/ +const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`) +const doctype = /^<!DOCTYPE [^>]+>/i +// #7298: escape - to avoid being pased as HTML comment when inlined in page +const comment = /^<!\--/ +const conditionalComment = /^<!\[/ + +let IS_REGEX_CAPTURING_BROKEN = false +'x'.replace(/x(.)?/g, function (m, g) { + IS_REGEX_CAPTURING_BROKEN = g === '' +}) + +// Special Elements (can contain anything) +export const isPlainTextElement = makeMap('script,style,textarea', true) +const reCache = {} + +const decodingMap = { + '<': '<', + '>': '>', + '"': '"', + '&': '&', + ' ': '\n', + '	': '\t' +} +const encodedAttr = /&(?:lt|gt|quot|amp);/g +const encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#10|#9);/g + +// #5992 +const isIgnoreNewlineTag = makeMap('pre,textarea', true) +const shouldIgnoreFirstNewline = (tag, html) => tag && isIgnoreNewlineTag(tag) && html[0] === '\n' + +function decodeAttr (value, shouldDecodeNewlines) { + const re = shouldDecodeNewlines ? encodedAttrWithNewLines : encodedAttr + return value.replace(re, match => decodingMap[match]) +} + +export function parseHTML (html, options) { + const stack = [] + const expectHTML = options.expectHTML + const isUnaryTag = options.isUnaryTag || no + const canBeLeftOpenTag = options.canBeLeftOpenTag || no + let index = 0 + let last, lastTag + while (html) { + last = html + // Make sure we're not in a plaintext content element like script/style + if (!lastTag || !isPlainTextElement(lastTag)) { + let textEnd = html.indexOf('<') + if (textEnd === 0) { + // Comment: + if (comment.test(html)) { + const commentEnd = html.indexOf('-->') + + if (commentEnd >= 0) { + if (options.shouldKeepComment) { + options.comment(html.substring(4, commentEnd)) + } + advance(commentEnd + 3) + continue + } + } + + // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment + if (conditionalComment.test(html)) { + const conditionalEnd = html.indexOf(']>') + + if (conditionalEnd >= 0) { + advance(conditionalEnd + 2) + continue + } + } + + // Doctype: + const doctypeMatch = html.match(doctype) + if (doctypeMatch) { + advance(doctypeMatch[0].length) + continue + } + + // End tag: + const endTagMatch = html.match(endTag) + if (endTagMatch) { + const curIndex = index + advance(endTagMatch[0].length) + parseEndTag(endTagMatch[1], curIndex, index) + continue + } + + // Start tag: + const startTagMatch = parseStartTag() + if (startTagMatch) { + handleStartTag(startTagMatch) + if (shouldIgnoreFirstNewline(lastTag, html)) { + advance(1) + } + continue + } + } + + let text, rest, next + if (textEnd >= 0) { + rest = html.slice(textEnd) + while ( + !endTag.test(rest) && + !startTagOpen.test(rest) && + !comment.test(rest) && + !conditionalComment.test(rest) + ) { + // < in plain text, be forgiving and treat it as text + next = rest.indexOf('<', 1) + if (next < 0) break + textEnd += next + rest = html.slice(textEnd) + } + text = html.substring(0, textEnd) + advance(textEnd) + } + + if (textEnd < 0) { + text = html + html = '' + } + + if (options.chars && text) { + options.chars(text) + } + } else { + let endTagLength = 0 + const stackedTag = lastTag.toLowerCase() + const reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i')) + const rest = html.replace(reStackedTag, function (all, text, endTag) { + endTagLength = endTag.length + if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') { + text = text + .replace(/<!\--([\s\S]*?)-->/g, '$1') // #7298 + .replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1') + } + if (shouldIgnoreFirstNewline(stackedTag, text)) { + text = text.slice(1) + } + if (options.chars) { + options.chars(text) + } + return '' + }) + index += html.length - rest.length + html = rest + parseEndTag(stackedTag, index - endTagLength, index) + } + + if (html === last) { + options.chars && options.chars(html) + if (process.env.NODE_ENV !== 'production' && !stack.length && options.warn) { + options.warn(`Mal-formatted tag at end of template: "${html}"`) + } + break + } + } + + // Clean up any remaining tags + parseEndTag() + + function advance (n) { + index += n + html = html.substring(n) + } + + function parseStartTag () { + const start = html.match(startTagOpen) + if (start) { + const match = { + tagName: start[1], + attrs: [], + start: index + } + advance(start[0].length) + let end, attr + while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) { + advance(attr[0].length) + match.attrs.push(attr) + } + if (end) { + match.unarySlash = end[1] + advance(end[0].length) + match.end = index + return match + } + } + } + + function handleStartTag (match) { + const tagName = match.tagName + const unarySlash = match.unarySlash + + if (expectHTML) { + if (lastTag === 'p' && isNonPhrasingTag(tagName)) { + parseEndTag(lastTag) + } + if (canBeLeftOpenTag(tagName) && lastTag === tagName) { + parseEndTag(tagName) + } + } + + const unary = isUnaryTag(tagName) || !!unarySlash + + const l = match.attrs.length + const attrs = new Array(l) + for (let i = 0; i < l; i++) { + const args = match.attrs[i] + // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778 + if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) { + if (args[3] === '') { delete args[3] } + if (args[4] === '') { delete args[4] } + if (args[5] === '') { delete args[5] } + } + const value = args[3] || args[4] || args[5] || '' + const shouldDecodeNewlines = tagName === 'a' && args[1] === 'href' + ? options.shouldDecodeNewlinesForHref + : options.shouldDecodeNewlines + attrs[i] = { + name: args[1], + value: decodeAttr(value, shouldDecodeNewlines) + } + } + + if (!unary) { + stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs }) + lastTag = tagName + } + + if (options.start) { + options.start(tagName, attrs, unary, match.start, match.end) + } + } + + function parseEndTag (tagName, start, end) { + let pos, lowerCasedTagName + if (start == null) start = index + if (end == null) end = index + + if (tagName) { + lowerCasedTagName = tagName.toLowerCase() + } + + // Find the closest opened tag of the same type + if (tagName) { + for (pos = stack.length - 1; pos >= 0; pos--) { + if (stack[pos].lowerCasedTag === lowerCasedTagName) { + break + } + } + } else { + // If no tag name is provided, clean shop + pos = 0 + } + + if (pos >= 0) { + // Close all the open elements, up the stack + for (let i = stack.length - 1; i >= pos; i--) { + if (process.env.NODE_ENV !== 'production' && + (i > pos || !tagName) && + options.warn + ) { + options.warn( + `tag <${stack[i].tag}> has no matching end tag.` + ) + } + if (options.end) { + options.end(stack[i].tag, start, end) + } + } + + // Remove the open elements from the stack + stack.length = pos + lastTag = pos && stack[pos - 1].tag + } else if (lowerCasedTagName === 'br') { + if (options.start) { + options.start(tagName, [], true, start, end) + } + } else if (lowerCasedTagName === 'p') { + if (options.start) { + options.start(tagName, [], false, start, end) + } + if (options.end) { + options.end(tagName, start, end) + } + } + } +} diff --git a/node_modules/vue/src/compiler/parser/index.js b/node_modules/vue/src/compiler/parser/index.js new file mode 100644 index 00000000..a603b36b --- /dev/null +++ b/node_modules/vue/src/compiler/parser/index.js @@ -0,0 +1,675 @@ +/* @flow */ + +import he from 'he' +import { parseHTML } from './html-parser' +import { parseText } from './text-parser' +import { parseFilters } from './filter-parser' +import { genAssignmentCode } from '../directives/model' +import { extend, cached, no, camelize } from 'shared/util' +import { isIE, isEdge, isServerRendering } from 'core/util/env' + +import { + addProp, + addAttr, + baseWarn, + addHandler, + addDirective, + getBindingAttr, + getAndRemoveAttr, + pluckModuleFunction +} from '../helpers' + +export const onRE = /^@|^v-on:/ +export const dirRE = /^v-|^@|^:/ +export const forAliasRE = /([^]*?)\s+(?:in|of)\s+([^]*)/ +export const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/ +const stripParensRE = /^\(|\)$/g + +const argRE = /:(.*)$/ +export const bindRE = /^:|^v-bind:/ +const modifierRE = /\.[^.]+/g + +const decodeHTMLCached = cached(he.decode) + +// configurable state +export let warn: any +let delimiters +let transforms +let preTransforms +let postTransforms +let platformIsPreTag +let platformMustUseProp +let platformGetTagNamespace + +type Attr = { name: string; value: string }; + +export function createASTElement ( + tag: string, + attrs: Array<Attr>, + parent: ASTElement | void +): ASTElement { + return { + type: 1, + tag, + attrsList: attrs, + attrsMap: makeAttrsMap(attrs), + parent, + children: [] + } +} + +/** + * Convert HTML string to AST. + */ +export function parse ( + template: string, + options: CompilerOptions +): ASTElement | void { + warn = options.warn || baseWarn + + platformIsPreTag = options.isPreTag || no + platformMustUseProp = options.mustUseProp || no + platformGetTagNamespace = options.getTagNamespace || no + + transforms = pluckModuleFunction(options.modules, 'transformNode') + preTransforms = pluckModuleFunction(options.modules, 'preTransformNode') + postTransforms = pluckModuleFunction(options.modules, 'postTransformNode') + + delimiters = options.delimiters + + const stack = [] + const preserveWhitespace = options.preserveWhitespace !== false + let root + let currentParent + let inVPre = false + let inPre = false + let warned = false + + function warnOnce (msg) { + if (!warned) { + warned = true + warn(msg) + } + } + + function closeElement (element) { + // check pre state + if (element.pre) { + inVPre = false + } + if (platformIsPreTag(element.tag)) { + inPre = false + } + // apply post-transforms + for (let i = 0; i < postTransforms.length; i++) { + postTransforms[i](element, options) + } + } + + parseHTML(template, { + warn, + expectHTML: options.expectHTML, + isUnaryTag: options.isUnaryTag, + canBeLeftOpenTag: options.canBeLeftOpenTag, + shouldDecodeNewlines: options.shouldDecodeNewlines, + shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref, + shouldKeepComment: options.comments, + start (tag, attrs, unary) { + // check namespace. + // inherit parent ns if there is one + const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag) + + // handle IE svg bug + /* istanbul ignore if */ + if (isIE && ns === 'svg') { + attrs = guardIESVGBug(attrs) + } + + let element: ASTElement = createASTElement(tag, attrs, currentParent) + if (ns) { + element.ns = ns + } + + if (isForbiddenTag(element) && !isServerRendering()) { + element.forbidden = true + process.env.NODE_ENV !== 'production' && warn( + 'Templates should only be responsible for mapping the state to the ' + + 'UI. Avoid placing tags with side-effects in your templates, such as ' + + `<${tag}>` + ', as they will not be parsed.' + ) + } + + // apply pre-transforms + for (let i = 0; i < preTransforms.length; i++) { + element = preTransforms[i](element, options) || element + } + + if (!inVPre) { + processPre(element) + if (element.pre) { + inVPre = true + } + } + if (platformIsPreTag(element.tag)) { + inPre = true + } + if (inVPre) { + processRawAttrs(element) + } else if (!element.processed) { + // structural directives + processFor(element) + processIf(element) + processOnce(element) + // element-scope stuff + processElement(element, options) + } + + function checkRootConstraints (el) { + if (process.env.NODE_ENV !== 'production') { + if (el.tag === 'slot' || el.tag === 'template') { + warnOnce( + `Cannot use <${el.tag}> as component root element because it may ` + + 'contain multiple nodes.' + ) + } + if (el.attrsMap.hasOwnProperty('v-for')) { + warnOnce( + 'Cannot use v-for on stateful component root element because ' + + 'it renders multiple elements.' + ) + } + } + } + + // tree management + if (!root) { + root = element + checkRootConstraints(root) + } else if (!stack.length) { + // allow root elements with v-if, v-else-if and v-else + if (root.if && (element.elseif || element.else)) { + checkRootConstraints(element) + addIfCondition(root, { + exp: element.elseif, + block: element + }) + } else if (process.env.NODE_ENV !== 'production') { + warnOnce( + `Component template should contain exactly one root element. ` + + `If you are using v-if on multiple elements, ` + + `use v-else-if to chain them instead.` + ) + } + } + if (currentParent && !element.forbidden) { + if (element.elseif || element.else) { + processIfConditions(element, currentParent) + } else if (element.slotScope) { // scoped slot + currentParent.plain = false + const name = element.slotTarget || '"default"' + ;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element + } else { + currentParent.children.push(element) + element.parent = currentParent + } + } + if (!unary) { + currentParent = element + stack.push(element) + } else { + closeElement(element) + } + }, + + end () { + // remove trailing whitespace + const element = stack[stack.length - 1] + const lastNode = element.children[element.children.length - 1] + if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) { + element.children.pop() + } + // pop stack + stack.length -= 1 + currentParent = stack[stack.length - 1] + closeElement(element) + }, + + chars (text: string) { + if (!currentParent) { + if (process.env.NODE_ENV !== 'production') { + if (text === template) { + warnOnce( + 'Component template requires a root element, rather than just text.' + ) + } else if ((text = text.trim())) { + warnOnce( + `text "${text}" outside root element will be ignored.` + ) + } + } + return + } + // IE textarea placeholder bug + /* istanbul ignore if */ + if (isIE && + currentParent.tag === 'textarea' && + currentParent.attrsMap.placeholder === text + ) { + return + } + const children = currentParent.children + text = inPre || text.trim() + ? isTextTag(currentParent) ? text : decodeHTMLCached(text) + // only preserve whitespace if its not right after a starting tag + : preserveWhitespace && children.length ? ' ' : '' + if (text) { + let res + if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) { + children.push({ + type: 2, + expression: res.expression, + tokens: res.tokens, + text + }) + } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') { + children.push({ + type: 3, + text + }) + } + } + }, + comment (text: string) { + currentParent.children.push({ + type: 3, + text, + isComment: true + }) + } + }) + return root +} + +function processPre (el) { + if (getAndRemoveAttr(el, 'v-pre') != null) { + el.pre = true + } +} + +function processRawAttrs (el) { + const l = el.attrsList.length + if (l) { + const attrs = el.attrs = new Array(l) + for (let i = 0; i < l; i++) { + attrs[i] = { + name: el.attrsList[i].name, + value: JSON.stringify(el.attrsList[i].value) + } + } + } else if (!el.pre) { + // non root node in pre blocks with no attributes + el.plain = true + } +} + +export function processElement (element: ASTElement, options: CompilerOptions) { + processKey(element) + + // determine whether this is a plain element after + // removing structural attributes + element.plain = !element.key && !element.attrsList.length + + processRef(element) + processSlot(element) + processComponent(element) + for (let i = 0; i < transforms.length; i++) { + element = transforms[i](element, options) || element + } + processAttrs(element) +} + +function processKey (el) { + const exp = getBindingAttr(el, 'key') + if (exp) { + if (process.env.NODE_ENV !== 'production' && el.tag === 'template') { + warn(`<template> cannot be keyed. Place the key on real elements instead.`) + } + el.key = exp + } +} + +function processRef (el) { + const ref = getBindingAttr(el, 'ref') + if (ref) { + el.ref = ref + el.refInFor = checkInFor(el) + } +} + +export function processFor (el: ASTElement) { + let exp + if ((exp = getAndRemoveAttr(el, 'v-for'))) { + const res = parseFor(exp) + if (res) { + extend(el, res) + } else if (process.env.NODE_ENV !== 'production') { + warn( + `Invalid v-for expression: ${exp}` + ) + } + } +} + +type ForParseResult = { + for: string; + alias: string; + iterator1?: string; + iterator2?: string; +}; + +export function parseFor (exp: string): ?ForParseResult { + const inMatch = exp.match(forAliasRE) + if (!inMatch) return + const res = {} + res.for = inMatch[2].trim() + const alias = inMatch[1].trim().replace(stripParensRE, '') + const iteratorMatch = alias.match(forIteratorRE) + if (iteratorMatch) { + res.alias = alias.replace(forIteratorRE, '') + res.iterator1 = iteratorMatch[1].trim() + if (iteratorMatch[2]) { + res.iterator2 = iteratorMatch[2].trim() + } + } else { + res.alias = alias + } + return res +} + +function processIf (el) { + const exp = getAndRemoveAttr(el, 'v-if') + if (exp) { + el.if = exp + addIfCondition(el, { + exp: exp, + block: el + }) + } else { + if (getAndRemoveAttr(el, 'v-else') != null) { + el.else = true + } + const elseif = getAndRemoveAttr(el, 'v-else-if') + if (elseif) { + el.elseif = elseif + } + } +} + +function processIfConditions (el, parent) { + const prev = findPrevElement(parent.children) + if (prev && prev.if) { + addIfCondition(prev, { + exp: el.elseif, + block: el + }) + } else if (process.env.NODE_ENV !== 'production') { + warn( + `v-${el.elseif ? ('else-if="' + el.elseif + '"') : 'else'} ` + + `used on element <${el.tag}> without corresponding v-if.` + ) + } +} + +function findPrevElement (children: Array<any>): ASTElement | void { + let i = children.length + while (i--) { + if (children[i].type === 1) { + return children[i] + } else { + if (process.env.NODE_ENV !== 'production' && children[i].text !== ' ') { + warn( + `text "${children[i].text.trim()}" between v-if and v-else(-if) ` + + `will be ignored.` + ) + } + children.pop() + } + } +} + +export function addIfCondition (el: ASTElement, condition: ASTIfCondition) { + if (!el.ifConditions) { + el.ifConditions = [] + } + el.ifConditions.push(condition) +} + +function processOnce (el) { + const once = getAndRemoveAttr(el, 'v-once') + if (once != null) { + el.once = true + } +} + +function processSlot (el) { + if (el.tag === 'slot') { + el.slotName = getBindingAttr(el, 'name') + if (process.env.NODE_ENV !== 'production' && el.key) { + warn( + `\`key\` does not work on <slot> because slots are abstract outlets ` + + `and can possibly expand into multiple elements. ` + + `Use the key on a wrapping element instead.` + ) + } + } else { + let slotScope + if (el.tag === 'template') { + slotScope = getAndRemoveAttr(el, 'scope') + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && slotScope) { + warn( + `the "scope" attribute for scoped slots have been deprecated and ` + + `replaced by "slot-scope" since 2.5. The new "slot-scope" attribute ` + + `can also be used on plain elements in addition to <template> to ` + + `denote scoped slots.`, + true + ) + } + el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope') + } else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) { + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && el.attrsMap['v-for']) { + warn( + `Ambiguous combined usage of slot-scope and v-for on <${el.tag}> ` + + `(v-for takes higher priority). Use a wrapper <template> for the ` + + `scoped slot to make it clearer.`, + true + ) + } + el.slotScope = slotScope + } + const slotTarget = getBindingAttr(el, 'slot') + if (slotTarget) { + el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget + // preserve slot as an attribute for native shadow DOM compat + // only for non-scoped slots. + if (el.tag !== 'template' && !el.slotScope) { + addAttr(el, 'slot', slotTarget) + } + } + } +} + +function processComponent (el) { + let binding + if ((binding = getBindingAttr(el, 'is'))) { + el.component = binding + } + if (getAndRemoveAttr(el, 'inline-template') != null) { + el.inlineTemplate = true + } +} + +function processAttrs (el) { + const list = el.attrsList + let i, l, name, rawName, value, modifiers, isProp + for (i = 0, l = list.length; i < l; i++) { + name = rawName = list[i].name + value = list[i].value + if (dirRE.test(name)) { + // mark element as dynamic + el.hasBindings = true + // modifiers + modifiers = parseModifiers(name) + if (modifiers) { + name = name.replace(modifierRE, '') + } + if (bindRE.test(name)) { // v-bind + name = name.replace(bindRE, '') + value = parseFilters(value) + isProp = false + if (modifiers) { + if (modifiers.prop) { + isProp = true + name = camelize(name) + if (name === 'innerHtml') name = 'innerHTML' + } + if (modifiers.camel) { + name = camelize(name) + } + if (modifiers.sync) { + addHandler( + el, + `update:${camelize(name)}`, + genAssignmentCode(value, `$event`) + ) + } + } + if (isProp || ( + !el.component && platformMustUseProp(el.tag, el.attrsMap.type, name) + )) { + addProp(el, name, value) + } else { + addAttr(el, name, value) + } + } else if (onRE.test(name)) { // v-on + name = name.replace(onRE, '') + addHandler(el, name, value, modifiers, false, warn) + } else { // normal directives + name = name.replace(dirRE, '') + // parse arg + const argMatch = name.match(argRE) + const arg = argMatch && argMatch[1] + if (arg) { + name = name.slice(0, -(arg.length + 1)) + } + addDirective(el, name, rawName, value, arg, modifiers) + if (process.env.NODE_ENV !== 'production' && name === 'model') { + checkForAliasModel(el, value) + } + } + } else { + // literal attribute + if (process.env.NODE_ENV !== 'production') { + const res = parseText(value, delimiters) + if (res) { + warn( + `${name}="${value}": ` + + 'Interpolation inside attributes has been removed. ' + + 'Use v-bind or the colon shorthand instead. For example, ' + + 'instead of <div id="{{ val }}">, use <div :id="val">.' + ) + } + } + addAttr(el, name, JSON.stringify(value)) + // #6887 firefox doesn't update muted state if set via attribute + // even immediately after element creation + if (!el.component && + name === 'muted' && + platformMustUseProp(el.tag, el.attrsMap.type, name)) { + addProp(el, name, 'true') + } + } + } +} + +function checkInFor (el: ASTElement): boolean { + let parent = el + while (parent) { + if (parent.for !== undefined) { + return true + } + parent = parent.parent + } + return false +} + +function parseModifiers (name: string): Object | void { + const match = name.match(modifierRE) + if (match) { + const ret = {} + match.forEach(m => { ret[m.slice(1)] = true }) + return ret + } +} + +function makeAttrsMap (attrs: Array<Object>): Object { + const map = {} + for (let i = 0, l = attrs.length; i < l; i++) { + if ( + process.env.NODE_ENV !== 'production' && + map[attrs[i].name] && !isIE && !isEdge + ) { + warn('duplicate attribute: ' + attrs[i].name) + } + map[attrs[i].name] = attrs[i].value + } + return map +} + +// for script (e.g. type="x/template") or style, do not decode content +function isTextTag (el): boolean { + return el.tag === 'script' || el.tag === 'style' +} + +function isForbiddenTag (el): boolean { + return ( + el.tag === 'style' || + (el.tag === 'script' && ( + !el.attrsMap.type || + el.attrsMap.type === 'text/javascript' + )) + ) +} + +const ieNSBug = /^xmlns:NS\d+/ +const ieNSPrefix = /^NS\d+:/ + +/* istanbul ignore next */ +function guardIESVGBug (attrs) { + const res = [] + for (let i = 0; i < attrs.length; i++) { + const attr = attrs[i] + if (!ieNSBug.test(attr.name)) { + attr.name = attr.name.replace(ieNSPrefix, '') + res.push(attr) + } + } + return res +} + +function checkForAliasModel (el, value) { + let _el = el + while (_el) { + if (_el.for && _el.alias === value) { + warn( + `<${el.tag} v-model="${value}">: ` + + `You are binding v-model directly to a v-for iteration alias. ` + + `This will not be able to modify the v-for source array because ` + + `writing to the alias is like modifying a function local variable. ` + + `Consider using an array of objects and use v-model on an object property instead.` + ) + } + _el = _el.parent + } +} diff --git a/node_modules/vue/src/compiler/parser/text-parser.js b/node_modules/vue/src/compiler/parser/text-parser.js new file mode 100644 index 00000000..284fca33 --- /dev/null +++ b/node_modules/vue/src/compiler/parser/text-parser.js @@ -0,0 +1,53 @@ +/* @flow */ + +import { cached } from 'shared/util' +import { parseFilters } from './filter-parser' + +const defaultTagRE = /\{\{((?:.|\n)+?)\}\}/g +const regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g + +const buildRegex = cached(delimiters => { + const open = delimiters[0].replace(regexEscapeRE, '\\$&') + const close = delimiters[1].replace(regexEscapeRE, '\\$&') + return new RegExp(open + '((?:.|\\n)+?)' + close, 'g') +}) + +type TextParseResult = { + expression: string, + tokens: Array<string | { '@binding': string }> +} + +export function parseText ( + text: string, + delimiters?: [string, string] +): TextParseResult | void { + const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE + if (!tagRE.test(text)) { + return + } + const tokens = [] + const rawTokens = [] + let lastIndex = tagRE.lastIndex = 0 + let match, index, tokenValue + while ((match = tagRE.exec(text))) { + index = match.index + // push text token + if (index > lastIndex) { + rawTokens.push(tokenValue = text.slice(lastIndex, index)) + tokens.push(JSON.stringify(tokenValue)) + } + // tag token + const exp = parseFilters(match[1].trim()) + tokens.push(`_s(${exp})`) + rawTokens.push({ '@binding': exp }) + lastIndex = index + match[0].length + } + if (lastIndex < text.length) { + rawTokens.push(tokenValue = text.slice(lastIndex)) + tokens.push(JSON.stringify(tokenValue)) + } + return { + expression: tokens.join('+'), + tokens: rawTokens + } +} diff --git a/node_modules/vue/src/compiler/to-function.js b/node_modules/vue/src/compiler/to-function.js new file mode 100644 index 00000000..7fe0f13f --- /dev/null +++ b/node_modules/vue/src/compiler/to-function.js @@ -0,0 +1,99 @@ +/* @flow */ + +import { noop, extend } from 'shared/util' +import { warn as baseWarn, tip } from 'core/util/debug' + +type CompiledFunctionResult = { + render: Function; + staticRenderFns: Array<Function>; +}; + +function createFunction (code, errors) { + try { + return new Function(code) + } catch (err) { + errors.push({ err, code }) + return noop + } +} + +export function createCompileToFunctionFn (compile: Function): Function { + const cache = Object.create(null) + + return function compileToFunctions ( + template: string, + options?: CompilerOptions, + vm?: Component + ): CompiledFunctionResult { + options = extend({}, options) + const warn = options.warn || baseWarn + delete options.warn + + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production') { + // detect possible CSP restriction + try { + new Function('return 1') + } catch (e) { + if (e.toString().match(/unsafe-eval|CSP/)) { + warn( + 'It seems you are using the standalone build of Vue.js in an ' + + 'environment with Content Security Policy that prohibits unsafe-eval. ' + + 'The template compiler cannot work in this environment. Consider ' + + 'relaxing the policy to allow unsafe-eval or pre-compiling your ' + + 'templates into render functions.' + ) + } + } + } + + // check cache + const key = options.delimiters + ? String(options.delimiters) + template + : template + if (cache[key]) { + return cache[key] + } + + // compile + const compiled = compile(template, options) + + // check compilation errors/tips + if (process.env.NODE_ENV !== 'production') { + if (compiled.errors && compiled.errors.length) { + warn( + `Error compiling template:\n\n${template}\n\n` + + compiled.errors.map(e => `- ${e}`).join('\n') + '\n', + vm + ) + } + if (compiled.tips && compiled.tips.length) { + compiled.tips.forEach(msg => tip(msg, vm)) + } + } + + // turn code into functions + const res = {} + const fnGenErrors = [] + res.render = createFunction(compiled.render, fnGenErrors) + res.staticRenderFns = compiled.staticRenderFns.map(code => { + return createFunction(code, fnGenErrors) + }) + + // check function generation errors. + // this should only happen if there is a bug in the compiler itself. + // mostly for codegen development use + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production') { + if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) { + warn( + `Failed to generate render function:\n\n` + + fnGenErrors.map(({ err, code }) => `${err.toString()} in\n\n${code}\n`).join('\n'), + vm + ) + } + } + + return (cache[key] = res) + } +} diff --git a/node_modules/vue/src/core/components/index.js b/node_modules/vue/src/core/components/index.js new file mode 100644 index 00000000..5461c763 --- /dev/null +++ b/node_modules/vue/src/core/components/index.js @@ -0,0 +1,5 @@ +import KeepAlive from './keep-alive' + +export default { + KeepAlive +} diff --git a/node_modules/vue/src/core/components/keep-alive.js b/node_modules/vue/src/core/components/keep-alive.js new file mode 100644 index 00000000..fb4cf1e8 --- /dev/null +++ b/node_modules/vue/src/core/components/keep-alive.js @@ -0,0 +1,124 @@ +/* @flow */ + +import { isRegExp, remove } from 'shared/util' +import { getFirstComponentChild } from 'core/vdom/helpers/index' + +type VNodeCache = { [key: string]: ?VNode }; + +function getComponentName (opts: ?VNodeComponentOptions): ?string { + return opts && (opts.Ctor.options.name || opts.tag) +} + +function matches (pattern: string | RegExp | Array<string>, name: string): boolean { + if (Array.isArray(pattern)) { + return pattern.indexOf(name) > -1 + } else if (typeof pattern === 'string') { + return pattern.split(',').indexOf(name) > -1 + } else if (isRegExp(pattern)) { + return pattern.test(name) + } + /* istanbul ignore next */ + return false +} + +function pruneCache (keepAliveInstance: any, filter: Function) { + const { cache, keys, _vnode } = keepAliveInstance + for (const key in cache) { + const cachedNode: ?VNode = cache[key] + if (cachedNode) { + const name: ?string = getComponentName(cachedNode.componentOptions) + if (name && !filter(name)) { + pruneCacheEntry(cache, key, keys, _vnode) + } + } + } +} + +function pruneCacheEntry ( + cache: VNodeCache, + key: string, + keys: Array<string>, + current?: VNode +) { + const cached = cache[key] + if (cached && (!current || cached.tag !== current.tag)) { + cached.componentInstance.$destroy() + } + cache[key] = null + remove(keys, key) +} + +const patternTypes: Array<Function> = [String, RegExp, Array] + +export default { + name: 'keep-alive', + abstract: true, + + props: { + include: patternTypes, + exclude: patternTypes, + max: [String, Number] + }, + + created () { + this.cache = Object.create(null) + this.keys = [] + }, + + destroyed () { + for (const key in this.cache) { + pruneCacheEntry(this.cache, key, this.keys) + } + }, + + mounted () { + this.$watch('include', val => { + pruneCache(this, name => matches(val, name)) + }) + this.$watch('exclude', val => { + pruneCache(this, name => !matches(val, name)) + }) + }, + + render () { + const slot = this.$slots.default + const vnode: VNode = getFirstComponentChild(slot) + const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions + if (componentOptions) { + // check pattern + const name: ?string = getComponentName(componentOptions) + const { include, exclude } = this + if ( + // not included + (include && (!name || !matches(include, name))) || + // excluded + (exclude && name && matches(exclude, name)) + ) { + return vnode + } + + const { cache, keys } = this + const key: ?string = vnode.key == null + // same constructor may get registered as different local components + // so cid alone is not enough (#3269) + ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') + : vnode.key + if (cache[key]) { + vnode.componentInstance = cache[key].componentInstance + // make current key freshest + remove(keys, key) + keys.push(key) + } else { + cache[key] = vnode + keys.push(key) + // prune oldest entry + if (this.max && keys.length > parseInt(this.max)) { + pruneCacheEntry(cache, keys[0], keys, this._vnode) + } + } + + vnode.data.keepAlive = true + } + return vnode || (slot && slot[0]) + } +} diff --git a/node_modules/vue/src/core/config.js b/node_modules/vue/src/core/config.js new file mode 100644 index 00000000..f25f09d1 --- /dev/null +++ b/node_modules/vue/src/core/config.js @@ -0,0 +1,121 @@ +/* @flow */ + +import { + no, + noop, + identity +} from 'shared/util' + +import { LIFECYCLE_HOOKS } from 'shared/constants' + +export type Config = { + // user + optionMergeStrategies: { [key: string]: Function }; + silent: boolean; + productionTip: boolean; + performance: boolean; + devtools: boolean; + errorHandler: ?(err: Error, vm: Component, info: string) => void; + warnHandler: ?(msg: string, vm: Component, trace: string) => void; + ignoredElements: Array<string | RegExp>; + keyCodes: { [key: string]: number | Array<number> }; + + // platform + isReservedTag: (x?: string) => boolean; + isReservedAttr: (x?: string) => boolean; + parsePlatformTagName: (x: string) => string; + isUnknownElement: (x?: string) => boolean; + getTagNamespace: (x?: string) => string | void; + mustUseProp: (tag: string, type: ?string, name: string) => boolean; + + // legacy + _lifecycleHooks: Array<string>; +}; + +export default ({ + /** + * Option merge strategies (used in core/util/options) + */ + // $flow-disable-line + optionMergeStrategies: Object.create(null), + + /** + * Whether to suppress warnings. + */ + silent: false, + + /** + * Show production mode tip message on boot? + */ + productionTip: process.env.NODE_ENV !== 'production', + + /** + * Whether to enable devtools + */ + devtools: process.env.NODE_ENV !== 'production', + + /** + * Whether to record perf + */ + performance: false, + + /** + * Error handler for watcher errors + */ + errorHandler: null, + + /** + * Warn handler for watcher warns + */ + warnHandler: null, + + /** + * Ignore certain custom elements + */ + ignoredElements: [], + + /** + * Custom user key aliases for v-on + */ + // $flow-disable-line + keyCodes: Object.create(null), + + /** + * Check if a tag is reserved so that it cannot be registered as a + * component. This is platform-dependent and may be overwritten. + */ + isReservedTag: no, + + /** + * Check if an attribute is reserved so that it cannot be used as a component + * prop. This is platform-dependent and may be overwritten. + */ + isReservedAttr: no, + + /** + * Check if a tag is an unknown element. + * Platform-dependent. + */ + isUnknownElement: no, + + /** + * Get the namespace of an element + */ + getTagNamespace: noop, + + /** + * Parse the real tag name for the specific platform. + */ + parsePlatformTagName: identity, + + /** + * Check if an attribute must be bound using property, e.g. value + * Platform-dependent. + */ + mustUseProp: no, + + /** + * Exposed for legacy reasons + */ + _lifecycleHooks: LIFECYCLE_HOOKS +}: Config) diff --git a/node_modules/vue/src/core/global-api/assets.js b/node_modules/vue/src/core/global-api/assets.js new file mode 100644 index 00000000..d975a6a5 --- /dev/null +++ b/node_modules/vue/src/core/global-api/assets.js @@ -0,0 +1,34 @@ +/* @flow */ + +import { ASSET_TYPES } from 'shared/constants' +import { isPlainObject, validateComponentName } from '../util/index' + +export function initAssetRegisters (Vue: GlobalAPI) { + /** + * Create asset registration methods. + */ + ASSET_TYPES.forEach(type => { + Vue[type] = function ( + id: string, + definition: Function | Object + ): Function | Object | void { + if (!definition) { + return this.options[type + 's'][id] + } else { + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && type === 'component') { + validateComponentName(id) + } + if (type === 'component' && isPlainObject(definition)) { + definition.name = definition.name || id + definition = this.options._base.extend(definition) + } + if (type === 'directive' && typeof definition === 'function') { + definition = { bind: definition, update: definition } + } + this.options[type + 's'][id] = definition + return definition + } + } + }) +} diff --git a/node_modules/vue/src/core/global-api/extend.js b/node_modules/vue/src/core/global-api/extend.js new file mode 100644 index 00000000..c1bbda3f --- /dev/null +++ b/node_modules/vue/src/core/global-api/extend.js @@ -0,0 +1,95 @@ +/* @flow */ + +import { ASSET_TYPES } from 'shared/constants' +import { defineComputed, proxy } from '../instance/state' +import { extend, mergeOptions, validateComponentName } from '../util/index' + +export function initExtend (Vue: GlobalAPI) { + /** + * Each instance constructor, including Vue, has a unique + * cid. This enables us to create wrapped "child + * constructors" for prototypal inheritance and cache them. + */ + Vue.cid = 0 + let cid = 1 + + /** + * Class inheritance + */ + Vue.extend = function (extendOptions: Object): Function { + extendOptions = extendOptions || {} + const Super = this + const SuperId = Super.cid + const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}) + if (cachedCtors[SuperId]) { + return cachedCtors[SuperId] + } + + const name = extendOptions.name || Super.options.name + if (process.env.NODE_ENV !== 'production' && name) { + validateComponentName(name) + } + + const Sub = function VueComponent (options) { + this._init(options) + } + Sub.prototype = Object.create(Super.prototype) + Sub.prototype.constructor = Sub + Sub.cid = cid++ + Sub.options = mergeOptions( + Super.options, + extendOptions + ) + Sub['super'] = Super + + // For props and computed properties, we define the proxy getters on + // the Vue instances at extension time, on the extended prototype. This + // avoids Object.defineProperty calls for each instance created. + if (Sub.options.props) { + initProps(Sub) + } + if (Sub.options.computed) { + initComputed(Sub) + } + + // allow further extension/mixin/plugin usage + Sub.extend = Super.extend + Sub.mixin = Super.mixin + Sub.use = Super.use + + // create asset registers, so extended classes + // can have their private assets too. + ASSET_TYPES.forEach(function (type) { + Sub[type] = Super[type] + }) + // enable recursive self-lookup + if (name) { + Sub.options.components[name] = Sub + } + + // keep a reference to the super options at extension time. + // later at instantiation we can check if Super's options have + // been updated. + Sub.superOptions = Super.options + Sub.extendOptions = extendOptions + Sub.sealedOptions = extend({}, Sub.options) + + // cache constructor + cachedCtors[SuperId] = Sub + return Sub + } +} + +function initProps (Comp) { + const props = Comp.options.props + for (const key in props) { + proxy(Comp.prototype, `_props`, key) + } +} + +function initComputed (Comp) { + const computed = Comp.options.computed + for (const key in computed) { + defineComputed(Comp.prototype, key, computed[key]) + } +} diff --git a/node_modules/vue/src/core/global-api/index.js b/node_modules/vue/src/core/global-api/index.js new file mode 100644 index 00000000..03ffa0fb --- /dev/null +++ b/node_modules/vue/src/core/global-api/index.js @@ -0,0 +1,62 @@ +/* @flow */ + +import config from '../config' +import { initUse } from './use' +import { initMixin } from './mixin' +import { initExtend } from './extend' +import { initAssetRegisters } from './assets' +import { set, del } from '../observer/index' +import { ASSET_TYPES } from 'shared/constants' +import builtInComponents from '../components/index' + +import { + warn, + extend, + nextTick, + mergeOptions, + defineReactive +} from '../util/index' + +export function initGlobalAPI (Vue: GlobalAPI) { + // config + const configDef = {} + configDef.get = () => config + if (process.env.NODE_ENV !== 'production') { + configDef.set = () => { + warn( + 'Do not replace the Vue.config object, set individual fields instead.' + ) + } + } + Object.defineProperty(Vue, 'config', configDef) + + // exposed util methods. + // NOTE: these are not considered part of the public API - avoid relying on + // them unless you are aware of the risk. + Vue.util = { + warn, + extend, + mergeOptions, + defineReactive + } + + Vue.set = set + Vue.delete = del + Vue.nextTick = nextTick + + Vue.options = Object.create(null) + ASSET_TYPES.forEach(type => { + Vue.options[type + 's'] = Object.create(null) + }) + + // this is used to identify the "base" constructor to extend all plain-object + // components with in Weex's multi-instance scenarios. + Vue.options._base = Vue + + extend(Vue.options.components, builtInComponents) + + initUse(Vue) + initMixin(Vue) + initExtend(Vue) + initAssetRegisters(Vue) +} diff --git a/node_modules/vue/src/core/global-api/mixin.js b/node_modules/vue/src/core/global-api/mixin.js new file mode 100644 index 00000000..36017a32 --- /dev/null +++ b/node_modules/vue/src/core/global-api/mixin.js @@ -0,0 +1,10 @@ +/* @flow */ + +import { mergeOptions } from '../util/index' + +export function initMixin (Vue: GlobalAPI) { + Vue.mixin = function (mixin: Object) { + this.options = mergeOptions(this.options, mixin) + return this + } +} diff --git a/node_modules/vue/src/core/global-api/use.js b/node_modules/vue/src/core/global-api/use.js new file mode 100644 index 00000000..a5b69988 --- /dev/null +++ b/node_modules/vue/src/core/global-api/use.js @@ -0,0 +1,23 @@ +/* @flow */ + +import { toArray } from '../util/index' + +export function initUse (Vue: GlobalAPI) { + Vue.use = function (plugin: Function | Object) { + const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) + if (installedPlugins.indexOf(plugin) > -1) { + return this + } + + // additional parameters + const args = toArray(arguments, 1) + args.unshift(this) + if (typeof plugin.install === 'function') { + plugin.install.apply(plugin, args) + } else if (typeof plugin === 'function') { + plugin.apply(null, args) + } + installedPlugins.push(plugin) + return this + } +} diff --git a/node_modules/vue/src/core/index.js b/node_modules/vue/src/core/index.js new file mode 100644 index 00000000..daf6203b --- /dev/null +++ b/node_modules/vue/src/core/index.js @@ -0,0 +1,26 @@ +import Vue from './instance/index' +import { initGlobalAPI } from './global-api/index' +import { isServerRendering } from 'core/util/env' +import { FunctionalRenderContext } from 'core/vdom/create-functional-component' + +initGlobalAPI(Vue) + +Object.defineProperty(Vue.prototype, '$isServer', { + get: isServerRendering +}) + +Object.defineProperty(Vue.prototype, '$ssrContext', { + get () { + /* istanbul ignore next */ + return this.$vnode && this.$vnode.ssrContext + } +}) + +// expose FunctionalRenderContext for ssr runtime helper installation +Object.defineProperty(Vue, 'FunctionalRenderContext', { + value: FunctionalRenderContext +}) + +Vue.version = '__VERSION__' + +export default Vue diff --git a/node_modules/vue/src/core/instance/events.js b/node_modules/vue/src/core/instance/events.js new file mode 100644 index 00000000..58779996 --- /dev/null +++ b/node_modules/vue/src/core/instance/events.js @@ -0,0 +1,142 @@ +/* @flow */ + +import { + tip, + toArray, + hyphenate, + handleError, + formatComponentName +} from '../util/index' +import { updateListeners } from '../vdom/helpers/index' + +export function initEvents (vm: Component) { + vm._events = Object.create(null) + vm._hasHookEvent = false + // init parent attached events + const listeners = vm.$options._parentListeners + if (listeners) { + updateComponentListeners(vm, listeners) + } +} + +let target: any + +function add (event, fn, once) { + if (once) { + target.$once(event, fn) + } else { + target.$on(event, fn) + } +} + +function remove (event, fn) { + target.$off(event, fn) +} + +export function updateComponentListeners ( + vm: Component, + listeners: Object, + oldListeners: ?Object +) { + target = vm + updateListeners(listeners, oldListeners || {}, add, remove, vm) + target = undefined +} + +export function eventsMixin (Vue: Class<Component>) { + const hookRE = /^hook:/ + Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component { + const vm: Component = this + if (Array.isArray(event)) { + for (let i = 0, l = event.length; i < l; i++) { + this.$on(event[i], fn) + } + } else { + (vm._events[event] || (vm._events[event] = [])).push(fn) + // optimize hook:event cost by using a boolean flag marked at registration + // instead of a hash lookup + if (hookRE.test(event)) { + vm._hasHookEvent = true + } + } + return vm + } + + Vue.prototype.$once = function (event: string, fn: Function): Component { + const vm: Component = this + function on () { + vm.$off(event, on) + fn.apply(vm, arguments) + } + on.fn = fn + vm.$on(event, on) + return vm + } + + Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component { + const vm: Component = this + // all + if (!arguments.length) { + vm._events = Object.create(null) + return vm + } + // array of events + if (Array.isArray(event)) { + for (let i = 0, l = event.length; i < l; i++) { + this.$off(event[i], fn) + } + return vm + } + // specific event + const cbs = vm._events[event] + if (!cbs) { + return vm + } + if (!fn) { + vm._events[event] = null + return vm + } + if (fn) { + // specific handler + let cb + let i = cbs.length + while (i--) { + cb = cbs[i] + if (cb === fn || cb.fn === fn) { + cbs.splice(i, 1) + break + } + } + } + return vm + } + + Vue.prototype.$emit = function (event: string): Component { + const vm: Component = this + if (process.env.NODE_ENV !== 'production') { + const lowerCaseEvent = event.toLowerCase() + if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) { + tip( + `Event "${lowerCaseEvent}" is emitted in component ` + + `${formatComponentName(vm)} but the handler is registered for "${event}". ` + + `Note that HTML attributes are case-insensitive and you cannot use ` + + `v-on to listen to camelCase events when using in-DOM templates. ` + + `You should probably use "${hyphenate(event)}" instead of "${event}".` + ) + } + } + let cbs = vm._events[event] + if (cbs) { + cbs = cbs.length > 1 ? toArray(cbs) : cbs + const args = toArray(arguments, 1) + for (let i = 0, l = cbs.length; i < l; i++) { + try { + cbs[i].apply(vm, args) + } catch (e) { + handleError(e, vm, `event handler for "${event}"`) + } + } + } + return vm + } +} diff --git a/node_modules/vue/src/core/instance/index.js b/node_modules/vue/src/core/instance/index.js new file mode 100644 index 00000000..8a440231 --- /dev/null +++ b/node_modules/vue/src/core/instance/index.js @@ -0,0 +1,23 @@ +import { initMixin } from './init' +import { stateMixin } from './state' +import { renderMixin } from './render' +import { eventsMixin } from './events' +import { lifecycleMixin } from './lifecycle' +import { warn } from '../util/index' + +function Vue (options) { + if (process.env.NODE_ENV !== 'production' && + !(this instanceof Vue) + ) { + warn('Vue is a constructor and should be called with the `new` keyword') + } + this._init(options) +} + +initMixin(Vue) +stateMixin(Vue) +eventsMixin(Vue) +lifecycleMixin(Vue) +renderMixin(Vue) + +export default Vue diff --git a/node_modules/vue/src/core/instance/init.js b/node_modules/vue/src/core/instance/init.js new file mode 100644 index 00000000..ed2550d0 --- /dev/null +++ b/node_modules/vue/src/core/instance/init.js @@ -0,0 +1,150 @@ +/* @flow */ + +import config from '../config' +import { initProxy } from './proxy' +import { initState } from './state' +import { initRender } from './render' +import { initEvents } from './events' +import { mark, measure } from '../util/perf' +import { initLifecycle, callHook } from './lifecycle' +import { initProvide, initInjections } from './inject' +import { extend, mergeOptions, formatComponentName } from '../util/index' + +let uid = 0 + +export function initMixin (Vue: Class<Component>) { + Vue.prototype._init = function (options?: Object) { + const vm: Component = this + // a uid + vm._uid = uid++ + + let startTag, endTag + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && config.performance && mark) { + startTag = `vue-perf-start:${vm._uid}` + endTag = `vue-perf-end:${vm._uid}` + mark(startTag) + } + + // a flag to avoid this being observed + vm._isVue = true + // merge options + if (options && options._isComponent) { + // optimize internal component instantiation + // since dynamic options merging is pretty slow, and none of the + // internal component options needs special treatment. + initInternalComponent(vm, options) + } else { + vm.$options = mergeOptions( + resolveConstructorOptions(vm.constructor), + options || {}, + vm + ) + } + /* istanbul ignore else */ + if (process.env.NODE_ENV !== 'production') { + initProxy(vm) + } else { + vm._renderProxy = vm + } + // expose real self + vm._self = vm + initLifecycle(vm) + initEvents(vm) + initRender(vm) + callHook(vm, 'beforeCreate') + initInjections(vm) // resolve injections before data/props + initState(vm) + initProvide(vm) // resolve provide after data/props + callHook(vm, 'created') + + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && config.performance && mark) { + vm._name = formatComponentName(vm, false) + mark(endTag) + measure(`vue ${vm._name} init`, startTag, endTag) + } + + if (vm.$options.el) { + vm.$mount(vm.$options.el) + } + } +} + +export function initInternalComponent (vm: Component, options: InternalComponentOptions) { + const opts = vm.$options = Object.create(vm.constructor.options) + // doing this because it's faster than dynamic enumeration. + const parentVnode = options._parentVnode + opts.parent = options.parent + opts._parentVnode = parentVnode + opts._parentElm = options._parentElm + opts._refElm = options._refElm + + const vnodeComponentOptions = parentVnode.componentOptions + opts.propsData = vnodeComponentOptions.propsData + opts._parentListeners = vnodeComponentOptions.listeners + opts._renderChildren = vnodeComponentOptions.children + opts._componentTag = vnodeComponentOptions.tag + + if (options.render) { + opts.render = options.render + opts.staticRenderFns = options.staticRenderFns + } +} + +export function resolveConstructorOptions (Ctor: Class<Component>) { + let options = Ctor.options + if (Ctor.super) { + const superOptions = resolveConstructorOptions(Ctor.super) + const cachedSuperOptions = Ctor.superOptions + if (superOptions !== cachedSuperOptions) { + // super option changed, + // need to resolve new options. + Ctor.superOptions = superOptions + // check if there are any late-modified/attached options (#4976) + const modifiedOptions = resolveModifiedOptions(Ctor) + // update base extend options + if (modifiedOptions) { + extend(Ctor.extendOptions, modifiedOptions) + } + options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions) + if (options.name) { + options.components[options.name] = Ctor + } + } + } + return options +} + +function resolveModifiedOptions (Ctor: Class<Component>): ?Object { + let modified + const latest = Ctor.options + const extended = Ctor.extendOptions + const sealed = Ctor.sealedOptions + for (const key in latest) { + if (latest[key] !== sealed[key]) { + if (!modified) modified = {} + modified[key] = dedupe(latest[key], extended[key], sealed[key]) + } + } + return modified +} + +function dedupe (latest, extended, sealed) { + // compare latest and sealed to ensure lifecycle hooks won't be duplicated + // between merges + if (Array.isArray(latest)) { + const res = [] + sealed = Array.isArray(sealed) ? sealed : [sealed] + extended = Array.isArray(extended) ? extended : [extended] + for (let i = 0; i < latest.length; i++) { + // push original options and not sealed options to exclude duplicated options + if (extended.indexOf(latest[i]) >= 0 || sealed.indexOf(latest[i]) < 0) { + res.push(latest[i]) + } + } + return res + } else { + return latest + } +} diff --git a/node_modules/vue/src/core/instance/inject.js b/node_modules/vue/src/core/instance/inject.js new file mode 100644 index 00000000..e464e12a --- /dev/null +++ b/node_modules/vue/src/core/instance/inject.js @@ -0,0 +1,74 @@ +/* @flow */ + +import { hasOwn } from 'shared/util' +import { warn, hasSymbol } from '../util/index' +import { defineReactive, toggleObserving } from '../observer/index' + +export function initProvide (vm: Component) { + const provide = vm.$options.provide + if (provide) { + vm._provided = typeof provide === 'function' + ? provide.call(vm) + : provide + } +} + +export function initInjections (vm: Component) { + const result = resolveInject(vm.$options.inject, vm) + if (result) { + toggleObserving(false) + Object.keys(result).forEach(key => { + /* istanbul ignore else */ + if (process.env.NODE_ENV !== 'production') { + defineReactive(vm, key, result[key], () => { + warn( + `Avoid mutating an injected value directly since the changes will be ` + + `overwritten whenever the provided component re-renders. ` + + `injection being mutated: "${key}"`, + vm + ) + }) + } else { + defineReactive(vm, key, result[key]) + } + }) + toggleObserving(true) + } +} + +export function resolveInject (inject: any, vm: Component): ?Object { + if (inject) { + // inject is :any because flow is not smart enough to figure out cached + const result = Object.create(null) + const keys = hasSymbol + ? Reflect.ownKeys(inject).filter(key => { + /* istanbul ignore next */ + return Object.getOwnPropertyDescriptor(inject, key).enumerable + }) + : Object.keys(inject) + + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + const provideKey = inject[key].from + let source = vm + while (source) { + if (source._provided && hasOwn(source._provided, provideKey)) { + result[key] = source._provided[provideKey] + break + } + source = source.$parent + } + if (!source) { + if ('default' in inject[key]) { + const provideDefault = inject[key].default + result[key] = typeof provideDefault === 'function' + ? provideDefault.call(vm) + : provideDefault + } else if (process.env.NODE_ENV !== 'production') { + warn(`Injection "${key}" not found`, vm) + } + } + } + return result + } +} diff --git a/node_modules/vue/src/core/instance/lifecycle.js b/node_modules/vue/src/core/instance/lifecycle.js new file mode 100644 index 00000000..169a54a6 --- /dev/null +++ b/node_modules/vue/src/core/instance/lifecycle.js @@ -0,0 +1,336 @@ +/* @flow */ + +import config from '../config' +import Watcher from '../observer/watcher' +import { mark, measure } from '../util/perf' +import { createEmptyVNode } from '../vdom/vnode' +import { updateComponentListeners } from './events' +import { resolveSlots } from './render-helpers/resolve-slots' +import { toggleObserving } from '../observer/index' +import { pushTarget, popTarget } from '../observer/dep' + +import { + warn, + noop, + remove, + handleError, + emptyObject, + validateProp +} from '../util/index' + +export let activeInstance: any = null +export let isUpdatingChildComponent: boolean = false + +export function initLifecycle (vm: Component) { + const options = vm.$options + + // locate first non-abstract parent + let parent = options.parent + if (parent && !options.abstract) { + while (parent.$options.abstract && parent.$parent) { + parent = parent.$parent + } + parent.$children.push(vm) + } + + vm.$parent = parent + vm.$root = parent ? parent.$root : vm + + vm.$children = [] + vm.$refs = {} + + vm._watcher = null + vm._inactive = null + vm._directInactive = false + vm._isMounted = false + vm._isDestroyed = false + vm._isBeingDestroyed = false +} + +export function lifecycleMixin (Vue: Class<Component>) { + Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { + const vm: Component = this + if (vm._isMounted) { + callHook(vm, 'beforeUpdate') + } + const prevEl = vm.$el + const prevVnode = vm._vnode + const prevActiveInstance = activeInstance + activeInstance = vm + vm._vnode = vnode + // Vue.prototype.__patch__ is injected in entry points + // based on the rendering backend used. + if (!prevVnode) { + // initial render + vm.$el = vm.__patch__( + vm.$el, vnode, hydrating, false /* removeOnly */, + vm.$options._parentElm, + vm.$options._refElm + ) + // no need for the ref nodes after initial patch + // this prevents keeping a detached DOM tree in memory (#5851) + vm.$options._parentElm = vm.$options._refElm = null + } else { + // updates + vm.$el = vm.__patch__(prevVnode, vnode) + } + activeInstance = prevActiveInstance + // update __vue__ reference + if (prevEl) { + prevEl.__vue__ = null + } + if (vm.$el) { + vm.$el.__vue__ = vm + } + // if parent is an HOC, update its $el as well + if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) { + vm.$parent.$el = vm.$el + } + // updated hook is called by the scheduler to ensure that children are + // updated in a parent's updated hook. + } + + Vue.prototype.$forceUpdate = function () { + const vm: Component = this + if (vm._watcher) { + vm._watcher.update() + } + } + + Vue.prototype.$destroy = function () { + const vm: Component = this + if (vm._isBeingDestroyed) { + return + } + callHook(vm, 'beforeDestroy') + vm._isBeingDestroyed = true + // remove self from parent + const parent = vm.$parent + if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { + remove(parent.$children, vm) + } + // teardown watchers + if (vm._watcher) { + vm._watcher.teardown() + } + let i = vm._watchers.length + while (i--) { + vm._watchers[i].teardown() + } + // remove reference from data ob + // frozen object may not have observer. + if (vm._data.__ob__) { + vm._data.__ob__.vmCount-- + } + // call the last hook... + vm._isDestroyed = true + // invoke destroy hooks on current rendered tree + vm.__patch__(vm._vnode, null) + // fire destroyed hook + callHook(vm, 'destroyed') + // turn off all instance listeners. + vm.$off() + // remove __vue__ reference + if (vm.$el) { + vm.$el.__vue__ = null + } + // release circular reference (#6759) + if (vm.$vnode) { + vm.$vnode.parent = null + } + } +} + +export function mountComponent ( + vm: Component, + el: ?Element, + hydrating?: boolean +): Component { + vm.$el = el + if (!vm.$options.render) { + vm.$options.render = createEmptyVNode + if (process.env.NODE_ENV !== 'production') { + /* istanbul ignore if */ + if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') || + vm.$options.el || el) { + warn( + 'You are using the runtime-only build of Vue where the template ' + + 'compiler is not available. Either pre-compile the templates into ' + + 'render functions, or use the compiler-included build.', + vm + ) + } else { + warn( + 'Failed to mount component: template or render function not defined.', + vm + ) + } + } + } + callHook(vm, 'beforeMount') + + let updateComponent + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && config.performance && mark) { + updateComponent = () => { + const name = vm._name + const id = vm._uid + const startTag = `vue-perf-start:${id}` + const endTag = `vue-perf-end:${id}` + + mark(startTag) + const vnode = vm._render() + mark(endTag) + measure(`vue ${name} render`, startTag, endTag) + + mark(startTag) + vm._update(vnode, hydrating) + mark(endTag) + measure(`vue ${name} patch`, startTag, endTag) + } + } else { + updateComponent = () => { + vm._update(vm._render(), hydrating) + } + } + + // we set this to vm._watcher inside the watcher's constructor + // since the watcher's initial patch may call $forceUpdate (e.g. inside child + // component's mounted hook), which relies on vm._watcher being already defined + new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */) + hydrating = false + + // manually mounted instance, call mounted on self + // mounted is called for render-created child components in its inserted hook + if (vm.$vnode == null) { + vm._isMounted = true + callHook(vm, 'mounted') + } + return vm +} + +export function updateChildComponent ( + vm: Component, + propsData: ?Object, + listeners: ?Object, + parentVnode: MountedComponentVNode, + renderChildren: ?Array<VNode> +) { + if (process.env.NODE_ENV !== 'production') { + isUpdatingChildComponent = true + } + + // determine whether component has slot children + // we need to do this before overwriting $options._renderChildren + const hasChildren = !!( + renderChildren || // has new static slots + vm.$options._renderChildren || // has old static slots + parentVnode.data.scopedSlots || // has new scoped slots + vm.$scopedSlots !== emptyObject // has old scoped slots + ) + + vm.$options._parentVnode = parentVnode + vm.$vnode = parentVnode // update vm's placeholder node without re-render + + if (vm._vnode) { // update child tree's parent + vm._vnode.parent = parentVnode + } + vm.$options._renderChildren = renderChildren + + // update $attrs and $listeners hash + // these are also reactive so they may trigger child update if the child + // used them during render + vm.$attrs = parentVnode.data.attrs || emptyObject + vm.$listeners = listeners || emptyObject + + // update props + if (propsData && vm.$options.props) { + toggleObserving(false) + const props = vm._props + const propKeys = vm.$options._propKeys || [] + for (let i = 0; i < propKeys.length; i++) { + const key = propKeys[i] + const propOptions: any = vm.$options.props // wtf flow? + props[key] = validateProp(key, propOptions, propsData, vm) + } + toggleObserving(true) + // keep a copy of raw propsData + vm.$options.propsData = propsData + } + + // update listeners + listeners = listeners || emptyObject + const oldListeners = vm.$options._parentListeners + vm.$options._parentListeners = listeners + updateComponentListeners(vm, listeners, oldListeners) + + // resolve slots + force update if has children + if (hasChildren) { + vm.$slots = resolveSlots(renderChildren, parentVnode.context) + vm.$forceUpdate() + } + + if (process.env.NODE_ENV !== 'production') { + isUpdatingChildComponent = false + } +} + +function isInInactiveTree (vm) { + while (vm && (vm = vm.$parent)) { + if (vm._inactive) return true + } + return false +} + +export function activateChildComponent (vm: Component, direct?: boolean) { + if (direct) { + vm._directInactive = false + if (isInInactiveTree(vm)) { + return + } + } else if (vm._directInactive) { + return + } + if (vm._inactive || vm._inactive === null) { + vm._inactive = false + for (let i = 0; i < vm.$children.length; i++) { + activateChildComponent(vm.$children[i]) + } + callHook(vm, 'activated') + } +} + +export function deactivateChildComponent (vm: Component, direct?: boolean) { + if (direct) { + vm._directInactive = true + if (isInInactiveTree(vm)) { + return + } + } + if (!vm._inactive) { + vm._inactive = true + for (let i = 0; i < vm.$children.length; i++) { + deactivateChildComponent(vm.$children[i]) + } + callHook(vm, 'deactivated') + } +} + +export function callHook (vm: Component, hook: string) { + // #7573 disable dep collection when invoking lifecycle hooks + pushTarget() + const handlers = vm.$options[hook] + if (handlers) { + for (let i = 0, j = handlers.length; i < j; i++) { + try { + handlers[i].call(vm) + } catch (e) { + handleError(e, vm, `${hook} hook`) + } + } + } + if (vm._hasHookEvent) { + vm.$emit('hook:' + hook) + } + popTarget() +} diff --git a/node_modules/vue/src/core/instance/proxy.js b/node_modules/vue/src/core/instance/proxy.js new file mode 100644 index 00000000..0d6dcf80 --- /dev/null +++ b/node_modules/vue/src/core/instance/proxy.js @@ -0,0 +1,79 @@ +/* not type checking this file because flow doesn't play well with Proxy */ + +import config from 'core/config' +import { warn, makeMap, isNative } from '../util/index' + +let initProxy + +if (process.env.NODE_ENV !== 'production') { + const allowedGlobals = makeMap( + 'Infinity,undefined,NaN,isFinite,isNaN,' + + 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + + 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' + + 'require' // for Webpack/Browserify + ) + + const warnNonPresent = (target, key) => { + warn( + `Property or method "${key}" is not defined on the instance but ` + + 'referenced during render. Make sure that this property is reactive, ' + + 'either in the data option, or for class-based components, by ' + + 'initializing the property. ' + + 'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.', + target + ) + } + + const hasProxy = + typeof Proxy !== 'undefined' && isNative(Proxy) + + if (hasProxy) { + const isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact') + config.keyCodes = new Proxy(config.keyCodes, { + set (target, key, value) { + if (isBuiltInModifier(key)) { + warn(`Avoid overwriting built-in modifier in config.keyCodes: .${key}`) + return false + } else { + target[key] = value + return true + } + } + }) + } + + const hasHandler = { + has (target, key) { + const has = key in target + const isAllowed = allowedGlobals(key) || key.charAt(0) === '_' + if (!has && !isAllowed) { + warnNonPresent(target, key) + } + return has || !isAllowed + } + } + + const getHandler = { + get (target, key) { + if (typeof key === 'string' && !(key in target)) { + warnNonPresent(target, key) + } + return target[key] + } + } + + initProxy = function initProxy (vm) { + if (hasProxy) { + // determine which proxy handler to use + const options = vm.$options + const handlers = options.render && options.render._withStripped + ? getHandler + : hasHandler + vm._renderProxy = new Proxy(vm, handlers) + } else { + vm._renderProxy = vm + } + } +} + +export { initProxy } diff --git a/node_modules/vue/src/core/instance/render-helpers/bind-object-listeners.js b/node_modules/vue/src/core/instance/render-helpers/bind-object-listeners.js new file mode 100644 index 00000000..5bfc7514 --- /dev/null +++ b/node_modules/vue/src/core/instance/render-helpers/bind-object-listeners.js @@ -0,0 +1,22 @@ +/* @flow */ + +import { warn, extend, isPlainObject } from 'core/util/index' + +export function bindObjectListeners (data: any, value: any): VNodeData { + if (value) { + if (!isPlainObject(value)) { + process.env.NODE_ENV !== 'production' && warn( + 'v-on without argument expects an Object value', + this + ) + } else { + const on = data.on = data.on ? extend({}, data.on) : {} + for (const key in value) { + const existing = on[key] + const ours = value[key] + on[key] = existing ? [].concat(existing, ours) : ours + } + } + } + return data +} diff --git a/node_modules/vue/src/core/instance/render-helpers/bind-object-props.js b/node_modules/vue/src/core/instance/render-helpers/bind-object-props.js new file mode 100644 index 00000000..c71dce5c --- /dev/null +++ b/node_modules/vue/src/core/instance/render-helpers/bind-object-props.js @@ -0,0 +1,60 @@ +/* @flow */ + +import config from 'core/config' + +import { + warn, + isObject, + toObject, + isReservedAttribute +} from 'core/util/index' + +/** + * Runtime helper for merging v-bind="object" into a VNode's data. + */ +export function bindObjectProps ( + data: any, + tag: string, + value: any, + asProp: boolean, + isSync?: boolean +): VNodeData { + if (value) { + if (!isObject(value)) { + process.env.NODE_ENV !== 'production' && warn( + 'v-bind without argument expects an Object or Array value', + this + ) + } else { + if (Array.isArray(value)) { + value = toObject(value) + } + let hash + for (const key in value) { + if ( + key === 'class' || + key === 'style' || + isReservedAttribute(key) + ) { + hash = data + } else { + const type = data.attrs && data.attrs.type + hash = asProp || config.mustUseProp(tag, type, key) + ? data.domProps || (data.domProps = {}) + : data.attrs || (data.attrs = {}) + } + if (!(key in hash)) { + hash[key] = value[key] + + if (isSync) { + const on = data.on || (data.on = {}) + on[`update:${key}`] = function ($event) { + value[key] = $event + } + } + } + } + } + } + return data +} diff --git a/node_modules/vue/src/core/instance/render-helpers/check-keycodes.js b/node_modules/vue/src/core/instance/render-helpers/check-keycodes.js new file mode 100644 index 00000000..c12402b1 --- /dev/null +++ b/node_modules/vue/src/core/instance/render-helpers/check-keycodes.js @@ -0,0 +1,34 @@ +/* @flow */ + +import config from 'core/config' +import { hyphenate } from 'shared/util' + +function isKeyNotMatch<T> (expect: T | Array<T>, actual: T): boolean { + if (Array.isArray(expect)) { + return expect.indexOf(actual) === -1 + } else { + return expect !== actual + } +} + +/** + * Runtime helper for checking keyCodes from config. + * exposed as Vue.prototype._k + * passing in eventKeyName as last argument separately for backwards compat + */ +export function checkKeyCodes ( + eventKeyCode: number, + key: string, + builtInKeyCode?: number | Array<number>, + eventKeyName?: string, + builtInKeyName?: string | Array<string> +): ?boolean { + const mappedKeyCode = config.keyCodes[key] || builtInKeyCode + if (builtInKeyName && eventKeyName && !config.keyCodes[key]) { + return isKeyNotMatch(builtInKeyName, eventKeyName) + } else if (mappedKeyCode) { + return isKeyNotMatch(mappedKeyCode, eventKeyCode) + } else if (eventKeyName) { + return hyphenate(eventKeyName) !== key + } +} diff --git a/node_modules/vue/src/core/instance/render-helpers/index.js b/node_modules/vue/src/core/instance/render-helpers/index.js new file mode 100644 index 00000000..38414a9a --- /dev/null +++ b/node_modules/vue/src/core/instance/render-helpers/index.js @@ -0,0 +1,30 @@ +/* @flow */ + +import { toNumber, toString, looseEqual, looseIndexOf } from 'shared/util' +import { createTextVNode, createEmptyVNode } from 'core/vdom/vnode' +import { renderList } from './render-list' +import { renderSlot } from './render-slot' +import { resolveFilter } from './resolve-filter' +import { checkKeyCodes } from './check-keycodes' +import { bindObjectProps } from './bind-object-props' +import { renderStatic, markOnce } from './render-static' +import { bindObjectListeners } from './bind-object-listeners' +import { resolveScopedSlots } from './resolve-slots' + +export function installRenderHelpers (target: any) { + target._o = markOnce + target._n = toNumber + target._s = toString + target._l = renderList + target._t = renderSlot + target._q = looseEqual + target._i = looseIndexOf + target._m = renderStatic + target._f = resolveFilter + target._k = checkKeyCodes + target._b = bindObjectProps + target._v = createTextVNode + target._e = createEmptyVNode + target._u = resolveScopedSlots + target._g = bindObjectListeners +} diff --git a/node_modules/vue/src/core/instance/render-helpers/render-list.js b/node_modules/vue/src/core/instance/render-helpers/render-list.js new file mode 100644 index 00000000..abe99db4 --- /dev/null +++ b/node_modules/vue/src/core/instance/render-helpers/render-list.js @@ -0,0 +1,39 @@ +/* @flow */ + +import { isObject, isDef } from 'core/util/index' + +/** + * Runtime helper for rendering v-for lists. + */ +export function renderList ( + val: any, + render: ( + val: any, + keyOrIndex: string | number, + index?: number + ) => VNode +): ?Array<VNode> { + let ret: ?Array<VNode>, i, l, keys, key + if (Array.isArray(val) || typeof val === 'string') { + ret = new Array(val.length) + for (i = 0, l = val.length; i < l; i++) { + ret[i] = render(val[i], i) + } + } else if (typeof val === 'number') { + ret = new Array(val) + for (i = 0; i < val; i++) { + ret[i] = render(i + 1, i) + } + } else if (isObject(val)) { + keys = Object.keys(val) + ret = new Array(keys.length) + for (i = 0, l = keys.length; i < l; i++) { + key = keys[i] + ret[i] = render(val[key], key, i) + } + } + if (isDef(ret)) { + (ret: any)._isVList = true + } + return ret +} diff --git a/node_modules/vue/src/core/instance/render-helpers/render-slot.js b/node_modules/vue/src/core/instance/render-helpers/render-slot.js new file mode 100644 index 00000000..a58daa74 --- /dev/null +++ b/node_modules/vue/src/core/instance/render-helpers/render-slot.js @@ -0,0 +1,50 @@ +/* @flow */ + +import { extend, warn, isObject } from 'core/util/index' + +/** + * Runtime helper for rendering <slot> + */ +export function renderSlot ( + name: string, + fallback: ?Array<VNode>, + props: ?Object, + bindObject: ?Object +): ?Array<VNode> { + const scopedSlotFn = this.$scopedSlots[name] + let nodes + if (scopedSlotFn) { // scoped slot + props = props || {} + if (bindObject) { + if (process.env.NODE_ENV !== 'production' && !isObject(bindObject)) { + warn( + 'slot v-bind without argument expects an Object', + this + ) + } + props = extend(extend({}, bindObject), props) + } + nodes = scopedSlotFn(props) || fallback + } else { + const slotNodes = this.$slots[name] + // warn duplicate slot usage + if (slotNodes) { + if (process.env.NODE_ENV !== 'production' && slotNodes._rendered) { + warn( + `Duplicate presence of slot "${name}" found in the same render tree ` + + `- this will likely cause render errors.`, + this + ) + } + slotNodes._rendered = true + } + nodes = slotNodes || fallback + } + + const target = props && props.slot + if (target) { + return this.$createElement('template', { slot: target }, nodes) + } else { + return nodes + } +} diff --git a/node_modules/vue/src/core/instance/render-helpers/render-static.js b/node_modules/vue/src/core/instance/render-helpers/render-static.js new file mode 100644 index 00000000..f56510b3 --- /dev/null +++ b/node_modules/vue/src/core/instance/render-helpers/render-static.js @@ -0,0 +1,60 @@ +/* @flow */ + +/** + * Runtime helper for rendering static trees. + */ +export function renderStatic ( + index: number, + isInFor: boolean +): VNode | Array<VNode> { + const cached = this._staticTrees || (this._staticTrees = []) + let tree = cached[index] + // if has already-rendered static tree and not inside v-for, + // we can reuse the same tree. + if (tree && !isInFor) { + return tree + } + // otherwise, render a fresh tree. + tree = cached[index] = this.$options.staticRenderFns[index].call( + this._renderProxy, + null, + this // for render fns generated for functional component templates + ) + markStatic(tree, `__static__${index}`, false) + return tree +} + +/** + * Runtime helper for v-once. + * Effectively it means marking the node as static with a unique key. + */ +export function markOnce ( + tree: VNode | Array<VNode>, + index: number, + key: string +) { + markStatic(tree, `__once__${index}${key ? `_${key}` : ``}`, true) + return tree +} + +function markStatic ( + tree: VNode | Array<VNode>, + key: string, + isOnce: boolean +) { + if (Array.isArray(tree)) { + for (let i = 0; i < tree.length; i++) { + if (tree[i] && typeof tree[i] !== 'string') { + markStaticNode(tree[i], `${key}_${i}`, isOnce) + } + } + } else { + markStaticNode(tree, key, isOnce) + } +} + +function markStaticNode (node, key, isOnce) { + node.isStatic = true + node.key = key + node.isOnce = isOnce +} diff --git a/node_modules/vue/src/core/instance/render-helpers/resolve-filter.js b/node_modules/vue/src/core/instance/render-helpers/resolve-filter.js new file mode 100644 index 00000000..08a81e14 --- /dev/null +++ b/node_modules/vue/src/core/instance/render-helpers/resolve-filter.js @@ -0,0 +1,10 @@ +/* @flow */ + +import { identity, resolveAsset } from 'core/util/index' + +/** + * Runtime helper for resolving filters + */ +export function resolveFilter (id: string): Function { + return resolveAsset(this.$options, 'filters', id, true) || identity +} diff --git a/node_modules/vue/src/core/instance/render-helpers/resolve-slots.js b/node_modules/vue/src/core/instance/render-helpers/resolve-slots.js new file mode 100644 index 00000000..02f007cf --- /dev/null +++ b/node_modules/vue/src/core/instance/render-helpers/resolve-slots.js @@ -0,0 +1,65 @@ +/* @flow */ + +import type VNode from 'core/vdom/vnode' + +/** + * Runtime helper for resolving raw children VNodes into a slot object. + */ +export function resolveSlots ( + children: ?Array<VNode>, + context: ?Component +): { [key: string]: Array<VNode> } { + const slots = {} + if (!children) { + return slots + } + for (let i = 0, l = children.length; i < l; i++) { + const child = children[i] + const data = child.data + // remove slot attribute if the node is resolved as a Vue slot node + if (data && data.attrs && data.attrs.slot) { + delete data.attrs.slot + } + // named slots should only be respected if the vnode was rendered in the + // same context. + if ((child.context === context || child.fnContext === context) && + data && data.slot != null + ) { + const name = data.slot + const slot = (slots[name] || (slots[name] = [])) + if (child.tag === 'template') { + slot.push.apply(slot, child.children || []) + } else { + slot.push(child) + } + } else { + (slots.default || (slots.default = [])).push(child) + } + } + // ignore slots that contains only whitespace + for (const name in slots) { + if (slots[name].every(isWhitespace)) { + delete slots[name] + } + } + return slots +} + +function isWhitespace (node: VNode): boolean { + return (node.isComment && !node.asyncFactory) || node.text === ' ' +} + +export function resolveScopedSlots ( + fns: ScopedSlotsData, // see flow/vnode + res?: Object +): { [key: string]: Function } { + res = res || {} + for (let i = 0; i < fns.length; i++) { + if (Array.isArray(fns[i])) { + resolveScopedSlots(fns[i], res) + } else { + res[fns[i].key] = fns[i].fn + } + } + return res +} diff --git a/node_modules/vue/src/core/instance/render.js b/node_modules/vue/src/core/instance/render.js new file mode 100644 index 00000000..70fb2c0e --- /dev/null +++ b/node_modules/vue/src/core/instance/render.js @@ -0,0 +1,119 @@ +/* @flow */ + +import { + warn, + nextTick, + emptyObject, + handleError, + defineReactive +} from '../util/index' + +import { createElement } from '../vdom/create-element' +import { installRenderHelpers } from './render-helpers/index' +import { resolveSlots } from './render-helpers/resolve-slots' +import VNode, { createEmptyVNode } from '../vdom/vnode' + +import { isUpdatingChildComponent } from './lifecycle' + +export function initRender (vm: Component) { + vm._vnode = null // the root of the child tree + vm._staticTrees = null // v-once cached trees + const options = vm.$options + const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree + const renderContext = parentVnode && parentVnode.context + vm.$slots = resolveSlots(options._renderChildren, renderContext) + vm.$scopedSlots = emptyObject + // bind the createElement fn to this instance + // so that we get proper render context inside it. + // args order: tag, data, children, normalizationType, alwaysNormalize + // internal version is used by render functions compiled from templates + vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) + // normalization is always applied for the public version, used in + // user-written render functions. + vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) + + // $attrs & $listeners are exposed for easier HOC creation. + // they need to be reactive so that HOCs using them are always updated + const parentData = parentVnode && parentVnode.data + + /* istanbul ignore else */ + if (process.env.NODE_ENV !== 'production') { + defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => { + !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm) + }, true) + defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => { + !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm) + }, true) + } else { + defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true) + defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true) + } +} + +export function renderMixin (Vue: Class<Component>) { + // install runtime convenience helpers + installRenderHelpers(Vue.prototype) + + Vue.prototype.$nextTick = function (fn: Function) { + return nextTick(fn, this) + } + + Vue.prototype._render = function (): VNode { + const vm: Component = this + const { render, _parentVnode } = vm.$options + + // reset _rendered flag on slots for duplicate slot check + if (process.env.NODE_ENV !== 'production') { + for (const key in vm.$slots) { + // $flow-disable-line + vm.$slots[key]._rendered = false + } + } + + if (_parentVnode) { + vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject + } + + // set parent vnode. this allows render functions to have access + // to the data on the placeholder node. + vm.$vnode = _parentVnode + // render self + let vnode + try { + vnode = render.call(vm._renderProxy, vm.$createElement) + } catch (e) { + handleError(e, vm, `render`) + // return error render result, + // or previous vnode to prevent render error causing blank component + /* istanbul ignore else */ + if (process.env.NODE_ENV !== 'production') { + if (vm.$options.renderError) { + try { + vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e) + } catch (e) { + handleError(e, vm, `renderError`) + vnode = vm._vnode + } + } else { + vnode = vm._vnode + } + } else { + vnode = vm._vnode + } + } + // return empty vnode in case the render function errored out + if (!(vnode instanceof VNode)) { + if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) { + warn( + 'Multiple root nodes returned from render function. Render function ' + + 'should return a single root node.', + vm + ) + } + vnode = createEmptyVNode() + } + // set parent + vnode.parent = _parentVnode + return vnode + } +} diff --git a/node_modules/vue/src/core/instance/state.js b/node_modules/vue/src/core/instance/state.js new file mode 100644 index 00000000..b1549b0d --- /dev/null +++ b/node_modules/vue/src/core/instance/state.js @@ -0,0 +1,360 @@ +/* @flow */ + +import config from '../config' +import Watcher from '../observer/watcher' +import Dep, { pushTarget, popTarget } from '../observer/dep' +import { isUpdatingChildComponent } from './lifecycle' + +import { + set, + del, + observe, + defineReactive, + toggleObserving +} from '../observer/index' + +import { + warn, + bind, + noop, + hasOwn, + hyphenate, + isReserved, + handleError, + nativeWatch, + validateProp, + isPlainObject, + isServerRendering, + isReservedAttribute +} from '../util/index' + +const sharedPropertyDefinition = { + enumerable: true, + configurable: true, + get: noop, + set: noop +} + +export function proxy (target: Object, sourceKey: string, key: string) { + sharedPropertyDefinition.get = function proxyGetter () { + return this[sourceKey][key] + } + sharedPropertyDefinition.set = function proxySetter (val) { + this[sourceKey][key] = val + } + Object.defineProperty(target, key, sharedPropertyDefinition) +} + +export function initState (vm: Component) { + vm._watchers = [] + const opts = vm.$options + if (opts.props) initProps(vm, opts.props) + if (opts.methods) initMethods(vm, opts.methods) + if (opts.data) { + initData(vm) + } else { + observe(vm._data = {}, true /* asRootData */) + } + if (opts.computed) initComputed(vm, opts.computed) + if (opts.watch && opts.watch !== nativeWatch) { + initWatch(vm, opts.watch) + } +} + +function initProps (vm: Component, propsOptions: Object) { + const propsData = vm.$options.propsData || {} + const props = vm._props = {} + // cache prop keys so that future props updates can iterate using Array + // instead of dynamic object key enumeration. + const keys = vm.$options._propKeys = [] + const isRoot = !vm.$parent + // root instance props should be converted + if (!isRoot) { + toggleObserving(false) + } + for (const key in propsOptions) { + keys.push(key) + const value = validateProp(key, propsOptions, propsData, vm) + /* istanbul ignore else */ + if (process.env.NODE_ENV !== 'production') { + const hyphenatedKey = hyphenate(key) + if (isReservedAttribute(hyphenatedKey) || + config.isReservedAttr(hyphenatedKey)) { + warn( + `"${hyphenatedKey}" is a reserved attribute and cannot be used as component prop.`, + vm + ) + } + defineReactive(props, key, value, () => { + if (vm.$parent && !isUpdatingChildComponent) { + warn( + `Avoid mutating a prop directly since the value will be ` + + `overwritten whenever the parent component re-renders. ` + + `Instead, use a data or computed property based on the prop's ` + + `value. Prop being mutated: "${key}"`, + vm + ) + } + }) + } else { + defineReactive(props, key, value) + } + // static props are already proxied on the component's prototype + // during Vue.extend(). We only need to proxy props defined at + // instantiation here. + if (!(key in vm)) { + proxy(vm, `_props`, key) + } + } + toggleObserving(true) +} + +function initData (vm: Component) { + let data = vm.$options.data + data = vm._data = typeof data === 'function' + ? getData(data, vm) + : data || {} + if (!isPlainObject(data)) { + data = {} + process.env.NODE_ENV !== 'production' && warn( + 'data functions should return an object:\n' + + 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', + vm + ) + } + // proxy data on instance + const keys = Object.keys(data) + const props = vm.$options.props + const methods = vm.$options.methods + let i = keys.length + while (i--) { + const key = keys[i] + if (process.env.NODE_ENV !== 'production') { + if (methods && hasOwn(methods, key)) { + warn( + `Method "${key}" has already been defined as a data property.`, + vm + ) + } + } + if (props && hasOwn(props, key)) { + process.env.NODE_ENV !== 'production' && warn( + `The data property "${key}" is already declared as a prop. ` + + `Use prop default value instead.`, + vm + ) + } else if (!isReserved(key)) { + proxy(vm, `_data`, key) + } + } + // observe data + observe(data, true /* asRootData */) +} + +export function getData (data: Function, vm: Component): any { + // #7573 disable dep collection when invoking data getters + pushTarget() + try { + return data.call(vm, vm) + } catch (e) { + handleError(e, vm, `data()`) + return {} + } finally { + popTarget() + } +} + +const computedWatcherOptions = { lazy: true } + +function initComputed (vm: Component, computed: Object) { + // $flow-disable-line + const watchers = vm._computedWatchers = Object.create(null) + // computed properties are just getters during SSR + const isSSR = isServerRendering() + + for (const key in computed) { + const userDef = computed[key] + const getter = typeof userDef === 'function' ? userDef : userDef.get + if (process.env.NODE_ENV !== 'production' && getter == null) { + warn( + `Getter is missing for computed property "${key}".`, + vm + ) + } + + if (!isSSR) { + // create internal watcher for the computed property. + watchers[key] = new Watcher( + vm, + getter || noop, + noop, + computedWatcherOptions + ) + } + + // component-defined computed properties are already defined on the + // component prototype. We only need to define computed properties defined + // at instantiation here. + if (!(key in vm)) { + defineComputed(vm, key, userDef) + } else if (process.env.NODE_ENV !== 'production') { + if (key in vm.$data) { + warn(`The computed property "${key}" is already defined in data.`, vm) + } else if (vm.$options.props && key in vm.$options.props) { + warn(`The computed property "${key}" is already defined as a prop.`, vm) + } + } + } +} + +export function defineComputed ( + target: any, + key: string, + userDef: Object | Function +) { + const shouldCache = !isServerRendering() + if (typeof userDef === 'function') { + sharedPropertyDefinition.get = shouldCache + ? createComputedGetter(key) + : userDef + sharedPropertyDefinition.set = noop + } else { + sharedPropertyDefinition.get = userDef.get + ? shouldCache && userDef.cache !== false + ? createComputedGetter(key) + : userDef.get + : noop + sharedPropertyDefinition.set = userDef.set + ? userDef.set + : noop + } + if (process.env.NODE_ENV !== 'production' && + sharedPropertyDefinition.set === noop) { + sharedPropertyDefinition.set = function () { + warn( + `Computed property "${key}" was assigned to but it has no setter.`, + this + ) + } + } + Object.defineProperty(target, key, sharedPropertyDefinition) +} + +function createComputedGetter (key) { + return function computedGetter () { + const watcher = this._computedWatchers && this._computedWatchers[key] + if (watcher) { + if (watcher.dirty) { + watcher.evaluate() + } + if (Dep.target) { + watcher.depend() + } + return watcher.value + } + } +} + +function initMethods (vm: Component, methods: Object) { + const props = vm.$options.props + for (const key in methods) { + if (process.env.NODE_ENV !== 'production') { + if (methods[key] == null) { + warn( + `Method "${key}" has an undefined value in the component definition. ` + + `Did you reference the function correctly?`, + vm + ) + } + if (props && hasOwn(props, key)) { + warn( + `Method "${key}" has already been defined as a prop.`, + vm + ) + } + if ((key in vm) && isReserved(key)) { + warn( + `Method "${key}" conflicts with an existing Vue instance method. ` + + `Avoid defining component methods that start with _ or $.` + ) + } + } + vm[key] = methods[key] == null ? noop : bind(methods[key], vm) + } +} + +function initWatch (vm: Component, watch: Object) { + for (const key in watch) { + const handler = watch[key] + if (Array.isArray(handler)) { + for (let i = 0; i < handler.length; i++) { + createWatcher(vm, key, handler[i]) + } + } else { + createWatcher(vm, key, handler) + } + } +} + +function createWatcher ( + vm: Component, + expOrFn: string | Function, + handler: any, + options?: Object +) { + if (isPlainObject(handler)) { + options = handler + handler = handler.handler + } + if (typeof handler === 'string') { + handler = vm[handler] + } + return vm.$watch(expOrFn, handler, options) +} + +export function stateMixin (Vue: Class<Component>) { + // flow somehow has problems with directly declared definition object + // when using Object.defineProperty, so we have to procedurally build up + // the object here. + const dataDef = {} + dataDef.get = function () { return this._data } + const propsDef = {} + propsDef.get = function () { return this._props } + if (process.env.NODE_ENV !== 'production') { + dataDef.set = function (newData: Object) { + warn( + 'Avoid replacing instance root $data. ' + + 'Use nested data properties instead.', + this + ) + } + propsDef.set = function () { + warn(`$props is readonly.`, this) + } + } + Object.defineProperty(Vue.prototype, '$data', dataDef) + Object.defineProperty(Vue.prototype, '$props', propsDef) + + Vue.prototype.$set = set + Vue.prototype.$delete = del + + Vue.prototype.$watch = function ( + expOrFn: string | Function, + cb: any, + options?: Object + ): Function { + const vm: Component = this + if (isPlainObject(cb)) { + return createWatcher(vm, expOrFn, cb, options) + } + options = options || {} + options.user = true + const watcher = new Watcher(vm, expOrFn, cb, options) + if (options.immediate) { + cb.call(vm, watcher.value) + } + return function unwatchFn () { + watcher.teardown() + } + } +} diff --git a/node_modules/vue/src/core/observer/array.js b/node_modules/vue/src/core/observer/array.js new file mode 100644 index 00000000..684e37ec --- /dev/null +++ b/node_modules/vue/src/core/observer/array.js @@ -0,0 +1,45 @@ +/* + * not type checking this file because flow doesn't play well with + * dynamically accessing methods on Array prototype + */ + +import { def } from '../util/index' + +const arrayProto = Array.prototype +export const arrayMethods = Object.create(arrayProto) + +const methodsToPatch = [ + 'push', + 'pop', + 'shift', + 'unshift', + 'splice', + 'sort', + 'reverse' +] + +/** + * Intercept mutating methods and emit events + */ +methodsToPatch.forEach(function (method) { + // cache original method + const original = arrayProto[method] + def(arrayMethods, method, function mutator (...args) { + const result = original.apply(this, args) + const ob = this.__ob__ + let inserted + switch (method) { + case 'push': + case 'unshift': + inserted = args + break + case 'splice': + inserted = args.slice(2) + break + } + if (inserted) ob.observeArray(inserted) + // notify change + ob.dep.notify() + return result + }) +}) diff --git a/node_modules/vue/src/core/observer/dep.js b/node_modules/vue/src/core/observer/dep.js new file mode 100644 index 00000000..abf3b275 --- /dev/null +++ b/node_modules/vue/src/core/observer/dep.js @@ -0,0 +1,58 @@ +/* @flow */ + +import type Watcher from './watcher' +import { remove } from '../util/index' + +let uid = 0 + +/** + * A dep is an observable that can have multiple + * directives subscribing to it. + */ +export default class Dep { + static target: ?Watcher; + id: number; + subs: Array<Watcher>; + + constructor () { + this.id = uid++ + this.subs = [] + } + + addSub (sub: Watcher) { + this.subs.push(sub) + } + + removeSub (sub: Watcher) { + remove(this.subs, sub) + } + + depend () { + if (Dep.target) { + Dep.target.addDep(this) + } + } + + notify () { + // stabilize the subscriber list first + const subs = this.subs.slice() + for (let i = 0, l = subs.length; i < l; i++) { + subs[i].update() + } + } +} + +// the current target watcher being evaluated. +// this is globally unique because there could be only one +// watcher being evaluated at any time. +Dep.target = null +const targetStack = [] + +export function pushTarget (_target: ?Watcher) { + if (Dep.target) targetStack.push(Dep.target) + Dep.target = _target +} + +export function popTarget () { + Dep.target = targetStack.pop() +} diff --git a/node_modules/vue/src/core/observer/index.js b/node_modules/vue/src/core/observer/index.js new file mode 100644 index 00000000..4a030ed7 --- /dev/null +++ b/node_modules/vue/src/core/observer/index.js @@ -0,0 +1,273 @@ +/* @flow */ + +import Dep from './dep' +import VNode from '../vdom/vnode' +import { arrayMethods } from './array' +import { + def, + warn, + hasOwn, + hasProto, + isObject, + isPlainObject, + isPrimitive, + isUndef, + isValidArrayIndex, + isServerRendering +} from '../util/index' + +const arrayKeys = Object.getOwnPropertyNames(arrayMethods) + +/** + * In some cases we may want to disable observation inside a component's + * update computation. + */ +export let shouldObserve: boolean = true + +export function toggleObserving (value: boolean) { + shouldObserve = value +} + +/** + * Observer class that is attached to each observed + * object. Once attached, the observer converts the target + * object's property keys into getter/setters that + * collect dependencies and dispatch updates. + */ +export class Observer { + value: any; + dep: Dep; + vmCount: number; // number of vms that has this object as root $data + + constructor (value: any) { + this.value = value + this.dep = new Dep() + this.vmCount = 0 + def(value, '__ob__', this) + if (Array.isArray(value)) { + const augment = hasProto + ? protoAugment + : copyAugment + augment(value, arrayMethods, arrayKeys) + this.observeArray(value) + } else { + this.walk(value) + } + } + + /** + * Walk through each property and convert them into + * getter/setters. This method should only be called when + * value type is Object. + */ + walk (obj: Object) { + const keys = Object.keys(obj) + for (let i = 0; i < keys.length; i++) { + defineReactive(obj, keys[i]) + } + } + + /** + * Observe a list of Array items. + */ + observeArray (items: Array<any>) { + for (let i = 0, l = items.length; i < l; i++) { + observe(items[i]) + } + } +} + +// helpers + +/** + * Augment an target Object or Array by intercepting + * the prototype chain using __proto__ + */ +function protoAugment (target, src: Object, keys: any) { + /* eslint-disable no-proto */ + target.__proto__ = src + /* eslint-enable no-proto */ +} + +/** + * Augment an target Object or Array by defining + * hidden properties. + */ +/* istanbul ignore next */ +function copyAugment (target: Object, src: Object, keys: Array<string>) { + for (let i = 0, l = keys.length; i < l; i++) { + const key = keys[i] + def(target, key, src[key]) + } +} + +/** + * Attempt to create an observer instance for a value, + * returns the new observer if successfully observed, + * or the existing observer if the value already has one. + */ +export function observe (value: any, asRootData: ?boolean): Observer | void { + if (!isObject(value) || value instanceof VNode) { + return + } + let ob: Observer | void + if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { + ob = value.__ob__ + } else if ( + shouldObserve && + !isServerRendering() && + (Array.isArray(value) || isPlainObject(value)) && + Object.isExtensible(value) && + !value._isVue + ) { + ob = new Observer(value) + } + if (asRootData && ob) { + ob.vmCount++ + } + return ob +} + +/** + * Define a reactive property on an Object. + */ +export function defineReactive ( + obj: Object, + key: string, + val: any, + customSetter?: ?Function, + shallow?: boolean +) { + const dep = new Dep() + + const property = Object.getOwnPropertyDescriptor(obj, key) + if (property && property.configurable === false) { + return + } + + // cater for pre-defined getter/setters + const getter = property && property.get + if (!getter && arguments.length === 2) { + val = obj[key] + } + const setter = property && property.set + + let childOb = !shallow && observe(val) + Object.defineProperty(obj, key, { + enumerable: true, + configurable: true, + get: function reactiveGetter () { + const value = getter ? getter.call(obj) : val + if (Dep.target) { + dep.depend() + if (childOb) { + childOb.dep.depend() + if (Array.isArray(value)) { + dependArray(value) + } + } + } + return value + }, + set: function reactiveSetter (newVal) { + const value = getter ? getter.call(obj) : val + /* eslint-disable no-self-compare */ + if (newVal === value || (newVal !== newVal && value !== value)) { + return + } + /* eslint-enable no-self-compare */ + if (process.env.NODE_ENV !== 'production' && customSetter) { + customSetter() + } + if (setter) { + setter.call(obj, newVal) + } else { + val = newVal + } + childOb = !shallow && observe(newVal) + dep.notify() + } + }) +} + +/** + * Set a property on an object. Adds the new property and + * triggers change notification if the property doesn't + * already exist. + */ +export function set (target: Array<any> | Object, key: any, val: any): any { + if (process.env.NODE_ENV !== 'production' && + (isUndef(target) || isPrimitive(target)) + ) { + warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`) + } + if (Array.isArray(target) && isValidArrayIndex(key)) { + target.length = Math.max(target.length, key) + target.splice(key, 1, val) + return val + } + if (key in target && !(key in Object.prototype)) { + target[key] = val + return val + } + const ob = (target: any).__ob__ + if (target._isVue || (ob && ob.vmCount)) { + process.env.NODE_ENV !== 'production' && warn( + 'Avoid adding reactive properties to a Vue instance or its root $data ' + + 'at runtime - declare it upfront in the data option.' + ) + return val + } + if (!ob) { + target[key] = val + return val + } + defineReactive(ob.value, key, val) + ob.dep.notify() + return val +} + +/** + * Delete a property and trigger change if necessary. + */ +export function del (target: Array<any> | Object, key: any) { + if (process.env.NODE_ENV !== 'production' && + (isUndef(target) || isPrimitive(target)) + ) { + warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`) + } + if (Array.isArray(target) && isValidArrayIndex(key)) { + target.splice(key, 1) + return + } + const ob = (target: any).__ob__ + if (target._isVue || (ob && ob.vmCount)) { + process.env.NODE_ENV !== 'production' && warn( + 'Avoid deleting properties on a Vue instance or its root $data ' + + '- just set it to null.' + ) + return + } + if (!hasOwn(target, key)) { + return + } + delete target[key] + if (!ob) { + return + } + ob.dep.notify() +} + +/** + * Collect dependencies on array elements when the array is touched, since + * we cannot intercept array element access like property getters. + */ +function dependArray (value: Array<any>) { + for (let e, i = 0, l = value.length; i < l; i++) { + e = value[i] + e && e.__ob__ && e.__ob__.dep.depend() + if (Array.isArray(e)) { + dependArray(e) + } + } +} diff --git a/node_modules/vue/src/core/observer/scheduler.js b/node_modules/vue/src/core/observer/scheduler.js new file mode 100644 index 00000000..fce86e5f --- /dev/null +++ b/node_modules/vue/src/core/observer/scheduler.js @@ -0,0 +1,148 @@ +/* @flow */ + +import type Watcher from './watcher' +import config from '../config' +import { callHook, activateChildComponent } from '../instance/lifecycle' + +import { + warn, + nextTick, + devtools +} from '../util/index' + +export const MAX_UPDATE_COUNT = 100 + +const queue: Array<Watcher> = [] +const activatedChildren: Array<Component> = [] +let has: { [key: number]: ?true } = {} +let circular: { [key: number]: number } = {} +let waiting = false +let flushing = false +let index = 0 + +/** + * Reset the scheduler's state. + */ +function resetSchedulerState () { + index = queue.length = activatedChildren.length = 0 + has = {} + if (process.env.NODE_ENV !== 'production') { + circular = {} + } + waiting = flushing = false +} + +/** + * Flush both queues and run the watchers. + */ +function flushSchedulerQueue () { + flushing = true + let watcher, id + + // Sort queue before flush. + // This ensures that: + // 1. Components are updated from parent to child. (because parent is always + // created before the child) + // 2. A component's user watchers are run before its render watcher (because + // user watchers are created before the render watcher) + // 3. If a component is destroyed during a parent component's watcher run, + // its watchers can be skipped. + queue.sort((a, b) => a.id - b.id) + + // do not cache length because more watchers might be pushed + // as we run existing watchers + for (index = 0; index < queue.length; index++) { + watcher = queue[index] + id = watcher.id + has[id] = null + watcher.run() + // in dev build, check and stop circular updates. + if (process.env.NODE_ENV !== 'production' && has[id] != null) { + circular[id] = (circular[id] || 0) + 1 + if (circular[id] > MAX_UPDATE_COUNT) { + warn( + 'You may have an infinite update loop ' + ( + watcher.user + ? `in watcher with expression "${watcher.expression}"` + : `in a component render function.` + ), + watcher.vm + ) + break + } + } + } + + // keep copies of post queues before resetting state + const activatedQueue = activatedChildren.slice() + const updatedQueue = queue.slice() + + resetSchedulerState() + + // call component updated and activated hooks + callActivatedHooks(activatedQueue) + callUpdatedHooks(updatedQueue) + + // devtool hook + /* istanbul ignore if */ + if (devtools && config.devtools) { + devtools.emit('flush') + } +} + +function callUpdatedHooks (queue) { + let i = queue.length + while (i--) { + const watcher = queue[i] + const vm = watcher.vm + if (vm._watcher === watcher && vm._isMounted) { + callHook(vm, 'updated') + } + } +} + +/** + * Queue a kept-alive component that was activated during patch. + * The queue will be processed after the entire tree has been patched. + */ +export function queueActivatedComponent (vm: Component) { + // setting _inactive to false here so that a render function can + // rely on checking whether it's in an inactive tree (e.g. router-view) + vm._inactive = false + activatedChildren.push(vm) +} + +function callActivatedHooks (queue) { + for (let i = 0; i < queue.length; i++) { + queue[i]._inactive = true + activateChildComponent(queue[i], true /* true */) + } +} + +/** + * Push a watcher into the watcher queue. + * Jobs with duplicate IDs will be skipped unless it's + * pushed when the queue is being flushed. + */ +export function queueWatcher (watcher: Watcher) { + const id = watcher.id + if (has[id] == null) { + has[id] = true + if (!flushing) { + queue.push(watcher) + } else { + // if already flushing, splice the watcher based on its id + // if already past its id, it will be run next immediately. + let i = queue.length - 1 + while (i > index && queue[i].id > watcher.id) { + i-- + } + queue.splice(i + 1, 0, watcher) + } + // queue the flush + if (!waiting) { + waiting = true + nextTick(flushSchedulerQueue) + } + } +} diff --git a/node_modules/vue/src/core/observer/traverse.js b/node_modules/vue/src/core/observer/traverse.js new file mode 100644 index 00000000..6b26524a --- /dev/null +++ b/node_modules/vue/src/core/observer/traverse.js @@ -0,0 +1,40 @@ +/* @flow */ + +import { _Set as Set, isObject } from '../util/index' +import type { SimpleSet } from '../util/index' +import VNode from '../vdom/vnode' + +const seenObjects = new Set() + +/** + * Recursively traverse an object to evoke all converted + * getters, so that every nested property inside the object + * is collected as a "deep" dependency. + */ +export function traverse (val: any) { + _traverse(val, seenObjects) + seenObjects.clear() +} + +function _traverse (val: any, seen: SimpleSet) { + let i, keys + const isA = Array.isArray(val) + if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) { + return + } + if (val.__ob__) { + const depId = val.__ob__.dep.id + if (seen.has(depId)) { + return + } + seen.add(depId) + } + if (isA) { + i = val.length + while (i--) _traverse(val[i], seen) + } else { + keys = Object.keys(val) + i = keys.length + while (i--) _traverse(val[keys[i]], seen) + } +} diff --git a/node_modules/vue/src/core/observer/watcher.js b/node_modules/vue/src/core/observer/watcher.js new file mode 100644 index 00000000..48a2e612 --- /dev/null +++ b/node_modules/vue/src/core/observer/watcher.js @@ -0,0 +1,240 @@ +/* @flow */ + +import { + warn, + remove, + isObject, + parsePath, + _Set as Set, + handleError +} from '../util/index' + +import { traverse } from './traverse' +import { queueWatcher } from './scheduler' +import Dep, { pushTarget, popTarget } from './dep' + +import type { SimpleSet } from '../util/index' + +let uid = 0 + +/** + * A watcher parses an expression, collects dependencies, + * and fires callback when the expression value changes. + * This is used for both the $watch() api and directives. + */ +export default class Watcher { + vm: Component; + expression: string; + cb: Function; + id: number; + deep: boolean; + user: boolean; + lazy: boolean; + sync: boolean; + dirty: boolean; + active: boolean; + deps: Array<Dep>; + newDeps: Array<Dep>; + depIds: SimpleSet; + newDepIds: SimpleSet; + getter: Function; + value: any; + + constructor ( + vm: Component, + expOrFn: string | Function, + cb: Function, + options?: ?Object, + isRenderWatcher?: boolean + ) { + this.vm = vm + if (isRenderWatcher) { + vm._watcher = this + } + vm._watchers.push(this) + // options + if (options) { + this.deep = !!options.deep + this.user = !!options.user + this.lazy = !!options.lazy + this.sync = !!options.sync + } else { + this.deep = this.user = this.lazy = this.sync = false + } + this.cb = cb + this.id = ++uid // uid for batching + this.active = true + this.dirty = this.lazy // for lazy watchers + this.deps = [] + this.newDeps = [] + this.depIds = new Set() + this.newDepIds = new Set() + this.expression = process.env.NODE_ENV !== 'production' + ? expOrFn.toString() + : '' + // parse expression for getter + if (typeof expOrFn === 'function') { + this.getter = expOrFn + } else { + this.getter = parsePath(expOrFn) + if (!this.getter) { + this.getter = function () {} + process.env.NODE_ENV !== 'production' && warn( + `Failed watching path: "${expOrFn}" ` + + 'Watcher only accepts simple dot-delimited paths. ' + + 'For full control, use a function instead.', + vm + ) + } + } + this.value = this.lazy + ? undefined + : this.get() + } + + /** + * Evaluate the getter, and re-collect dependencies. + */ + get () { + pushTarget(this) + let value + const vm = this.vm + try { + value = this.getter.call(vm, vm) + } catch (e) { + if (this.user) { + handleError(e, vm, `getter for watcher "${this.expression}"`) + } else { + throw e + } + } finally { + // "touch" every property so they are all tracked as + // dependencies for deep watching + if (this.deep) { + traverse(value) + } + popTarget() + this.cleanupDeps() + } + return value + } + + /** + * Add a dependency to this directive. + */ + addDep (dep: Dep) { + const id = dep.id + if (!this.newDepIds.has(id)) { + this.newDepIds.add(id) + this.newDeps.push(dep) + if (!this.depIds.has(id)) { + dep.addSub(this) + } + } + } + + /** + * Clean up for dependency collection. + */ + cleanupDeps () { + let i = this.deps.length + while (i--) { + const dep = this.deps[i] + if (!this.newDepIds.has(dep.id)) { + dep.removeSub(this) + } + } + let tmp = this.depIds + this.depIds = this.newDepIds + this.newDepIds = tmp + this.newDepIds.clear() + tmp = this.deps + this.deps = this.newDeps + this.newDeps = tmp + this.newDeps.length = 0 + } + + /** + * Subscriber interface. + * Will be called when a dependency changes. + */ + update () { + /* istanbul ignore else */ + if (this.lazy) { + this.dirty = true + } else if (this.sync) { + this.run() + } else { + queueWatcher(this) + } + } + + /** + * Scheduler job interface. + * Will be called by the scheduler. + */ + run () { + if (this.active) { + const value = this.get() + if ( + value !== this.value || + // Deep watchers and watchers on Object/Arrays should fire even + // when the value is the same, because the value may + // have mutated. + isObject(value) || + this.deep + ) { + // set new value + const oldValue = this.value + this.value = value + if (this.user) { + try { + this.cb.call(this.vm, value, oldValue) + } catch (e) { + handleError(e, this.vm, `callback for watcher "${this.expression}"`) + } + } else { + this.cb.call(this.vm, value, oldValue) + } + } + } + } + + /** + * Evaluate the value of the watcher. + * This only gets called for lazy watchers. + */ + evaluate () { + this.value = this.get() + this.dirty = false + } + + /** + * Depend on all deps collected by this watcher. + */ + depend () { + let i = this.deps.length + while (i--) { + this.deps[i].depend() + } + } + + /** + * Remove self from all dependencies' subscriber list. + */ + teardown () { + if (this.active) { + // remove self from vm's watcher list + // this is a somewhat expensive operation so we skip it + // if the vm is being destroyed. + if (!this.vm._isBeingDestroyed) { + remove(this.vm._watchers, this) + } + let i = this.deps.length + while (i--) { + this.deps[i].removeSub(this) + } + this.active = false + } + } +} diff --git a/node_modules/vue/src/core/util/debug.js b/node_modules/vue/src/core/util/debug.js new file mode 100644 index 00000000..6b7315f4 --- /dev/null +++ b/node_modules/vue/src/core/util/debug.js @@ -0,0 +1,100 @@ +/* @flow */ + +import config from '../config' +import { noop } from 'shared/util' + +export let warn = noop +export let tip = noop +export let generateComponentTrace = (noop: any) // work around flow check +export let formatComponentName = (noop: any) + +if (process.env.NODE_ENV !== 'production') { + const hasConsole = typeof console !== 'undefined' + const classifyRE = /(?:^|[-_])(\w)/g + const classify = str => str + .replace(classifyRE, c => c.toUpperCase()) + .replace(/[-_]/g, '') + + warn = (msg, vm) => { + const trace = vm ? generateComponentTrace(vm) : '' + + if (config.warnHandler) { + config.warnHandler.call(null, msg, vm, trace) + } else if (hasConsole && (!config.silent)) { + console.error(`[Vue warn]: ${msg}${trace}`) + } + } + + tip = (msg, vm) => { + if (hasConsole && (!config.silent)) { + console.warn(`[Vue tip]: ${msg}` + ( + vm ? generateComponentTrace(vm) : '' + )) + } + } + + formatComponentName = (vm, includeFile) => { + if (vm.$root === vm) { + return '<Root>' + } + const options = typeof vm === 'function' && vm.cid != null + ? vm.options + : vm._isVue + ? vm.$options || vm.constructor.options + : vm || {} + let name = options.name || options._componentTag + const file = options.__file + if (!name && file) { + const match = file.match(/([^/\\]+)\.vue$/) + name = match && match[1] + } + + return ( + (name ? `<${classify(name)}>` : `<Anonymous>`) + + (file && includeFile !== false ? ` at ${file}` : '') + ) + } + + const repeat = (str, n) => { + let res = '' + while (n) { + if (n % 2 === 1) res += str + if (n > 1) str += str + n >>= 1 + } + return res + } + + generateComponentTrace = vm => { + if (vm._isVue && vm.$parent) { + const tree = [] + let currentRecursiveSequence = 0 + while (vm) { + if (tree.length > 0) { + const last = tree[tree.length - 1] + if (last.constructor === vm.constructor) { + currentRecursiveSequence++ + vm = vm.$parent + continue + } else if (currentRecursiveSequence > 0) { + tree[tree.length - 1] = [last, currentRecursiveSequence] + currentRecursiveSequence = 0 + } + } + tree.push(vm) + vm = vm.$parent + } + return '\n\nfound in\n\n' + tree + .map((vm, i) => `${ + i === 0 ? '---> ' : repeat(' ', 5 + i * 2) + }${ + Array.isArray(vm) + ? `${formatComponentName(vm[0])}... (${vm[1]} recursive calls)` + : formatComponentName(vm) + }`) + .join('\n') + } else { + return `\n\n(found in ${formatComponentName(vm)})` + } + } +} diff --git a/node_modules/vue/src/core/util/env.js b/node_modules/vue/src/core/util/env.js new file mode 100644 index 00000000..07cadd9c --- /dev/null +++ b/node_modules/vue/src/core/util/env.js @@ -0,0 +1,95 @@ +/* @flow */ + +// can we use __proto__? +export const hasProto = '__proto__' in {} + +// Browser environment sniffing +export const inBrowser = typeof window !== 'undefined' +export const inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform +export const weexPlatform = inWeex && WXEnvironment.platform.toLowerCase() +export const UA = inBrowser && window.navigator.userAgent.toLowerCase() +export const isIE = UA && /msie|trident/.test(UA) +export const isIE9 = UA && UA.indexOf('msie 9.0') > 0 +export const isEdge = UA && UA.indexOf('edge/') > 0 +export const isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android') +export const isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios') +export const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge + +// Firefox has a "watch" function on Object.prototype... +export const nativeWatch = ({}).watch + +export let supportsPassive = false +if (inBrowser) { + try { + const opts = {} + Object.defineProperty(opts, 'passive', ({ + get () { + /* istanbul ignore next */ + supportsPassive = true + } + }: Object)) // https://github.com/facebook/flow/issues/285 + window.addEventListener('test-passive', null, opts) + } catch (e) {} +} + +// this needs to be lazy-evaled because vue may be required before +// vue-server-renderer can set VUE_ENV +let _isServer +export const isServerRendering = () => { + if (_isServer === undefined) { + /* istanbul ignore if */ + if (!inBrowser && !inWeex && typeof global !== 'undefined') { + // detect presence of vue-server-renderer and avoid + // Webpack shimming the process + _isServer = global['process'].env.VUE_ENV === 'server' + } else { + _isServer = false + } + } + return _isServer +} + +// detect devtools +export const devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__ + +/* istanbul ignore next */ +export function isNative (Ctor: any): boolean { + return typeof Ctor === 'function' && /native code/.test(Ctor.toString()) +} + +export const hasSymbol = + typeof Symbol !== 'undefined' && isNative(Symbol) && + typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys) + +let _Set +/* istanbul ignore if */ // $flow-disable-line +if (typeof Set !== 'undefined' && isNative(Set)) { + // use native Set when available. + _Set = Set +} else { + // a non-standard Set polyfill that only works with primitive keys. + _Set = class Set implements SimpleSet { + set: Object; + constructor () { + this.set = Object.create(null) + } + has (key: string | number) { + return this.set[key] === true + } + add (key: string | number) { + this.set[key] = true + } + clear () { + this.set = Object.create(null) + } + } +} + +interface SimpleSet { + has(key: string | number): boolean; + add(key: string | number): mixed; + clear(): void; +} + +export { _Set } +export type { SimpleSet } diff --git a/node_modules/vue/src/core/util/error.js b/node_modules/vue/src/core/util/error.js new file mode 100644 index 00000000..b7d05a6d --- /dev/null +++ b/node_modules/vue/src/core/util/error.js @@ -0,0 +1,48 @@ +/* @flow */ + +import config from '../config' +import { warn } from './debug' +import { inBrowser, inWeex } from './env' + +export function handleError (err: Error, vm: any, info: string) { + if (vm) { + let cur = vm + while ((cur = cur.$parent)) { + const hooks = cur.$options.errorCaptured + if (hooks) { + for (let i = 0; i < hooks.length; i++) { + try { + const capture = hooks[i].call(cur, err, vm, info) === false + if (capture) return + } catch (e) { + globalHandleError(e, cur, 'errorCaptured hook') + } + } + } + } + } + globalHandleError(err, vm, info) +} + +function globalHandleError (err, vm, info) { + if (config.errorHandler) { + try { + return config.errorHandler.call(null, err, vm, info) + } catch (e) { + logError(e, null, 'config.errorHandler') + } + } + logError(err, vm, info) +} + +function logError (err, vm, info) { + if (process.env.NODE_ENV !== 'production') { + warn(`Error in ${info}: "${err.toString()}"`, vm) + } + /* istanbul ignore else */ + if ((inBrowser || inWeex) && typeof console !== 'undefined') { + console.error(err) + } else { + throw err + } +} diff --git a/node_modules/vue/src/core/util/index.js b/node_modules/vue/src/core/util/index.js new file mode 100644 index 00000000..115d77cd --- /dev/null +++ b/node_modules/vue/src/core/util/index.js @@ -0,0 +1,11 @@ +/* @flow */ + +export * from 'shared/util' +export * from './lang' +export * from './env' +export * from './options' +export * from './debug' +export * from './props' +export * from './error' +export * from './next-tick' +export { defineReactive } from '../observer/index' diff --git a/node_modules/vue/src/core/util/lang.js b/node_modules/vue/src/core/util/lang.js new file mode 100644 index 00000000..96b8219a --- /dev/null +++ b/node_modules/vue/src/core/util/lang.js @@ -0,0 +1,39 @@ +/* @flow */ + +/** + * Check if a string starts with $ or _ + */ +export function isReserved (str: string): boolean { + const c = (str + '').charCodeAt(0) + return c === 0x24 || c === 0x5F +} + +/** + * Define a property. + */ +export function def (obj: Object, key: string, val: any, enumerable?: boolean) { + Object.defineProperty(obj, key, { + value: val, + enumerable: !!enumerable, + writable: true, + configurable: true + }) +} + +/** + * Parse simple path. + */ +const bailRE = /[^\w.$]/ +export function parsePath (path: string): any { + if (bailRE.test(path)) { + return + } + const segments = path.split('.') + return function (obj) { + for (let i = 0; i < segments.length; i++) { + if (!obj) return + obj = obj[segments[i]] + } + return obj + } +} diff --git a/node_modules/vue/src/core/util/next-tick.js b/node_modules/vue/src/core/util/next-tick.js new file mode 100644 index 00000000..07a9e841 --- /dev/null +++ b/node_modules/vue/src/core/util/next-tick.js @@ -0,0 +1,117 @@ +/* @flow */ +/* globals MessageChannel */ + +import { noop } from 'shared/util' +import { handleError } from './error' +import { isIOS, isNative } from './env' + +const callbacks = [] +let pending = false + +function flushCallbacks () { + pending = false + const copies = callbacks.slice(0) + callbacks.length = 0 + for (let i = 0; i < copies.length; i++) { + copies[i]() + } +} + +// Here we have async deferring wrappers using both microtasks and (macro) tasks. +// In < 2.4 we used microtasks everywhere, but there are some scenarios where +// microtasks have too high a priority and fire in between supposedly +// sequential events (e.g. #4521, #6690) or even between bubbling of the same +// event (#6566). However, using (macro) tasks everywhere also has subtle problems +// when state is changed right before repaint (e.g. #6813, out-in transitions). +// Here we use microtask by default, but expose a way to force (macro) task when +// needed (e.g. in event handlers attached by v-on). +let microTimerFunc +let macroTimerFunc +let useMacroTask = false + +// Determine (macro) task defer implementation. +// Technically setImmediate should be the ideal choice, but it's only available +// in IE. The only polyfill that consistently queues the callback after all DOM +// events triggered in the same loop is by using MessageChannel. +/* istanbul ignore if */ +if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { + macroTimerFunc = () => { + setImmediate(flushCallbacks) + } +} else if (typeof MessageChannel !== 'undefined' && ( + isNative(MessageChannel) || + // PhantomJS + MessageChannel.toString() === '[object MessageChannelConstructor]' +)) { + const channel = new MessageChannel() + const port = channel.port2 + channel.port1.onmessage = flushCallbacks + macroTimerFunc = () => { + port.postMessage(1) + } +} else { + /* istanbul ignore next */ + macroTimerFunc = () => { + setTimeout(flushCallbacks, 0) + } +} + +// Determine microtask defer implementation. +/* istanbul ignore next, $flow-disable-line */ +if (typeof Promise !== 'undefined' && isNative(Promise)) { + const p = Promise.resolve() + microTimerFunc = () => { + p.then(flushCallbacks) + // in problematic UIWebViews, Promise.then doesn't completely break, but + // it can get stuck in a weird state where callbacks are pushed into the + // microtask queue but the queue isn't being flushed, until the browser + // needs to do some other work, e.g. handle a timer. Therefore we can + // "force" the microtask queue to be flushed by adding an empty timer. + if (isIOS) setTimeout(noop) + } +} else { + // fallback to macro + microTimerFunc = macroTimerFunc +} + +/** + * Wrap a function so that if any code inside triggers state change, + * the changes are queued using a (macro) task instead of a microtask. + */ +export function withMacroTask (fn: Function): Function { + return fn._withTask || (fn._withTask = function () { + useMacroTask = true + const res = fn.apply(null, arguments) + useMacroTask = false + return res + }) +} + +export function nextTick (cb?: Function, ctx?: Object) { + let _resolve + callbacks.push(() => { + if (cb) { + try { + cb.call(ctx) + } catch (e) { + handleError(e, ctx, 'nextTick') + } + } else if (_resolve) { + _resolve(ctx) + } + }) + if (!pending) { + pending = true + if (useMacroTask) { + macroTimerFunc() + } else { + microTimerFunc() + } + } + // $flow-disable-line + if (!cb && typeof Promise !== 'undefined') { + return new Promise(resolve => { + _resolve = resolve + }) + } +} diff --git a/node_modules/vue/src/core/util/options.js b/node_modules/vue/src/core/util/options.js new file mode 100644 index 00000000..5f0d10d3 --- /dev/null +++ b/node_modules/vue/src/core/util/options.js @@ -0,0 +1,438 @@ +/* @flow */ + +import config from '../config' +import { warn } from './debug' +import { nativeWatch } from './env' +import { set } from '../observer/index' + +import { + ASSET_TYPES, + LIFECYCLE_HOOKS +} from 'shared/constants' + +import { + extend, + hasOwn, + camelize, + toRawType, + capitalize, + isBuiltInTag, + isPlainObject +} from 'shared/util' + +/** + * Option overwriting strategies are functions that handle + * how to merge a parent option value and a child option + * value into the final value. + */ +const strats = config.optionMergeStrategies + +/** + * Options with restrictions + */ +if (process.env.NODE_ENV !== 'production') { + strats.el = strats.propsData = function (parent, child, vm, key) { + if (!vm) { + warn( + `option "${key}" can only be used during instance ` + + 'creation with the `new` keyword.' + ) + } + return defaultStrat(parent, child) + } +} + +/** + * Helper that recursively merges two data objects together. + */ +function mergeData (to: Object, from: ?Object): Object { + if (!from) return to + let key, toVal, fromVal + const keys = Object.keys(from) + for (let i = 0; i < keys.length; i++) { + key = keys[i] + toVal = to[key] + fromVal = from[key] + if (!hasOwn(to, key)) { + set(to, key, fromVal) + } else if (isPlainObject(toVal) && isPlainObject(fromVal)) { + mergeData(toVal, fromVal) + } + } + return to +} + +/** + * Data + */ +export function mergeDataOrFn ( + parentVal: any, + childVal: any, + vm?: Component +): ?Function { + if (!vm) { + // in a Vue.extend merge, both should be functions + if (!childVal) { + return parentVal + } + if (!parentVal) { + return childVal + } + // when parentVal & childVal are both present, + // we need to return a function that returns the + // merged result of both functions... no need to + // check if parentVal is a function here because + // it has to be a function to pass previous merges. + return function mergedDataFn () { + return mergeData( + typeof childVal === 'function' ? childVal.call(this, this) : childVal, + typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal + ) + } + } else { + return function mergedInstanceDataFn () { + // instance merge + const instanceData = typeof childVal === 'function' + ? childVal.call(vm, vm) + : childVal + const defaultData = typeof parentVal === 'function' + ? parentVal.call(vm, vm) + : parentVal + if (instanceData) { + return mergeData(instanceData, defaultData) + } else { + return defaultData + } + } + } +} + +strats.data = function ( + parentVal: any, + childVal: any, + vm?: Component +): ?Function { + if (!vm) { + if (childVal && typeof childVal !== 'function') { + process.env.NODE_ENV !== 'production' && warn( + 'The "data" option should be a function ' + + 'that returns a per-instance value in component ' + + 'definitions.', + vm + ) + + return parentVal + } + return mergeDataOrFn(parentVal, childVal) + } + + return mergeDataOrFn(parentVal, childVal, vm) +} + +/** + * Hooks and props are merged as arrays. + */ +function mergeHook ( + parentVal: ?Array<Function>, + childVal: ?Function | ?Array<Function> +): ?Array<Function> { + return childVal + ? parentVal + ? parentVal.concat(childVal) + : Array.isArray(childVal) + ? childVal + : [childVal] + : parentVal +} + +LIFECYCLE_HOOKS.forEach(hook => { + strats[hook] = mergeHook +}) + +/** + * Assets + * + * When a vm is present (instance creation), we need to do + * a three-way merge between constructor options, instance + * options and parent options. + */ +function mergeAssets ( + parentVal: ?Object, + childVal: ?Object, + vm?: Component, + key: string +): Object { + const res = Object.create(parentVal || null) + if (childVal) { + process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm) + return extend(res, childVal) + } else { + return res + } +} + +ASSET_TYPES.forEach(function (type) { + strats[type + 's'] = mergeAssets +}) + +/** + * Watchers. + * + * Watchers hashes should not overwrite one + * another, so we merge them as arrays. + */ +strats.watch = function ( + parentVal: ?Object, + childVal: ?Object, + vm?: Component, + key: string +): ?Object { + // work around Firefox's Object.prototype.watch... + if (parentVal === nativeWatch) parentVal = undefined + if (childVal === nativeWatch) childVal = undefined + /* istanbul ignore if */ + if (!childVal) return Object.create(parentVal || null) + if (process.env.NODE_ENV !== 'production') { + assertObjectType(key, childVal, vm) + } + if (!parentVal) return childVal + const ret = {} + extend(ret, parentVal) + for (const key in childVal) { + let parent = ret[key] + const child = childVal[key] + if (parent && !Array.isArray(parent)) { + parent = [parent] + } + ret[key] = parent + ? parent.concat(child) + : Array.isArray(child) ? child : [child] + } + return ret +} + +/** + * Other object hashes. + */ +strats.props = +strats.methods = +strats.inject = +strats.computed = function ( + parentVal: ?Object, + childVal: ?Object, + vm?: Component, + key: string +): ?Object { + if (childVal && process.env.NODE_ENV !== 'production') { + assertObjectType(key, childVal, vm) + } + if (!parentVal) return childVal + const ret = Object.create(null) + extend(ret, parentVal) + if (childVal) extend(ret, childVal) + return ret +} +strats.provide = mergeDataOrFn + +/** + * Default strategy. + */ +const defaultStrat = function (parentVal: any, childVal: any): any { + return childVal === undefined + ? parentVal + : childVal +} + +/** + * Validate component names + */ +function checkComponents (options: Object) { + for (const key in options.components) { + validateComponentName(key) + } +} + +export function validateComponentName (name: string) { + if (!/^[a-zA-Z][\w-]*$/.test(name)) { + warn( + 'Invalid component name: "' + name + '". Component names ' + + 'can only contain alphanumeric characters and the hyphen, ' + + 'and must start with a letter.' + ) + } + if (isBuiltInTag(name) || config.isReservedTag(name)) { + warn( + 'Do not use built-in or reserved HTML elements as component ' + + 'id: ' + name + ) + } +} + +/** + * Ensure all props option syntax are normalized into the + * Object-based format. + */ +function normalizeProps (options: Object, vm: ?Component) { + const props = options.props + if (!props) return + const res = {} + let i, val, name + if (Array.isArray(props)) { + i = props.length + while (i--) { + val = props[i] + if (typeof val === 'string') { + name = camelize(val) + res[name] = { type: null } + } else if (process.env.NODE_ENV !== 'production') { + warn('props must be strings when using array syntax.') + } + } + } else if (isPlainObject(props)) { + for (const key in props) { + val = props[key] + name = camelize(key) + res[name] = isPlainObject(val) + ? val + : { type: val } + } + } else if (process.env.NODE_ENV !== 'production') { + warn( + `Invalid value for option "props": expected an Array or an Object, ` + + `but got ${toRawType(props)}.`, + vm + ) + } + options.props = res +} + +/** + * Normalize all injections into Object-based format + */ +function normalizeInject (options: Object, vm: ?Component) { + const inject = options.inject + if (!inject) return + const normalized = options.inject = {} + if (Array.isArray(inject)) { + for (let i = 0; i < inject.length; i++) { + normalized[inject[i]] = { from: inject[i] } + } + } else if (isPlainObject(inject)) { + for (const key in inject) { + const val = inject[key] + normalized[key] = isPlainObject(val) + ? extend({ from: key }, val) + : { from: val } + } + } else if (process.env.NODE_ENV !== 'production') { + warn( + `Invalid value for option "inject": expected an Array or an Object, ` + + `but got ${toRawType(inject)}.`, + vm + ) + } +} + +/** + * Normalize raw function directives into object format. + */ +function normalizeDirectives (options: Object) { + const dirs = options.directives + if (dirs) { + for (const key in dirs) { + const def = dirs[key] + if (typeof def === 'function') { + dirs[key] = { bind: def, update: def } + } + } + } +} + +function assertObjectType (name: string, value: any, vm: ?Component) { + if (!isPlainObject(value)) { + warn( + `Invalid value for option "${name}": expected an Object, ` + + `but got ${toRawType(value)}.`, + vm + ) + } +} + +/** + * Merge two option objects into a new one. + * Core utility used in both instantiation and inheritance. + */ +export function mergeOptions ( + parent: Object, + child: Object, + vm?: Component +): Object { + if (process.env.NODE_ENV !== 'production') { + checkComponents(child) + } + + if (typeof child === 'function') { + child = child.options + } + + normalizeProps(child, vm) + normalizeInject(child, vm) + normalizeDirectives(child) + const extendsFrom = child.extends + if (extendsFrom) { + parent = mergeOptions(parent, extendsFrom, vm) + } + if (child.mixins) { + for (let i = 0, l = child.mixins.length; i < l; i++) { + parent = mergeOptions(parent, child.mixins[i], vm) + } + } + const options = {} + let key + for (key in parent) { + mergeField(key) + } + for (key in child) { + if (!hasOwn(parent, key)) { + mergeField(key) + } + } + function mergeField (key) { + const strat = strats[key] || defaultStrat + options[key] = strat(parent[key], child[key], vm, key) + } + return options +} + +/** + * Resolve an asset. + * This function is used because child instances need access + * to assets defined in its ancestor chain. + */ +export function resolveAsset ( + options: Object, + type: string, + id: string, + warnMissing?: boolean +): any { + /* istanbul ignore if */ + if (typeof id !== 'string') { + return + } + const assets = options[type] + // check local registration variations first + if (hasOwn(assets, id)) return assets[id] + const camelizedId = camelize(id) + if (hasOwn(assets, camelizedId)) return assets[camelizedId] + const PascalCaseId = capitalize(camelizedId) + if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId] + // fallback to prototype chain + const res = assets[id] || assets[camelizedId] || assets[PascalCaseId] + if (process.env.NODE_ENV !== 'production' && warnMissing && !res) { + warn( + 'Failed to resolve ' + type.slice(0, -1) + ': ' + id, + options + ) + } + return res +} diff --git a/node_modules/vue/src/core/util/perf.js b/node_modules/vue/src/core/util/perf.js new file mode 100644 index 00000000..4a537b2d --- /dev/null +++ b/node_modules/vue/src/core/util/perf.js @@ -0,0 +1,24 @@ +import { inBrowser } from './env' + +export let mark +export let measure + +if (process.env.NODE_ENV !== 'production') { + const perf = inBrowser && window.performance + /* istanbul ignore if */ + if ( + perf && + perf.mark && + perf.measure && + perf.clearMarks && + perf.clearMeasures + ) { + mark = tag => perf.mark(tag) + measure = (name, startTag, endTag) => { + perf.measure(name, startTag, endTag) + perf.clearMarks(startTag) + perf.clearMarks(endTag) + perf.clearMeasures(name) + } + } +} diff --git a/node_modules/vue/src/core/util/props.js b/node_modules/vue/src/core/util/props.js new file mode 100644 index 00000000..a92643f9 --- /dev/null +++ b/node_modules/vue/src/core/util/props.js @@ -0,0 +1,202 @@ +/* @flow */ + +import { warn } from './debug' +import { observe, toggleObserving, shouldObserve } from '../observer/index' +import { + hasOwn, + isObject, + toRawType, + hyphenate, + capitalize, + isPlainObject +} from 'shared/util' + +type PropOptions = { + type: Function | Array<Function> | null, + default: any, + required: ?boolean, + validator: ?Function +}; + +export function validateProp ( + key: string, + propOptions: Object, + propsData: Object, + vm?: Component +): any { + const prop = propOptions[key] + const absent = !hasOwn(propsData, key) + let value = propsData[key] + // boolean casting + const booleanIndex = getTypeIndex(Boolean, prop.type) + if (booleanIndex > -1) { + if (absent && !hasOwn(prop, 'default')) { + value = false + } else if (value === '' || value === hyphenate(key)) { + // only cast empty string / same name to boolean if + // boolean has higher priority + const stringIndex = getTypeIndex(String, prop.type) + if (stringIndex < 0 || booleanIndex < stringIndex) { + value = true + } + } + } + // check default value + if (value === undefined) { + value = getPropDefaultValue(vm, prop, key) + // since the default value is a fresh copy, + // make sure to observe it. + const prevShouldObserve = shouldObserve + toggleObserving(true) + observe(value) + toggleObserving(prevShouldObserve) + } + if ( + process.env.NODE_ENV !== 'production' && + // skip validation for weex recycle-list child component props + !(__WEEX__ && isObject(value) && ('@binding' in value)) + ) { + assertProp(prop, key, value, vm, absent) + } + return value +} + +/** + * Get the default value of a prop. + */ +function getPropDefaultValue (vm: ?Component, prop: PropOptions, key: string): any { + // no default, return undefined + if (!hasOwn(prop, 'default')) { + return undefined + } + const def = prop.default + // warn against non-factory defaults for Object & Array + if (process.env.NODE_ENV !== 'production' && isObject(def)) { + warn( + 'Invalid default value for prop "' + key + '": ' + + 'Props with type Object/Array must use a factory function ' + + 'to return the default value.', + vm + ) + } + // the raw prop value was also undefined from previous render, + // return previous default value to avoid unnecessary watcher trigger + if (vm && vm.$options.propsData && + vm.$options.propsData[key] === undefined && + vm._props[key] !== undefined + ) { + return vm._props[key] + } + // call factory function for non-Function types + // a value is Function if its prototype is function even across different execution context + return typeof def === 'function' && getType(prop.type) !== 'Function' + ? def.call(vm) + : def +} + +/** + * Assert whether a prop is valid. + */ +function assertProp ( + prop: PropOptions, + name: string, + value: any, + vm: ?Component, + absent: boolean +) { + if (prop.required && absent) { + warn( + 'Missing required prop: "' + name + '"', + vm + ) + return + } + if (value == null && !prop.required) { + return + } + let type = prop.type + let valid = !type || type === true + const expectedTypes = [] + if (type) { + if (!Array.isArray(type)) { + type = [type] + } + for (let i = 0; i < type.length && !valid; i++) { + const assertedType = assertType(value, type[i]) + expectedTypes.push(assertedType.expectedType || '') + valid = assertedType.valid + } + } + if (!valid) { + warn( + `Invalid prop: type check failed for prop "${name}".` + + ` Expected ${expectedTypes.map(capitalize).join(', ')}` + + `, got ${toRawType(value)}.`, + vm + ) + return + } + const validator = prop.validator + if (validator) { + if (!validator(value)) { + warn( + 'Invalid prop: custom validator check failed for prop "' + name + '".', + vm + ) + } + } +} + +const simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/ + +function assertType (value: any, type: Function): { + valid: boolean; + expectedType: string; +} { + let valid + const expectedType = getType(type) + if (simpleCheckRE.test(expectedType)) { + const t = typeof value + valid = t === expectedType.toLowerCase() + // for primitive wrapper objects + if (!valid && t === 'object') { + valid = value instanceof type + } + } else if (expectedType === 'Object') { + valid = isPlainObject(value) + } else if (expectedType === 'Array') { + valid = Array.isArray(value) + } else { + valid = value instanceof type + } + return { + valid, + expectedType + } +} + +/** + * Use function string name to check built-in types, + * because a simple equality check will fail when running + * across different vms / iframes. + */ +function getType (fn) { + const match = fn && fn.toString().match(/^\s*function (\w+)/) + return match ? match[1] : '' +} + +function isSameType (a, b) { + return getType(a) === getType(b) +} + +function getTypeIndex (type, expectedTypes): number { + if (!Array.isArray(expectedTypes)) { + return isSameType(expectedTypes, type) ? 0 : -1 + } + for (let i = 0, len = expectedTypes.length; i < len; i++) { + if (isSameType(expectedTypes[i], type)) { + return i + } + } + return -1 +} diff --git a/node_modules/vue/src/core/vdom/create-component.js b/node_modules/vue/src/core/vdom/create-component.js new file mode 100644 index 00000000..89578224 --- /dev/null +++ b/node_modules/vue/src/core/vdom/create-component.js @@ -0,0 +1,257 @@ +/* @flow */ + +import VNode from './vnode' +import { resolveConstructorOptions } from 'core/instance/init' +import { queueActivatedComponent } from 'core/observer/scheduler' +import { createFunctionalComponent } from './create-functional-component' + +import { + warn, + isDef, + isUndef, + isTrue, + isObject +} from '../util/index' + +import { + resolveAsyncComponent, + createAsyncPlaceholder, + extractPropsFromVNodeData +} from './helpers/index' + +import { + callHook, + activeInstance, + updateChildComponent, + activateChildComponent, + deactivateChildComponent +} from '../instance/lifecycle' + +import { + isRecyclableComponent, + renderRecyclableComponentTemplate +} from 'weex/runtime/recycle-list/render-component-template' + +// inline hooks to be invoked on component VNodes during patch +const componentVNodeHooks = { + init ( + vnode: VNodeWithData, + hydrating: boolean, + parentElm: ?Node, + refElm: ?Node + ): ?boolean { + if ( + vnode.componentInstance && + !vnode.componentInstance._isDestroyed && + vnode.data.keepAlive + ) { + // kept-alive components, treat as a patch + const mountedNode: any = vnode // work around flow + componentVNodeHooks.prepatch(mountedNode, mountedNode) + } else { + const child = vnode.componentInstance = createComponentInstanceForVnode( + vnode, + activeInstance, + parentElm, + refElm + ) + child.$mount(hydrating ? vnode.elm : undefined, hydrating) + } + }, + + prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) { + const options = vnode.componentOptions + const child = vnode.componentInstance = oldVnode.componentInstance + updateChildComponent( + child, + options.propsData, // updated props + options.listeners, // updated listeners + vnode, // new parent vnode + options.children // new children + ) + }, + + insert (vnode: MountedComponentVNode) { + const { context, componentInstance } = vnode + if (!componentInstance._isMounted) { + componentInstance._isMounted = true + callHook(componentInstance, 'mounted') + } + if (vnode.data.keepAlive) { + if (context._isMounted) { + // vue-router#1212 + // During updates, a kept-alive component's child components may + // change, so directly walking the tree here may call activated hooks + // on incorrect children. Instead we push them into a queue which will + // be processed after the whole patch process ended. + queueActivatedComponent(componentInstance) + } else { + activateChildComponent(componentInstance, true /* direct */) + } + } + }, + + destroy (vnode: MountedComponentVNode) { + const { componentInstance } = vnode + if (!componentInstance._isDestroyed) { + if (!vnode.data.keepAlive) { + componentInstance.$destroy() + } else { + deactivateChildComponent(componentInstance, true /* direct */) + } + } + } +} + +const hooksToMerge = Object.keys(componentVNodeHooks) + +export function createComponent ( + Ctor: Class<Component> | Function | Object | void, + data: ?VNodeData, + context: Component, + children: ?Array<VNode>, + tag?: string +): VNode | Array<VNode> | void { + if (isUndef(Ctor)) { + return + } + + const baseCtor = context.$options._base + + // plain options object: turn it into a constructor + if (isObject(Ctor)) { + Ctor = baseCtor.extend(Ctor) + } + + // if at this stage it's not a constructor or an async component factory, + // reject. + if (typeof Ctor !== 'function') { + if (process.env.NODE_ENV !== 'production') { + warn(`Invalid Component definition: ${String(Ctor)}`, context) + } + return + } + + // async component + let asyncFactory + if (isUndef(Ctor.cid)) { + asyncFactory = Ctor + Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context) + if (Ctor === undefined) { + // return a placeholder node for async component, which is rendered + // as a comment node but preserves all the raw information for the node. + // the information will be used for async server-rendering and hydration. + return createAsyncPlaceholder( + asyncFactory, + data, + context, + children, + tag + ) + } + } + + data = data || {} + + // resolve constructor options in case global mixins are applied after + // component constructor creation + resolveConstructorOptions(Ctor) + + // transform component v-model data into props & events + if (isDef(data.model)) { + transformModel(Ctor.options, data) + } + + // extract props + const propsData = extractPropsFromVNodeData(data, Ctor, tag) + + // functional component + if (isTrue(Ctor.options.functional)) { + return createFunctionalComponent(Ctor, propsData, data, context, children) + } + + // extract listeners, since these needs to be treated as + // child component listeners instead of DOM listeners + const listeners = data.on + // replace with listeners with .native modifier + // so it gets processed during parent component patch. + data.on = data.nativeOn + + if (isTrue(Ctor.options.abstract)) { + // abstract components do not keep anything + // other than props & listeners & slot + + // work around flow + const slot = data.slot + data = {} + if (slot) { + data.slot = slot + } + } + + // install component management hooks onto the placeholder node + installComponentHooks(data) + + // return a placeholder vnode + const name = Ctor.options.name || tag + const vnode = new VNode( + `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`, + data, undefined, undefined, undefined, context, + { Ctor, propsData, listeners, tag, children }, + asyncFactory + ) + + // Weex specific: invoke recycle-list optimized @render function for + // extracting cell-slot template. + // https://github.com/Hanks10100/weex-native-directive/tree/master/component + /* istanbul ignore if */ + if (__WEEX__ && isRecyclableComponent(vnode)) { + return renderRecyclableComponentTemplate(vnode) + } + + return vnode +} + +export function createComponentInstanceForVnode ( + vnode: any, // we know it's MountedComponentVNode but flow doesn't + parent: any, // activeInstance in lifecycle state + parentElm?: ?Node, + refElm?: ?Node +): Component { + const options: InternalComponentOptions = { + _isComponent: true, + parent, + _parentVnode: vnode, + _parentElm: parentElm || null, + _refElm: refElm || null + } + // check inline-template render functions + const inlineTemplate = vnode.data.inlineTemplate + if (isDef(inlineTemplate)) { + options.render = inlineTemplate.render + options.staticRenderFns = inlineTemplate.staticRenderFns + } + return new vnode.componentOptions.Ctor(options) +} + +function installComponentHooks (data: VNodeData) { + const hooks = data.hook || (data.hook = {}) + for (let i = 0; i < hooksToMerge.length; i++) { + const key = hooksToMerge[i] + hooks[key] = componentVNodeHooks[key] + } +} + +// transform component v-model info (value and callback) into +// prop and event handler respectively. +function transformModel (options, data: any) { + const prop = (options.model && options.model.prop) || 'value' + const event = (options.model && options.model.event) || 'input' + ;(data.props || (data.props = {}))[prop] = data.model.value + const on = data.on || (data.on = {}) + if (isDef(on[event])) { + on[event] = [data.model.callback].concat(on[event]) + } else { + on[event] = data.model.callback + } +} diff --git a/node_modules/vue/src/core/vdom/create-element.js b/node_modules/vue/src/core/vdom/create-element.js new file mode 100644 index 00000000..c5cde2c6 --- /dev/null +++ b/node_modules/vue/src/core/vdom/create-element.js @@ -0,0 +1,160 @@ +/* @flow */ + +import config from '../config' +import VNode, { createEmptyVNode } from './vnode' +import { createComponent } from './create-component' +import { traverse } from '../observer/traverse' + +import { + warn, + isDef, + isUndef, + isTrue, + isObject, + isPrimitive, + resolveAsset +} from '../util/index' + +import { + normalizeChildren, + simpleNormalizeChildren +} from './helpers/index' + +const SIMPLE_NORMALIZE = 1 +const ALWAYS_NORMALIZE = 2 + +// wrapper function for providing a more flexible interface +// without getting yelled at by flow +export function createElement ( + context: Component, + tag: any, + data: any, + children: any, + normalizationType: any, + alwaysNormalize: boolean +): VNode | Array<VNode> { + if (Array.isArray(data) || isPrimitive(data)) { + normalizationType = children + children = data + data = undefined + } + if (isTrue(alwaysNormalize)) { + normalizationType = ALWAYS_NORMALIZE + } + return _createElement(context, tag, data, children, normalizationType) +} + +export function _createElement ( + context: Component, + tag?: string | Class<Component> | Function | Object, + data?: VNodeData, + children?: any, + normalizationType?: number +): VNode | Array<VNode> { + if (isDef(data) && isDef((data: any).__ob__)) { + process.env.NODE_ENV !== 'production' && warn( + `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` + + 'Always create fresh vnode data objects in each render!', + context + ) + return createEmptyVNode() + } + // object syntax in v-bind + if (isDef(data) && isDef(data.is)) { + tag = data.is + } + if (!tag) { + // in case of component :is set to falsy value + return createEmptyVNode() + } + // warn against non-primitive key + if (process.env.NODE_ENV !== 'production' && + isDef(data) && isDef(data.key) && !isPrimitive(data.key) + ) { + if (!__WEEX__ || !('@binding' in data.key)) { + warn( + 'Avoid using non-primitive value as key, ' + + 'use string/number value instead.', + context + ) + } + } + // support single function children as default scoped slot + if (Array.isArray(children) && + typeof children[0] === 'function' + ) { + data = data || {} + data.scopedSlots = { default: children[0] } + children.length = 0 + } + if (normalizationType === ALWAYS_NORMALIZE) { + children = normalizeChildren(children) + } else if (normalizationType === SIMPLE_NORMALIZE) { + children = simpleNormalizeChildren(children) + } + let vnode, ns + if (typeof tag === 'string') { + let Ctor + ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) + if (config.isReservedTag(tag)) { + // platform built-in elements + vnode = new VNode( + config.parsePlatformTagName(tag), data, children, + undefined, undefined, context + ) + } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { + // component + vnode = createComponent(Ctor, data, context, children, tag) + } else { + // unknown or unlisted namespaced elements + // check at runtime because it may get assigned a namespace when its + // parent normalizes children + vnode = new VNode( + tag, data, children, + undefined, undefined, context + ) + } + } else { + // direct component options / constructor + vnode = createComponent(tag, data, context, children) + } + if (Array.isArray(vnode)) { + return vnode + } else if (isDef(vnode)) { + if (isDef(ns)) applyNS(vnode, ns) + if (isDef(data)) registerDeepBindings(data) + return vnode + } else { + return createEmptyVNode() + } +} + +function applyNS (vnode, ns, force) { + vnode.ns = ns + if (vnode.tag === 'foreignObject') { + // use default namespace inside foreignObject + ns = undefined + force = true + } + if (isDef(vnode.children)) { + for (let i = 0, l = vnode.children.length; i < l; i++) { + const child = vnode.children[i] + if (isDef(child.tag) && ( + isUndef(child.ns) || (isTrue(force) && child.tag !== 'svg'))) { + applyNS(child, ns, force) + } + } + } +} + +// ref #5318 +// necessary to ensure parent re-render when deep bindings like :style and +// :class are used on slot nodes +function registerDeepBindings (data) { + if (isObject(data.style)) { + traverse(data.style) + } + if (isObject(data.class)) { + traverse(data.class) + } +} diff --git a/node_modules/vue/src/core/vdom/create-functional-component.js b/node_modules/vue/src/core/vdom/create-functional-component.js new file mode 100644 index 00000000..efed801d --- /dev/null +++ b/node_modules/vue/src/core/vdom/create-functional-component.js @@ -0,0 +1,136 @@ +/* @flow */ + +import VNode, { cloneVNode } from './vnode' +import { createElement } from './create-element' +import { resolveInject } from '../instance/inject' +import { normalizeChildren } from '../vdom/helpers/normalize-children' +import { resolveSlots } from '../instance/render-helpers/resolve-slots' +import { installRenderHelpers } from '../instance/render-helpers/index' + +import { + isDef, + isTrue, + hasOwn, + camelize, + emptyObject, + validateProp +} from '../util/index' + +export function FunctionalRenderContext ( + data: VNodeData, + props: Object, + children: ?Array<VNode>, + parent: Component, + Ctor: Class<Component> +) { + const options = Ctor.options + // ensure the createElement function in functional components + // gets a unique context - this is necessary for correct named slot check + let contextVm + if (hasOwn(parent, '_uid')) { + contextVm = Object.create(parent) + // $flow-disable-line + contextVm._original = parent + } else { + // the context vm passed in is a functional context as well. + // in this case we want to make sure we are able to get a hold to the + // real context instance. + contextVm = parent + // $flow-disable-line + parent = parent._original + } + const isCompiled = isTrue(options._compiled) + const needNormalization = !isCompiled + + this.data = data + this.props = props + this.children = children + this.parent = parent + this.listeners = data.on || emptyObject + this.injections = resolveInject(options.inject, parent) + this.slots = () => resolveSlots(children, parent) + + // support for compiled functional template + if (isCompiled) { + // exposing $options for renderStatic() + this.$options = options + // pre-resolve slots for renderSlot() + this.$slots = this.slots() + this.$scopedSlots = data.scopedSlots || emptyObject + } + + if (options._scopeId) { + this._c = (a, b, c, d) => { + const vnode = createElement(contextVm, a, b, c, d, needNormalization) + if (vnode && !Array.isArray(vnode)) { + vnode.fnScopeId = options._scopeId + vnode.fnContext = parent + } + return vnode + } + } else { + this._c = (a, b, c, d) => createElement(contextVm, a, b, c, d, needNormalization) + } +} + +installRenderHelpers(FunctionalRenderContext.prototype) + +export function createFunctionalComponent ( + Ctor: Class<Component>, + propsData: ?Object, + data: VNodeData, + contextVm: Component, + children: ?Array<VNode> +): VNode | Array<VNode> | void { + const options = Ctor.options + const props = {} + const propOptions = options.props + if (isDef(propOptions)) { + for (const key in propOptions) { + props[key] = validateProp(key, propOptions, propsData || emptyObject) + } + } else { + if (isDef(data.attrs)) mergeProps(props, data.attrs) + if (isDef(data.props)) mergeProps(props, data.props) + } + + const renderContext = new FunctionalRenderContext( + data, + props, + children, + contextVm, + Ctor + ) + + const vnode = options.render.call(null, renderContext._c, renderContext) + + if (vnode instanceof VNode) { + return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options) + } else if (Array.isArray(vnode)) { + const vnodes = normalizeChildren(vnode) || [] + const res = new Array(vnodes.length) + for (let i = 0; i < vnodes.length; i++) { + res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options) + } + return res + } +} + +function cloneAndMarkFunctionalResult (vnode, data, contextVm, options) { + // #7817 clone node before setting fnContext, otherwise if the node is reused + // (e.g. it was from a cached normal slot) the fnContext causes named slots + // that should not be matched to match. + const clone = cloneVNode(vnode) + clone.fnContext = contextVm + clone.fnOptions = options + if (data.slot) { + (clone.data || (clone.data = {})).slot = data.slot + } + return clone +} + +function mergeProps (to, from) { + for (const key in from) { + to[camelize(key)] = from[key] + } +} diff --git a/node_modules/vue/src/core/vdom/helpers/extract-props.js b/node_modules/vue/src/core/vdom/helpers/extract-props.js new file mode 100644 index 00000000..f2a41661 --- /dev/null +++ b/node_modules/vue/src/core/vdom/helpers/extract-props.js @@ -0,0 +1,75 @@ +/* @flow */ + +import { + tip, + hasOwn, + isDef, + isUndef, + hyphenate, + formatComponentName +} from 'core/util/index' + +export function extractPropsFromVNodeData ( + data: VNodeData, + Ctor: Class<Component>, + tag?: string +): ?Object { + // we are only extracting raw values here. + // validation and default values are handled in the child + // component itself. + const propOptions = Ctor.options.props + if (isUndef(propOptions)) { + return + } + const res = {} + const { attrs, props } = data + if (isDef(attrs) || isDef(props)) { + for (const key in propOptions) { + const altKey = hyphenate(key) + if (process.env.NODE_ENV !== 'production') { + const keyInLowerCase = key.toLowerCase() + if ( + key !== keyInLowerCase && + attrs && hasOwn(attrs, keyInLowerCase) + ) { + tip( + `Prop "${keyInLowerCase}" is passed to component ` + + `${formatComponentName(tag || Ctor)}, but the declared prop name is` + + ` "${key}". ` + + `Note that HTML attributes are case-insensitive and camelCased ` + + `props need to use their kebab-case equivalents when using in-DOM ` + + `templates. You should probably use "${altKey}" instead of "${key}".` + ) + } + } + checkProp(res, props, key, altKey, true) || + checkProp(res, attrs, key, altKey, false) + } + } + return res +} + +function checkProp ( + res: Object, + hash: ?Object, + key: string, + altKey: string, + preserve: boolean +): boolean { + if (isDef(hash)) { + if (hasOwn(hash, key)) { + res[key] = hash[key] + if (!preserve) { + delete hash[key] + } + return true + } else if (hasOwn(hash, altKey)) { + res[key] = hash[altKey] + if (!preserve) { + delete hash[altKey] + } + return true + } + } + return false +} diff --git a/node_modules/vue/src/core/vdom/helpers/get-first-component-child.js b/node_modules/vue/src/core/vdom/helpers/get-first-component-child.js new file mode 100644 index 00000000..f8649dd8 --- /dev/null +++ b/node_modules/vue/src/core/vdom/helpers/get-first-component-child.js @@ -0,0 +1,15 @@ +/* @flow */ + +import { isDef } from 'shared/util' +import { isAsyncPlaceholder } from './is-async-placeholder' + +export function getFirstComponentChild (children: ?Array<VNode>): ?VNode { + if (Array.isArray(children)) { + for (let i = 0; i < children.length; i++) { + const c = children[i] + if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) { + return c + } + } + } +} diff --git a/node_modules/vue/src/core/vdom/helpers/index.js b/node_modules/vue/src/core/vdom/helpers/index.js new file mode 100644 index 00000000..320cf4c4 --- /dev/null +++ b/node_modules/vue/src/core/vdom/helpers/index.js @@ -0,0 +1,9 @@ +/* @flow */ + +export * from './merge-hook' +export * from './extract-props' +export * from './update-listeners' +export * from './normalize-children' +export * from './resolve-async-component' +export * from './get-first-component-child' +export * from './is-async-placeholder' diff --git a/node_modules/vue/src/core/vdom/helpers/is-async-placeholder.js b/node_modules/vue/src/core/vdom/helpers/is-async-placeholder.js new file mode 100644 index 00000000..6c4a6596 --- /dev/null +++ b/node_modules/vue/src/core/vdom/helpers/is-async-placeholder.js @@ -0,0 +1,5 @@ +/* @flow */ + +export function isAsyncPlaceholder (node: VNode): boolean { + return node.isComment && node.asyncFactory +} diff --git a/node_modules/vue/src/core/vdom/helpers/merge-hook.js b/node_modules/vue/src/core/vdom/helpers/merge-hook.js new file mode 100644 index 00000000..0bb96e8b --- /dev/null +++ b/node_modules/vue/src/core/vdom/helpers/merge-hook.js @@ -0,0 +1,38 @@ +/* @flow */ + +import VNode from '../vnode' +import { createFnInvoker } from './update-listeners' +import { remove, isDef, isUndef, isTrue } from 'shared/util' + +export function mergeVNodeHook (def: Object, hookKey: string, hook: Function) { + if (def instanceof VNode) { + def = def.data.hook || (def.data.hook = {}) + } + let invoker + const oldHook = def[hookKey] + + function wrappedHook () { + hook.apply(this, arguments) + // important: remove merged hook to ensure it's called only once + // and prevent memory leak + remove(invoker.fns, wrappedHook) + } + + if (isUndef(oldHook)) { + // no existing hook + invoker = createFnInvoker([wrappedHook]) + } else { + /* istanbul ignore if */ + if (isDef(oldHook.fns) && isTrue(oldHook.merged)) { + // already a merged invoker + invoker = oldHook + invoker.fns.push(wrappedHook) + } else { + // existing plain hook + invoker = createFnInvoker([oldHook, wrappedHook]) + } + } + + invoker.merged = true + def[hookKey] = invoker +} diff --git a/node_modules/vue/src/core/vdom/helpers/normalize-children.js b/node_modules/vue/src/core/vdom/helpers/normalize-children.js new file mode 100644 index 00000000..a4622ef4 --- /dev/null +++ b/node_modules/vue/src/core/vdom/helpers/normalize-children.js @@ -0,0 +1,89 @@ +/* @flow */ + +import VNode, { createTextVNode } from 'core/vdom/vnode' +import { isFalse, isTrue, isDef, isUndef, isPrimitive } from 'shared/util' + +// The template compiler attempts to minimize the need for normalization by +// statically analyzing the template at compile time. +// +// For plain HTML markup, normalization can be completely skipped because the +// generated render function is guaranteed to return Array<VNode>. There are +// two cases where extra normalization is needed: + +// 1. When the children contains components - because a functional component +// may return an Array instead of a single root. In this case, just a simple +// normalization is needed - if any child is an Array, we flatten the whole +// thing with Array.prototype.concat. It is guaranteed to be only 1-level deep +// because functional components already normalize their own children. +export function simpleNormalizeChildren (children: any) { + for (let i = 0; i < children.length; i++) { + if (Array.isArray(children[i])) { + return Array.prototype.concat.apply([], children) + } + } + return children +} + +// 2. When the children contains constructs that always generated nested Arrays, +// e.g. <template>, <slot>, v-for, or when the children is provided by user +// with hand-written render functions / JSX. In such cases a full normalization +// is needed to cater to all possible types of children values. +export function normalizeChildren (children: any): ?Array<VNode> { + return isPrimitive(children) + ? [createTextVNode(children)] + : Array.isArray(children) + ? normalizeArrayChildren(children) + : undefined +} + +function isTextNode (node): boolean { + return isDef(node) && isDef(node.text) && isFalse(node.isComment) +} + +function normalizeArrayChildren (children: any, nestedIndex?: string): Array<VNode> { + const res = [] + let i, c, lastIndex, last + for (i = 0; i < children.length; i++) { + c = children[i] + if (isUndef(c) || typeof c === 'boolean') continue + lastIndex = res.length - 1 + last = res[lastIndex] + // nested + if (Array.isArray(c)) { + if (c.length > 0) { + c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`) + // merge adjacent text nodes + if (isTextNode(c[0]) && isTextNode(last)) { + res[lastIndex] = createTextVNode(last.text + (c[0]: any).text) + c.shift() + } + res.push.apply(res, c) + } + } else if (isPrimitive(c)) { + if (isTextNode(last)) { + // merge adjacent text nodes + // this is necessary for SSR hydration because text nodes are + // essentially merged when rendered to HTML strings + res[lastIndex] = createTextVNode(last.text + c) + } else if (c !== '') { + // convert primitive to vnode + res.push(createTextVNode(c)) + } + } else { + if (isTextNode(c) && isTextNode(last)) { + // merge adjacent text nodes + res[lastIndex] = createTextVNode(last.text + c.text) + } else { + // default key for nested array children (likely generated by v-for) + if (isTrue(children._isVList) && + isDef(c.tag) && + isUndef(c.key) && + isDef(nestedIndex)) { + c.key = `__vlist${nestedIndex}_${i}__` + } + res.push(c) + } + } + } + return res +} diff --git a/node_modules/vue/src/core/vdom/helpers/resolve-async-component.js b/node_modules/vue/src/core/vdom/helpers/resolve-async-component.js new file mode 100644 index 00000000..99ce5ce4 --- /dev/null +++ b/node_modules/vue/src/core/vdom/helpers/resolve-async-component.js @@ -0,0 +1,140 @@ +/* @flow */ + +import { + warn, + once, + isDef, + isUndef, + isTrue, + isObject, + hasSymbol +} from 'core/util/index' + +import { createEmptyVNode } from 'core/vdom/vnode' + +function ensureCtor (comp: any, base) { + if ( + comp.__esModule || + (hasSymbol && comp[Symbol.toStringTag] === 'Module') + ) { + comp = comp.default + } + return isObject(comp) + ? base.extend(comp) + : comp +} + +export function createAsyncPlaceholder ( + factory: Function, + data: ?VNodeData, + context: Component, + children: ?Array<VNode>, + tag: ?string +): VNode { + const node = createEmptyVNode() + node.asyncFactory = factory + node.asyncMeta = { data, context, children, tag } + return node +} + +export function resolveAsyncComponent ( + factory: Function, + baseCtor: Class<Component>, + context: Component +): Class<Component> | void { + if (isTrue(factory.error) && isDef(factory.errorComp)) { + return factory.errorComp + } + + if (isDef(factory.resolved)) { + return factory.resolved + } + + if (isTrue(factory.loading) && isDef(factory.loadingComp)) { + return factory.loadingComp + } + + if (isDef(factory.contexts)) { + // already pending + factory.contexts.push(context) + } else { + const contexts = factory.contexts = [context] + let sync = true + + const forceRender = () => { + for (let i = 0, l = contexts.length; i < l; i++) { + contexts[i].$forceUpdate() + } + } + + const resolve = once((res: Object | Class<Component>) => { + // cache resolved + factory.resolved = ensureCtor(res, baseCtor) + // invoke callbacks only if this is not a synchronous resolve + // (async resolves are shimmed as synchronous during SSR) + if (!sync) { + forceRender() + } + }) + + const reject = once(reason => { + process.env.NODE_ENV !== 'production' && warn( + `Failed to resolve async component: ${String(factory)}` + + (reason ? `\nReason: ${reason}` : '') + ) + if (isDef(factory.errorComp)) { + factory.error = true + forceRender() + } + }) + + const res = factory(resolve, reject) + + if (isObject(res)) { + if (typeof res.then === 'function') { + // () => Promise + if (isUndef(factory.resolved)) { + res.then(resolve, reject) + } + } else if (isDef(res.component) && typeof res.component.then === 'function') { + res.component.then(resolve, reject) + + if (isDef(res.error)) { + factory.errorComp = ensureCtor(res.error, baseCtor) + } + + if (isDef(res.loading)) { + factory.loadingComp = ensureCtor(res.loading, baseCtor) + if (res.delay === 0) { + factory.loading = true + } else { + setTimeout(() => { + if (isUndef(factory.resolved) && isUndef(factory.error)) { + factory.loading = true + forceRender() + } + }, res.delay || 200) + } + } + + if (isDef(res.timeout)) { + setTimeout(() => { + if (isUndef(factory.resolved)) { + reject( + process.env.NODE_ENV !== 'production' + ? `timeout (${res.timeout}ms)` + : null + ) + } + }, res.timeout) + } + } + } + + sync = false + // return in case resolved synchronously + return factory.loading + ? factory.loadingComp + : factory.resolved + } +} diff --git a/node_modules/vue/src/core/vdom/helpers/update-listeners.js b/node_modules/vue/src/core/vdom/helpers/update-listeners.js new file mode 100644 index 00000000..11c5a2cd --- /dev/null +++ b/node_modules/vue/src/core/vdom/helpers/update-listeners.js @@ -0,0 +1,83 @@ +/* @flow */ + +import { warn } from 'core/util/index' +import { cached, isUndef, isPlainObject } from 'shared/util' + +const normalizeEvent = cached((name: string): { + name: string, + once: boolean, + capture: boolean, + passive: boolean, + handler?: Function, + params?: Array<any> +} => { + const passive = name.charAt(0) === '&' + name = passive ? name.slice(1) : name + const once = name.charAt(0) === '~' // Prefixed last, checked first + name = once ? name.slice(1) : name + const capture = name.charAt(0) === '!' + name = capture ? name.slice(1) : name + return { + name, + once, + capture, + passive + } +}) + +export function createFnInvoker (fns: Function | Array<Function>): Function { + function invoker () { + const fns = invoker.fns + if (Array.isArray(fns)) { + const cloned = fns.slice() + for (let i = 0; i < cloned.length; i++) { + cloned[i].apply(null, arguments) + } + } else { + // return handler return value for single handlers + return fns.apply(null, arguments) + } + } + invoker.fns = fns + return invoker +} + +export function updateListeners ( + on: Object, + oldOn: Object, + add: Function, + remove: Function, + vm: Component +) { + let name, def, cur, old, event + for (name in on) { + def = cur = on[name] + old = oldOn[name] + event = normalizeEvent(name) + /* istanbul ignore if */ + if (__WEEX__ && isPlainObject(def)) { + cur = def.handler + event.params = def.params + } + if (isUndef(cur)) { + process.env.NODE_ENV !== 'production' && warn( + `Invalid handler for event "${event.name}": got ` + String(cur), + vm + ) + } else if (isUndef(old)) { + if (isUndef(cur.fns)) { + cur = on[name] = createFnInvoker(cur) + } + add(event.name, cur, event.once, event.capture, event.passive, event.params) + } else if (cur !== old) { + old.fns = cur + on[name] = old + } + } + for (name in oldOn) { + if (isUndef(on[name])) { + event = normalizeEvent(name) + remove(event.name, oldOn[name], event.capture) + } + } +} diff --git a/node_modules/vue/src/core/vdom/modules/directives.js b/node_modules/vue/src/core/vdom/modules/directives.js new file mode 100644 index 00000000..639982fa --- /dev/null +++ b/node_modules/vue/src/core/vdom/modules/directives.js @@ -0,0 +1,119 @@ +/* @flow */ + +import { emptyNode } from 'core/vdom/patch' +import { resolveAsset, handleError } from 'core/util/index' +import { mergeVNodeHook } from 'core/vdom/helpers/index' + +export default { + create: updateDirectives, + update: updateDirectives, + destroy: function unbindDirectives (vnode: VNodeWithData) { + updateDirectives(vnode, emptyNode) + } +} + +function updateDirectives (oldVnode: VNodeWithData, vnode: VNodeWithData) { + if (oldVnode.data.directives || vnode.data.directives) { + _update(oldVnode, vnode) + } +} + +function _update (oldVnode, vnode) { + const isCreate = oldVnode === emptyNode + const isDestroy = vnode === emptyNode + const oldDirs = normalizeDirectives(oldVnode.data.directives, oldVnode.context) + const newDirs = normalizeDirectives(vnode.data.directives, vnode.context) + + const dirsWithInsert = [] + const dirsWithPostpatch = [] + + let key, oldDir, dir + for (key in newDirs) { + oldDir = oldDirs[key] + dir = newDirs[key] + if (!oldDir) { + // new directive, bind + callHook(dir, 'bind', vnode, oldVnode) + if (dir.def && dir.def.inserted) { + dirsWithInsert.push(dir) + } + } else { + // existing directive, update + dir.oldValue = oldDir.value + callHook(dir, 'update', vnode, oldVnode) + if (dir.def && dir.def.componentUpdated) { + dirsWithPostpatch.push(dir) + } + } + } + + if (dirsWithInsert.length) { + const callInsert = () => { + for (let i = 0; i < dirsWithInsert.length; i++) { + callHook(dirsWithInsert[i], 'inserted', vnode, oldVnode) + } + } + if (isCreate) { + mergeVNodeHook(vnode, 'insert', callInsert) + } else { + callInsert() + } + } + + if (dirsWithPostpatch.length) { + mergeVNodeHook(vnode, 'postpatch', () => { + for (let i = 0; i < dirsWithPostpatch.length; i++) { + callHook(dirsWithPostpatch[i], 'componentUpdated', vnode, oldVnode) + } + }) + } + + if (!isCreate) { + for (key in oldDirs) { + if (!newDirs[key]) { + // no longer present, unbind + callHook(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy) + } + } + } +} + +const emptyModifiers = Object.create(null) + +function normalizeDirectives ( + dirs: ?Array<VNodeDirective>, + vm: Component +): { [key: string]: VNodeDirective } { + const res = Object.create(null) + if (!dirs) { + // $flow-disable-line + return res + } + let i, dir + for (i = 0; i < dirs.length; i++) { + dir = dirs[i] + if (!dir.modifiers) { + // $flow-disable-line + dir.modifiers = emptyModifiers + } + res[getRawDirName(dir)] = dir + dir.def = resolveAsset(vm.$options, 'directives', dir.name, true) + } + // $flow-disable-line + return res +} + +function getRawDirName (dir: VNodeDirective): string { + return dir.rawName || `${dir.name}.${Object.keys(dir.modifiers || {}).join('.')}` +} + +function callHook (dir, hook, vnode, oldVnode, isDestroy) { + const fn = dir.def && dir.def[hook] + if (fn) { + try { + fn(vnode.elm, dir, vnode, oldVnode, isDestroy) + } catch (e) { + handleError(e, vnode.context, `directive ${dir.name} ${hook} hook`) + } + } +} diff --git a/node_modules/vue/src/core/vdom/modules/index.js b/node_modules/vue/src/core/vdom/modules/index.js new file mode 100644 index 00000000..727c8646 --- /dev/null +++ b/node_modules/vue/src/core/vdom/modules/index.js @@ -0,0 +1,7 @@ +import directives from './directives' +import ref from './ref' + +export default [ + ref, + directives +] diff --git a/node_modules/vue/src/core/vdom/modules/ref.js b/node_modules/vue/src/core/vdom/modules/ref.js new file mode 100644 index 00000000..aa9bdcfc --- /dev/null +++ b/node_modules/vue/src/core/vdom/modules/ref.js @@ -0,0 +1,45 @@ +/* @flow */ + +import { remove, isDef } from 'shared/util' + +export default { + create (_: any, vnode: VNodeWithData) { + registerRef(vnode) + }, + update (oldVnode: VNodeWithData, vnode: VNodeWithData) { + if (oldVnode.data.ref !== vnode.data.ref) { + registerRef(oldVnode, true) + registerRef(vnode) + } + }, + destroy (vnode: VNodeWithData) { + registerRef(vnode, true) + } +} + +export function registerRef (vnode: VNodeWithData, isRemoval: ?boolean) { + const key = vnode.data.ref + if (!isDef(key)) return + + const vm = vnode.context + const ref = vnode.componentInstance || vnode.elm + const refs = vm.$refs + if (isRemoval) { + if (Array.isArray(refs[key])) { + remove(refs[key], ref) + } else if (refs[key] === ref) { + refs[key] = undefined + } + } else { + if (vnode.data.refInFor) { + if (!Array.isArray(refs[key])) { + refs[key] = [ref] + } else if (refs[key].indexOf(ref) < 0) { + // $flow-disable-line + refs[key].push(ref) + } + } else { + refs[key] = ref + } + } +} diff --git a/node_modules/vue/src/core/vdom/patch.js b/node_modules/vue/src/core/vdom/patch.js new file mode 100644 index 00000000..9f3bcc65 --- /dev/null +++ b/node_modules/vue/src/core/vdom/patch.js @@ -0,0 +1,787 @@ +/** + * Virtual DOM patching algorithm based on Snabbdom by + * Simon Friis Vindum (@paldepind) + * Licensed under the MIT License + * https://github.com/paldepind/snabbdom/blob/master/LICENSE + * + * modified by Evan You (@yyx990803) + * + * Not type-checking this because this file is perf-critical and the cost + * of making flow understand it is not worth it. + */ + +import VNode, { cloneVNode } from './vnode' +import config from '../config' +import { SSR_ATTR } from 'shared/constants' +import { registerRef } from './modules/ref' +import { traverse } from '../observer/traverse' +import { activeInstance } from '../instance/lifecycle' +import { isTextInputType } from 'web/util/element' + +import { + warn, + isDef, + isUndef, + isTrue, + makeMap, + isRegExp, + isPrimitive +} from '../util/index' + +export const emptyNode = new VNode('', {}, []) + +const hooks = ['create', 'activate', 'update', 'remove', 'destroy'] + +function sameVnode (a, b) { + return ( + a.key === b.key && ( + ( + a.tag === b.tag && + a.isComment === b.isComment && + isDef(a.data) === isDef(b.data) && + sameInputType(a, b) + ) || ( + isTrue(a.isAsyncPlaceholder) && + a.asyncFactory === b.asyncFactory && + isUndef(b.asyncFactory.error) + ) + ) + ) +} + +function sameInputType (a, b) { + if (a.tag !== 'input') return true + let i + const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type + const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type + return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB) +} + +function createKeyToOldIdx (children, beginIdx, endIdx) { + let i, key + const map = {} + for (i = beginIdx; i <= endIdx; ++i) { + key = children[i].key + if (isDef(key)) map[key] = i + } + return map +} + +export function createPatchFunction (backend) { + let i, j + const cbs = {} + + const { modules, nodeOps } = backend + + for (i = 0; i < hooks.length; ++i) { + cbs[hooks[i]] = [] + for (j = 0; j < modules.length; ++j) { + if (isDef(modules[j][hooks[i]])) { + cbs[hooks[i]].push(modules[j][hooks[i]]) + } + } + } + + function emptyNodeAt (elm) { + return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm) + } + + function createRmCb (childElm, listeners) { + function remove () { + if (--remove.listeners === 0) { + removeNode(childElm) + } + } + remove.listeners = listeners + return remove + } + + function removeNode (el) { + const parent = nodeOps.parentNode(el) + // element may have already been removed due to v-html / v-text + if (isDef(parent)) { + nodeOps.removeChild(parent, el) + } + } + + function isUnknownElement (vnode, inVPre) { + return ( + !inVPre && + !vnode.ns && + !( + config.ignoredElements.length && + config.ignoredElements.some(ignore => { + return isRegExp(ignore) + ? ignore.test(vnode.tag) + : ignore === vnode.tag + }) + ) && + config.isUnknownElement(vnode.tag) + ) + } + + let creatingElmInVPre = 0 + + function createElm ( + vnode, + insertedVnodeQueue, + parentElm, + refElm, + nested, + ownerArray, + index + ) { + if (isDef(vnode.elm) && isDef(ownerArray)) { + // This vnode was used in a previous render! + // now it's used as a new node, overwriting its elm would cause + // potential patch errors down the road when it's used as an insertion + // reference node. Instead, we clone the node on-demand before creating + // associated DOM element for it. + vnode = ownerArray[index] = cloneVNode(vnode) + } + + vnode.isRootInsert = !nested // for transition enter check + if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { + return + } + + const data = vnode.data + const children = vnode.children + const tag = vnode.tag + if (isDef(tag)) { + if (process.env.NODE_ENV !== 'production') { + if (data && data.pre) { + creatingElmInVPre++ + } + if (isUnknownElement(vnode, creatingElmInVPre)) { + warn( + 'Unknown custom element: <' + tag + '> - did you ' + + 'register the component correctly? For recursive components, ' + + 'make sure to provide the "name" option.', + vnode.context + ) + } + } + + vnode.elm = vnode.ns + ? nodeOps.createElementNS(vnode.ns, tag) + : nodeOps.createElement(tag, vnode) + setScope(vnode) + + /* istanbul ignore if */ + if (__WEEX__) { + // in Weex, the default insertion order is parent-first. + // List items can be optimized to use children-first insertion + // with append="tree". + const appendAsTree = isDef(data) && isTrue(data.appendAsTree) + if (!appendAsTree) { + if (isDef(data)) { + invokeCreateHooks(vnode, insertedVnodeQueue) + } + insert(parentElm, vnode.elm, refElm) + } + createChildren(vnode, children, insertedVnodeQueue) + if (appendAsTree) { + if (isDef(data)) { + invokeCreateHooks(vnode, insertedVnodeQueue) + } + insert(parentElm, vnode.elm, refElm) + } + } else { + createChildren(vnode, children, insertedVnodeQueue) + if (isDef(data)) { + invokeCreateHooks(vnode, insertedVnodeQueue) + } + insert(parentElm, vnode.elm, refElm) + } + + if (process.env.NODE_ENV !== 'production' && data && data.pre) { + creatingElmInVPre-- + } + } else if (isTrue(vnode.isComment)) { + vnode.elm = nodeOps.createComment(vnode.text) + insert(parentElm, vnode.elm, refElm) + } else { + vnode.elm = nodeOps.createTextNode(vnode.text) + insert(parentElm, vnode.elm, refElm) + } + } + + function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) { + let i = vnode.data + if (isDef(i)) { + const isReactivated = isDef(vnode.componentInstance) && i.keepAlive + if (isDef(i = i.hook) && isDef(i = i.init)) { + i(vnode, false /* hydrating */, parentElm, refElm) + } + // after calling the init hook, if the vnode is a child component + // it should've created a child instance and mounted it. the child + // component also has set the placeholder vnode's elm. + // in that case we can just return the element and be done. + if (isDef(vnode.componentInstance)) { + initComponent(vnode, insertedVnodeQueue) + if (isTrue(isReactivated)) { + reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm) + } + return true + } + } + } + + function initComponent (vnode, insertedVnodeQueue) { + if (isDef(vnode.data.pendingInsert)) { + insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert) + vnode.data.pendingInsert = null + } + vnode.elm = vnode.componentInstance.$el + if (isPatchable(vnode)) { + invokeCreateHooks(vnode, insertedVnodeQueue) + setScope(vnode) + } else { + // empty component root. + // skip all element-related modules except for ref (#3455) + registerRef(vnode) + // make sure to invoke the insert hook + insertedVnodeQueue.push(vnode) + } + } + + function reactivateComponent (vnode, insertedVnodeQueue, parentElm, refElm) { + let i + // hack for #4339: a reactivated component with inner transition + // does not trigger because the inner node's created hooks are not called + // again. It's not ideal to involve module-specific logic in here but + // there doesn't seem to be a better way to do it. + let innerNode = vnode + while (innerNode.componentInstance) { + innerNode = innerNode.componentInstance._vnode + if (isDef(i = innerNode.data) && isDef(i = i.transition)) { + for (i = 0; i < cbs.activate.length; ++i) { + cbs.activate[i](emptyNode, innerNode) + } + insertedVnodeQueue.push(innerNode) + break + } + } + // unlike a newly created component, + // a reactivated keep-alive component doesn't insert itself + insert(parentElm, vnode.elm, refElm) + } + + function insert (parent, elm, ref) { + if (isDef(parent)) { + if (isDef(ref)) { + if (ref.parentNode === parent) { + nodeOps.insertBefore(parent, elm, ref) + } + } else { + nodeOps.appendChild(parent, elm) + } + } + } + + function createChildren (vnode, children, insertedVnodeQueue) { + if (Array.isArray(children)) { + if (process.env.NODE_ENV !== 'production') { + checkDuplicateKeys(children) + } + for (let i = 0; i < children.length; ++i) { + createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i) + } + } else if (isPrimitive(vnode.text)) { + nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text))) + } + } + + function isPatchable (vnode) { + while (vnode.componentInstance) { + vnode = vnode.componentInstance._vnode + } + return isDef(vnode.tag) + } + + function invokeCreateHooks (vnode, insertedVnodeQueue) { + for (let i = 0; i < cbs.create.length; ++i) { + cbs.create[i](emptyNode, vnode) + } + i = vnode.data.hook // Reuse variable + if (isDef(i)) { + if (isDef(i.create)) i.create(emptyNode, vnode) + if (isDef(i.insert)) insertedVnodeQueue.push(vnode) + } + } + + // set scope id attribute for scoped CSS. + // this is implemented as a special case to avoid the overhead + // of going through the normal attribute patching process. + function setScope (vnode) { + let i + if (isDef(i = vnode.fnScopeId)) { + nodeOps.setStyleScope(vnode.elm, i) + } else { + let ancestor = vnode + while (ancestor) { + if (isDef(i = ancestor.context) && isDef(i = i.$options._scopeId)) { + nodeOps.setStyleScope(vnode.elm, i) + } + ancestor = ancestor.parent + } + } + // for slot content they should also get the scopeId from the host instance. + if (isDef(i = activeInstance) && + i !== vnode.context && + i !== vnode.fnContext && + isDef(i = i.$options._scopeId) + ) { + nodeOps.setStyleScope(vnode.elm, i) + } + } + + function addVnodes (parentElm, refElm, vnodes, startIdx, endIdx, insertedVnodeQueue) { + for (; startIdx <= endIdx; ++startIdx) { + createElm(vnodes[startIdx], insertedVnodeQueue, parentElm, refElm, false, vnodes, startIdx) + } + } + + function invokeDestroyHook (vnode) { + let i, j + const data = vnode.data + if (isDef(data)) { + if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode) + for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode) + } + if (isDef(i = vnode.children)) { + for (j = 0; j < vnode.children.length; ++j) { + invokeDestroyHook(vnode.children[j]) + } + } + } + + function removeVnodes (parentElm, vnodes, startIdx, endIdx) { + for (; startIdx <= endIdx; ++startIdx) { + const ch = vnodes[startIdx] + if (isDef(ch)) { + if (isDef(ch.tag)) { + removeAndInvokeRemoveHook(ch) + invokeDestroyHook(ch) + } else { // Text node + removeNode(ch.elm) + } + } + } + } + + function removeAndInvokeRemoveHook (vnode, rm) { + if (isDef(rm) || isDef(vnode.data)) { + let i + const listeners = cbs.remove.length + 1 + if (isDef(rm)) { + // we have a recursively passed down rm callback + // increase the listeners count + rm.listeners += listeners + } else { + // directly removing + rm = createRmCb(vnode.elm, listeners) + } + // recursively invoke hooks on child component root node + if (isDef(i = vnode.componentInstance) && isDef(i = i._vnode) && isDef(i.data)) { + removeAndInvokeRemoveHook(i, rm) + } + for (i = 0; i < cbs.remove.length; ++i) { + cbs.remove[i](vnode, rm) + } + if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) { + i(vnode, rm) + } else { + rm() + } + } else { + removeNode(vnode.elm) + } + } + + function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) { + let oldStartIdx = 0 + let newStartIdx = 0 + let oldEndIdx = oldCh.length - 1 + let oldStartVnode = oldCh[0] + let oldEndVnode = oldCh[oldEndIdx] + let newEndIdx = newCh.length - 1 + let newStartVnode = newCh[0] + let newEndVnode = newCh[newEndIdx] + let oldKeyToIdx, idxInOld, vnodeToMove, refElm + + // removeOnly is a special flag used only by <transition-group> + // to ensure removed elements stay in correct relative positions + // during leaving transitions + const canMove = !removeOnly + + if (process.env.NODE_ENV !== 'production') { + checkDuplicateKeys(newCh) + } + + while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { + if (isUndef(oldStartVnode)) { + oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left + } else if (isUndef(oldEndVnode)) { + oldEndVnode = oldCh[--oldEndIdx] + } else if (sameVnode(oldStartVnode, newStartVnode)) { + patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue) + oldStartVnode = oldCh[++oldStartIdx] + newStartVnode = newCh[++newStartIdx] + } else if (sameVnode(oldEndVnode, newEndVnode)) { + patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue) + oldEndVnode = oldCh[--oldEndIdx] + newEndVnode = newCh[--newEndIdx] + } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right + patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue) + canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)) + oldStartVnode = oldCh[++oldStartIdx] + newEndVnode = newCh[--newEndIdx] + } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left + patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue) + canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) + oldEndVnode = oldCh[--oldEndIdx] + newStartVnode = newCh[++newStartIdx] + } else { + if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) + idxInOld = isDef(newStartVnode.key) + ? oldKeyToIdx[newStartVnode.key] + : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) + if (isUndef(idxInOld)) { // New element + createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) + } else { + vnodeToMove = oldCh[idxInOld] + if (sameVnode(vnodeToMove, newStartVnode)) { + patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue) + oldCh[idxInOld] = undefined + canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm) + } else { + // same key but different element. treat as new element + createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) + } + } + newStartVnode = newCh[++newStartIdx] + } + } + if (oldStartIdx > oldEndIdx) { + refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm + addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue) + } else if (newStartIdx > newEndIdx) { + removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx) + } + } + + function checkDuplicateKeys (children) { + const seenKeys = {} + for (let i = 0; i < children.length; i++) { + const vnode = children[i] + const key = vnode.key + if (isDef(key)) { + if (seenKeys[key]) { + warn( + `Duplicate keys detected: '${key}'. This may cause an update error.`, + vnode.context + ) + } else { + seenKeys[key] = true + } + } + } + } + + function findIdxInOld (node, oldCh, start, end) { + for (let i = start; i < end; i++) { + const c = oldCh[i] + if (isDef(c) && sameVnode(node, c)) return i + } + } + + function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) { + if (oldVnode === vnode) { + return + } + + const elm = vnode.elm = oldVnode.elm + + if (isTrue(oldVnode.isAsyncPlaceholder)) { + if (isDef(vnode.asyncFactory.resolved)) { + hydrate(oldVnode.elm, vnode, insertedVnodeQueue) + } else { + vnode.isAsyncPlaceholder = true + } + return + } + + // reuse element for static trees. + // note we only do this if the vnode is cloned - + // if the new node is not cloned it means the render functions have been + // reset by the hot-reload-api and we need to do a proper re-render. + if (isTrue(vnode.isStatic) && + isTrue(oldVnode.isStatic) && + vnode.key === oldVnode.key && + (isTrue(vnode.isCloned) || isTrue(vnode.isOnce)) + ) { + vnode.componentInstance = oldVnode.componentInstance + return + } + + let i + const data = vnode.data + if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { + i(oldVnode, vnode) + } + + const oldCh = oldVnode.children + const ch = vnode.children + if (isDef(data) && isPatchable(vnode)) { + for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode) + if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode) + } + if (isUndef(vnode.text)) { + if (isDef(oldCh) && isDef(ch)) { + if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) + } else if (isDef(ch)) { + if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '') + addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) + } else if (isDef(oldCh)) { + removeVnodes(elm, oldCh, 0, oldCh.length - 1) + } else if (isDef(oldVnode.text)) { + nodeOps.setTextContent(elm, '') + } + } else if (oldVnode.text !== vnode.text) { + nodeOps.setTextContent(elm, vnode.text) + } + if (isDef(data)) { + if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode) + } + } + + function invokeInsertHook (vnode, queue, initial) { + // delay insert hooks for component root nodes, invoke them after the + // element is really inserted + if (isTrue(initial) && isDef(vnode.parent)) { + vnode.parent.data.pendingInsert = queue + } else { + for (let i = 0; i < queue.length; ++i) { + queue[i].data.hook.insert(queue[i]) + } + } + } + + let hydrationBailed = false + // list of modules that can skip create hook during hydration because they + // are already rendered on the client or has no need for initialization + // Note: style is excluded because it relies on initial clone for future + // deep updates (#7063). + const isRenderedModule = makeMap('attrs,class,staticClass,staticStyle,key') + + // Note: this is a browser-only function so we can assume elms are DOM nodes. + function hydrate (elm, vnode, insertedVnodeQueue, inVPre) { + let i + const { tag, data, children } = vnode + inVPre = inVPre || (data && data.pre) + vnode.elm = elm + + if (isTrue(vnode.isComment) && isDef(vnode.asyncFactory)) { + vnode.isAsyncPlaceholder = true + return true + } + // assert node match + if (process.env.NODE_ENV !== 'production') { + if (!assertNodeMatch(elm, vnode, inVPre)) { + return false + } + } + if (isDef(data)) { + if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode, true /* hydrating */) + if (isDef(i = vnode.componentInstance)) { + // child component. it should have hydrated its own tree. + initComponent(vnode, insertedVnodeQueue) + return true + } + } + if (isDef(tag)) { + if (isDef(children)) { + // empty element, allow client to pick up and populate children + if (!elm.hasChildNodes()) { + createChildren(vnode, children, insertedVnodeQueue) + } else { + // v-html and domProps: innerHTML + if (isDef(i = data) && isDef(i = i.domProps) && isDef(i = i.innerHTML)) { + if (i !== elm.innerHTML) { + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && + typeof console !== 'undefined' && + !hydrationBailed + ) { + hydrationBailed = true + console.warn('Parent: ', elm) + console.warn('server innerHTML: ', i) + console.warn('client innerHTML: ', elm.innerHTML) + } + return false + } + } else { + // iterate and compare children lists + let childrenMatch = true + let childNode = elm.firstChild + for (let i = 0; i < children.length; i++) { + if (!childNode || !hydrate(childNode, children[i], insertedVnodeQueue, inVPre)) { + childrenMatch = false + break + } + childNode = childNode.nextSibling + } + // if childNode is not null, it means the actual childNodes list is + // longer than the virtual children list. + if (!childrenMatch || childNode) { + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && + typeof console !== 'undefined' && + !hydrationBailed + ) { + hydrationBailed = true + console.warn('Parent: ', elm) + console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children) + } + return false + } + } + } + } + if (isDef(data)) { + let fullInvoke = false + for (const key in data) { + if (!isRenderedModule(key)) { + fullInvoke = true + invokeCreateHooks(vnode, insertedVnodeQueue) + break + } + } + if (!fullInvoke && data['class']) { + // ensure collecting deps for deep class bindings for future updates + traverse(data['class']) + } + } + } else if (elm.data !== vnode.text) { + elm.data = vnode.text + } + return true + } + + function assertNodeMatch (node, vnode, inVPre) { + if (isDef(vnode.tag)) { + return vnode.tag.indexOf('vue-component') === 0 || ( + !isUnknownElement(vnode, inVPre) && + vnode.tag.toLowerCase() === (node.tagName && node.tagName.toLowerCase()) + ) + } else { + return node.nodeType === (vnode.isComment ? 8 : 3) + } + } + + return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) { + if (isUndef(vnode)) { + if (isDef(oldVnode)) invokeDestroyHook(oldVnode) + return + } + + let isInitialPatch = false + const insertedVnodeQueue = [] + + if (isUndef(oldVnode)) { + // empty mount (likely as component), create new root element + isInitialPatch = true + createElm(vnode, insertedVnodeQueue, parentElm, refElm) + } else { + const isRealElement = isDef(oldVnode.nodeType) + if (!isRealElement && sameVnode(oldVnode, vnode)) { + // patch existing root node + patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly) + } else { + if (isRealElement) { + // mounting to a real element + // check if this is server-rendered content and if we can perform + // a successful hydration. + if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) { + oldVnode.removeAttribute(SSR_ATTR) + hydrating = true + } + if (isTrue(hydrating)) { + if (hydrate(oldVnode, vnode, insertedVnodeQueue)) { + invokeInsertHook(vnode, insertedVnodeQueue, true) + return oldVnode + } else if (process.env.NODE_ENV !== 'production') { + warn( + 'The client-side rendered virtual DOM tree is not matching ' + + 'server-rendered content. This is likely caused by incorrect ' + + 'HTML markup, for example nesting block-level elements inside ' + + '<p>, or missing <tbody>. Bailing hydration and performing ' + + 'full client-side render.' + ) + } + } + // either not server-rendered, or hydration failed. + // create an empty node and replace it + oldVnode = emptyNodeAt(oldVnode) + } + + // replacing existing element + const oldElm = oldVnode.elm + const parentElm = nodeOps.parentNode(oldElm) + + // create new node + createElm( + vnode, + insertedVnodeQueue, + // extremely rare edge case: do not insert if old element is in a + // leaving transition. Only happens when combining transition + + // keep-alive + HOCs. (#4590) + oldElm._leaveCb ? null : parentElm, + nodeOps.nextSibling(oldElm) + ) + + // update parent placeholder node element, recursively + if (isDef(vnode.parent)) { + let ancestor = vnode.parent + const patchable = isPatchable(vnode) + while (ancestor) { + for (let i = 0; i < cbs.destroy.length; ++i) { + cbs.destroy[i](ancestor) + } + ancestor.elm = vnode.elm + if (patchable) { + for (let i = 0; i < cbs.create.length; ++i) { + cbs.create[i](emptyNode, ancestor) + } + // #6513 + // invoke insert hooks that may have been merged by create hooks. + // e.g. for directives that uses the "inserted" hook. + const insert = ancestor.data.hook.insert + if (insert.merged) { + // start at index 1 to avoid re-invoking component mounted hook + for (let i = 1; i < insert.fns.length; i++) { + insert.fns[i]() + } + } + } else { + registerRef(ancestor) + } + ancestor = ancestor.parent + } + } + + // destroy old node + if (isDef(parentElm)) { + removeVnodes(parentElm, [oldVnode], 0, 0) + } else if (isDef(oldVnode.tag)) { + invokeDestroyHook(oldVnode) + } + } + } + + invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) + return vnode.elm + } +} diff --git a/node_modules/vue/src/core/vdom/vnode.js b/node_modules/vue/src/core/vdom/vnode.js new file mode 100644 index 00000000..88e80cec --- /dev/null +++ b/node_modules/vue/src/core/vdom/vnode.js @@ -0,0 +1,108 @@ +/* @flow */ + +export default class VNode { + tag: string | void; + data: VNodeData | void; + children: ?Array<VNode>; + text: string | void; + elm: Node | void; + ns: string | void; + context: Component | void; // rendered in this component's scope + key: string | number | void; + componentOptions: VNodeComponentOptions | void; + componentInstance: Component | void; // component instance + parent: VNode | void; // component placeholder node + + // strictly internal + raw: boolean; // contains raw HTML? (server only) + isStatic: boolean; // hoisted static node + isRootInsert: boolean; // necessary for enter transition check + isComment: boolean; // empty comment placeholder? + isCloned: boolean; // is a cloned node? + isOnce: boolean; // is a v-once node? + asyncFactory: Function | void; // async component factory function + asyncMeta: Object | void; + isAsyncPlaceholder: boolean; + ssrContext: Object | void; + fnContext: Component | void; // real context vm for functional nodes + fnOptions: ?ComponentOptions; // for SSR caching + fnScopeId: ?string; // functional scope id support + + constructor ( + tag?: string, + data?: VNodeData, + children?: ?Array<VNode>, + text?: string, + elm?: Node, + context?: Component, + componentOptions?: VNodeComponentOptions, + asyncFactory?: Function + ) { + this.tag = tag + this.data = data + this.children = children + this.text = text + this.elm = elm + this.ns = undefined + this.context = context + this.fnContext = undefined + this.fnOptions = undefined + this.fnScopeId = undefined + this.key = data && data.key + this.componentOptions = componentOptions + this.componentInstance = undefined + this.parent = undefined + this.raw = false + this.isStatic = false + this.isRootInsert = true + this.isComment = false + this.isCloned = false + this.isOnce = false + this.asyncFactory = asyncFactory + this.asyncMeta = undefined + this.isAsyncPlaceholder = false + } + + // DEPRECATED: alias for componentInstance for backwards compat. + /* istanbul ignore next */ + get child (): Component | void { + return this.componentInstance + } +} + +export const createEmptyVNode = (text: string = '') => { + const node = new VNode() + node.text = text + node.isComment = true + return node +} + +export function createTextVNode (val: string | number) { + return new VNode(undefined, undefined, undefined, String(val)) +} + +// optimized shallow clone +// used for static nodes and slot nodes because they may be reused across +// multiple renders, cloning them avoids errors when DOM manipulations rely +// on their elm reference. +export function cloneVNode (vnode: VNode): VNode { + const cloned = new VNode( + vnode.tag, + vnode.data, + vnode.children, + vnode.text, + vnode.elm, + vnode.context, + vnode.componentOptions, + vnode.asyncFactory + ) + cloned.ns = vnode.ns + cloned.isStatic = vnode.isStatic + cloned.key = vnode.key + cloned.isComment = vnode.isComment + cloned.fnContext = vnode.fnContext + cloned.fnOptions = vnode.fnOptions + cloned.fnScopeId = vnode.fnScopeId + cloned.isCloned = true + return cloned +} diff --git a/node_modules/vue/src/platforms/web/compiler/directives/html.js b/node_modules/vue/src/platforms/web/compiler/directives/html.js new file mode 100644 index 00000000..58569d8e --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/directives/html.js @@ -0,0 +1,9 @@ +/* @flow */ + +import { addProp } from 'compiler/helpers' + +export default function html (el: ASTElement, dir: ASTDirective) { + if (dir.value) { + addProp(el, 'innerHTML', `_s(${dir.value})`) + } +} diff --git a/node_modules/vue/src/platforms/web/compiler/directives/index.js b/node_modules/vue/src/platforms/web/compiler/directives/index.js new file mode 100644 index 00000000..0955b51e --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/directives/index.js @@ -0,0 +1,9 @@ +import model from './model' +import text from './text' +import html from './html' + +export default { + model, + text, + html +} diff --git a/node_modules/vue/src/platforms/web/compiler/directives/model.js b/node_modules/vue/src/platforms/web/compiler/directives/model.js new file mode 100644 index 00000000..126f0c7d --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/directives/model.js @@ -0,0 +1,172 @@ +/* @flow */ + +import config from 'core/config' +import { addHandler, addProp, getBindingAttr } from 'compiler/helpers' +import { genComponentModel, genAssignmentCode } from 'compiler/directives/model' + +let warn + +// in some cases, the event used has to be determined at runtime +// so we used some reserved tokens during compile. +export const RANGE_TOKEN = '__r' +export const CHECKBOX_RADIO_TOKEN = '__c' + +export default function model ( + el: ASTElement, + dir: ASTDirective, + _warn: Function +): ?boolean { + warn = _warn + const value = dir.value + const modifiers = dir.modifiers + const tag = el.tag + const type = el.attrsMap.type + + if (process.env.NODE_ENV !== 'production') { + // inputs with type="file" are read only and setting the input's + // value will throw an error. + if (tag === 'input' && type === 'file') { + warn( + `<${el.tag} v-model="${value}" type="file">:\n` + + `File inputs are read only. Use a v-on:change listener instead.` + ) + } + } + + if (el.component) { + genComponentModel(el, value, modifiers) + // component v-model doesn't need extra runtime + return false + } else if (tag === 'select') { + genSelect(el, value, modifiers) + } else if (tag === 'input' && type === 'checkbox') { + genCheckboxModel(el, value, modifiers) + } else if (tag === 'input' && type === 'radio') { + genRadioModel(el, value, modifiers) + } else if (tag === 'input' || tag === 'textarea') { + genDefaultModel(el, value, modifiers) + } else if (!config.isReservedTag(tag)) { + genComponentModel(el, value, modifiers) + // component v-model doesn't need extra runtime + return false + } else if (process.env.NODE_ENV !== 'production') { + warn( + `<${el.tag} v-model="${value}">: ` + + `v-model is not supported on this element type. ` + + 'If you are working with contenteditable, it\'s recommended to ' + + 'wrap a library dedicated for that purpose inside a custom component.' + ) + } + + // ensure runtime directive metadata + return true +} + +function genCheckboxModel ( + el: ASTElement, + value: string, + modifiers: ?ASTModifiers +) { + const number = modifiers && modifiers.number + const valueBinding = getBindingAttr(el, 'value') || 'null' + const trueValueBinding = getBindingAttr(el, 'true-value') || 'true' + const falseValueBinding = getBindingAttr(el, 'false-value') || 'false' + addProp(el, 'checked', + `Array.isArray(${value})` + + `?_i(${value},${valueBinding})>-1` + ( + trueValueBinding === 'true' + ? `:(${value})` + : `:_q(${value},${trueValueBinding})` + ) + ) + addHandler(el, 'change', + `var $$a=${value},` + + '$$el=$event.target,' + + `$$c=$$el.checked?(${trueValueBinding}):(${falseValueBinding});` + + 'if(Array.isArray($$a)){' + + `var $$v=${number ? '_n(' + valueBinding + ')' : valueBinding},` + + '$$i=_i($$a,$$v);' + + `if($$el.checked){$$i<0&&(${genAssignmentCode(value, '$$a.concat([$$v])')})}` + + `else{$$i>-1&&(${genAssignmentCode(value, '$$a.slice(0,$$i).concat($$a.slice($$i+1))')})}` + + `}else{${genAssignmentCode(value, '$$c')}}`, + null, true + ) +} + +function genRadioModel ( + el: ASTElement, + value: string, + modifiers: ?ASTModifiers +) { + const number = modifiers && modifiers.number + let valueBinding = getBindingAttr(el, 'value') || 'null' + valueBinding = number ? `_n(${valueBinding})` : valueBinding + addProp(el, 'checked', `_q(${value},${valueBinding})`) + addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true) +} + +function genSelect ( + el: ASTElement, + value: string, + modifiers: ?ASTModifiers +) { + const number = modifiers && modifiers.number + const selectedVal = `Array.prototype.filter` + + `.call($event.target.options,function(o){return o.selected})` + + `.map(function(o){var val = "_value" in o ? o._value : o.value;` + + `return ${number ? '_n(val)' : 'val'}})` + + const assignment = '$event.target.multiple ? $$selectedVal : $$selectedVal[0]' + let code = `var $$selectedVal = ${selectedVal};` + code = `${code} ${genAssignmentCode(value, assignment)}` + addHandler(el, 'change', code, null, true) +} + +function genDefaultModel ( + el: ASTElement, + value: string, + modifiers: ?ASTModifiers +): ?boolean { + const type = el.attrsMap.type + + // warn if v-bind:value conflicts with v-model + // except for inputs with v-bind:type + if (process.env.NODE_ENV !== 'production') { + const value = el.attrsMap['v-bind:value'] || el.attrsMap[':value'] + const typeBinding = el.attrsMap['v-bind:type'] || el.attrsMap[':type'] + if (value && !typeBinding) { + const binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value' + warn( + `${binding}="${value}" conflicts with v-model on the same element ` + + 'because the latter already expands to a value binding internally' + ) + } + } + + const { lazy, number, trim } = modifiers || {} + const needCompositionGuard = !lazy && type !== 'range' + const event = lazy + ? 'change' + : type === 'range' + ? RANGE_TOKEN + : 'input' + + let valueExpression = '$event.target.value' + if (trim) { + valueExpression = `$event.target.value.trim()` + } + if (number) { + valueExpression = `_n(${valueExpression})` + } + + let code = genAssignmentCode(value, valueExpression) + if (needCompositionGuard) { + code = `if($event.target.composing)return;${code}` + } + + addProp(el, 'value', `(${value})`) + addHandler(el, event, code, null, true) + if (trim || number) { + addHandler(el, 'blur', '$forceUpdate()') + } +} diff --git a/node_modules/vue/src/platforms/web/compiler/directives/text.js b/node_modules/vue/src/platforms/web/compiler/directives/text.js new file mode 100644 index 00000000..1af914f8 --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/directives/text.js @@ -0,0 +1,9 @@ +/* @flow */ + +import { addProp } from 'compiler/helpers' + +export default function text (el: ASTElement, dir: ASTDirective) { + if (dir.value) { + addProp(el, 'textContent', `_s(${dir.value})`) + } +} diff --git a/node_modules/vue/src/platforms/web/compiler/index.js b/node_modules/vue/src/platforms/web/compiler/index.js new file mode 100644 index 00000000..7ace0936 --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/index.js @@ -0,0 +1,8 @@ +/* @flow */ + +import { baseOptions } from './options' +import { createCompiler } from 'compiler/index' + +const { compile, compileToFunctions } = createCompiler(baseOptions) + +export { compile, compileToFunctions } diff --git a/node_modules/vue/src/platforms/web/compiler/modules/class.js b/node_modules/vue/src/platforms/web/compiler/modules/class.js new file mode 100644 index 00000000..ffdca250 --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/modules/class.js @@ -0,0 +1,48 @@ +/* @flow */ + +import { parseText } from 'compiler/parser/text-parser' +import { + getAndRemoveAttr, + getBindingAttr, + baseWarn +} from 'compiler/helpers' + +function transformNode (el: ASTElement, options: CompilerOptions) { + const warn = options.warn || baseWarn + const staticClass = getAndRemoveAttr(el, 'class') + if (process.env.NODE_ENV !== 'production' && staticClass) { + const res = parseText(staticClass, options.delimiters) + if (res) { + warn( + `class="${staticClass}": ` + + 'Interpolation inside attributes has been removed. ' + + 'Use v-bind or the colon shorthand instead. For example, ' + + 'instead of <div class="{{ val }}">, use <div :class="val">.' + ) + } + } + if (staticClass) { + el.staticClass = JSON.stringify(staticClass) + } + const classBinding = getBindingAttr(el, 'class', false /* getStatic */) + if (classBinding) { + el.classBinding = classBinding + } +} + +function genData (el: ASTElement): string { + let data = '' + if (el.staticClass) { + data += `staticClass:${el.staticClass},` + } + if (el.classBinding) { + data += `class:${el.classBinding},` + } + return data +} + +export default { + staticKeys: ['staticClass'], + transformNode, + genData +} diff --git a/node_modules/vue/src/platforms/web/compiler/modules/index.js b/node_modules/vue/src/platforms/web/compiler/modules/index.js new file mode 100644 index 00000000..29114a53 --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/modules/index.js @@ -0,0 +1,9 @@ +import klass from './class' +import style from './style' +import model from './model' + +export default [ + klass, + style, + model +] diff --git a/node_modules/vue/src/platforms/web/compiler/modules/model.js b/node_modules/vue/src/platforms/web/compiler/modules/model.js new file mode 100644 index 00000000..8d784fe4 --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/modules/model.js @@ -0,0 +1,94 @@ +/* @flow */ + +/** + * Expand input[v-model] with dyanmic type bindings into v-if-else chains + * Turn this: + * <input v-model="data[type]" :type="type"> + * into this: + * <input v-if="type === 'checkbox'" type="checkbox" v-model="data[type]"> + * <input v-else-if="type === 'radio'" type="radio" v-model="data[type]"> + * <input v-else :type="type" v-model="data[type]"> + */ + +import { + addRawAttr, + getBindingAttr, + getAndRemoveAttr +} from 'compiler/helpers' + +import { + processFor, + processElement, + addIfCondition, + createASTElement +} from 'compiler/parser/index' + +function preTransformNode (el: ASTElement, options: CompilerOptions) { + if (el.tag === 'input') { + const map = el.attrsMap + if (!map['v-model']) { + return + } + + let typeBinding + if (map[':type'] || map['v-bind:type']) { + typeBinding = getBindingAttr(el, 'type') + } + if (!map.type && !typeBinding && map['v-bind']) { + typeBinding = `(${map['v-bind']}).type` + } + + if (typeBinding) { + const ifCondition = getAndRemoveAttr(el, 'v-if', true) + const ifConditionExtra = ifCondition ? `&&(${ifCondition})` : `` + const hasElse = getAndRemoveAttr(el, 'v-else', true) != null + const elseIfCondition = getAndRemoveAttr(el, 'v-else-if', true) + // 1. checkbox + const branch0 = cloneASTElement(el) + // process for on the main node + processFor(branch0) + addRawAttr(branch0, 'type', 'checkbox') + processElement(branch0, options) + branch0.processed = true // prevent it from double-processed + branch0.if = `(${typeBinding})==='checkbox'` + ifConditionExtra + addIfCondition(branch0, { + exp: branch0.if, + block: branch0 + }) + // 2. add radio else-if condition + const branch1 = cloneASTElement(el) + getAndRemoveAttr(branch1, 'v-for', true) + addRawAttr(branch1, 'type', 'radio') + processElement(branch1, options) + addIfCondition(branch0, { + exp: `(${typeBinding})==='radio'` + ifConditionExtra, + block: branch1 + }) + // 3. other + const branch2 = cloneASTElement(el) + getAndRemoveAttr(branch2, 'v-for', true) + addRawAttr(branch2, ':type', typeBinding) + processElement(branch2, options) + addIfCondition(branch0, { + exp: ifCondition, + block: branch2 + }) + + if (hasElse) { + branch0.else = true + } else if (elseIfCondition) { + branch0.elseif = elseIfCondition + } + + return branch0 + } + } +} + +function cloneASTElement (el) { + return createASTElement(el.tag, el.attrsList.slice(), el.parent) +} + +export default { + preTransformNode +} diff --git a/node_modules/vue/src/platforms/web/compiler/modules/style.js b/node_modules/vue/src/platforms/web/compiler/modules/style.js new file mode 100644 index 00000000..2722b92b --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/modules/style.js @@ -0,0 +1,51 @@ +/* @flow */ + +import { parseText } from 'compiler/parser/text-parser' +import { parseStyleText } from 'web/util/style' +import { + getAndRemoveAttr, + getBindingAttr, + baseWarn +} from 'compiler/helpers' + +function transformNode (el: ASTElement, options: CompilerOptions) { + const warn = options.warn || baseWarn + const staticStyle = getAndRemoveAttr(el, 'style') + if (staticStyle) { + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production') { + const res = parseText(staticStyle, options.delimiters) + if (res) { + warn( + `style="${staticStyle}": ` + + 'Interpolation inside attributes has been removed. ' + + 'Use v-bind or the colon shorthand instead. For example, ' + + 'instead of <div style="{{ val }}">, use <div :style="val">.' + ) + } + } + el.staticStyle = JSON.stringify(parseStyleText(staticStyle)) + } + + const styleBinding = getBindingAttr(el, 'style', false /* getStatic */) + if (styleBinding) { + el.styleBinding = styleBinding + } +} + +function genData (el: ASTElement): string { + let data = '' + if (el.staticStyle) { + data += `staticStyle:${el.staticStyle},` + } + if (el.styleBinding) { + data += `style:(${el.styleBinding}),` + } + return data +} + +export default { + staticKeys: ['staticStyle'], + transformNode, + genData +} diff --git a/node_modules/vue/src/platforms/web/compiler/options.js b/node_modules/vue/src/platforms/web/compiler/options.js new file mode 100644 index 00000000..70c0c488 --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/options.js @@ -0,0 +1,26 @@ +/* @flow */ + +import { + isPreTag, + mustUseProp, + isReservedTag, + getTagNamespace +} from '../util/index' + +import modules from './modules/index' +import directives from './directives/index' +import { genStaticKeys } from 'shared/util' +import { isUnaryTag, canBeLeftOpenTag } from './util' + +export const baseOptions: CompilerOptions = { + expectHTML: true, + modules, + directives, + isPreTag, + isUnaryTag, + mustUseProp, + canBeLeftOpenTag, + isReservedTag, + getTagNamespace, + staticKeys: genStaticKeys(modules) +} diff --git a/node_modules/vue/src/platforms/web/compiler/util.js b/node_modules/vue/src/platforms/web/compiler/util.js new file mode 100644 index 00000000..7d9394d3 --- /dev/null +++ b/node_modules/vue/src/platforms/web/compiler/util.js @@ -0,0 +1,24 @@ +/* @flow */ + +import { makeMap } from 'shared/util' + +export const isUnaryTag = makeMap( + 'area,base,br,col,embed,frame,hr,img,input,isindex,keygen,' + + 'link,meta,param,source,track,wbr' +) + +// Elements that you can, intentionally, leave open +// (and which close themselves) +export const canBeLeftOpenTag = makeMap( + 'colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source' +) + +// HTML5 tags https://html.spec.whatwg.org/multipage/indices.html#elements-3 +// Phrasing Content https://html.spec.whatwg.org/multipage/dom.html#phrasing-content +export const isNonPhrasingTag = makeMap( + 'address,article,aside,base,blockquote,body,caption,col,colgroup,dd,' + + 'details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,' + + 'h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,' + + 'optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,' + + 'title,tr,track' +) diff --git a/node_modules/vue/src/platforms/web/entry-compiler.js b/node_modules/vue/src/platforms/web/entry-compiler.js new file mode 100644 index 00000000..ee63476e --- /dev/null +++ b/node_modules/vue/src/platforms/web/entry-compiler.js @@ -0,0 +1,5 @@ +/* @flow */ + +export { parseComponent } from 'sfc/parser' +export { compile, compileToFunctions } from './compiler/index' +export { ssrCompile, ssrCompileToFunctions } from './server/compiler' diff --git a/node_modules/vue/src/platforms/web/entry-runtime-with-compiler.js b/node_modules/vue/src/platforms/web/entry-runtime-with-compiler.js new file mode 100644 index 00000000..1ebc102d --- /dev/null +++ b/node_modules/vue/src/platforms/web/entry-runtime-with-compiler.js @@ -0,0 +1,100 @@ +/* @flow */ + +import config from 'core/config' +import { warn, cached } from 'core/util/index' +import { mark, measure } from 'core/util/perf' + +import Vue from './runtime/index' +import { query } from './util/index' +import { compileToFunctions } from './compiler/index' +import { shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat' + +const idToTemplate = cached(id => { + const el = query(id) + return el && el.innerHTML +}) + +const mount = Vue.prototype.$mount +Vue.prototype.$mount = function ( + el?: string | Element, + hydrating?: boolean +): Component { + el = el && query(el) + + /* istanbul ignore if */ + if (el === document.body || el === document.documentElement) { + process.env.NODE_ENV !== 'production' && warn( + `Do not mount Vue to <html> or <body> - mount to normal elements instead.` + ) + return this + } + + const options = this.$options + // resolve template/el and convert to render function + if (!options.render) { + let template = options.template + if (template) { + if (typeof template === 'string') { + if (template.charAt(0) === '#') { + template = idToTemplate(template) + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && !template) { + warn( + `Template element not found or is empty: ${options.template}`, + this + ) + } + } + } else if (template.nodeType) { + template = template.innerHTML + } else { + if (process.env.NODE_ENV !== 'production') { + warn('invalid template option:' + template, this) + } + return this + } + } else if (el) { + template = getOuterHTML(el) + } + if (template) { + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && config.performance && mark) { + mark('compile') + } + + const { render, staticRenderFns } = compileToFunctions(template, { + shouldDecodeNewlines, + shouldDecodeNewlinesForHref, + delimiters: options.delimiters, + comments: options.comments + }, this) + options.render = render + options.staticRenderFns = staticRenderFns + + /* istanbul ignore if */ + if (process.env.NODE_ENV !== 'production' && config.performance && mark) { + mark('compile end') + measure(`vue ${this._name} compile`, 'compile', 'compile end') + } + } + } + return mount.call(this, el, hydrating) +} + +/** + * Get outerHTML of elements, taking care + * of SVG elements in IE as well. + */ +function getOuterHTML (el: Element): string { + if (el.outerHTML) { + return el.outerHTML + } else { + const container = document.createElement('div') + container.appendChild(el.cloneNode(true)) + return container.innerHTML + } +} + +Vue.compile = compileToFunctions + +export default Vue diff --git a/node_modules/vue/src/platforms/web/entry-runtime.js b/node_modules/vue/src/platforms/web/entry-runtime.js new file mode 100644 index 00000000..27c97ff0 --- /dev/null +++ b/node_modules/vue/src/platforms/web/entry-runtime.js @@ -0,0 +1,5 @@ +/* @flow */ + +import Vue from './runtime/index' + +export default Vue diff --git a/node_modules/vue/src/platforms/web/entry-server-basic-renderer.js b/node_modules/vue/src/platforms/web/entry-server-basic-renderer.js new file mode 100644 index 00000000..c4b973bd --- /dev/null +++ b/node_modules/vue/src/platforms/web/entry-server-basic-renderer.js @@ -0,0 +1,13 @@ +/* @flow */ + +import modules from './server/modules/index' +import directives from './server/directives/index' +import { isUnaryTag, canBeLeftOpenTag } from './compiler/util' +import { createBasicRenderer } from 'server/create-basic-renderer' + +export default createBasicRenderer({ + modules, + directives, + isUnaryTag, + canBeLeftOpenTag +}) diff --git a/node_modules/vue/src/platforms/web/entry-server-renderer.js b/node_modules/vue/src/platforms/web/entry-server-renderer.js new file mode 100644 index 00000000..f61f601b --- /dev/null +++ b/node_modules/vue/src/platforms/web/entry-server-renderer.js @@ -0,0 +1,27 @@ +/* @flow */ + +process.env.VUE_ENV = 'server' + +import { extend } from 'shared/util' +import modules from './server/modules/index' +import baseDirectives from './server/directives/index' +import { isUnaryTag, canBeLeftOpenTag } from './compiler/util' + +import { createRenderer as _createRenderer } from 'server/create-renderer' +import { createBundleRendererCreator } from 'server/bundle-renderer/create-bundle-renderer' + +export function createRenderer (options?: Object = {}): { + renderToString: Function, + renderToStream: Function +} { + return _createRenderer(extend(extend({}, options), { + isUnaryTag, + canBeLeftOpenTag, + modules, + // user can provide server-side implementations for custom directives + // when creating the renderer. + directives: extend(baseDirectives, options.directives) + })) +} + +export const createBundleRenderer = createBundleRendererCreator(createRenderer) diff --git a/node_modules/vue/src/platforms/web/runtime/class-util.js b/node_modules/vue/src/platforms/web/runtime/class-util.js new file mode 100644 index 00000000..709526c2 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/class-util.js @@ -0,0 +1,61 @@ +/* @flow */ + +/** + * Add class with compatibility for SVG since classList is not supported on + * SVG elements in IE + */ +export function addClass (el: HTMLElement, cls: ?string) { + /* istanbul ignore if */ + if (!cls || !(cls = cls.trim())) { + return + } + + /* istanbul ignore else */ + if (el.classList) { + if (cls.indexOf(' ') > -1) { + cls.split(/\s+/).forEach(c => el.classList.add(c)) + } else { + el.classList.add(cls) + } + } else { + const cur = ` ${el.getAttribute('class') || ''} ` + if (cur.indexOf(' ' + cls + ' ') < 0) { + el.setAttribute('class', (cur + cls).trim()) + } + } +} + +/** + * Remove class with compatibility for SVG since classList is not supported on + * SVG elements in IE + */ +export function removeClass (el: HTMLElement, cls: ?string) { + /* istanbul ignore if */ + if (!cls || !(cls = cls.trim())) { + return + } + + /* istanbul ignore else */ + if (el.classList) { + if (cls.indexOf(' ') > -1) { + cls.split(/\s+/).forEach(c => el.classList.remove(c)) + } else { + el.classList.remove(cls) + } + if (!el.classList.length) { + el.removeAttribute('class') + } + } else { + let cur = ` ${el.getAttribute('class') || ''} ` + const tar = ' ' + cls + ' ' + while (cur.indexOf(tar) >= 0) { + cur = cur.replace(tar, ' ') + } + cur = cur.trim() + if (cur) { + el.setAttribute('class', cur) + } else { + el.removeAttribute('class') + } + } +} diff --git a/node_modules/vue/src/platforms/web/runtime/components/index.js b/node_modules/vue/src/platforms/web/runtime/components/index.js new file mode 100644 index 00000000..6bfe5780 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/components/index.js @@ -0,0 +1,7 @@ +import Transition from './transition' +import TransitionGroup from './transition-group' + +export default { + Transition, + TransitionGroup +} diff --git a/node_modules/vue/src/platforms/web/runtime/components/transition-group.js b/node_modules/vue/src/platforms/web/runtime/components/transition-group.js new file mode 100644 index 00000000..219ff01c --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/components/transition-group.js @@ -0,0 +1,180 @@ +/* @flow */ + +// Provides transition support for list items. +// supports move transitions using the FLIP technique. + +// Because the vdom's children update algorithm is "unstable" - i.e. +// it doesn't guarantee the relative positioning of removed elements, +// we force transition-group to update its children into two passes: +// in the first pass, we remove all nodes that need to be removed, +// triggering their leaving transition; in the second pass, we insert/move +// into the final desired state. This way in the second pass removed +// nodes will remain where they should be. + +import { warn, extend } from 'core/util/index' +import { addClass, removeClass } from '../class-util' +import { transitionProps, extractTransitionData } from './transition' + +import { + hasTransition, + getTransitionInfo, + transitionEndEvent, + addTransitionClass, + removeTransitionClass +} from '../transition-util' + +const props = extend({ + tag: String, + moveClass: String +}, transitionProps) + +delete props.mode + +export default { + props, + + render (h: Function) { + const tag: string = this.tag || this.$vnode.data.tag || 'span' + const map: Object = Object.create(null) + const prevChildren: Array<VNode> = this.prevChildren = this.children + const rawChildren: Array<VNode> = this.$slots.default || [] + const children: Array<VNode> = this.children = [] + const transitionData: Object = extractTransitionData(this) + + for (let i = 0; i < rawChildren.length; i++) { + const c: VNode = rawChildren[i] + if (c.tag) { + if (c.key != null && String(c.key).indexOf('__vlist') !== 0) { + children.push(c) + map[c.key] = c + ;(c.data || (c.data = {})).transition = transitionData + } else if (process.env.NODE_ENV !== 'production') { + const opts: ?VNodeComponentOptions = c.componentOptions + const name: string = opts ? (opts.Ctor.options.name || opts.tag || '') : c.tag + warn(`<transition-group> children must be keyed: <${name}>`) + } + } + } + + if (prevChildren) { + const kept: Array<VNode> = [] + const removed: Array<VNode> = [] + for (let i = 0; i < prevChildren.length; i++) { + const c: VNode = prevChildren[i] + c.data.transition = transitionData + c.data.pos = c.elm.getBoundingClientRect() + if (map[c.key]) { + kept.push(c) + } else { + removed.push(c) + } + } + this.kept = h(tag, null, kept) + this.removed = removed + } + + return h(tag, null, children) + }, + + beforeUpdate () { + // force removing pass + this.__patch__( + this._vnode, + this.kept, + false, // hydrating + true // removeOnly (!important, avoids unnecessary moves) + ) + this._vnode = this.kept + }, + + updated () { + const children: Array<VNode> = this.prevChildren + const moveClass: string = this.moveClass || ((this.name || 'v') + '-move') + if (!children.length || !this.hasMove(children[0].elm, moveClass)) { + return + } + + // we divide the work into three loops to avoid mixing DOM reads and writes + // in each iteration - which helps prevent layout thrashing. + children.forEach(callPendingCbs) + children.forEach(recordPosition) + children.forEach(applyTranslation) + + // force reflow to put everything in position + // assign to this to avoid being removed in tree-shaking + // $flow-disable-line + this._reflow = document.body.offsetHeight + + children.forEach((c: VNode) => { + if (c.data.moved) { + var el: any = c.elm + var s: any = el.style + addTransitionClass(el, moveClass) + s.transform = s.WebkitTransform = s.transitionDuration = '' + el.addEventListener(transitionEndEvent, el._moveCb = function cb (e) { + if (!e || /transform$/.test(e.propertyName)) { + el.removeEventListener(transitionEndEvent, cb) + el._moveCb = null + removeTransitionClass(el, moveClass) + } + }) + } + }) + }, + + methods: { + hasMove (el: any, moveClass: string): boolean { + /* istanbul ignore if */ + if (!hasTransition) { + return false + } + /* istanbul ignore if */ + if (this._hasMove) { + return this._hasMove + } + // Detect whether an element with the move class applied has + // CSS transitions. Since the element may be inside an entering + // transition at this very moment, we make a clone of it and remove + // all other transition classes applied to ensure only the move class + // is applied. + const clone: HTMLElement = el.cloneNode() + if (el._transitionClasses) { + el._transitionClasses.forEach((cls: string) => { removeClass(clone, cls) }) + } + addClass(clone, moveClass) + clone.style.display = 'none' + this.$el.appendChild(clone) + const info: Object = getTransitionInfo(clone) + this.$el.removeChild(clone) + return (this._hasMove = info.hasTransform) + } + } +} + +function callPendingCbs (c: VNode) { + /* istanbul ignore if */ + if (c.elm._moveCb) { + c.elm._moveCb() + } + /* istanbul ignore if */ + if (c.elm._enterCb) { + c.elm._enterCb() + } +} + +function recordPosition (c: VNode) { + c.data.newPos = c.elm.getBoundingClientRect() +} + +function applyTranslation (c: VNode) { + const oldPos = c.data.pos + const newPos = c.data.newPos + const dx = oldPos.left - newPos.left + const dy = oldPos.top - newPos.top + if (dx || dy) { + c.data.moved = true + const s = c.elm.style + s.transform = s.WebkitTransform = `translate(${dx}px,${dy}px)` + s.transitionDuration = '0s' + } +} diff --git a/node_modules/vue/src/platforms/web/runtime/components/transition.js b/node_modules/vue/src/platforms/web/runtime/components/transition.js new file mode 100644 index 00000000..6111d8cb --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/components/transition.js @@ -0,0 +1,194 @@ +/* @flow */ + +// Provides transition support for a single element/component. +// supports transition mode (out-in / in-out) + +import { warn } from 'core/util/index' +import { camelize, extend, isPrimitive } from 'shared/util' +import { + mergeVNodeHook, + isAsyncPlaceholder, + getFirstComponentChild +} from 'core/vdom/helpers/index' + +export const transitionProps = { + name: String, + appear: Boolean, + css: Boolean, + mode: String, + type: String, + enterClass: String, + leaveClass: String, + enterToClass: String, + leaveToClass: String, + enterActiveClass: String, + leaveActiveClass: String, + appearClass: String, + appearActiveClass: String, + appearToClass: String, + duration: [Number, String, Object] +} + +// in case the child is also an abstract component, e.g. <keep-alive> +// we want to recursively retrieve the real component to be rendered +function getRealChild (vnode: ?VNode): ?VNode { + const compOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions + if (compOptions && compOptions.Ctor.options.abstract) { + return getRealChild(getFirstComponentChild(compOptions.children)) + } else { + return vnode + } +} + +export function extractTransitionData (comp: Component): Object { + const data = {} + const options: ComponentOptions = comp.$options + // props + for (const key in options.propsData) { + data[key] = comp[key] + } + // events. + // extract listeners and pass them directly to the transition methods + const listeners: ?Object = options._parentListeners + for (const key in listeners) { + data[camelize(key)] = listeners[key] + } + return data +} + +function placeholder (h: Function, rawChild: VNode): ?VNode { + if (/\d-keep-alive$/.test(rawChild.tag)) { + return h('keep-alive', { + props: rawChild.componentOptions.propsData + }) + } +} + +function hasParentTransition (vnode: VNode): ?boolean { + while ((vnode = vnode.parent)) { + if (vnode.data.transition) { + return true + } + } +} + +function isSameChild (child: VNode, oldChild: VNode): boolean { + return oldChild.key === child.key && oldChild.tag === child.tag +} + +export default { + name: 'transition', + props: transitionProps, + abstract: true, + + render (h: Function) { + let children: any = this.$slots.default + if (!children) { + return + } + + // filter out text nodes (possible whitespaces) + children = children.filter((c: VNode) => c.tag || isAsyncPlaceholder(c)) + /* istanbul ignore if */ + if (!children.length) { + return + } + + // warn multiple elements + if (process.env.NODE_ENV !== 'production' && children.length > 1) { + warn( + '<transition> can only be used on a single element. Use ' + + '<transition-group> for lists.', + this.$parent + ) + } + + const mode: string = this.mode + + // warn invalid mode + if (process.env.NODE_ENV !== 'production' && + mode && mode !== 'in-out' && mode !== 'out-in' + ) { + warn( + 'invalid <transition> mode: ' + mode, + this.$parent + ) + } + + const rawChild: VNode = children[0] + + // if this is a component root node and the component's + // parent container node also has transition, skip. + if (hasParentTransition(this.$vnode)) { + return rawChild + } + + // apply transition data to child + // use getRealChild() to ignore abstract components e.g. keep-alive + const child: ?VNode = getRealChild(rawChild) + /* istanbul ignore if */ + if (!child) { + return rawChild + } + + if (this._leaving) { + return placeholder(h, rawChild) + } + + // ensure a key that is unique to the vnode type and to this transition + // component instance. This key will be used to remove pending leaving nodes + // during entering. + const id: string = `__transition-${this._uid}-` + child.key = child.key == null + ? child.isComment + ? id + 'comment' + : id + child.tag + : isPrimitive(child.key) + ? (String(child.key).indexOf(id) === 0 ? child.key : id + child.key) + : child.key + + const data: Object = (child.data || (child.data = {})).transition = extractTransitionData(this) + const oldRawChild: VNode = this._vnode + const oldChild: VNode = getRealChild(oldRawChild) + + // mark v-show + // so that the transition module can hand over the control to the directive + if (child.data.directives && child.data.directives.some(d => d.name === 'show')) { + child.data.show = true + } + + if ( + oldChild && + oldChild.data && + !isSameChild(child, oldChild) && + !isAsyncPlaceholder(oldChild) && + // #6687 component root is a comment node + !(oldChild.componentInstance && oldChild.componentInstance._vnode.isComment) + ) { + // replace old child transition data with fresh one + // important for dynamic transitions! + const oldData: Object = oldChild.data.transition = extend({}, data) + // handle transition mode + if (mode === 'out-in') { + // return placeholder node and queue update when leave finishes + this._leaving = true + mergeVNodeHook(oldData, 'afterLeave', () => { + this._leaving = false + this.$forceUpdate() + }) + return placeholder(h, rawChild) + } else if (mode === 'in-out') { + if (isAsyncPlaceholder(child)) { + return oldRawChild + } + let delayedLeave + const performLeave = () => { delayedLeave() } + mergeVNodeHook(data, 'afterEnter', performLeave) + mergeVNodeHook(data, 'enterCancelled', performLeave) + mergeVNodeHook(oldData, 'delayLeave', leave => { delayedLeave = leave }) + } + } + + return rawChild + } +} diff --git a/node_modules/vue/src/platforms/web/runtime/directives/index.js b/node_modules/vue/src/platforms/web/runtime/directives/index.js new file mode 100644 index 00000000..b673f11a --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/directives/index.js @@ -0,0 +1,7 @@ +import model from './model' +import show from './show' + +export default { + model, + show +} diff --git a/node_modules/vue/src/platforms/web/runtime/directives/model.js b/node_modules/vue/src/platforms/web/runtime/directives/model.js new file mode 100644 index 00000000..cba8ded0 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/directives/model.js @@ -0,0 +1,147 @@ +/** + * Not type checking this file because flow doesn't like attaching + * properties to Elements. + */ + +import { isTextInputType } from 'web/util/element' +import { looseEqual, looseIndexOf } from 'shared/util' +import { mergeVNodeHook } from 'core/vdom/helpers/index' +import { warn, isIE9, isIE, isEdge } from 'core/util/index' + +/* istanbul ignore if */ +if (isIE9) { + // http://www.matts411.com/post/internet-explorer-9-oninput/ + document.addEventListener('selectionchange', () => { + const el = document.activeElement + if (el && el.vmodel) { + trigger(el, 'input') + } + }) +} + +const directive = { + inserted (el, binding, vnode, oldVnode) { + if (vnode.tag === 'select') { + // #6903 + if (oldVnode.elm && !oldVnode.elm._vOptions) { + mergeVNodeHook(vnode, 'postpatch', () => { + directive.componentUpdated(el, binding, vnode) + }) + } else { + setSelected(el, binding, vnode.context) + } + el._vOptions = [].map.call(el.options, getValue) + } else if (vnode.tag === 'textarea' || isTextInputType(el.type)) { + el._vModifiers = binding.modifiers + if (!binding.modifiers.lazy) { + el.addEventListener('compositionstart', onCompositionStart) + el.addEventListener('compositionend', onCompositionEnd) + // Safari < 10.2 & UIWebView doesn't fire compositionend when + // switching focus before confirming composition choice + // this also fixes the issue where some browsers e.g. iOS Chrome + // fires "change" instead of "input" on autocomplete. + el.addEventListener('change', onCompositionEnd) + /* istanbul ignore if */ + if (isIE9) { + el.vmodel = true + } + } + } + }, + + componentUpdated (el, binding, vnode) { + if (vnode.tag === 'select') { + setSelected(el, binding, vnode.context) + // in case the options rendered by v-for have changed, + // it's possible that the value is out-of-sync with the rendered options. + // detect such cases and filter out values that no longer has a matching + // option in the DOM. + const prevOptions = el._vOptions + const curOptions = el._vOptions = [].map.call(el.options, getValue) + if (curOptions.some((o, i) => !looseEqual(o, prevOptions[i]))) { + // trigger change event if + // no matching option found for at least one value + const needReset = el.multiple + ? binding.value.some(v => hasNoMatchingOption(v, curOptions)) + : binding.value !== binding.oldValue && hasNoMatchingOption(binding.value, curOptions) + if (needReset) { + trigger(el, 'change') + } + } + } + } +} + +function setSelected (el, binding, vm) { + actuallySetSelected(el, binding, vm) + /* istanbul ignore if */ + if (isIE || isEdge) { + setTimeout(() => { + actuallySetSelected(el, binding, vm) + }, 0) + } +} + +function actuallySetSelected (el, binding, vm) { + const value = binding.value + const isMultiple = el.multiple + if (isMultiple && !Array.isArray(value)) { + process.env.NODE_ENV !== 'production' && warn( + `<select multiple v-model="${binding.expression}"> ` + + `expects an Array value for its binding, but got ${ + Object.prototype.toString.call(value).slice(8, -1) + }`, + vm + ) + return + } + let selected, option + for (let i = 0, l = el.options.length; i < l; i++) { + option = el.options[i] + if (isMultiple) { + selected = looseIndexOf(value, getValue(option)) > -1 + if (option.selected !== selected) { + option.selected = selected + } + } else { + if (looseEqual(getValue(option), value)) { + if (el.selectedIndex !== i) { + el.selectedIndex = i + } + return + } + } + } + if (!isMultiple) { + el.selectedIndex = -1 + } +} + +function hasNoMatchingOption (value, options) { + return options.every(o => !looseEqual(o, value)) +} + +function getValue (option) { + return '_value' in option + ? option._value + : option.value +} + +function onCompositionStart (e) { + e.target.composing = true +} + +function onCompositionEnd (e) { + // prevent triggering an input event for no reason + if (!e.target.composing) return + e.target.composing = false + trigger(e.target, 'input') +} + +function trigger (el, type) { + const e = document.createEvent('HTMLEvents') + e.initEvent(type, true, true) + el.dispatchEvent(e) +} + +export default directive diff --git a/node_modules/vue/src/platforms/web/runtime/directives/show.js b/node_modules/vue/src/platforms/web/runtime/directives/show.js new file mode 100644 index 00000000..17fb5dec --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/directives/show.js @@ -0,0 +1,60 @@ +/* @flow */ + +import { enter, leave } from '../modules/transition' + +// recursively search for possible transition defined inside the component root +function locateNode (vnode: VNode): VNodeWithData { + return vnode.componentInstance && (!vnode.data || !vnode.data.transition) + ? locateNode(vnode.componentInstance._vnode) + : vnode +} + +export default { + bind (el: any, { value }: VNodeDirective, vnode: VNodeWithData) { + vnode = locateNode(vnode) + const transition = vnode.data && vnode.data.transition + const originalDisplay = el.__vOriginalDisplay = + el.style.display === 'none' ? '' : el.style.display + if (value && transition) { + vnode.data.show = true + enter(vnode, () => { + el.style.display = originalDisplay + }) + } else { + el.style.display = value ? originalDisplay : 'none' + } + }, + + update (el: any, { value, oldValue }: VNodeDirective, vnode: VNodeWithData) { + /* istanbul ignore if */ + if (!value === !oldValue) return + vnode = locateNode(vnode) + const transition = vnode.data && vnode.data.transition + if (transition) { + vnode.data.show = true + if (value) { + enter(vnode, () => { + el.style.display = el.__vOriginalDisplay + }) + } else { + leave(vnode, () => { + el.style.display = 'none' + }) + } + } else { + el.style.display = value ? el.__vOriginalDisplay : 'none' + } + }, + + unbind ( + el: any, + binding: VNodeDirective, + vnode: VNodeWithData, + oldVnode: VNodeWithData, + isDestroy: boolean + ) { + if (!isDestroy) { + el.style.display = el.__vOriginalDisplay + } + } +} diff --git a/node_modules/vue/src/platforms/web/runtime/index.js b/node_modules/vue/src/platforms/web/runtime/index.js new file mode 100644 index 00000000..1283f521 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/index.js @@ -0,0 +1,77 @@ +/* @flow */ + +import Vue from 'core/index' +import config from 'core/config' +import { extend, noop } from 'shared/util' +import { mountComponent } from 'core/instance/lifecycle' +import { devtools, inBrowser, isChrome } from 'core/util/index' + +import { + query, + mustUseProp, + isReservedTag, + isReservedAttr, + getTagNamespace, + isUnknownElement +} from 'web/util/index' + +import { patch } from './patch' +import platformDirectives from './directives/index' +import platformComponents from './components/index' + +// install platform specific utils +Vue.config.mustUseProp = mustUseProp +Vue.config.isReservedTag = isReservedTag +Vue.config.isReservedAttr = isReservedAttr +Vue.config.getTagNamespace = getTagNamespace +Vue.config.isUnknownElement = isUnknownElement + +// install platform runtime directives & components +extend(Vue.options.directives, platformDirectives) +extend(Vue.options.components, platformComponents) + +// install platform patch function +Vue.prototype.__patch__ = inBrowser ? patch : noop + +// public mount method +Vue.prototype.$mount = function ( + el?: string | Element, + hydrating?: boolean +): Component { + el = el && inBrowser ? query(el) : undefined + return mountComponent(this, el, hydrating) +} + +// devtools global hook +/* istanbul ignore next */ +if (inBrowser) { + setTimeout(() => { + if (config.devtools) { + if (devtools) { + devtools.emit('init', Vue) + } else if ( + process.env.NODE_ENV !== 'production' && + process.env.NODE_ENV !== 'test' && + isChrome + ) { + console[console.info ? 'info' : 'log']( + 'Download the Vue Devtools extension for a better development experience:\n' + + 'https://github.com/vuejs/vue-devtools' + ) + } + } + if (process.env.NODE_ENV !== 'production' && + process.env.NODE_ENV !== 'test' && + config.productionTip !== false && + typeof console !== 'undefined' + ) { + console[console.info ? 'info' : 'log']( + `You are running Vue in development mode.\n` + + `Make sure to turn on production mode when deploying for production.\n` + + `See more tips at https://vuejs.org/guide/deployment.html` + ) + } + }, 0) +} + +export default Vue diff --git a/node_modules/vue/src/platforms/web/runtime/modules/attrs.js b/node_modules/vue/src/platforms/web/runtime/modules/attrs.js new file mode 100644 index 00000000..78ef28a1 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/modules/attrs.js @@ -0,0 +1,118 @@ +/* @flow */ + +import { isIE, isIE9, isEdge } from 'core/util/env' + +import { + extend, + isDef, + isUndef +} from 'shared/util' + +import { + isXlink, + xlinkNS, + getXlinkProp, + isBooleanAttr, + isEnumeratedAttr, + isFalsyAttrValue +} from 'web/util/index' + +function updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) { + const opts = vnode.componentOptions + if (isDef(opts) && opts.Ctor.options.inheritAttrs === false) { + return + } + if (isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs)) { + return + } + let key, cur, old + const elm = vnode.elm + const oldAttrs = oldVnode.data.attrs || {} + let attrs: any = vnode.data.attrs || {} + // clone observed objects, as the user probably wants to mutate it + if (isDef(attrs.__ob__)) { + attrs = vnode.data.attrs = extend({}, attrs) + } + + for (key in attrs) { + cur = attrs[key] + old = oldAttrs[key] + if (old !== cur) { + setAttr(elm, key, cur) + } + } + // #4391: in IE9, setting type can reset value for input[type=radio] + // #6666: IE/Edge forces progress value down to 1 before setting a max + /* istanbul ignore if */ + if ((isIE || isEdge) && attrs.value !== oldAttrs.value) { + setAttr(elm, 'value', attrs.value) + } + for (key in oldAttrs) { + if (isUndef(attrs[key])) { + if (isXlink(key)) { + elm.removeAttributeNS(xlinkNS, getXlinkProp(key)) + } else if (!isEnumeratedAttr(key)) { + elm.removeAttribute(key) + } + } + } +} + +function setAttr (el: Element, key: string, value: any) { + if (el.tagName.indexOf('-') > -1) { + baseSetAttr(el, key, value) + } else if (isBooleanAttr(key)) { + // set attribute for blank value + // e.g. <option disabled>Select one</option> + if (isFalsyAttrValue(value)) { + el.removeAttribute(key) + } else { + // technically allowfullscreen is a boolean attribute for <iframe>, + // but Flash expects a value of "true" when used on <embed> tag + value = key === 'allowfullscreen' && el.tagName === 'EMBED' + ? 'true' + : key + el.setAttribute(key, value) + } + } else if (isEnumeratedAttr(key)) { + el.setAttribute(key, isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true') + } else if (isXlink(key)) { + if (isFalsyAttrValue(value)) { + el.removeAttributeNS(xlinkNS, getXlinkProp(key)) + } else { + el.setAttributeNS(xlinkNS, key, value) + } + } else { + baseSetAttr(el, key, value) + } +} + +function baseSetAttr (el, key, value) { + if (isFalsyAttrValue(value)) { + el.removeAttribute(key) + } else { + // #7138: IE10 & 11 fires input event when setting placeholder on + // <textarea>... block the first input event and remove the blocker + // immediately. + /* istanbul ignore if */ + if ( + isIE && !isIE9 && + el.tagName === 'TEXTAREA' && + key === 'placeholder' && !el.__ieph + ) { + const blocker = e => { + e.stopImmediatePropagation() + el.removeEventListener('input', blocker) + } + el.addEventListener('input', blocker) + // $flow-disable-line + el.__ieph = true /* IE placeholder patched */ + } + el.setAttribute(key, value) + } +} + +export default { + create: updateAttrs, + update: updateAttrs +} diff --git a/node_modules/vue/src/platforms/web/runtime/modules/class.js b/node_modules/vue/src/platforms/web/runtime/modules/class.js new file mode 100644 index 00000000..29fd2c11 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/modules/class.js @@ -0,0 +1,48 @@ +/* @flow */ + +import { + isDef, + isUndef +} from 'shared/util' + +import { + concat, + stringifyClass, + genClassForVnode +} from 'web/util/index' + +function updateClass (oldVnode: any, vnode: any) { + const el = vnode.elm + const data: VNodeData = vnode.data + const oldData: VNodeData = oldVnode.data + if ( + isUndef(data.staticClass) && + isUndef(data.class) && ( + isUndef(oldData) || ( + isUndef(oldData.staticClass) && + isUndef(oldData.class) + ) + ) + ) { + return + } + + let cls = genClassForVnode(vnode) + + // handle transition classes + const transitionClass = el._transitionClasses + if (isDef(transitionClass)) { + cls = concat(cls, stringifyClass(transitionClass)) + } + + // set the class + if (cls !== el._prevClass) { + el.setAttribute('class', cls) + el._prevClass = cls + } +} + +export default { + create: updateClass, + update: updateClass +} diff --git a/node_modules/vue/src/platforms/web/runtime/modules/dom-props.js b/node_modules/vue/src/platforms/web/runtime/modules/dom-props.js new file mode 100644 index 00000000..f3888aed --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/modules/dom-props.js @@ -0,0 +1,95 @@ +/* @flow */ + +import { isDef, isUndef, extend, toNumber } from 'shared/util' + +function updateDOMProps (oldVnode: VNodeWithData, vnode: VNodeWithData) { + if (isUndef(oldVnode.data.domProps) && isUndef(vnode.data.domProps)) { + return + } + let key, cur + const elm: any = vnode.elm + const oldProps = oldVnode.data.domProps || {} + let props = vnode.data.domProps || {} + // clone observed objects, as the user probably wants to mutate it + if (isDef(props.__ob__)) { + props = vnode.data.domProps = extend({}, props) + } + + for (key in oldProps) { + if (isUndef(props[key])) { + elm[key] = '' + } + } + for (key in props) { + cur = props[key] + // ignore children if the node has textContent or innerHTML, + // as these will throw away existing DOM nodes and cause removal errors + // on subsequent patches (#3360) + if (key === 'textContent' || key === 'innerHTML') { + if (vnode.children) vnode.children.length = 0 + if (cur === oldProps[key]) continue + // #6601 work around Chrome version <= 55 bug where single textNode + // replaced by innerHTML/textContent retains its parentNode property + if (elm.childNodes.length === 1) { + elm.removeChild(elm.childNodes[0]) + } + } + + if (key === 'value') { + // store value as _value as well since + // non-string values will be stringified + elm._value = cur + // avoid resetting cursor position when value is the same + const strCur = isUndef(cur) ? '' : String(cur) + if (shouldUpdateValue(elm, strCur)) { + elm.value = strCur + } + } else { + elm[key] = cur + } + } +} + +// check platforms/web/util/attrs.js acceptValue +type acceptValueElm = HTMLInputElement | HTMLSelectElement | HTMLOptionElement; + +function shouldUpdateValue (elm: acceptValueElm, checkVal: string): boolean { + return (!elm.composing && ( + elm.tagName === 'OPTION' || + isNotInFocusAndDirty(elm, checkVal) || + isDirtyWithModifiers(elm, checkVal) + )) +} + +function isNotInFocusAndDirty (elm: acceptValueElm, checkVal: string): boolean { + // return true when textbox (.number and .trim) loses focus and its value is + // not equal to the updated value + let notInFocus = true + // #6157 + // work around IE bug when accessing document.activeElement in an iframe + try { notInFocus = document.activeElement !== elm } catch (e) {} + return notInFocus && elm.value !== checkVal +} + +function isDirtyWithModifiers (elm: any, newVal: string): boolean { + const value = elm.value + const modifiers = elm._vModifiers // injected by v-model runtime + if (isDef(modifiers)) { + if (modifiers.lazy) { + // inputs with lazy should only be updated when not in focus + return false + } + if (modifiers.number) { + return toNumber(value) !== toNumber(newVal) + } + if (modifiers.trim) { + return value.trim() !== newVal.trim() + } + } + return value !== newVal +} + +export default { + create: updateDOMProps, + update: updateDOMProps +} diff --git a/node_modules/vue/src/platforms/web/runtime/modules/events.js b/node_modules/vue/src/platforms/web/runtime/modules/events.js new file mode 100644 index 00000000..1e71b667 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/modules/events.js @@ -0,0 +1,87 @@ +/* @flow */ + +import { isDef, isUndef } from 'shared/util' +import { updateListeners } from 'core/vdom/helpers/index' +import { withMacroTask, isIE, supportsPassive } from 'core/util/index' +import { RANGE_TOKEN, CHECKBOX_RADIO_TOKEN } from 'web/compiler/directives/model' + +// normalize v-model event tokens that can only be determined at runtime. +// it's important to place the event as the first in the array because +// the whole point is ensuring the v-model callback gets called before +// user-attached handlers. +function normalizeEvents (on) { + /* istanbul ignore if */ + if (isDef(on[RANGE_TOKEN])) { + // IE input[type=range] only supports `change` event + const event = isIE ? 'change' : 'input' + on[event] = [].concat(on[RANGE_TOKEN], on[event] || []) + delete on[RANGE_TOKEN] + } + // This was originally intended to fix #4521 but no longer necessary + // after 2.5. Keeping it for backwards compat with generated code from < 2.4 + /* istanbul ignore if */ + if (isDef(on[CHECKBOX_RADIO_TOKEN])) { + on.change = [].concat(on[CHECKBOX_RADIO_TOKEN], on.change || []) + delete on[CHECKBOX_RADIO_TOKEN] + } +} + +let target: any + +function createOnceHandler (handler, event, capture) { + const _target = target // save current target element in closure + return function onceHandler () { + const res = handler.apply(null, arguments) + if (res !== null) { + remove(event, onceHandler, capture, _target) + } + } +} + +function add ( + event: string, + handler: Function, + once: boolean, + capture: boolean, + passive: boolean +) { + handler = withMacroTask(handler) + if (once) handler = createOnceHandler(handler, event, capture) + target.addEventListener( + event, + handler, + supportsPassive + ? { capture, passive } + : capture + ) +} + +function remove ( + event: string, + handler: Function, + capture: boolean, + _target?: HTMLElement +) { + (_target || target).removeEventListener( + event, + handler._withTask || handler, + capture + ) +} + +function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) { + if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) { + return + } + const on = vnode.data.on || {} + const oldOn = oldVnode.data.on || {} + target = vnode.elm + normalizeEvents(on) + updateListeners(on, oldOn, add, remove, vnode.context) + target = undefined +} + +export default { + create: updateDOMListeners, + update: updateDOMListeners +} diff --git a/node_modules/vue/src/platforms/web/runtime/modules/index.js b/node_modules/vue/src/platforms/web/runtime/modules/index.js new file mode 100644 index 00000000..9f6ad8f9 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/modules/index.js @@ -0,0 +1,15 @@ +import attrs from './attrs' +import klass from './class' +import events from './events' +import domProps from './dom-props' +import style from './style' +import transition from './transition' + +export default [ + attrs, + klass, + events, + domProps, + style, + transition +] diff --git a/node_modules/vue/src/platforms/web/runtime/modules/style.js b/node_modules/vue/src/platforms/web/runtime/modules/style.js new file mode 100644 index 00000000..29e1c3c1 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/modules/style.js @@ -0,0 +1,93 @@ +/* @flow */ + +import { getStyle, normalizeStyleBinding } from 'web/util/style' +import { cached, camelize, extend, isDef, isUndef } from 'shared/util' + +const cssVarRE = /^--/ +const importantRE = /\s*!important$/ +const setProp = (el, name, val) => { + /* istanbul ignore if */ + if (cssVarRE.test(name)) { + el.style.setProperty(name, val) + } else if (importantRE.test(val)) { + el.style.setProperty(name, val.replace(importantRE, ''), 'important') + } else { + const normalizedName = normalize(name) + if (Array.isArray(val)) { + // Support values array created by autoprefixer, e.g. + // {display: ["-webkit-box", "-ms-flexbox", "flex"]} + // Set them one by one, and the browser will only set those it can recognize + for (let i = 0, len = val.length; i < len; i++) { + el.style[normalizedName] = val[i] + } + } else { + el.style[normalizedName] = val + } + } +} + +const vendorNames = ['Webkit', 'Moz', 'ms'] + +let emptyStyle +const normalize = cached(function (prop) { + emptyStyle = emptyStyle || document.createElement('div').style + prop = camelize(prop) + if (prop !== 'filter' && (prop in emptyStyle)) { + return prop + } + const capName = prop.charAt(0).toUpperCase() + prop.slice(1) + for (let i = 0; i < vendorNames.length; i++) { + const name = vendorNames[i] + capName + if (name in emptyStyle) { + return name + } + } +}) + +function updateStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) { + const data = vnode.data + const oldData = oldVnode.data + + if (isUndef(data.staticStyle) && isUndef(data.style) && + isUndef(oldData.staticStyle) && isUndef(oldData.style) + ) { + return + } + + let cur, name + const el: any = vnode.elm + const oldStaticStyle: any = oldData.staticStyle + const oldStyleBinding: any = oldData.normalizedStyle || oldData.style || {} + + // if static style exists, stylebinding already merged into it when doing normalizeStyleData + const oldStyle = oldStaticStyle || oldStyleBinding + + const style = normalizeStyleBinding(vnode.data.style) || {} + + // store normalized style under a different key for next diff + // make sure to clone it if it's reactive, since the user likely wants + // to mutate it. + vnode.data.normalizedStyle = isDef(style.__ob__) + ? extend({}, style) + : style + + const newStyle = getStyle(vnode, true) + + for (name in oldStyle) { + if (isUndef(newStyle[name])) { + setProp(el, name, '') + } + } + for (name in newStyle) { + cur = newStyle[name] + if (cur !== oldStyle[name]) { + // ie9 setting to null has no effect, must use empty string + setProp(el, name, cur == null ? '' : cur) + } + } +} + +export default { + create: updateStyle, + update: updateStyle +} diff --git a/node_modules/vue/src/platforms/web/runtime/modules/transition.js b/node_modules/vue/src/platforms/web/runtime/modules/transition.js new file mode 100644 index 00000000..0796b5da --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/modules/transition.js @@ -0,0 +1,343 @@ +/* @flow */ + +import { inBrowser, isIE9, warn } from 'core/util/index' +import { mergeVNodeHook } from 'core/vdom/helpers/index' +import { activeInstance } from 'core/instance/lifecycle' + +import { + once, + isDef, + isUndef, + isObject, + toNumber +} from 'shared/util' + +import { + nextFrame, + resolveTransition, + whenTransitionEnds, + addTransitionClass, + removeTransitionClass +} from '../transition-util' + +export function enter (vnode: VNodeWithData, toggleDisplay: ?() => void) { + const el: any = vnode.elm + + // call leave callback now + if (isDef(el._leaveCb)) { + el._leaveCb.cancelled = true + el._leaveCb() + } + + const data = resolveTransition(vnode.data.transition) + if (isUndef(data)) { + return + } + + /* istanbul ignore if */ + if (isDef(el._enterCb) || el.nodeType !== 1) { + return + } + + const { + css, + type, + enterClass, + enterToClass, + enterActiveClass, + appearClass, + appearToClass, + appearActiveClass, + beforeEnter, + enter, + afterEnter, + enterCancelled, + beforeAppear, + appear, + afterAppear, + appearCancelled, + duration + } = data + + // activeInstance will always be the <transition> component managing this + // transition. One edge case to check is when the <transition> is placed + // as the root node of a child component. In that case we need to check + // <transition>'s parent for appear check. + let context = activeInstance + let transitionNode = activeInstance.$vnode + while (transitionNode && transitionNode.parent) { + transitionNode = transitionNode.parent + context = transitionNode.context + } + + const isAppear = !context._isMounted || !vnode.isRootInsert + + if (isAppear && !appear && appear !== '') { + return + } + + const startClass = isAppear && appearClass + ? appearClass + : enterClass + const activeClass = isAppear && appearActiveClass + ? appearActiveClass + : enterActiveClass + const toClass = isAppear && appearToClass + ? appearToClass + : enterToClass + + const beforeEnterHook = isAppear + ? (beforeAppear || beforeEnter) + : beforeEnter + const enterHook = isAppear + ? (typeof appear === 'function' ? appear : enter) + : enter + const afterEnterHook = isAppear + ? (afterAppear || afterEnter) + : afterEnter + const enterCancelledHook = isAppear + ? (appearCancelled || enterCancelled) + : enterCancelled + + const explicitEnterDuration: any = toNumber( + isObject(duration) + ? duration.enter + : duration + ) + + if (process.env.NODE_ENV !== 'production' && explicitEnterDuration != null) { + checkDuration(explicitEnterDuration, 'enter', vnode) + } + + const expectsCSS = css !== false && !isIE9 + const userWantsControl = getHookArgumentsLength(enterHook) + + const cb = el._enterCb = once(() => { + if (expectsCSS) { + removeTransitionClass(el, toClass) + removeTransitionClass(el, activeClass) + } + if (cb.cancelled) { + if (expectsCSS) { + removeTransitionClass(el, startClass) + } + enterCancelledHook && enterCancelledHook(el) + } else { + afterEnterHook && afterEnterHook(el) + } + el._enterCb = null + }) + + if (!vnode.data.show) { + // remove pending leave element on enter by injecting an insert hook + mergeVNodeHook(vnode, 'insert', () => { + const parent = el.parentNode + const pendingNode = parent && parent._pending && parent._pending[vnode.key] + if (pendingNode && + pendingNode.tag === vnode.tag && + pendingNode.elm._leaveCb + ) { + pendingNode.elm._leaveCb() + } + enterHook && enterHook(el, cb) + }) + } + + // start enter transition + beforeEnterHook && beforeEnterHook(el) + if (expectsCSS) { + addTransitionClass(el, startClass) + addTransitionClass(el, activeClass) + nextFrame(() => { + removeTransitionClass(el, startClass) + if (!cb.cancelled) { + addTransitionClass(el, toClass) + if (!userWantsControl) { + if (isValidDuration(explicitEnterDuration)) { + setTimeout(cb, explicitEnterDuration) + } else { + whenTransitionEnds(el, type, cb) + } + } + } + }) + } + + if (vnode.data.show) { + toggleDisplay && toggleDisplay() + enterHook && enterHook(el, cb) + } + + if (!expectsCSS && !userWantsControl) { + cb() + } +} + +export function leave (vnode: VNodeWithData, rm: Function) { + const el: any = vnode.elm + + // call enter callback now + if (isDef(el._enterCb)) { + el._enterCb.cancelled = true + el._enterCb() + } + + const data = resolveTransition(vnode.data.transition) + if (isUndef(data) || el.nodeType !== 1) { + return rm() + } + + /* istanbul ignore if */ + if (isDef(el._leaveCb)) { + return + } + + const { + css, + type, + leaveClass, + leaveToClass, + leaveActiveClass, + beforeLeave, + leave, + afterLeave, + leaveCancelled, + delayLeave, + duration + } = data + + const expectsCSS = css !== false && !isIE9 + const userWantsControl = getHookArgumentsLength(leave) + + const explicitLeaveDuration: any = toNumber( + isObject(duration) + ? duration.leave + : duration + ) + + if (process.env.NODE_ENV !== 'production' && isDef(explicitLeaveDuration)) { + checkDuration(explicitLeaveDuration, 'leave', vnode) + } + + const cb = el._leaveCb = once(() => { + if (el.parentNode && el.parentNode._pending) { + el.parentNode._pending[vnode.key] = null + } + if (expectsCSS) { + removeTransitionClass(el, leaveToClass) + removeTransitionClass(el, leaveActiveClass) + } + if (cb.cancelled) { + if (expectsCSS) { + removeTransitionClass(el, leaveClass) + } + leaveCancelled && leaveCancelled(el) + } else { + rm() + afterLeave && afterLeave(el) + } + el._leaveCb = null + }) + + if (delayLeave) { + delayLeave(performLeave) + } else { + performLeave() + } + + function performLeave () { + // the delayed leave may have already been cancelled + if (cb.cancelled) { + return + } + // record leaving element + if (!vnode.data.show) { + (el.parentNode._pending || (el.parentNode._pending = {}))[(vnode.key: any)] = vnode + } + beforeLeave && beforeLeave(el) + if (expectsCSS) { + addTransitionClass(el, leaveClass) + addTransitionClass(el, leaveActiveClass) + nextFrame(() => { + removeTransitionClass(el, leaveClass) + if (!cb.cancelled) { + addTransitionClass(el, leaveToClass) + if (!userWantsControl) { + if (isValidDuration(explicitLeaveDuration)) { + setTimeout(cb, explicitLeaveDuration) + } else { + whenTransitionEnds(el, type, cb) + } + } + } + }) + } + leave && leave(el, cb) + if (!expectsCSS && !userWantsControl) { + cb() + } + } +} + +// only used in dev mode +function checkDuration (val, name, vnode) { + if (typeof val !== 'number') { + warn( + `<transition> explicit ${name} duration is not a valid number - ` + + `got ${JSON.stringify(val)}.`, + vnode.context + ) + } else if (isNaN(val)) { + warn( + `<transition> explicit ${name} duration is NaN - ` + + 'the duration expression might be incorrect.', + vnode.context + ) + } +} + +function isValidDuration (val) { + return typeof val === 'number' && !isNaN(val) +} + +/** + * Normalize a transition hook's argument length. The hook may be: + * - a merged hook (invoker) with the original in .fns + * - a wrapped component method (check ._length) + * - a plain function (.length) + */ +function getHookArgumentsLength (fn: Function): boolean { + if (isUndef(fn)) { + return false + } + const invokerFns = fn.fns + if (isDef(invokerFns)) { + // invoker + return getHookArgumentsLength( + Array.isArray(invokerFns) + ? invokerFns[0] + : invokerFns + ) + } else { + return (fn._length || fn.length) > 1 + } +} + +function _enter (_: any, vnode: VNodeWithData) { + if (vnode.data.show !== true) { + enter(vnode) + } +} + +export default inBrowser ? { + create: _enter, + activate: _enter, + remove (vnode: VNode, rm: Function) { + /* istanbul ignore else */ + if (vnode.data.show !== true) { + leave(vnode, rm) + } else { + rm() + } + } +} : {} diff --git a/node_modules/vue/src/platforms/web/runtime/node-ops.js b/node_modules/vue/src/platforms/web/runtime/node-ops.js new file mode 100644 index 00000000..3a2978a7 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/node-ops.js @@ -0,0 +1,59 @@ +/* @flow */ + +import { namespaceMap } from 'web/util/index' + +export function createElement (tagName: string, vnode: VNode): Element { + const elm = document.createElement(tagName) + if (tagName !== 'select') { + return elm + } + // false or null will remove the attribute but undefined will not + if (vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined) { + elm.setAttribute('multiple', 'multiple') + } + return elm +} + +export function createElementNS (namespace: string, tagName: string): Element { + return document.createElementNS(namespaceMap[namespace], tagName) +} + +export function createTextNode (text: string): Text { + return document.createTextNode(text) +} + +export function createComment (text: string): Comment { + return document.createComment(text) +} + +export function insertBefore (parentNode: Node, newNode: Node, referenceNode: Node) { + parentNode.insertBefore(newNode, referenceNode) +} + +export function removeChild (node: Node, child: Node) { + node.removeChild(child) +} + +export function appendChild (node: Node, child: Node) { + node.appendChild(child) +} + +export function parentNode (node: Node): ?Node { + return node.parentNode +} + +export function nextSibling (node: Node): ?Node { + return node.nextSibling +} + +export function tagName (node: Element): string { + return node.tagName +} + +export function setTextContent (node: Node, text: string) { + node.textContent = text +} + +export function setStyleScope (node: Element, scopeId: string) { + node.setAttribute(scopeId, '') +} diff --git a/node_modules/vue/src/platforms/web/runtime/patch.js b/node_modules/vue/src/platforms/web/runtime/patch.js new file mode 100644 index 00000000..d982afe9 --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/patch.js @@ -0,0 +1,12 @@ +/* @flow */ + +import * as nodeOps from 'web/runtime/node-ops' +import { createPatchFunction } from 'core/vdom/patch' +import baseModules from 'core/vdom/modules/index' +import platformModules from 'web/runtime/modules/index' + +// the directive module should be applied last, after all +// built-in modules have been applied. +const modules = platformModules.concat(baseModules) + +export const patch: Function = createPatchFunction({ nodeOps, modules }) diff --git a/node_modules/vue/src/platforms/web/runtime/transition-util.js b/node_modules/vue/src/platforms/web/runtime/transition-util.js new file mode 100644 index 00000000..47668f0c --- /dev/null +++ b/node_modules/vue/src/platforms/web/runtime/transition-util.js @@ -0,0 +1,185 @@ +/* @flow */ + +import { inBrowser, isIE9 } from 'core/util/index' +import { addClass, removeClass } from './class-util' +import { remove, extend, cached } from 'shared/util' + +export function resolveTransition (def?: string | Object): ?Object { + if (!def) { + return + } + /* istanbul ignore else */ + if (typeof def === 'object') { + const res = {} + if (def.css !== false) { + extend(res, autoCssTransition(def.name || 'v')) + } + extend(res, def) + return res + } else if (typeof def === 'string') { + return autoCssTransition(def) + } +} + +const autoCssTransition: (name: string) => Object = cached(name => { + return { + enterClass: `${name}-enter`, + enterToClass: `${name}-enter-to`, + enterActiveClass: `${name}-enter-active`, + leaveClass: `${name}-leave`, + leaveToClass: `${name}-leave-to`, + leaveActiveClass: `${name}-leave-active` + } +}) + +export const hasTransition = inBrowser && !isIE9 +const TRANSITION = 'transition' +const ANIMATION = 'animation' + +// Transition property/event sniffing +export let transitionProp = 'transition' +export let transitionEndEvent = 'transitionend' +export let animationProp = 'animation' +export let animationEndEvent = 'animationend' +if (hasTransition) { + /* istanbul ignore if */ + if (window.ontransitionend === undefined && + window.onwebkittransitionend !== undefined + ) { + transitionProp = 'WebkitTransition' + transitionEndEvent = 'webkitTransitionEnd' + } + if (window.onanimationend === undefined && + window.onwebkitanimationend !== undefined + ) { + animationProp = 'WebkitAnimation' + animationEndEvent = 'webkitAnimationEnd' + } +} + +// binding to window is necessary to make hot reload work in IE in strict mode +const raf = inBrowser + ? window.requestAnimationFrame + ? window.requestAnimationFrame.bind(window) + : setTimeout + : /* istanbul ignore next */ fn => fn() + +export function nextFrame (fn: Function) { + raf(() => { + raf(fn) + }) +} + +export function addTransitionClass (el: any, cls: string) { + const transitionClasses = el._transitionClasses || (el._transitionClasses = []) + if (transitionClasses.indexOf(cls) < 0) { + transitionClasses.push(cls) + addClass(el, cls) + } +} + +export function removeTransitionClass (el: any, cls: string) { + if (el._transitionClasses) { + remove(el._transitionClasses, cls) + } + removeClass(el, cls) +} + +export function whenTransitionEnds ( + el: Element, + expectedType: ?string, + cb: Function +) { + const { type, timeout, propCount } = getTransitionInfo(el, expectedType) + if (!type) return cb() + const event: string = type === TRANSITION ? transitionEndEvent : animationEndEvent + let ended = 0 + const end = () => { + el.removeEventListener(event, onEnd) + cb() + } + const onEnd = e => { + if (e.target === el) { + if (++ended >= propCount) { + end() + } + } + } + setTimeout(() => { + if (ended < propCount) { + end() + } + }, timeout + 1) + el.addEventListener(event, onEnd) +} + +const transformRE = /\b(transform|all)(,|$)/ + +export function getTransitionInfo (el: Element, expectedType?: ?string): { + type: ?string; + propCount: number; + timeout: number; + hasTransform: boolean; +} { + const styles: any = window.getComputedStyle(el) + const transitionDelays: Array<string> = styles[transitionProp + 'Delay'].split(', ') + const transitionDurations: Array<string> = styles[transitionProp + 'Duration'].split(', ') + const transitionTimeout: number = getTimeout(transitionDelays, transitionDurations) + const animationDelays: Array<string> = styles[animationProp + 'Delay'].split(', ') + const animationDurations: Array<string> = styles[animationProp + 'Duration'].split(', ') + const animationTimeout: number = getTimeout(animationDelays, animationDurations) + + let type: ?string + let timeout = 0 + let propCount = 0 + /* istanbul ignore if */ + if (expectedType === TRANSITION) { + if (transitionTimeout > 0) { + type = TRANSITION + timeout = transitionTimeout + propCount = transitionDurations.length + } + } else if (expectedType === ANIMATION) { + if (animationTimeout > 0) { + type = ANIMATION + timeout = animationTimeout + propCount = animationDurations.length + } + } else { + timeout = Math.max(transitionTimeout, animationTimeout) + type = timeout > 0 + ? transitionTimeout > animationTimeout + ? TRANSITION + : ANIMATION + : null + propCount = type + ? type === TRANSITION + ? transitionDurations.length + : animationDurations.length + : 0 + } + const hasTransform: boolean = + type === TRANSITION && + transformRE.test(styles[transitionProp + 'Property']) + return { + type, + timeout, + propCount, + hasTransform + } +} + +function getTimeout (delays: Array<string>, durations: Array<string>): number { + /* istanbul ignore next */ + while (delays.length < durations.length) { + delays = delays.concat(delays) + } + + return Math.max.apply(null, durations.map((d, i) => { + return toMs(d) + toMs(delays[i]) + })) +} + +function toMs (s: string): number { + return Number(s.slice(0, -1)) * 1000 +} diff --git a/node_modules/vue/src/platforms/web/server/compiler.js b/node_modules/vue/src/platforms/web/server/compiler.js new file mode 100644 index 00000000..20e57367 --- /dev/null +++ b/node_modules/vue/src/platforms/web/server/compiler.js @@ -0,0 +1,11 @@ +/* @flow */ + +import { baseOptions } from '../compiler/options' +import { createCompiler } from 'server/optimizing-compiler/index' + +const { compile, compileToFunctions } = createCompiler(baseOptions) + +export { + compile as ssrCompile, + compileToFunctions as ssrCompileToFunctions +} diff --git a/node_modules/vue/src/platforms/web/server/directives/index.js b/node_modules/vue/src/platforms/web/server/directives/index.js new file mode 100644 index 00000000..fd7fce0a --- /dev/null +++ b/node_modules/vue/src/platforms/web/server/directives/index.js @@ -0,0 +1,7 @@ +import show from './show' +import model from './model' + +export default { + show, + model +} diff --git a/node_modules/vue/src/platforms/web/server/directives/model.js b/node_modules/vue/src/platforms/web/server/directives/model.js new file mode 100644 index 00000000..7962d6c8 --- /dev/null +++ b/node_modules/vue/src/platforms/web/server/directives/model.js @@ -0,0 +1,44 @@ +/* @flow */ + +import { looseEqual, looseIndexOf } from 'shared/util' + +// this is only applied for <select v-model> because it is the only edge case +// that must be done at runtime instead of compile time. +export default function model (node: VNodeWithData, dir: VNodeDirective) { + if (!node.children) return + const value = dir.value + const isMultiple = node.data.attrs && node.data.attrs.multiple + for (let i = 0, l = node.children.length; i < l; i++) { + const option = node.children[i] + if (option.tag === 'option') { + if (isMultiple) { + const selected = + Array.isArray(value) && + (looseIndexOf(value, getValue(option)) > -1) + if (selected) { + setSelected(option) + } + } else { + if (looseEqual(value, getValue(option))) { + setSelected(option) + return + } + } + } + } +} + +function getValue (option) { + const data = option.data || {} + return ( + (data.attrs && data.attrs.value) || + (data.domProps && data.domProps.value) || + (option.children && option.children[0] && option.children[0].text) + ) +} + +function setSelected (option) { + const data = option.data || (option.data = {}) + const attrs = data.attrs || (data.attrs = {}) + attrs.selected = '' +} diff --git a/node_modules/vue/src/platforms/web/server/directives/show.js b/node_modules/vue/src/platforms/web/server/directives/show.js new file mode 100644 index 00000000..7493b27e --- /dev/null +++ b/node_modules/vue/src/platforms/web/server/directives/show.js @@ -0,0 +1,12 @@ +/* @flow */ + +export default function show (node: VNodeWithData, dir: VNodeDirective) { + if (!dir.value) { + const style: any = node.data.style || (node.data.style = {}) + if (Array.isArray(style)) { + style.push({ display: 'none' }) + } else { + style.display = 'none' + } + } +} diff --git a/node_modules/vue/src/platforms/web/server/modules/attrs.js b/node_modules/vue/src/platforms/web/server/modules/attrs.js new file mode 100644 index 00000000..c3f73013 --- /dev/null +++ b/node_modules/vue/src/platforms/web/server/modules/attrs.js @@ -0,0 +1,62 @@ +/* @flow */ + +import { escape } from '../util' + +import { + isDef, + isUndef, + extend +} from 'shared/util' + +import { + isBooleanAttr, + isEnumeratedAttr, + isFalsyAttrValue +} from 'web/util/attrs' + +import { isSSRUnsafeAttr } from 'web/server/util' + +export default function renderAttrs (node: VNodeWithData): string { + let attrs = node.data.attrs + let res = '' + + const opts = node.parent && node.parent.componentOptions + if (isUndef(opts) || opts.Ctor.options.inheritAttrs !== false) { + let parent = node.parent + while (isDef(parent)) { + if (isDef(parent.data) && isDef(parent.data.attrs)) { + attrs = extend(extend({}, attrs), parent.data.attrs) + } + parent = parent.parent + } + } + + if (isUndef(attrs)) { + return res + } + + for (const key in attrs) { + if (isSSRUnsafeAttr(key)) { + continue + } + if (key === 'style') { + // leave it to the style module + continue + } + res += renderAttr(key, attrs[key]) + } + return res +} + +export function renderAttr (key: string, value: string): string { + if (isBooleanAttr(key)) { + if (!isFalsyAttrValue(value)) { + return ` ${key}="${key}"` + } + } else if (isEnumeratedAttr(key)) { + return ` ${key}="${isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true'}"` + } else if (!isFalsyAttrValue(value)) { + return ` ${key}="${escape(String(value))}"` + } + return '' +} diff --git a/node_modules/vue/src/platforms/web/server/modules/class.js b/node_modules/vue/src/platforms/web/server/modules/class.js new file mode 100644 index 00000000..c19040ed --- /dev/null +++ b/node_modules/vue/src/platforms/web/server/modules/class.js @@ -0,0 +1,11 @@ +/* @flow */ + +import { escape } from '../util' +import { genClassForVnode } from 'web/util/index' + +export default function renderClass (node: VNodeWithData): ?string { + const classList = genClassForVnode(node) + if (classList !== '') { + return ` class="${escape(classList)}"` + } +} diff --git a/node_modules/vue/src/platforms/web/server/modules/dom-props.js b/node_modules/vue/src/platforms/web/server/modules/dom-props.js new file mode 100644 index 00000000..57c18eef --- /dev/null +++ b/node_modules/vue/src/platforms/web/server/modules/dom-props.js @@ -0,0 +1,50 @@ +/* @flow */ + +import VNode from 'core/vdom/vnode' +import { renderAttr } from './attrs' +import { isDef, isUndef, extend } from 'shared/util' +import { propsToAttrMap, isRenderableAttr } from '../util' + +export default function renderDOMProps (node: VNodeWithData): string { + let props = node.data.domProps + let res = '' + + let parent = node.parent + while (isDef(parent)) { + if (parent.data && parent.data.domProps) { + props = extend(extend({}, props), parent.data.domProps) + } + parent = parent.parent + } + + if (isUndef(props)) { + return res + } + + const attrs = node.data.attrs + for (const key in props) { + if (key === 'innerHTML') { + setText(node, props[key], true) + } else if (key === 'textContent') { + setText(node, props[key], false) + } else if (key === 'value' && node.tag === 'textarea') { + setText(node, props[key], false) + } else { + // $flow-disable-line (WTF?) + const attr = propsToAttrMap[key] || key.toLowerCase() + if (isRenderableAttr(attr) && + // avoid rendering double-bound props/attrs twice + !(isDef(attrs) && isDef(attrs[attr])) + ) { + res += renderAttr(attr, props[key]) + } + } + } + return res +} + +function setText (node, text, raw) { + const child = new VNode(undefined, undefined, undefined, text) + child.raw = raw + node.children = [child] +} diff --git a/node_modules/vue/src/platforms/web/server/modules/index.js b/node_modules/vue/src/platforms/web/server/modules/index.js new file mode 100644 index 00000000..3d15bc50 --- /dev/null +++ b/node_modules/vue/src/platforms/web/server/modules/index.js @@ -0,0 +1,11 @@ +import attrs from './attrs' +import domProps from './dom-props' +import klass from './class' +import style from './style' + +export default [ + attrs, + domProps, + klass, + style +] diff --git a/node_modules/vue/src/platforms/web/server/modules/style.js b/node_modules/vue/src/platforms/web/server/modules/style.js new file mode 100644 index 00000000..fc043ef3 --- /dev/null +++ b/node_modules/vue/src/platforms/web/server/modules/style.js @@ -0,0 +1,28 @@ +/* @flow */ + +import { escape } from '../util' +import { hyphenate } from 'shared/util' +import { getStyle } from 'web/util/style' + +export function genStyle (style: Object): string { + let styleText = '' + for (const key in style) { + const value = style[key] + const hyphenatedKey = hyphenate(key) + if (Array.isArray(value)) { + for (let i = 0, len = value.length; i < len; i++) { + styleText += `${hyphenatedKey}:${value[i]};` + } + } else { + styleText += `${hyphenatedKey}:${value};` + } + } + return styleText +} + +export default function renderStyle (vnode: VNodeWithData): ?string { + const styleText = genStyle(getStyle(vnode, false)) + if (styleText !== '') { + return ` style=${JSON.stringify(escape(styleText))}` + } +} diff --git a/node_modules/vue/src/platforms/web/server/util.js b/node_modules/vue/src/platforms/web/server/util.js new file mode 100644 index 00000000..27489705 --- /dev/null +++ b/node_modules/vue/src/platforms/web/server/util.js @@ -0,0 +1,56 @@ +/* @flow */ + +import { makeMap } from 'shared/util' + +const isAttr = makeMap( + 'accept,accept-charset,accesskey,action,align,alt,async,autocomplete,' + + 'autofocus,autoplay,autosave,bgcolor,border,buffered,challenge,charset,' + + 'checked,cite,class,code,codebase,color,cols,colspan,content,http-equiv,' + + 'name,contenteditable,contextmenu,controls,coords,data,datetime,default,' + + 'defer,dir,dirname,disabled,download,draggable,dropzone,enctype,method,for,' + + 'form,formaction,headers,height,hidden,high,href,hreflang,http-equiv,' + + 'icon,id,ismap,itemprop,keytype,kind,label,lang,language,list,loop,low,' + + 'manifest,max,maxlength,media,method,GET,POST,min,multiple,email,file,' + + 'muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,' + + 'preload,radiogroup,readonly,rel,required,reversed,rows,rowspan,sandbox,' + + 'scope,scoped,seamless,selected,shape,size,type,text,password,sizes,span,' + + 'spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,' + + 'target,title,type,usemap,value,width,wrap' +) + +const unsafeAttrCharRE = /[>/="'\u0009\u000a\u000c\u0020]/ +export const isSSRUnsafeAttr = (name: string): boolean => { + return unsafeAttrCharRE.test(name) +} + +/* istanbul ignore next */ +const isRenderableAttr = (name: string): boolean => { + return ( + isAttr(name) || + name.indexOf('data-') === 0 || + name.indexOf('aria-') === 0 + ) +} +export { isRenderableAttr } + +export const propsToAttrMap = { + acceptCharset: 'accept-charset', + className: 'class', + htmlFor: 'for', + httpEquiv: 'http-equiv' +} + +const ESC = { + '<': '<', + '>': '>', + '"': '"', + '&': '&' +} + +export function escape (s: string) { + return s.replace(/[<>"&]/g, escapeChar) +} + +function escapeChar (a) { + return ESC[a] || a +} diff --git a/node_modules/vue/src/platforms/web/util/attrs.js b/node_modules/vue/src/platforms/web/util/attrs.js new file mode 100644 index 00000000..59517da9 --- /dev/null +++ b/node_modules/vue/src/platforms/web/util/attrs.js @@ -0,0 +1,43 @@ +/* @flow */ + +import { makeMap } from 'shared/util' + +// these are reserved for web because they are directly compiled away +// during template compilation +export const isReservedAttr = makeMap('style,class') + +// attributes that should be using props for binding +const acceptValue = makeMap('input,textarea,option,select,progress') +export const mustUseProp = (tag: string, type: ?string, attr: string): boolean => { + return ( + (attr === 'value' && acceptValue(tag)) && type !== 'button' || + (attr === 'selected' && tag === 'option') || + (attr === 'checked' && tag === 'input') || + (attr === 'muted' && tag === 'video') + ) +} + +export const isEnumeratedAttr = makeMap('contenteditable,draggable,spellcheck') + +export const isBooleanAttr = makeMap( + 'allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,' + + 'default,defaultchecked,defaultmuted,defaultselected,defer,disabled,' + + 'enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,' + + 'muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,' + + 'required,reversed,scoped,seamless,selected,sortable,translate,' + + 'truespeed,typemustmatch,visible' +) + +export const xlinkNS = 'http://www.w3.org/1999/xlink' + +export const isXlink = (name: string): boolean => { + return name.charAt(5) === ':' && name.slice(0, 5) === 'xlink' +} + +export const getXlinkProp = (name: string): string => { + return isXlink(name) ? name.slice(6, name.length) : '' +} + +export const isFalsyAttrValue = (val: any): boolean => { + return val == null || val === false +} diff --git a/node_modules/vue/src/platforms/web/util/class.js b/node_modules/vue/src/platforms/web/util/class.js new file mode 100644 index 00000000..542dcbc4 --- /dev/null +++ b/node_modules/vue/src/platforms/web/util/class.js @@ -0,0 +1,85 @@ +/* @flow */ + +import { isDef, isObject } from 'shared/util' + +export function genClassForVnode (vnode: VNodeWithData): string { + let data = vnode.data + let parentNode = vnode + let childNode = vnode + while (isDef(childNode.componentInstance)) { + childNode = childNode.componentInstance._vnode + if (childNode && childNode.data) { + data = mergeClassData(childNode.data, data) + } + } + while (isDef(parentNode = parentNode.parent)) { + if (parentNode && parentNode.data) { + data = mergeClassData(data, parentNode.data) + } + } + return renderClass(data.staticClass, data.class) +} + +function mergeClassData (child: VNodeData, parent: VNodeData): { + staticClass: string, + class: any +} { + return { + staticClass: concat(child.staticClass, parent.staticClass), + class: isDef(child.class) + ? [child.class, parent.class] + : parent.class + } +} + +export function renderClass ( + staticClass: ?string, + dynamicClass: any +): string { + if (isDef(staticClass) || isDef(dynamicClass)) { + return concat(staticClass, stringifyClass(dynamicClass)) + } + /* istanbul ignore next */ + return '' +} + +export function concat (a: ?string, b: ?string): string { + return a ? b ? (a + ' ' + b) : a : (b || '') +} + +export function stringifyClass (value: any): string { + if (Array.isArray(value)) { + return stringifyArray(value) + } + if (isObject(value)) { + return stringifyObject(value) + } + if (typeof value === 'string') { + return value + } + /* istanbul ignore next */ + return '' +} + +function stringifyArray (value: Array<any>): string { + let res = '' + let stringified + for (let i = 0, l = value.length; i < l; i++) { + if (isDef(stringified = stringifyClass(value[i])) && stringified !== '') { + if (res) res += ' ' + res += stringified + } + } + return res +} + +function stringifyObject (value: Object): string { + let res = '' + for (const key in value) { + if (value[key]) { + if (res) res += ' ' + res += key + } + } + return res +} diff --git a/node_modules/vue/src/platforms/web/util/compat.js b/node_modules/vue/src/platforms/web/util/compat.js new file mode 100644 index 00000000..d95759cc --- /dev/null +++ b/node_modules/vue/src/platforms/web/util/compat.js @@ -0,0 +1,16 @@ +/* @flow */ + +import { inBrowser } from 'core/util/index' + +// check whether current browser encodes a char inside attribute values +let div +function getShouldDecode (href: boolean): boolean { + div = div || document.createElement('div') + div.innerHTML = href ? `<a href="\n"/>` : `<div a="\n"/>` + return div.innerHTML.indexOf(' ') > 0 +} + +// #3663: IE encodes newlines inside attribute values while other browsers don't +export const shouldDecodeNewlines = inBrowser ? getShouldDecode(false) : false +// #6828: chrome encodes content in a[href] +export const shouldDecodeNewlinesForHref = inBrowser ? getShouldDecode(true) : false diff --git a/node_modules/vue/src/platforms/web/util/element.js b/node_modules/vue/src/platforms/web/util/element.js new file mode 100644 index 00000000..65f1aafb --- /dev/null +++ b/node_modules/vue/src/platforms/web/util/element.js @@ -0,0 +1,77 @@ +/* @flow */ + +import { inBrowser } from 'core/util/env' +import { makeMap } from 'shared/util' + +export const namespaceMap = { + svg: 'http://www.w3.org/2000/svg', + math: 'http://www.w3.org/1998/Math/MathML' +} + +export const isHTMLTag = makeMap( + 'html,body,base,head,link,meta,style,title,' + + 'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' + + 'div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,' + + 'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' + + 's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' + + 'embed,object,param,source,canvas,script,noscript,del,ins,' + + 'caption,col,colgroup,table,thead,tbody,td,th,tr,' + + 'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' + + 'output,progress,select,textarea,' + + 'details,dialog,menu,menuitem,summary,' + + 'content,element,shadow,template,blockquote,iframe,tfoot' +) + +// this map is intentionally selective, only covering SVG elements that may +// contain child elements. +export const isSVG = makeMap( + 'svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,' + + 'foreignObject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,' + + 'polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view', + true +) + +export const isPreTag = (tag: ?string): boolean => tag === 'pre' + +export const isReservedTag = (tag: string): ?boolean => { + return isHTMLTag(tag) || isSVG(tag) +} + +export function getTagNamespace (tag: string): ?string { + if (isSVG(tag)) { + return 'svg' + } + // basic support for MathML + // note it doesn't support other MathML elements being component roots + if (tag === 'math') { + return 'math' + } +} + +const unknownElementCache = Object.create(null) +export function isUnknownElement (tag: string): boolean { + /* istanbul ignore if */ + if (!inBrowser) { + return true + } + if (isReservedTag(tag)) { + return false + } + tag = tag.toLowerCase() + /* istanbul ignore if */ + if (unknownElementCache[tag] != null) { + return unknownElementCache[tag] + } + const el = document.createElement(tag) + if (tag.indexOf('-') > -1) { + // http://stackoverflow.com/a/28210364/1070244 + return (unknownElementCache[tag] = ( + el.constructor === window.HTMLUnknownElement || + el.constructor === window.HTMLElement + )) + } else { + return (unknownElementCache[tag] = /HTMLUnknownElement/.test(el.toString())) + } +} + +export const isTextInputType = makeMap('text,number,password,search,email,tel,url') diff --git a/node_modules/vue/src/platforms/web/util/index.js b/node_modules/vue/src/platforms/web/util/index.js new file mode 100644 index 00000000..da5e46ed --- /dev/null +++ b/node_modules/vue/src/platforms/web/util/index.js @@ -0,0 +1,25 @@ +/* @flow */ + +import { warn } from 'core/util/index' + +export * from './attrs' +export * from './class' +export * from './element' + +/** + * Query an element selector if it's not an element already. + */ +export function query (el: string | Element): Element { + if (typeof el === 'string') { + const selected = document.querySelector(el) + if (!selected) { + process.env.NODE_ENV !== 'production' && warn( + 'Cannot find element: ' + el + ) + return document.createElement('div') + } + return selected + } else { + return el + } +} diff --git a/node_modules/vue/src/platforms/web/util/style.js b/node_modules/vue/src/platforms/web/util/style.js new file mode 100644 index 00000000..fc119043 --- /dev/null +++ b/node_modules/vue/src/platforms/web/util/style.js @@ -0,0 +1,72 @@ +/* @flow */ + +import { cached, extend, toObject } from 'shared/util' + +export const parseStyleText = cached(function (cssText) { + const res = {} + const listDelimiter = /;(?![^(]*\))/g + const propertyDelimiter = /:(.+)/ + cssText.split(listDelimiter).forEach(function (item) { + if (item) { + var tmp = item.split(propertyDelimiter) + tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim()) + } + }) + return res +}) + +// merge static and dynamic style data on the same vnode +function normalizeStyleData (data: VNodeData): ?Object { + const style = normalizeStyleBinding(data.style) + // static style is pre-processed into an object during compilation + // and is always a fresh object, so it's safe to merge into it + return data.staticStyle + ? extend(data.staticStyle, style) + : style +} + +// normalize possible array / string values into Object +export function normalizeStyleBinding (bindingStyle: any): ?Object { + if (Array.isArray(bindingStyle)) { + return toObject(bindingStyle) + } + if (typeof bindingStyle === 'string') { + return parseStyleText(bindingStyle) + } + return bindingStyle +} + +/** + * parent component style should be after child's + * so that parent component's style could override it + */ +export function getStyle (vnode: VNodeWithData, checkChild: boolean): Object { + const res = {} + let styleData + + if (checkChild) { + let childNode = vnode + while (childNode.componentInstance) { + childNode = childNode.componentInstance._vnode + if ( + childNode && childNode.data && + (styleData = normalizeStyleData(childNode.data)) + ) { + extend(res, styleData) + } + } + } + + if ((styleData = normalizeStyleData(vnode.data))) { + extend(res, styleData) + } + + let parentNode = vnode + while ((parentNode = parentNode.parent)) { + if (parentNode.data && (styleData = normalizeStyleData(parentNode.data))) { + extend(res, styleData) + } + } + return res +} + diff --git a/node_modules/vue/src/platforms/weex/compiler/directives/index.js b/node_modules/vue/src/platforms/weex/compiler/directives/index.js new file mode 100755 index 00000000..12ced224 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/directives/index.js @@ -0,0 +1,5 @@ +import model from './model' + +export default { + model +} diff --git a/node_modules/vue/src/platforms/weex/compiler/directives/model.js b/node_modules/vue/src/platforms/weex/compiler/directives/model.js new file mode 100644 index 00000000..6c9b7685 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/directives/model.js @@ -0,0 +1,34 @@ +/* @flow */ + +import { addHandler, addAttr } from 'compiler/helpers' +import { genComponentModel, genAssignmentCode } from 'compiler/directives/model' + +export default function model ( + el: ASTElement, + dir: ASTDirective, + _warn: Function +): ?boolean { + if (el.tag === 'input' || el.tag === 'textarea') { + genDefaultModel(el, dir.value, dir.modifiers) + } else { + genComponentModel(el, dir.value, dir.modifiers) + } +} + +function genDefaultModel ( + el: ASTElement, + value: string, + modifiers: ?ASTModifiers +): ?boolean { + const { lazy, trim, number } = modifiers || {} + const event = lazy ? 'change' : 'input' + + let valueExpression = `$event.target.attr.value${trim ? '.trim()' : ''}` + if (number) { + valueExpression = `_n(${valueExpression})` + } + + const code = genAssignmentCode(value, valueExpression) + addAttr(el, 'value', `(${value})`) + addHandler(el, event, code, null, true) +} diff --git a/node_modules/vue/src/platforms/weex/compiler/index.js b/node_modules/vue/src/platforms/weex/compiler/index.js new file mode 100644 index 00000000..94f350b8 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/index.js @@ -0,0 +1,52 @@ +/* @flow */ + +import { genStaticKeys } from 'shared/util' +import { createCompiler } from 'compiler/index' + +import modules from './modules/index' +import directives from './directives/index' + +import { + isUnaryTag, + mustUseProp, + isReservedTag, + canBeLeftOpenTag, + getTagNamespace +} from '../util/element' + +export const baseOptions: WeexCompilerOptions = { + modules, + directives, + isUnaryTag, + mustUseProp, + canBeLeftOpenTag, + isReservedTag, + getTagNamespace, + preserveWhitespace: false, + recyclable: false, + staticKeys: genStaticKeys(modules) +} + +const compiler = createCompiler(baseOptions) + +export function compile ( + template: string, + options?: WeexCompilerOptions +): WeexCompiledResult { + let generateAltRender = false + if (options && options.recyclable === true) { + generateAltRender = true + options.recyclable = false + } + const result = compiler.compile(template, options) + + // generate @render function for <recycle-list> + if (options && generateAltRender) { + options.recyclable = true + // disable static optimizations + options.optimize = false + const { render } = compiler.compile(template, options) + result['@render'] = render + } + return result +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/append.js b/node_modules/vue/src/platforms/weex/compiler/modules/append.js new file mode 100644 index 00000000..6e7be267 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/append.js @@ -0,0 +1,27 @@ +/* @flow */ + +import { makeMap } from 'shared/util' + +// The "unitary tag" means that the tag node and its children +// must be sent to the native together. +const isUnitaryTag = makeMap('cell,header,cell-slot,recycle-list', true) + +function preTransformNode (el: ASTElement, options: CompilerOptions) { + if (isUnitaryTag(el.tag) && !el.attrsList.some(item => item.name === 'append')) { + el.attrsMap.append = 'tree' + el.attrsList.push({ name: 'append', value: 'tree' }) + } + if (el.attrsMap.append === 'tree') { + el.appendAsTree = true + } +} + +function genData (el: ASTElement): string { + return el.appendAsTree ? `appendAsTree:true,` : '' +} + +export default { + staticKeys: ['appendAsTree'], + preTransformNode, + genData +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/class.js b/node_modules/vue/src/platforms/weex/compiler/modules/class.js new file mode 100644 index 00000000..371a08ea --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/class.js @@ -0,0 +1,73 @@ +/* @flow */ + +import { parseText } from 'compiler/parser/text-parser' +import { + getAndRemoveAttr, + getBindingAttr, + baseWarn +} from 'compiler/helpers' + +type StaticClassResult = { + dynamic: boolean, + classResult: string +}; + +function transformNode (el: ASTElement, options: CompilerOptions) { + const warn = options.warn || baseWarn + const staticClass = getAndRemoveAttr(el, 'class') + const { dynamic, classResult } = parseStaticClass(staticClass, options) + if (process.env.NODE_ENV !== 'production' && dynamic && staticClass) { + warn( + `class="${staticClass}": ` + + 'Interpolation inside attributes has been deprecated. ' + + 'Use v-bind or the colon shorthand instead.' + ) + } + if (!dynamic && classResult) { + el.staticClass = classResult + } + const classBinding = getBindingAttr(el, 'class', false /* getStatic */) + if (classBinding) { + el.classBinding = classBinding + } else if (dynamic) { + el.classBinding = classResult + } +} + +function genData (el: ASTElement): string { + let data = '' + if (el.staticClass) { + data += `staticClass:${el.staticClass},` + } + if (el.classBinding) { + data += `class:${el.classBinding},` + } + return data +} + +function parseStaticClass (staticClass: ?string, options: CompilerOptions): StaticClassResult { + // "a b c" -> ["a", "b", "c"] => staticClass: ["a", "b", "c"] + // "a {{x}} c" -> ["a", x, "c"] => classBinding: '["a", x, "c"]' + let dynamic = false + let classResult = '' + if (staticClass) { + const classList = staticClass.trim().split(' ').map(name => { + const result = parseText(name, options.delimiters) + if (result) { + dynamic = true + return result.expression + } + return JSON.stringify(name) + }) + if (classList.length) { + classResult = '[' + classList.join(',') + ']' + } + } + return { dynamic, classResult } +} + +export default { + staticKeys: ['staticClass'], + transformNode, + genData +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/index.js b/node_modules/vue/src/platforms/weex/compiler/modules/index.js new file mode 100644 index 00000000..ac17809e --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/index.js @@ -0,0 +1,13 @@ +import klass from './class' +import style from './style' +import props from './props' +import append from './append' +import recycleList from './recycle-list/index' + +export default [ + recycleList, + klass, + style, + props, + append +] diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/props.js b/node_modules/vue/src/platforms/weex/compiler/modules/props.js new file mode 100644 index 00000000..8b0bb5f2 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/props.js @@ -0,0 +1,32 @@ +/* @flow */ + +import { cached, camelize } from 'shared/util' + +const normalize = cached(camelize) + +function normalizeKeyName (str: string): string { + if (str.match(/^v\-/)) { + return str.replace(/(v-[a-z\-]+\:)([a-z\-]+)$/i, ($, directive, prop) => { + return directive + normalize(prop) + }) + } + return normalize(str) +} + +function transformNode (el: ASTElement, options: CompilerOptions) { + if (Array.isArray(el.attrsList)) { + el.attrsList.forEach(attr => { + if (attr.name && attr.name.match(/\-/)) { + const realName = normalizeKeyName(attr.name) + if (el.attrsMap) { + el.attrsMap[realName] = el.attrsMap[attr.name] + delete el.attrsMap[attr.name] + } + attr.name = realName + } + }) + } +} +export default { + transformNode +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/component-root.js b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/component-root.js new file mode 100644 index 00000000..7d9fedea --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/component-root.js @@ -0,0 +1,16 @@ +/* @flow */ + +import { addAttr } from 'compiler/helpers' + +// mark component root nodes as +export function postTransformComponentRoot ( + el: ASTElement, + options: WeexCompilerOptions +) { + if (!el.parent) { + // component root + addAttr(el, '@isComponentRoot', 'true') + addAttr(el, '@templateId', '_uid') + addAttr(el, '@componentProps', '$props || {}') + } +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/component.js b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/component.js new file mode 100644 index 00000000..36d7dfd5 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/component.js @@ -0,0 +1,16 @@ +/* @flow */ + +import { addAttr } from 'compiler/helpers' +import { RECYCLE_LIST_MARKER } from 'weex/util/index' + +// mark components as inside recycle-list so that we know we need to invoke +// their special @render function instead of render in create-component.js +export function postTransformComponent ( + el: ASTElement, + options: WeexCompilerOptions +) { + // $flow-disable-line (we know isReservedTag is there) + if (!options.isReservedTag(el.tag) && el.tag !== 'cell-slot') { + addAttr(el, RECYCLE_LIST_MARKER, 'true') + } +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/index.js b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/index.js new file mode 100644 index 00000000..502cc782 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/index.js @@ -0,0 +1,60 @@ +/* @flow */ + +import { preTransformRecycleList } from './recycle-list' +import { postTransformComponent } from './component' +import { postTransformComponentRoot } from './component-root' +import { postTransformText } from './text' +import { preTransformVBind } from './v-bind' +import { preTransformVIf } from './v-if' +import { preTransformVFor } from './v-for' +import { postTransformVOn } from './v-on' +import { preTransformVOnce } from './v-once' + +let currentRecycleList = null + +function shouldCompile (el: ASTElement, options: WeexCompilerOptions) { + return options.recyclable || + (currentRecycleList && el !== currentRecycleList) +} + +function preTransformNode (el: ASTElement, options: WeexCompilerOptions) { + if (el.tag === 'recycle-list') { + preTransformRecycleList(el, options) + currentRecycleList = el + } + if (shouldCompile(el, options)) { + preTransformVBind(el, options) + preTransformVIf(el, options) // also v-else-if and v-else + preTransformVFor(el, options) + preTransformVOnce(el, options) + } +} + +function transformNode (el: ASTElement, options: WeexCompilerOptions) { + if (shouldCompile(el, options)) { + // do nothing yet + } +} + +function postTransformNode (el: ASTElement, options: WeexCompilerOptions) { + if (shouldCompile(el, options)) { + // mark child component in parent template + postTransformComponent(el, options) + // mark root in child component template + postTransformComponentRoot(el, options) + // <text>: transform children text into value attr + if (el.tag === 'text') { + postTransformText(el, options) + } + postTransformVOn(el, options) + } + if (el === currentRecycleList) { + currentRecycleList = null + } +} + +export default { + preTransformNode, + transformNode, + postTransformNode +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/recycle-list.js b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/recycle-list.js new file mode 100644 index 00000000..7fb65167 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/recycle-list.js @@ -0,0 +1,50 @@ +/* @flow */ + +import { parseFor } from 'compiler/parser/index' +import { getAndRemoveAttr, addRawAttr } from 'compiler/helpers' + +/** + * Map the following syntax to corresponding attrs: + * + * <recycle-list for="(item, i) in longList" switch="cellType"> + * <cell-slot case="A"> ... </cell-slot> + * <cell-slot case="B"> ... </cell-slot> + * </recycle-list> + */ + +export function preTransformRecycleList ( + el: ASTElement, + options: WeexCompilerOptions +) { + const exp = getAndRemoveAttr(el, 'for') + if (!exp) { + if (options.warn) { + options.warn(`Invalid <recycle-list> syntax: missing "for" expression.`) + } + return + } + + const res = parseFor(exp) + if (!res) { + if (options.warn) { + options.warn(`Invalid <recycle-list> syntax: ${exp}.`) + } + return + } + + addRawAttr(el, ':list-data', res.for) + addRawAttr(el, 'binding-expression', res.for) + addRawAttr(el, 'alias', res.alias) + if (res.iterator2) { + // (item, key, index) for object iteration + // is this even supported? + addRawAttr(el, 'index', res.iterator2) + } else if (res.iterator1) { + addRawAttr(el, 'index', res.iterator1) + } + + const switchKey = getAndRemoveAttr(el, 'switch') + if (switchKey) { + addRawAttr(el, 'switch', switchKey) + } +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/text.js b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/text.js new file mode 100644 index 00000000..575ec7f6 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/text.js @@ -0,0 +1,23 @@ +/* @flow */ + +import { addAttr } from 'compiler/helpers' + +function genText (node: ASTNode) { + const value = node.type === 3 + ? node.text + : node.type === 2 + ? node.tokens.length === 1 + ? node.tokens[0] + : node.tokens + : '' + return JSON.stringify(value) +} + +export function postTransformText (el: ASTElement, options: WeexCompilerOptions) { + // weex <text> can only contain text, so the parser + // always generates a single child. + if (el.children.length) { + addAttr(el, 'value', genText(el.children[0])) + el.children = [] + } +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-bind.js b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-bind.js new file mode 100644 index 00000000..effdf351 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-bind.js @@ -0,0 +1,21 @@ +/* @flow */ + +import { camelize } from 'shared/util' +import { generateBinding } from 'weex/util/parser' +import { bindRE } from 'compiler/parser/index' +import { getAndRemoveAttr, addRawAttr } from 'compiler/helpers' + +function parseAttrName (name: string): string { + return camelize(name.replace(bindRE, '')) +} + +export function preTransformVBind (el: ASTElement, options: WeexCompilerOptions) { + for (const attr in el.attrsMap) { + if (bindRE.test(attr)) { + const name: string = parseAttrName(attr) + const value = generateBinding(getAndRemoveAttr(el, attr)) + delete el.attrsMap[attr] + addRawAttr(el, name, value) + } + } +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-for.js b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-for.js new file mode 100644 index 00000000..9f09e4b4 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-for.js @@ -0,0 +1,33 @@ +/* @flow */ + +import { parseFor } from 'compiler/parser/index' +import { getAndRemoveAttr, addRawAttr } from 'compiler/helpers' + +export function preTransformVFor (el: ASTElement, options: WeexCompilerOptions) { + const exp = getAndRemoveAttr(el, 'v-for') + if (!exp) { + return + } + + const res = parseFor(exp) + if (!res) { + if (process.env.NODE_ENV !== 'production' && options.warn) { + options.warn(`Invalid v-for expression: ${exp}`) + } + return + } + + const desc: Object = { + '@expression': res.for, + '@alias': res.alias + } + if (res.iterator2) { + desc['@key'] = res.iterator1 + desc['@index'] = res.iterator2 + } else { + desc['@index'] = res.iterator1 + } + + delete el.attrsMap['v-for'] + addRawAttr(el, '[[repeat]]', desc) +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-if.js b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-if.js new file mode 100644 index 00000000..d82ba632 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-if.js @@ -0,0 +1,63 @@ +/* @flow */ + +import { addIfCondition } from 'compiler/parser/index' +import { getAndRemoveAttr, addRawAttr } from 'compiler/helpers' + +function hasConditionDirective (el: ASTElement): boolean { + for (const attr in el.attrsMap) { + if (/^v\-if|v\-else|v\-else\-if$/.test(attr)) { + return true + } + } + return false +} + +function getPreviousConditions (el: ASTElement): Array<string> { + const conditions = [] + if (el.parent && el.parent.children) { + for (let c = 0, n = el.parent.children.length; c < n; ++c) { + // $flow-disable-line + const ifConditions = el.parent.children[c].ifConditions + if (ifConditions) { + for (let i = 0, l = ifConditions.length; i < l; ++i) { + const condition = ifConditions[i] + if (condition && condition.exp) { + conditions.push(condition.exp) + } + } + } + } + } + return conditions +} + +export function preTransformVIf (el: ASTElement, options: WeexCompilerOptions) { + if (hasConditionDirective(el)) { + let exp + const ifExp = getAndRemoveAttr(el, 'v-if', true /* remove from attrsMap */) + const elseifExp = getAndRemoveAttr(el, 'v-else-if', true) + // don't need the value, but remove it to avoid being generated as a + // custom directive + getAndRemoveAttr(el, 'v-else', true) + if (ifExp) { + exp = ifExp + addIfCondition(el, { exp: ifExp, block: el }) + } else { + elseifExp && addIfCondition(el, { exp: elseifExp, block: el }) + const prevConditions = getPreviousConditions(el) + if (prevConditions.length) { + const prevMatch = prevConditions.join(' || ') + exp = elseifExp + ? `!(${prevMatch}) && (${elseifExp})` // v-else-if + : `!(${prevMatch})` // v-else + } else if (process.env.NODE_ENV !== 'production' && options.warn) { + options.warn( + `v-${elseifExp ? ('else-if="' + elseifExp + '"') : 'else'} ` + + `used on element <${el.tag}> without corresponding v-if.` + ) + return + } + } + addRawAttr(el, '[[match]]', exp) + } +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-on.js b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-on.js new file mode 100644 index 00000000..b1ba262c --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-on.js @@ -0,0 +1,25 @@ +/* @flow */ + +const inlineStatementRE = /^\s*([A-Za-z_$0-9\['\."\]]+)*\s*\(\s*(([A-Za-z_$0-9\['\."\]]+)?(\s*,\s*([A-Za-z_$0-9\['\."\]]+))*)\s*\)$/ + +function parseHandlerParams (handler: ASTElementHandler) { + const res = inlineStatementRE.exec(handler.value) + if (res && res[2]) { + handler.params = res[2].split(/\s*,\s*/) + } +} + +export function postTransformVOn (el: ASTElement, options: WeexCompilerOptions) { + const events: ASTElementHandlers | void = el.events + if (!events) { + return + } + for (const name in events) { + const handler: ASTElementHandler | Array<ASTElementHandler> = events[name] + if (Array.isArray(handler)) { + handler.map(fn => parseHandlerParams(fn)) + } else { + parseHandlerParams(handler) + } + } +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-once.js b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-once.js new file mode 100644 index 00000000..baff838a --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/recycle-list/v-once.js @@ -0,0 +1,19 @@ +/* @flow */ + +import { getAndRemoveAttr, addRawAttr } from 'compiler/helpers' + +function containVOnce (el: ASTElement): boolean { + for (const attr in el.attrsMap) { + if (/^v\-once$/i.test(attr)) { + return true + } + } + return false +} + +export function preTransformVOnce (el: ASTElement, options: WeexCompilerOptions) { + if (containVOnce(el)) { + getAndRemoveAttr(el, 'v-once', true) + addRawAttr(el, '[[once]]', true) + } +} diff --git a/node_modules/vue/src/platforms/weex/compiler/modules/style.js b/node_modules/vue/src/platforms/weex/compiler/modules/style.js new file mode 100644 index 00000000..7bf5027f --- /dev/null +++ b/node_modules/vue/src/platforms/weex/compiler/modules/style.js @@ -0,0 +1,86 @@ +/* @flow */ + +import { cached, camelize, isPlainObject } from 'shared/util' +import { parseText } from 'compiler/parser/text-parser' +import { + getAndRemoveAttr, + getBindingAttr, + baseWarn +} from 'compiler/helpers' + +type StaticStyleResult = { + dynamic: boolean, + styleResult: string | Object | void +}; + +const normalize = cached(camelize) + +function transformNode (el: ASTElement, options: CompilerOptions) { + const warn = options.warn || baseWarn + const staticStyle = getAndRemoveAttr(el, 'style') + const { dynamic, styleResult } = parseStaticStyle(staticStyle, options) + if (process.env.NODE_ENV !== 'production' && dynamic) { + warn( + `style="${String(staticStyle)}": ` + + 'Interpolation inside attributes has been deprecated. ' + + 'Use v-bind or the colon shorthand instead.' + ) + } + if (!dynamic && styleResult) { + // $flow-disable-line + el.staticStyle = styleResult + } + const styleBinding = getBindingAttr(el, 'style', false /* getStatic */) + if (styleBinding) { + el.styleBinding = styleBinding + } else if (dynamic) { + // $flow-disable-line + el.styleBinding = styleResult + } +} + +function genData (el: ASTElement): string { + let data = '' + if (el.staticStyle) { + data += `staticStyle:${el.staticStyle},` + } + if (el.styleBinding) { + data += `style:${el.styleBinding},` + } + return data +} + +function parseStaticStyle (staticStyle: ?string, options: CompilerOptions): StaticStyleResult { + // "width: 200px; height: 200px;" -> {width: 200, height: 200} + // "width: 200px; height: {{y}}" -> {width: 200, height: y} + let dynamic = false + let styleResult = '' + if (typeof staticStyle === 'string') { + const styleList = staticStyle.trim().split(';').map(style => { + const result = style.trim().split(':') + if (result.length !== 2) { + return + } + const key = normalize(result[0].trim()) + const value = result[1].trim() + const dynamicValue = parseText(value, options.delimiters) + if (dynamicValue) { + dynamic = true + return key + ':' + dynamicValue.expression + } + return key + ':' + JSON.stringify(value) + }).filter(result => result) + if (styleList.length) { + styleResult = '{' + styleList.join(',') + '}' + } + } else if (isPlainObject(staticStyle)) { + styleResult = JSON.stringify(staticStyle) || '' + } + return { dynamic, styleResult } +} + +export default { + staticKeys: ['staticStyle'], + transformNode, + genData +} diff --git a/node_modules/vue/src/platforms/weex/entry-compiler.js b/node_modules/vue/src/platforms/weex/entry-compiler.js new file mode 100644 index 00000000..18d79ab7 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/entry-compiler.js @@ -0,0 +1 @@ +export { compile } from 'weex/compiler/index' diff --git a/node_modules/vue/src/platforms/weex/entry-framework.js b/node_modules/vue/src/platforms/weex/entry-framework.js new file mode 100644 index 00000000..a23775d4 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/entry-framework.js @@ -0,0 +1,187 @@ +/* @flow */ + +// this will be preserved during build +// $flow-disable-line +const VueFactory = require('./factory') + +const instanceOptions: { [key: string]: WeexInstanceOption } = {} + +/** + * Create instance context. + */ +export function createInstanceContext ( + instanceId: string, + runtimeContext: WeexRuntimeContext, + data: Object = {} +): WeexInstanceContext { + const weex: Weex = runtimeContext.weex + const instance: WeexInstanceOption = instanceOptions[instanceId] = { + instanceId, + config: weex.config, + document: weex.document, + data + } + + // Each instance has a independent `Vue` module instance + const Vue = instance.Vue = createVueModuleInstance(instanceId, weex) + + // DEPRECATED + const timerAPIs = getInstanceTimer(instanceId, weex.requireModule) + + const instanceContext = Object.assign({ Vue }, timerAPIs) + Object.freeze(instanceContext) + return instanceContext +} + +/** + * Destroy an instance with id. It will make sure all memory of + * this instance released and no more leaks. + */ +export function destroyInstance (instanceId: string): void { + const instance = instanceOptions[instanceId] + if (instance && instance.app instanceof instance.Vue) { + try { + instance.app.$destroy() + instance.document.destroy() + } catch (e) {} + delete instance.document + delete instance.app + } + delete instanceOptions[instanceId] +} + +/** + * Refresh an instance with id and new top-level component data. + * It will use `Vue.set` on all keys of the new data. So it's better + * define all possible meaningful keys when instance created. + */ +export function refreshInstance ( + instanceId: string, + data: Object +): Error | void { + const instance = instanceOptions[instanceId] + if (!instance || !(instance.app instanceof instance.Vue)) { + return new Error(`refreshInstance: instance ${instanceId} not found!`) + } + if (instance.Vue && instance.Vue.set) { + for (const key in data) { + instance.Vue.set(instance.app, key, data[key]) + } + } + // Finally `refreshFinish` signal needed. + instance.document.taskCenter.send('dom', { action: 'refreshFinish' }, []) +} + +/** + * Create a fresh instance of Vue for each Weex instance. + */ +function createVueModuleInstance ( + instanceId: string, + weex: Weex +): GlobalAPI { + const exports = {} + VueFactory(exports, weex.document) + const Vue = exports.Vue + + const instance = instanceOptions[instanceId] + + // patch reserved tag detection to account for dynamically registered + // components + const weexRegex = /^weex:/i + const isReservedTag = Vue.config.isReservedTag || (() => false) + const isRuntimeComponent = Vue.config.isRuntimeComponent || (() => false) + Vue.config.isReservedTag = name => { + return (!isRuntimeComponent(name) && weex.supports(`@component/${name}`)) || + isReservedTag(name) || + weexRegex.test(name) + } + Vue.config.parsePlatformTagName = name => name.replace(weexRegex, '') + + // expose weex-specific info + Vue.prototype.$instanceId = instanceId + Vue.prototype.$document = instance.document + + // expose weex native module getter on subVue prototype so that + // vdom runtime modules can access native modules via vnode.context + Vue.prototype.$requireWeexModule = weex.requireModule + + // Hack `Vue` behavior to handle instance information and data + // before root component created. + Vue.mixin({ + beforeCreate () { + const options = this.$options + // root component (vm) + if (options.el) { + // set external data of instance + const dataOption = options.data + const internalData = (typeof dataOption === 'function' ? dataOption() : dataOption) || {} + options.data = Object.assign(internalData, instance.data) + // record instance by id + instance.app = this + } + }, + mounted () { + const options = this.$options + // root component (vm) + if (options.el && weex.document && instance.app === this) { + try { + // Send "createFinish" signal to native. + weex.document.taskCenter.send('dom', { action: 'createFinish' }, []) + } catch (e) {} + } + } + }) + + /** + * @deprecated Just instance variable `weex.config` + * Get instance config. + * @return {object} + */ + Vue.prototype.$getConfig = function () { + if (instance.app instanceof Vue) { + return instance.config + } + } + + return Vue +} + +/** + * DEPRECATED + * Generate HTML5 Timer APIs. An important point is that the callback + * will be converted into callback id when sent to native. So the + * framework can make sure no side effect of the callback happened after + * an instance destroyed. + */ +function getInstanceTimer ( + instanceId: string, + moduleGetter: Function +): Object { + const instance = instanceOptions[instanceId] + const timer = moduleGetter('timer') + const timerAPIs = { + setTimeout: (...args) => { + const handler = function () { + args[0](...args.slice(2)) + } + + timer.setTimeout(handler, args[1]) + return instance.document.taskCenter.callbackManager.lastCallbackId.toString() + }, + setInterval: (...args) => { + const handler = function () { + args[0](...args.slice(2)) + } + + timer.setInterval(handler, args[1]) + return instance.document.taskCenter.callbackManager.lastCallbackId.toString() + }, + clearTimeout: (n) => { + timer.clearTimeout(n) + }, + clearInterval: (n) => { + timer.clearInterval(n) + } + } + return timerAPIs +} diff --git a/node_modules/vue/src/platforms/weex/entry-runtime-factory.js b/node_modules/vue/src/platforms/weex/entry-runtime-factory.js new file mode 100644 index 00000000..166ddf2d --- /dev/null +++ b/node_modules/vue/src/platforms/weex/entry-runtime-factory.js @@ -0,0 +1,6 @@ +// this entry is built and wrapped with a factory function +// used to generate a fresh copy of Vue for every Weex instance. + +import Vue from './runtime/index' + +exports.Vue = Vue diff --git a/node_modules/vue/src/platforms/weex/runtime/components/index.js b/node_modules/vue/src/platforms/weex/runtime/components/index.js new file mode 100644 index 00000000..449db1ad --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/components/index.js @@ -0,0 +1,9 @@ +import Richtext from './richtext' +import Transition from './transition' +import TransitionGroup from './transition-group' + +export default { + Richtext, + Transition, + TransitionGroup +} diff --git a/node_modules/vue/src/platforms/weex/runtime/components/richtext.js b/node_modules/vue/src/platforms/weex/runtime/components/richtext.js new file mode 100644 index 00000000..a9164c7a --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/components/richtext.js @@ -0,0 +1,82 @@ +/* @flow */ + +function getVNodeType (vnode: VNode): string { + if (!vnode.tag) { + return '' + } + return vnode.tag.replace(/vue\-component\-(\d+\-)?/, '') +} + +function isSimpleSpan (vnode: VNode): boolean { + return vnode.children && + vnode.children.length === 1 && + !vnode.children[0].tag +} + +function parseStyle (vnode: VNode): Object | void { + if (!vnode || !vnode.data) { + return + } + const { staticStyle, staticClass } = vnode.data + if (vnode.data.style || vnode.data.class || staticStyle || staticClass) { + const styles = Object.assign({}, staticStyle, vnode.data.style) + const cssMap = vnode.context.$options.style || {} + const classList = [].concat(staticClass, vnode.data.class) + classList.forEach(name => { + if (name && cssMap[name]) { + Object.assign(styles, cssMap[name]) + } + }) + return styles + } +} + +function convertVNodeChildren (children: Array<VNode>): Array<VNode> | void { + if (!children.length) { + return + } + + return children.map(vnode => { + const type: string = getVNodeType(vnode) + const props: Object = { type } + + // convert raw text node + if (!type) { + props.type = 'span' + props.attr = { + value: (vnode.text || '').trim() + } + } else { + props.style = parseStyle(vnode) + if (vnode.data) { + props.attr = vnode.data.attrs + if (vnode.data.on) { + props.events = vnode.data.on + } + } + if (type === 'span' && isSimpleSpan(vnode)) { + props.attr = props.attr || {} + props.attr.value = vnode.children[0].text.trim() + return props + } + } + + if (vnode.children && vnode.children.length) { + props.children = convertVNodeChildren(vnode.children) + } + + return props + }) +} + +export default { + name: 'richtext', + render (h: Function) { + return h('weex:richtext', { + on: this._events, + attrs: { + value: convertVNodeChildren(this.$options._renderChildren || []) + } + }) + } +} diff --git a/node_modules/vue/src/platforms/weex/runtime/components/transition-group.js b/node_modules/vue/src/platforms/weex/runtime/components/transition-group.js new file mode 100644 index 00000000..4a65965f --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/components/transition-group.js @@ -0,0 +1,148 @@ +import { warn, extend } from 'core/util/index' +import { transitionProps, extractTransitionData } from './transition' + +const props = extend({ + tag: String, + moveClass: String +}, transitionProps) + +delete props.mode + +export default { + props, + + created () { + const dom = this.$requireWeexModule('dom') + this.getPosition = el => new Promise((resolve, reject) => { + dom.getComponentRect(el.ref, res => { + if (!res.result) { + reject(new Error(`failed to get rect for element: ${el.tag}`)) + } else { + resolve(res.size) + } + }) + }) + + const animation = this.$requireWeexModule('animation') + this.animate = (el, options) => new Promise(resolve => { + animation.transition(el.ref, options, resolve) + }) + }, + + render (h) { + const tag = this.tag || this.$vnode.data.tag || 'span' + const map = Object.create(null) + const prevChildren = this.prevChildren = this.children + const rawChildren = this.$slots.default || [] + const children = this.children = [] + const transitionData = extractTransitionData(this) + + for (let i = 0; i < rawChildren.length; i++) { + const c = rawChildren[i] + if (c.tag) { + if (c.key != null && String(c.key).indexOf('__vlist') !== 0) { + children.push(c) + map[c.key] = c + ;(c.data || (c.data = {})).transition = transitionData + } else if (process.env.NODE_ENV !== 'production') { + const opts = c.componentOptions + const name = opts + ? (opts.Ctor.options.name || opts.tag) + : c.tag + warn(`<transition-group> children must be keyed: <${name}>`) + } + } + } + + if (prevChildren) { + const kept = [] + const removed = [] + prevChildren.forEach(c => { + c.data.transition = transitionData + + // TODO: record before patch positions + + if (map[c.key]) { + kept.push(c) + } else { + removed.push(c) + } + }) + this.kept = h(tag, null, kept) + this.removed = removed + } + + return h(tag, null, children) + }, + + beforeUpdate () { + // force removing pass + this.__patch__( + this._vnode, + this.kept, + false, // hydrating + true // removeOnly (!important, avoids unnecessary moves) + ) + this._vnode = this.kept + }, + + updated () { + const children = this.prevChildren + const moveClass = this.moveClass || ((this.name || 'v') + '-move') + const moveData = children.length && this.getMoveData(children[0].context, moveClass) + if (!moveData) { + return + } + + // TODO: finish implementing move animations once + // we have access to sync getComponentRect() + + // children.forEach(callPendingCbs) + + // Promise.all(children.map(c => { + // const oldPos = c.data.pos + // const newPos = c.data.newPos + // const dx = oldPos.left - newPos.left + // const dy = oldPos.top - newPos.top + // if (dx || dy) { + // c.data.moved = true + // return this.animate(c.elm, { + // styles: { + // transform: `translate(${dx}px,${dy}px)` + // } + // }) + // } + // })).then(() => { + // children.forEach(c => { + // if (c.data.moved) { + // this.animate(c.elm, { + // styles: { + // transform: '' + // }, + // duration: moveData.duration || 0, + // delay: moveData.delay || 0, + // timingFunction: moveData.timingFunction || 'linear' + // }) + // } + // }) + // }) + }, + + methods: { + getMoveData (context, moveClass) { + const stylesheet = context.$options.style || {} + return stylesheet['@TRANSITION'] && stylesheet['@TRANSITION'][moveClass] + } + } +} + +// function callPendingCbs (c) { +// /* istanbul ignore if */ +// if (c.elm._moveCb) { +// c.elm._moveCb() +// } +// /* istanbul ignore if */ +// if (c.elm._enterCb) { +// c.elm._enterCb() +// } +// } diff --git a/node_modules/vue/src/platforms/weex/runtime/components/transition.js b/node_modules/vue/src/platforms/weex/runtime/components/transition.js new file mode 100644 index 00000000..20ba632d --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/components/transition.js @@ -0,0 +1,9 @@ +// reuse same transition component logic from web +export { + transitionProps, + extractTransitionData +} from 'web/runtime/components/transition' + +import Transition from 'web/runtime/components/transition' + +export default Transition diff --git a/node_modules/vue/src/platforms/weex/runtime/directives/index.js b/node_modules/vue/src/platforms/weex/runtime/directives/index.js new file mode 100755 index 00000000..efba7fa6 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/directives/index.js @@ -0,0 +1,2 @@ +export default { +} diff --git a/node_modules/vue/src/platforms/weex/runtime/index.js b/node_modules/vue/src/platforms/weex/runtime/index.js new file mode 100755 index 00000000..b8e09305 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/index.js @@ -0,0 +1,42 @@ +/* @flow */ + +import Vue from 'core/index' +import { patch } from 'weex/runtime/patch' +import { mountComponent } from 'core/instance/lifecycle' +import platformDirectives from 'weex/runtime/directives/index' +import platformComponents from 'weex/runtime/components/index' + +import { + query, + mustUseProp, + isReservedTag, + isRuntimeComponent, + isUnknownElement +} from 'weex/util/element' + +// install platform specific utils +Vue.config.mustUseProp = mustUseProp +Vue.config.isReservedTag = isReservedTag +Vue.config.isRuntimeComponent = isRuntimeComponent +Vue.config.isUnknownElement = isUnknownElement + +// install platform runtime directives and components +Vue.options.directives = platformDirectives +Vue.options.components = platformComponents + +// install platform patch function +Vue.prototype.__patch__ = patch + +// wrap mount +Vue.prototype.$mount = function ( + el?: any, + hydrating?: boolean +): Component { + return mountComponent( + this, + el && query(el, this.$document), + hydrating + ) +} + +export default Vue diff --git a/node_modules/vue/src/platforms/weex/runtime/modules/attrs.js b/node_modules/vue/src/platforms/weex/runtime/modules/attrs.js new file mode 100755 index 00000000..1b6185ca --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/modules/attrs.js @@ -0,0 +1,44 @@ +/* @flow */ + +import { extend } from 'shared/util' + +function updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) { + if (!oldVnode.data.attrs && !vnode.data.attrs) { + return + } + let key, cur, old + const elm = vnode.elm + const oldAttrs = oldVnode.data.attrs || {} + let attrs = vnode.data.attrs || {} + // clone observed objects, as the user probably wants to mutate it + if (attrs.__ob__) { + attrs = vnode.data.attrs = extend({}, attrs) + } + + const supportBatchUpdate = typeof elm.setAttrs === 'function' + const batchedAttrs = {} + for (key in attrs) { + cur = attrs[key] + old = oldAttrs[key] + if (old !== cur) { + supportBatchUpdate + ? (batchedAttrs[key] = cur) + : elm.setAttr(key, cur) + } + } + for (key in oldAttrs) { + if (attrs[key] == null) { + supportBatchUpdate + ? (batchedAttrs[key] = undefined) + : elm.setAttr(key) + } + } + if (supportBatchUpdate) { + elm.setAttrs(batchedAttrs) + } +} + +export default { + create: updateAttrs, + update: updateAttrs +} diff --git a/node_modules/vue/src/platforms/weex/runtime/modules/class.js b/node_modules/vue/src/platforms/weex/runtime/modules/class.js new file mode 100755 index 00000000..029b9e9e --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/modules/class.js @@ -0,0 +1,75 @@ +/* @flow */ + +import { extend } from 'shared/util' + +function updateClass (oldVnode: VNodeWithData, vnode: VNodeWithData) { + const el = vnode.elm + const ctx = vnode.context + + const data: VNodeData = vnode.data + const oldData: VNodeData = oldVnode.data + if (!data.staticClass && + !data.class && + (!oldData || (!oldData.staticClass && !oldData.class)) + ) { + return + } + + const oldClassList = [] + // unlike web, weex vnode staticClass is an Array + const oldStaticClass: any = oldData.staticClass + if (oldStaticClass) { + oldClassList.push.apply(oldClassList, oldStaticClass) + } + if (oldData.class) { + oldClassList.push.apply(oldClassList, oldData.class) + } + + const classList = [] + // unlike web, weex vnode staticClass is an Array + const staticClass: any = data.staticClass + if (staticClass) { + classList.push.apply(classList, staticClass) + } + if (data.class) { + classList.push.apply(classList, data.class) + } + + if (typeof el.setClassList === 'function') { + el.setClassList(classList) + } else { + const style = getStyle(oldClassList, classList, ctx) + if (typeof el.setStyles === 'function') { + el.setStyles(style) + } else { + for (const key in style) { + el.setStyle(key, style[key]) + } + } + } +} + +function getStyle (oldClassList: Array<string>, classList: Array<string>, ctx: Component): Object { + // style is a weex-only injected object + // compiled from <style> tags in weex files + const stylesheet: any = ctx.$options.style || {} + const result = {} + classList.forEach(name => { + const style = stylesheet[name] + extend(result, style) + }) + oldClassList.forEach(name => { + const style = stylesheet[name] + for (const key in style) { + if (!result.hasOwnProperty(key)) { + result[key] = '' + } + } + }) + return result +} + +export default { + create: updateClass, + update: updateClass +} diff --git a/node_modules/vue/src/platforms/weex/runtime/modules/events.js b/node_modules/vue/src/platforms/weex/runtime/modules/events.js new file mode 100755 index 00000000..a3f9e25a --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/modules/events.js @@ -0,0 +1,57 @@ +/* @flow */ + +import { updateListeners } from 'core/vdom/helpers/update-listeners' + +let target: any + +function add ( + event: string, + handler: Function, + once: boolean, + capture: boolean, + passive?: boolean, + params?: Array<any> +) { + if (capture) { + console.log('Weex do not support event in bubble phase.') + return + } + if (once) { + const oldHandler = handler + const _target = target // save current target element in closure + handler = function (ev) { + const res = arguments.length === 1 + ? oldHandler(ev) + : oldHandler.apply(null, arguments) + if (res !== null) { + remove(event, null, null, _target) + } + } + } + target.addEvent(event, handler, params) +} + +function remove ( + event: string, + handler: any, + capture: any, + _target?: any +) { + (_target || target).removeEvent(event) +} + +function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) { + if (!oldVnode.data.on && !vnode.data.on) { + return + } + const on = vnode.data.on || {} + const oldOn = oldVnode.data.on || {} + target = vnode.elm + updateListeners(on, oldOn, add, remove, vnode.context) + target = undefined +} + +export default { + create: updateDOMListeners, + update: updateDOMListeners +} diff --git a/node_modules/vue/src/platforms/weex/runtime/modules/index.js b/node_modules/vue/src/platforms/weex/runtime/modules/index.js new file mode 100755 index 00000000..dd384484 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/modules/index.js @@ -0,0 +1,13 @@ +import attrs from './attrs' +import klass from './class' +import events from './events' +import style from './style' +import transition from './transition' + +export default [ + attrs, + klass, + events, + style, + transition +] diff --git a/node_modules/vue/src/platforms/weex/runtime/modules/style.js b/node_modules/vue/src/platforms/weex/runtime/modules/style.js new file mode 100755 index 00000000..bdabd17b --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/modules/style.js @@ -0,0 +1,84 @@ +/* @flow */ + +import { extend, cached, camelize } from 'shared/util' + +const normalize = cached(camelize) + +function createStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) { + if (!vnode.data.staticStyle) { + updateStyle(oldVnode, vnode) + return + } + const elm = vnode.elm + const staticStyle = vnode.data.staticStyle + const supportBatchUpdate = typeof elm.setStyles === 'function' + const batchedStyles = {} + for (const name in staticStyle) { + if (staticStyle[name]) { + supportBatchUpdate + ? (batchedStyles[normalize(name)] = staticStyle[name]) + : elm.setStyle(normalize(name), staticStyle[name]) + } + } + if (supportBatchUpdate) { + elm.setStyles(batchedStyles) + } + updateStyle(oldVnode, vnode) +} + +function updateStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) { + if (!oldVnode.data.style && !vnode.data.style) { + return + } + let cur, name + const elm = vnode.elm + const oldStyle: any = oldVnode.data.style || {} + let style: any = vnode.data.style || {} + + const needClone = style.__ob__ + + // handle array syntax + if (Array.isArray(style)) { + style = vnode.data.style = toObject(style) + } + + // clone the style for future updates, + // in case the user mutates the style object in-place. + if (needClone) { + style = vnode.data.style = extend({}, style) + } + + const supportBatchUpdate = typeof elm.setStyles === 'function' + const batchedStyles = {} + for (name in oldStyle) { + if (!style[name]) { + supportBatchUpdate + ? (batchedStyles[normalize(name)] = '') + : elm.setStyle(normalize(name), '') + } + } + for (name in style) { + cur = style[name] + supportBatchUpdate + ? (batchedStyles[normalize(name)] = cur) + : elm.setStyle(normalize(name), cur) + } + if (supportBatchUpdate) { + elm.setStyles(batchedStyles) + } +} + +function toObject (arr) { + const res = {} + for (var i = 0; i < arr.length; i++) { + if (arr[i]) { + extend(res, arr[i]) + } + } + return res +} + +export default { + create: createStyle, + update: updateStyle +} diff --git a/node_modules/vue/src/platforms/weex/runtime/modules/transition.js b/node_modules/vue/src/platforms/weex/runtime/modules/transition.js new file mode 100644 index 00000000..e474c2b3 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/modules/transition.js @@ -0,0 +1,270 @@ +import { warn } from 'core/util/debug' +import { extend, once, noop } from 'shared/util' +import { activeInstance } from 'core/instance/lifecycle' +import { resolveTransition } from 'web/runtime/transition-util' + +export default { + create: enter, + activate: enter, + remove: leave +} + +function enter (_, vnode) { + const el = vnode.elm + + // call leave callback now + if (el._leaveCb) { + el._leaveCb.cancelled = true + el._leaveCb() + } + + const data = resolveTransition(vnode.data.transition) + if (!data) { + return + } + + /* istanbul ignore if */ + if (el._enterCb) { + return + } + + const { + enterClass, + enterToClass, + enterActiveClass, + appearClass, + appearToClass, + appearActiveClass, + beforeEnter, + enter, + afterEnter, + enterCancelled, + beforeAppear, + appear, + afterAppear, + appearCancelled + } = data + + let context = activeInstance + let transitionNode = activeInstance.$vnode + while (transitionNode && transitionNode.parent) { + transitionNode = transitionNode.parent + context = transitionNode.context + } + + const isAppear = !context._isMounted || !vnode.isRootInsert + + if (isAppear && !appear && appear !== '') { + return + } + + const startClass = isAppear ? appearClass : enterClass + const toClass = isAppear ? appearToClass : enterToClass + const activeClass = isAppear ? appearActiveClass : enterActiveClass + const beforeEnterHook = isAppear ? (beforeAppear || beforeEnter) : beforeEnter + const enterHook = isAppear ? (typeof appear === 'function' ? appear : enter) : enter + const afterEnterHook = isAppear ? (afterAppear || afterEnter) : afterEnter + const enterCancelledHook = isAppear ? (appearCancelled || enterCancelled) : enterCancelled + + const userWantsControl = + enterHook && + // enterHook may be a bound method which exposes + // the length of original fn as _length + (enterHook._length || enterHook.length) > 1 + + const stylesheet = vnode.context.$options.style || {} + const startState = stylesheet[startClass] + const transitionProperties = (stylesheet['@TRANSITION'] && stylesheet['@TRANSITION'][activeClass]) || {} + const endState = getEnterTargetState(el, stylesheet, startClass, toClass, activeClass, vnode.context) + const needAnimation = Object.keys(endState).length > 0 + + const cb = el._enterCb = once(() => { + if (cb.cancelled) { + enterCancelledHook && enterCancelledHook(el) + } else { + afterEnterHook && afterEnterHook(el) + } + el._enterCb = null + }) + + // We need to wait until the native element has been inserted, but currently + // there's no API to do that. So we have to wait "one frame" - not entirely + // sure if this is guaranteed to be enough (e.g. on slow devices?) + setTimeout(() => { + const parent = el.parentNode + const pendingNode = parent && parent._pending && parent._pending[vnode.key] + if (pendingNode && + pendingNode.context === vnode.context && + pendingNode.tag === vnode.tag && + pendingNode.elm._leaveCb + ) { + pendingNode.elm._leaveCb() + } + enterHook && enterHook(el, cb) + + if (needAnimation) { + const animation = vnode.context.$requireWeexModule('animation') + animation.transition(el.ref, { + styles: endState, + duration: transitionProperties.duration || 0, + delay: transitionProperties.delay || 0, + timingFunction: transitionProperties.timingFunction || 'linear' + }, userWantsControl ? noop : cb) + } else if (!userWantsControl) { + cb() + } + }, 16) + + // start enter transition + beforeEnterHook && beforeEnterHook(el) + + if (startState) { + if (typeof el.setStyles === 'function') { + el.setStyles(startState) + } else { + for (const key in startState) { + el.setStyle(key, startState[key]) + } + } + } + + if (!needAnimation && !userWantsControl) { + cb() + } +} + +function leave (vnode, rm) { + const el = vnode.elm + + // call enter callback now + if (el._enterCb) { + el._enterCb.cancelled = true + el._enterCb() + } + + const data = resolveTransition(vnode.data.transition) + if (!data) { + return rm() + } + + if (el._leaveCb) { + return + } + + const { + leaveClass, + leaveToClass, + leaveActiveClass, + beforeLeave, + leave, + afterLeave, + leaveCancelled, + delayLeave + } = data + + const userWantsControl = + leave && + // leave hook may be a bound method which exposes + // the length of original fn as _length + (leave._length || leave.length) > 1 + + const stylesheet = vnode.context.$options.style || {} + const startState = stylesheet[leaveClass] + const endState = stylesheet[leaveToClass] || stylesheet[leaveActiveClass] + const transitionProperties = (stylesheet['@TRANSITION'] && stylesheet['@TRANSITION'][leaveActiveClass]) || {} + + const cb = el._leaveCb = once(() => { + if (el.parentNode && el.parentNode._pending) { + el.parentNode._pending[vnode.key] = null + } + if (cb.cancelled) { + leaveCancelled && leaveCancelled(el) + } else { + rm() + afterLeave && afterLeave(el) + } + el._leaveCb = null + }) + + if (delayLeave) { + delayLeave(performLeave) + } else { + performLeave() + } + + function performLeave () { + const animation = vnode.context.$requireWeexModule('animation') + // the delayed leave may have already been cancelled + if (cb.cancelled) { + return + } + // record leaving element + if (!vnode.data.show) { + (el.parentNode._pending || (el.parentNode._pending = {}))[vnode.key] = vnode + } + beforeLeave && beforeLeave(el) + + if (startState) { + animation.transition(el.ref, { + styles: startState + }, next) + } else { + next() + } + + function next () { + animation.transition(el.ref, { + styles: endState, + duration: transitionProperties.duration || 0, + delay: transitionProperties.delay || 0, + timingFunction: transitionProperties.timingFunction || 'linear' + }, userWantsControl ? noop : cb) + } + + leave && leave(el, cb) + if (!endState && !userWantsControl) { + cb() + } + } +} + +// determine the target animation style for an entering transition. +function getEnterTargetState (el, stylesheet, startClass, endClass, activeClass, vm) { + const targetState = {} + const startState = stylesheet[startClass] + const endState = stylesheet[endClass] + const activeState = stylesheet[activeClass] + // 1. fallback to element's default styling + if (startState) { + for (const key in startState) { + targetState[key] = el.style[key] + if ( + process.env.NODE_ENV !== 'production' && + targetState[key] == null && + (!activeState || activeState[key] == null) && + (!endState || endState[key] == null) + ) { + warn( + `transition property "${key}" is declared in enter starting class (.${startClass}), ` + + `but not declared anywhere in enter ending class (.${endClass}), ` + + `enter active cass (.${activeClass}) or the element's default styling. ` + + `Note in Weex, CSS properties need explicit values to be transitionable.` + ) + } + } + } + // 2. if state is mixed in active state, extract them while excluding + // transition properties + if (activeState) { + for (const key in activeState) { + if (key.indexOf('transition') !== 0) { + targetState[key] = activeState[key] + } + } + } + // 3. explicit endState has highest priority + if (endState) { + extend(targetState, endState) + } + return targetState +} diff --git a/node_modules/vue/src/platforms/weex/runtime/node-ops.js b/node_modules/vue/src/platforms/weex/runtime/node-ops.js new file mode 100755 index 00000000..0438ec5c --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/node-ops.js @@ -0,0 +1,91 @@ +/* @flow */ +declare var document: WeexDocument; + +import TextNode from 'weex/runtime/text-node' + +export const namespaceMap = {} + +export function createElement (tagName: string): WeexElement { + return document.createElement(tagName) +} + +export function createElementNS (namespace: string, tagName: string): WeexElement { + return document.createElement(namespace + ':' + tagName) +} + +export function createTextNode (text: string) { + return new TextNode(text) +} + +export function createComment (text: string) { + return document.createComment(text) +} + +export function insertBefore ( + node: WeexElement, + target: WeexElement, + before: WeexElement +) { + if (target.nodeType === 3) { + if (node.type === 'text') { + node.setAttr('value', target.text) + target.parentNode = node + } else { + const text = createElement('text') + text.setAttr('value', target.text) + node.insertBefore(text, before) + } + return + } + node.insertBefore(target, before) +} + +export function removeChild (node: WeexElement, child: WeexElement) { + if (child.nodeType === 3) { + node.setAttr('value', '') + return + } + node.removeChild(child) +} + +export function appendChild (node: WeexElement, child: WeexElement) { + if (child.nodeType === 3) { + if (node.type === 'text') { + node.setAttr('value', child.text) + child.parentNode = node + } else { + const text = createElement('text') + text.setAttr('value', child.text) + node.appendChild(text) + } + return + } + + node.appendChild(child) +} + +export function parentNode (node: WeexElement): WeexElement | void { + return node.parentNode +} + +export function nextSibling (node: WeexElement): WeexElement | void { + return node.nextSibling +} + +export function tagName (node: WeexElement): string { + return node.type +} + +export function setTextContent (node: WeexElement, text: string) { + if (node.parentNode) { + node.parentNode.setAttr('value', text) + } +} + +export function setAttribute (node: WeexElement, key: string, val: any) { + node.setAttr(key, val) +} + +export function setStyleScope (node: WeexElement, scopeId: string) { + node.setAttr('@styleScope', scopeId) +} diff --git a/node_modules/vue/src/platforms/weex/runtime/patch.js b/node_modules/vue/src/platforms/weex/runtime/patch.js new file mode 100644 index 00000000..5e70d143 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/patch.js @@ -0,0 +1,16 @@ +/* @flow */ + +import * as nodeOps from 'weex/runtime/node-ops' +import { createPatchFunction } from 'core/vdom/patch' +import baseModules from 'core/vdom/modules/index' +import platformModules from 'weex/runtime/modules/index' + +// the directive module should be applied last, after all +// built-in modules have been applied. +const modules = platformModules.concat(baseModules) + +export const patch: Function = createPatchFunction({ + nodeOps, + modules, + LONG_LIST_THRESHOLD: 10 +}) diff --git a/node_modules/vue/src/platforms/weex/runtime/recycle-list/render-component-template.js b/node_modules/vue/src/platforms/weex/runtime/recycle-list/render-component-template.js new file mode 100644 index 00000000..17e8c018 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/recycle-list/render-component-template.js @@ -0,0 +1,34 @@ +/* @flow */ + +import { warn } from 'core/util/debug' +import { handleError } from 'core/util/error' +import { RECYCLE_LIST_MARKER } from 'weex/util/index' +import { createComponentInstanceForVnode } from 'core/vdom/create-component' +import { resolveVirtualComponent } from './virtual-component' + +export function isRecyclableComponent (vnode: VNodeWithData): boolean { + return vnode.data.attrs + ? (RECYCLE_LIST_MARKER in vnode.data.attrs) + : false +} + +export function renderRecyclableComponentTemplate (vnode: MountedComponentVNode): VNode { + // $flow-disable-line + delete vnode.data.attrs[RECYCLE_LIST_MARKER] + resolveVirtualComponent(vnode) + const vm = createComponentInstanceForVnode(vnode) + const render = (vm.$options: any)['@render'] + if (render) { + try { + return render.call(vm) + } catch (err) { + handleError(err, vm, `@render`) + } + } else { + warn( + `@render function not defined on component used in <recycle-list>. ` + + `Make sure to declare \`recyclable="true"\` on the component's template.`, + vm + ) + } +} diff --git a/node_modules/vue/src/platforms/weex/runtime/recycle-list/virtual-component.js b/node_modules/vue/src/platforms/weex/runtime/recycle-list/virtual-component.js new file mode 100644 index 00000000..e5498d26 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/recycle-list/virtual-component.js @@ -0,0 +1,136 @@ +/* @flow */ + +// https://github.com/Hanks10100/weex-native-directive/tree/master/component + +import { mergeOptions, isPlainObject, noop } from 'core/util/index' +import Watcher from 'core/observer/watcher' +import { initProxy } from 'core/instance/proxy' +import { initState, getData } from 'core/instance/state' +import { initRender } from 'core/instance/render' +import { initEvents } from 'core/instance/events' +import { initProvide, initInjections } from 'core/instance/inject' +import { initLifecycle, callHook } from 'core/instance/lifecycle' +import { initInternalComponent, resolveConstructorOptions } from 'core/instance/init' +import { registerComponentHook, updateComponentData } from '../../util/index' + +let uid = 0 + +// override Vue.prototype._init +function initVirtualComponent (options: Object = {}) { + const vm: Component = this + const componentId = options.componentId + + // virtual component uid + vm._uid = `virtual-component-${uid++}` + + // a flag to avoid this being observed + vm._isVue = true + // merge options + if (options && options._isComponent) { + // optimize internal component instantiation + // since dynamic options merging is pretty slow, and none of the + // internal component options needs special treatment. + initInternalComponent(vm, options) + } else { + vm.$options = mergeOptions( + resolveConstructorOptions(vm.constructor), + options || {}, + vm + ) + } + + /* istanbul ignore else */ + if (process.env.NODE_ENV !== 'production') { + initProxy(vm) + } else { + vm._renderProxy = vm + } + + vm._self = vm + initLifecycle(vm) + initEvents(vm) + initRender(vm) + callHook(vm, 'beforeCreate') + initInjections(vm) // resolve injections before data/props + initState(vm) + initProvide(vm) // resolve provide after data/props + callHook(vm, 'created') + + // send initial data to native + const data = vm.$options.data + const params = typeof data === 'function' + ? getData(data, vm) + : data || {} + if (isPlainObject(params)) { + updateComponentData(componentId, params) + } + + registerComponentHook(componentId, 'lifecycle', 'attach', () => { + callHook(vm, 'beforeMount') + + const updateComponent = () => { + vm._update(vm._vnode, false) + } + new Watcher(vm, updateComponent, noop, null, true) + + vm._isMounted = true + callHook(vm, 'mounted') + }) + + registerComponentHook(componentId, 'lifecycle', 'detach', () => { + vm.$destroy() + }) +} + +// override Vue.prototype._update +function updateVirtualComponent (vnode?: VNode) { + const vm: Component = this + const componentId = vm.$options.componentId + if (vm._isMounted) { + callHook(vm, 'beforeUpdate') + } + vm._vnode = vnode + if (vm._isMounted && componentId) { + // TODO: data should be filtered and without bindings + const data = Object.assign({}, vm._data) + updateComponentData(componentId, data, () => { + callHook(vm, 'updated') + }) + } +} + +// listening on native callback +export function resolveVirtualComponent (vnode: MountedComponentVNode): VNode { + const BaseCtor = vnode.componentOptions.Ctor + const VirtualComponent = BaseCtor.extend({}) + const cid = VirtualComponent.cid + VirtualComponent.prototype._init = initVirtualComponent + VirtualComponent.prototype._update = updateVirtualComponent + + vnode.componentOptions.Ctor = BaseCtor.extend({ + beforeCreate () { + // const vm: Component = this + + // TODO: listen on all events and dispatch them to the + // corresponding virtual components according to the componentId. + // vm._virtualComponents = {} + const createVirtualComponent = (componentId, propsData) => { + // create virtual component + // const subVm = + new VirtualComponent({ + componentId, + propsData + }) + // if (vm._virtualComponents) { + // vm._virtualComponents[componentId] = subVm + // } + } + + registerComponentHook(cid, 'lifecycle', 'create', createVirtualComponent) + }, + beforeDestroy () { + delete this._virtualComponents + } + }) +} + diff --git a/node_modules/vue/src/platforms/weex/runtime/text-node.js b/node_modules/vue/src/platforms/weex/runtime/text-node.js new file mode 100644 index 00000000..0720a05a --- /dev/null +++ b/node_modules/vue/src/platforms/weex/runtime/text-node.js @@ -0,0 +1,9 @@ +let latestNodeId = 1 + +export default function TextNode (text) { + this.instanceId = '' + this.nodeId = latestNodeId++ + this.parentNode = null + this.nodeType = 3 + this.text = text +} diff --git a/node_modules/vue/src/platforms/weex/util/element.js b/node_modules/vue/src/platforms/weex/util/element.js new file mode 100644 index 00000000..97e97feb --- /dev/null +++ b/node_modules/vue/src/platforms/weex/util/element.js @@ -0,0 +1,52 @@ +/* @flow */ + +// These util functions are split into its own file because Rollup cannot drop +// makeMap() due to potential side effects, so these variables end up +// bloating the web builds. + +import { makeMap } from 'shared/util' + +export const isReservedTag = makeMap( + 'template,script,style,element,content,slot,link,meta,svg,view,' + + 'a,div,img,image,text,span,input,switch,textarea,spinner,select,' + + 'slider,slider-neighbor,indicator,canvas,' + + 'list,cell,header,loading,loading-indicator,refresh,scrollable,scroller,' + + 'video,web,embed,tabbar,tabheader,datepicker,timepicker,marquee,countdown', + true +) + +// Elements that you can, intentionally, leave open (and which close themselves) +// more flexible than web +export const canBeLeftOpenTag = makeMap( + 'web,spinner,switch,video,textarea,canvas,' + + 'indicator,marquee,countdown', + true +) + +export const isRuntimeComponent = makeMap( + 'richtext,transition,transition-group', + true +) + +export const isUnaryTag = makeMap( + 'embed,img,image,input,link,meta', + true +) + +export function mustUseProp (tag: string, type: ?string, name: string): boolean { + return false +} + +export function getTagNamespace (tag?: string): string | void { } + +export function isUnknownElement (tag?: string): boolean { + return false +} + +export function query (el: string | Element, document: Object) { + // document is injected by weex factory wrapper + const placeholder = document.createComment('root') + placeholder.hasAttribute = placeholder.removeAttribute = function () {} // hack for patch + document.documentElement.appendChild(placeholder) + return placeholder +} diff --git a/node_modules/vue/src/platforms/weex/util/index.js b/node_modules/vue/src/platforms/weex/util/index.js new file mode 100755 index 00000000..8181d70e --- /dev/null +++ b/node_modules/vue/src/platforms/weex/util/index.js @@ -0,0 +1,40 @@ +/* @flow */ +declare var document: WeexDocument; + +import { warn } from 'core/util/index' + +export const RECYCLE_LIST_MARKER = '@inRecycleList' + +// Register the component hook to weex native render engine. +// The hook will be triggered by native, not javascript. +export function registerComponentHook ( + componentId: string, + type: string, // hook type, could be "lifecycle" or "instance" + hook: string, // hook name + fn: Function +) { + if (!document || !document.taskCenter) { + warn(`Can't find available "document" or "taskCenter".`) + return + } + if (typeof document.taskCenter.registerHook === 'function') { + return document.taskCenter.registerHook(componentId, type, hook, fn) + } + warn(`Failed to register component hook "${type}@${hook}#${componentId}".`) +} + +// Updates the state of the component to weex native render engine. +export function updateComponentData ( + componentId: string, + newData: Object | void, + callback?: Function +) { + if (!document || !document.taskCenter) { + warn(`Can't find available "document" or "taskCenter".`) + return + } + if (typeof document.taskCenter.updateData === 'function') { + return document.taskCenter.updateData(componentId, newData, callback) + } + warn(`Failed to update component data (${componentId}).`) +} diff --git a/node_modules/vue/src/platforms/weex/util/parser.js b/node_modules/vue/src/platforms/weex/util/parser.js new file mode 100644 index 00000000..081908e8 --- /dev/null +++ b/node_modules/vue/src/platforms/weex/util/parser.js @@ -0,0 +1,60 @@ +/* @flow */ + +// import { warn } from 'core/util/index' + +// this will be preserved during build +// $flow-disable-line +const acorn = require('acorn') // $flow-disable-line +const walk = require('acorn/dist/walk') // $flow-disable-line +const escodegen = require('escodegen') + +export function nodeToBinding (node: Object): any { + switch (node.type) { + case 'Literal': return node.value + case 'Identifier': + case 'UnaryExpression': + case 'BinaryExpression': + case 'LogicalExpression': + case 'ConditionalExpression': + case 'MemberExpression': return { '@binding': escodegen.generate(node) } + case 'ArrayExpression': return node.elements.map(_ => nodeToBinding(_)) + case 'ObjectExpression': { + const object = {} + node.properties.forEach(prop => { + if (!prop.key || prop.key.type !== 'Identifier') { + return + } + const key = escodegen.generate(prop.key) + const value = nodeToBinding(prop.value) + if (key && value) { + object[key] = value + } + }) + return object + } + default: { + // warn(`Not support ${node.type}: "${escodegen.generate(node)}"`) + return '' + } + } +} + +export function generateBinding (exp: ?string): any { + if (exp && typeof exp === 'string') { + let ast = null + try { + ast = acorn.parse(`(${exp})`) + } catch (e) { + // warn(`Failed to parse the expression: "${exp}"`) + return '' + } + + let output = '' + walk.simple(ast, { + Expression (node) { + output = nodeToBinding(node) + } + }) + return output + } +} diff --git a/node_modules/vue/src/server/bundle-renderer/create-bundle-renderer.js b/node_modules/vue/src/server/bundle-renderer/create-bundle-renderer.js new file mode 100644 index 00000000..05c60c35 --- /dev/null +++ b/node_modules/vue/src/server/bundle-renderer/create-bundle-renderer.js @@ -0,0 +1,151 @@ +/* @flow */ + +import { createPromiseCallback } from '../util' +import { createBundleRunner } from './create-bundle-runner' +import type { Renderer, RenderOptions } from '../create-renderer' +import { createSourceMapConsumers, rewriteErrorTrace } from './source-map-support' + +const fs = require('fs') +const path = require('path') +const PassThrough = require('stream').PassThrough + +const INVALID_MSG = + 'Invalid server-rendering bundle format. Should be a string ' + + 'or a bundle Object of type:\n\n' + +`{ + entry: string; + files: { [filename: string]: string; }; + maps: { [filename: string]: string; }; +}\n` + +// The render bundle can either be a string (single bundled file) +// or a bundle manifest object generated by vue-ssr-webpack-plugin. +type RenderBundle = { + basedir?: string; + entry: string; + files: { [filename: string]: string; }; + maps: { [filename: string]: string; }; + modules?: { [filename: string]: Array<string> }; +}; + +export function createBundleRendererCreator ( + createRenderer: (options?: RenderOptions) => Renderer +) { + return function createBundleRenderer ( + bundle: string | RenderBundle, + rendererOptions?: RenderOptions = {} + ) { + let files, entry, maps + let basedir = rendererOptions.basedir + + // load bundle if given filepath + if ( + typeof bundle === 'string' && + /\.js(on)?$/.test(bundle) && + path.isAbsolute(bundle) + ) { + if (fs.existsSync(bundle)) { + const isJSON = /\.json$/.test(bundle) + basedir = basedir || path.dirname(bundle) + bundle = fs.readFileSync(bundle, 'utf-8') + if (isJSON) { + try { + bundle = JSON.parse(bundle) + } catch (e) { + throw new Error(`Invalid JSON bundle file: ${bundle}`) + } + } + } else { + throw new Error(`Cannot locate bundle file: ${bundle}`) + } + } + + if (typeof bundle === 'object') { + entry = bundle.entry + files = bundle.files + basedir = basedir || bundle.basedir + maps = createSourceMapConsumers(bundle.maps) + if (typeof entry !== 'string' || typeof files !== 'object') { + throw new Error(INVALID_MSG) + } + } else if (typeof bundle === 'string') { + entry = '__vue_ssr_bundle__' + files = { '__vue_ssr_bundle__': bundle } + maps = {} + } else { + throw new Error(INVALID_MSG) + } + + const renderer = createRenderer(rendererOptions) + + const run = createBundleRunner( + entry, + files, + basedir, + rendererOptions.runInNewContext + ) + + return { + renderToString: (context?: Object, cb: any) => { + if (typeof context === 'function') { + cb = context + context = {} + } + + let promise + if (!cb) { + ({ promise, cb } = createPromiseCallback()) + } + + run(context).catch(err => { + rewriteErrorTrace(err, maps) + cb(err) + }).then(app => { + if (app) { + renderer.renderToString(app, context, (err, res) => { + rewriteErrorTrace(err, maps) + cb(err, res) + }) + } + }) + + return promise + }, + + renderToStream: (context?: Object) => { + const res = new PassThrough() + run(context).catch(err => { + rewriteErrorTrace(err, maps) + // avoid emitting synchronously before user can + // attach error listener + process.nextTick(() => { + res.emit('error', err) + }) + }).then(app => { + if (app) { + const renderStream = renderer.renderToStream(app, context) + + renderStream.on('error', err => { + rewriteErrorTrace(err, maps) + res.emit('error', err) + }) + + // relay HTMLStream special events + if (rendererOptions && rendererOptions.template) { + renderStream.on('beforeStart', () => { + res.emit('beforeStart') + }) + renderStream.on('beforeEnd', () => { + res.emit('beforeEnd') + }) + } + + renderStream.pipe(res) + } + }) + + return res + } + } + } +} diff --git a/node_modules/vue/src/server/bundle-renderer/create-bundle-runner.js b/node_modules/vue/src/server/bundle-renderer/create-bundle-runner.js new file mode 100644 index 00000000..16c33c41 --- /dev/null +++ b/node_modules/vue/src/server/bundle-renderer/create-bundle-runner.js @@ -0,0 +1,150 @@ +import { isPlainObject } from 'shared/util' + +const vm = require('vm') +const path = require('path') +const resolve = require('resolve') +const NativeModule = require('module') + +function createSandbox (context) { + const sandbox = { + Buffer, + console, + process, + setTimeout, + setInterval, + setImmediate, + clearTimeout, + clearInterval, + clearImmediate, + __VUE_SSR_CONTEXT__: context + } + sandbox.global = sandbox + return sandbox +} + +function compileModule (files, basedir, runInNewContext) { + const compiledScripts = {} + const resolvedModules = {} + + function getCompiledScript (filename) { + if (compiledScripts[filename]) { + return compiledScripts[filename] + } + const code = files[filename] + const wrapper = NativeModule.wrap(code) + const script = new vm.Script(wrapper, { + filename, + displayErrors: true + }) + compiledScripts[filename] = script + return script + } + + function evaluateModule (filename, sandbox, evaluatedFiles = {}) { + if (evaluatedFiles[filename]) { + return evaluatedFiles[filename] + } + + const script = getCompiledScript(filename) + const compiledWrapper = runInNewContext === false + ? script.runInThisContext() + : script.runInNewContext(sandbox) + const m = { exports: {}} + const r = file => { + file = path.posix.join('.', file) + if (files[file]) { + return evaluateModule(file, sandbox, evaluatedFiles) + } else if (basedir) { + return require( + resolvedModules[file] || + (resolvedModules[file] = resolve.sync(file, { basedir })) + ) + } else { + return require(file) + } + } + compiledWrapper.call(m.exports, m.exports, r, m) + + const res = Object.prototype.hasOwnProperty.call(m.exports, 'default') + ? m.exports.default + : m.exports + evaluatedFiles[filename] = res + return res + } + return evaluateModule +} + +function deepClone (val) { + if (isPlainObject(val)) { + const res = {} + for (const key in val) { + res[key] = deepClone(val[key]) + } + return res + } else if (Array.isArray(val)) { + return val.slice() + } else { + return val + } +} + +export function createBundleRunner (entry, files, basedir, runInNewContext) { + const evaluate = compileModule(files, basedir, runInNewContext) + if (runInNewContext !== false && runInNewContext !== 'once') { + // new context mode: creates a fresh context and re-evaluate the bundle + // on each render. Ensures entire application state is fresh for each + // render, but incurs extra evaluation cost. + return (userContext = {}) => new Promise(resolve => { + userContext._registeredComponents = new Set() + const res = evaluate(entry, createSandbox(userContext)) + resolve(typeof res === 'function' ? res(userContext) : res) + }) + } else { + // direct mode: instead of re-evaluating the whole bundle on + // each render, it simply calls the exported function. This avoids the + // module evaluation costs but requires the source code to be structured + // slightly differently. + let runner // lazy creation so that errors can be caught by user + let initialContext + return (userContext = {}) => new Promise(resolve => { + if (!runner) { + const sandbox = runInNewContext === 'once' + ? createSandbox() + : global + // the initial context is only used for collecting possible non-component + // styles injected by vue-style-loader. + initialContext = sandbox.__VUE_SSR_CONTEXT__ = {} + runner = evaluate(entry, sandbox) + // On subsequent renders, __VUE_SSR_CONTEXT__ will not be available + // to prevent cross-request pollution. + delete sandbox.__VUE_SSR_CONTEXT__ + if (typeof runner !== 'function') { + throw new Error( + 'bundle export should be a function when using ' + + '{ runInNewContext: false }.' + ) + } + } + userContext._registeredComponents = new Set() + + // vue-style-loader styles imported outside of component lifecycle hooks + if (initialContext._styles) { + userContext._styles = deepClone(initialContext._styles) + // #6353 ensure "styles" is exposed even if no styles are injected + // in component lifecycles. + // the renderStyles fn is exposed by vue-style-loader >= 3.0.3 + const renderStyles = initialContext._renderStyles + if (renderStyles) { + Object.defineProperty(userContext, 'styles', { + enumerable: true, + get () { + return renderStyles(userContext._styles) + } + }) + } + } + + resolve(runner(userContext)) + }) + } +} diff --git a/node_modules/vue/src/server/bundle-renderer/source-map-support.js b/node_modules/vue/src/server/bundle-renderer/source-map-support.js new file mode 100644 index 00000000..1010532e --- /dev/null +++ b/node_modules/vue/src/server/bundle-renderer/source-map-support.js @@ -0,0 +1,45 @@ +/* @flow */ + +const SourceMapConsumer = require('source-map').SourceMapConsumer + +const filenameRE = /\(([^)]+\.js):(\d+):(\d+)\)$/ + +export function createSourceMapConsumers (rawMaps: Object) { + const maps = {} + Object.keys(rawMaps).forEach(file => { + maps[file] = new SourceMapConsumer(rawMaps[file]) + }) + return maps +} + +export function rewriteErrorTrace (e: any, mapConsumers: { + [key: string]: SourceMapConsumer +}) { + if (e && typeof e.stack === 'string') { + e.stack = e.stack.split('\n').map(line => { + return rewriteTraceLine(line, mapConsumers) + }).join('\n') + } +} + +function rewriteTraceLine (trace: string, mapConsumers: { + [key: string]: SourceMapConsumer +}) { + const m = trace.match(filenameRE) + const map = m && mapConsumers[m[1]] + if (m != null && map) { + const originalPosition = map.originalPositionFor({ + line: Number(m[2]), + column: Number(m[3]) + }) + if (originalPosition.source != null) { + const { source, line, column } = originalPosition + const mappedPosition = `(${source.replace(/^webpack:\/\/\//, '')}:${String(line)}:${String(column)})` + return trace.replace(filenameRE, mappedPosition) + } else { + return trace + } + } else { + return trace + } +} diff --git a/node_modules/vue/src/server/create-basic-renderer.js b/node_modules/vue/src/server/create-basic-renderer.js new file mode 100644 index 00000000..2d53976d --- /dev/null +++ b/node_modules/vue/src/server/create-basic-renderer.js @@ -0,0 +1,37 @@ +/* @flow */ + +import { createWriteFunction } from './write' +import { createRenderFunction } from './render' +import type { RenderOptions } from './create-renderer' + +export function createBasicRenderer ({ + modules = [], + directives = {}, + isUnaryTag = (() => false), + cache +}: RenderOptions = {}) { + const render = createRenderFunction(modules, directives, isUnaryTag, cache) + + return function renderToString ( + component: Component, + context: any, + done: any + ): void { + if (typeof context === 'function') { + done = context + context = {} + } + let result = '' + const write = createWriteFunction(text => { + result += text + return false + }, done) + try { + render(component, write, context, () => { + done(null, result) + }) + } catch (e) { + done(e) + } + } +} diff --git a/node_modules/vue/src/server/create-renderer.js b/node_modules/vue/src/server/create-renderer.js new file mode 100644 index 00000000..c045a426 --- /dev/null +++ b/node_modules/vue/src/server/create-renderer.js @@ -0,0 +1,120 @@ +/* @flow */ + +import RenderStream from './render-stream' +import { createWriteFunction } from './write' +import { createRenderFunction } from './render' +import { createPromiseCallback } from './util' +import TemplateRenderer from './template-renderer/index' +import type { ClientManifest } from './template-renderer/index' + +export type Renderer = { + renderToString: (component: Component, context: any, cb: any) => ?Promise<string>; + renderToStream: (component: Component, context?: Object) => stream$Readable; +}; + +type RenderCache = { + get: (key: string, cb?: Function) => string | void; + set: (key: string, val: string) => void; + has?: (key: string, cb?: Function) => boolean | void; +}; + +export type RenderOptions = { + modules?: Array<(vnode: VNode) => ?string>; + directives?: Object; + isUnaryTag?: Function; + cache?: RenderCache; + template?: string; + inject?: boolean; + basedir?: string; + shouldPreload?: Function; + shouldPrefetch?: Function; + clientManifest?: ClientManifest; + runInNewContext?: boolean | 'once'; +}; + +export function createRenderer ({ + modules = [], + directives = {}, + isUnaryTag = (() => false), + template, + inject, + cache, + shouldPreload, + shouldPrefetch, + clientManifest +}: RenderOptions = {}): Renderer { + const render = createRenderFunction(modules, directives, isUnaryTag, cache) + const templateRenderer = new TemplateRenderer({ + template, + inject, + shouldPreload, + shouldPrefetch, + clientManifest + }) + + return { + renderToString ( + component: Component, + context: any, + cb: any + ): ?Promise<string> { + if (typeof context === 'function') { + cb = context + context = {} + } + if (context) { + templateRenderer.bindRenderFns(context) + } + + // no callback, return Promise + let promise + if (!cb) { + ({ promise, cb } = createPromiseCallback()) + } + + let result = '' + const write = createWriteFunction(text => { + result += text + return false + }, cb) + try { + render(component, write, context, err => { + if (template) { + result = templateRenderer.renderSync(result, context) + } + if (err) { + cb(err) + } else { + cb(null, result) + } + }) + } catch (e) { + cb(e) + } + + return promise + }, + + renderToStream ( + component: Component, + context?: Object + ): stream$Readable { + if (context) { + templateRenderer.bindRenderFns(context) + } + const renderStream = new RenderStream((write, done) => { + render(component, write, context, done) + }) + if (!template) { + return renderStream + } else { + const templateStream = templateRenderer.createStream(context) + renderStream.on('error', err => { + templateStream.emit('error', err) + }) + renderStream.pipe(templateStream) + return templateStream + } + } + } +} 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))}` +} diff --git a/node_modules/vue/src/server/render-context.js b/node_modules/vue/src/server/render-context.js new file mode 100644 index 00000000..178140cd --- /dev/null +++ b/node_modules/vue/src/server/render-context.js @@ -0,0 +1,130 @@ +/* @flow */ + +import { isUndef } from 'shared/util' + +type RenderState = { + type: 'Element'; + rendered: number; + total: number; + children: Array<VNode>; + endTag: string; +} | { + type: 'Fragment'; + rendered: number; + total: number; + children: Array<VNode>; +} | { + type: 'Component'; + prevActive: Component; +} | { + type: 'ComponentWithCache'; + buffer: Array<string>; + bufferIndex: number; + componentBuffer: Array<Set<Class<Component>>>; + key: string; +}; + +export class RenderContext { + userContext: ?Object; + activeInstance: Component; + renderStates: Array<RenderState>; + write: (text: string, next: Function) => void; + renderNode: (node: VNode, isRoot: boolean, context: RenderContext) => void; + next: () => void; + done: (err: ?Error) => void; + + modules: Array<(node: VNode) => ?string>; + directives: Object; + isUnaryTag: (tag: string) => boolean; + + cache: any; + get: ?(key: string, cb: Function) => void; + has: ?(key: string, cb: Function) => void; + + constructor (options: Object) { + this.userContext = options.userContext + this.activeInstance = options.activeInstance + this.renderStates = [] + + this.write = options.write + this.done = options.done + this.renderNode = options.renderNode + + this.isUnaryTag = options.isUnaryTag + this.modules = options.modules + this.directives = options.directives + + const cache = options.cache + if (cache && (!cache.get || !cache.set)) { + throw new Error('renderer cache must implement at least get & set.') + } + this.cache = cache + this.get = cache && normalizeAsync(cache, 'get') + this.has = cache && normalizeAsync(cache, 'has') + + this.next = this.next.bind(this) + } + + next () { + const lastState = this.renderStates[this.renderStates.length - 1] + if (isUndef(lastState)) { + return this.done() + } + switch (lastState.type) { + case 'Element': + case 'Fragment': + const { children, total } = lastState + const rendered = lastState.rendered++ + if (rendered < total) { + this.renderNode(children[rendered], false, this) + } else { + this.renderStates.pop() + if (lastState.type === 'Element') { + this.write(lastState.endTag, this.next) + } else { + this.next() + } + } + break + case 'Component': + this.renderStates.pop() + this.activeInstance = lastState.prevActive + this.next() + break + case 'ComponentWithCache': + this.renderStates.pop() + const { buffer, bufferIndex, componentBuffer, key } = lastState + const result = { + html: buffer[bufferIndex], + components: componentBuffer[bufferIndex] + } + this.cache.set(key, result) + if (bufferIndex === 0) { + // this is a top-level cached component, + // exit caching mode. + this.write.caching = false + } else { + // parent component is also being cached, + // merge self into parent's result + buffer[bufferIndex - 1] += result.html + const prev = componentBuffer[bufferIndex - 1] + result.components.forEach(c => prev.add(c)) + } + buffer.length = bufferIndex + componentBuffer.length = bufferIndex + this.next() + break + } + } +} + +function normalizeAsync (cache, method) { + const fn = cache[method] + if (isUndef(fn)) { + return + } else if (fn.length > 1) { + return (key, cb) => fn.call(cache, key, cb) + } else { + return (key, cb) => cb(fn.call(cache, key)) + } +} diff --git a/node_modules/vue/src/server/render-stream.js b/node_modules/vue/src/server/render-stream.js new file mode 100644 index 00000000..d76012af --- /dev/null +++ b/node_modules/vue/src/server/render-stream.js @@ -0,0 +1,94 @@ +/* @flow */ + +/** + * Original RenderStream implementation by Sasha Aickin (@aickin) + * Licensed under the Apache License, Version 2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Modified by Evan You (@yyx990803) + */ + +const stream = require('stream') + +import { isTrue, isUndef } from 'shared/util' +import { createWriteFunction } from './write' + +export default class RenderStream extends stream.Readable { + buffer: string; + render: (write: Function, done: Function) => void; + expectedSize: number; + write: Function; + next: Function; + end: Function; + done: boolean; + + constructor (render: Function) { + super() + this.buffer = '' + this.render = render + this.expectedSize = 0 + + this.write = createWriteFunction((text, next) => { + const n = this.expectedSize + this.buffer += text + if (this.buffer.length >= n) { + this.next = next + this.pushBySize(n) + return true // we will decide when to call next + } + return false + }, err => { + this.emit('error', err) + }) + + this.end = () => { + // the rendering is finished; we should push out the last of the buffer. + this.done = true + this.push(this.buffer) + } + } + + pushBySize (n: number) { + const bufferToPush = this.buffer.substring(0, n) + this.buffer = this.buffer.substring(n) + this.push(bufferToPush) + } + + tryRender () { + try { + this.render(this.write, this.end) + } catch (e) { + this.emit('error', e) + } + } + + tryNext () { + try { + this.next() + } catch (e) { + this.emit('error', e) + } + } + + _read (n: number) { + this.expectedSize = n + // it's possible that the last chunk added bumped the buffer up to > 2 * n, + // which means we will need to go through multiple read calls to drain it + // down to < n. + if (isTrue(this.done)) { + this.push(null) + return + } + if (this.buffer.length >= n) { + this.pushBySize(n) + return + } + if (isUndef(this.next)) { + // start the rendering chain. + this.tryRender() + } else { + // continue with the rendering. + this.tryNext() + } + } +} diff --git a/node_modules/vue/src/server/render.js b/node_modules/vue/src/server/render.js new file mode 100644 index 00000000..592a6175 --- /dev/null +++ b/node_modules/vue/src/server/render.js @@ -0,0 +1,396 @@ +/* @flow */ + +import { escape } from 'web/server/util' +import { SSR_ATTR } from 'shared/constants' +import { RenderContext } from './render-context' +import { generateComponentTrace } from 'core/util/debug' +import { ssrCompileToFunctions } from 'web/server/compiler' +import { installSSRHelpers } from './optimizing-compiler/runtime-helpers' + +import { isDef, isUndef, isTrue } from 'shared/util' + +import { + createComponent, + createComponentInstanceForVnode +} from 'core/vdom/create-component' + +let warned = Object.create(null) +const warnOnce = msg => { + if (!warned[msg]) { + warned[msg] = true + console.warn(`\n\u001b[31m${msg}\u001b[39m\n`) + } +} + +const onCompilationError = (err, vm) => { + const trace = vm ? generateComponentTrace(vm) : '' + throw new Error(`\n\u001b[31m${err}${trace}\u001b[39m\n`) +} + +const normalizeRender = vm => { + const { render, template, _scopeId } = vm.$options + if (isUndef(render)) { + if (template) { + const compiled = ssrCompileToFunctions(template, { + scopeId: _scopeId, + warn: onCompilationError + }, vm) + + vm.$options.render = compiled.render + vm.$options.staticRenderFns = compiled.staticRenderFns + } else { + throw new Error( + `render function or template not defined in component: ${ + vm.$options.name || vm.$options._componentTag || 'anonymous' + }` + ) + } + } +} + +function renderNode (node, isRoot, context) { + if (node.isString) { + renderStringNode(node, context) + } else if (isDef(node.componentOptions)) { + renderComponent(node, isRoot, context) + } else if (isDef(node.tag)) { + renderElement(node, isRoot, context) + } else if (isTrue(node.isComment)) { + if (isDef(node.asyncFactory)) { + // async component + renderAsyncComponent(node, isRoot, context) + } else { + context.write(`<!--${node.text}-->`, context.next) + } + } else { + context.write( + node.raw ? node.text : escape(String(node.text)), + context.next + ) + } +} + +function registerComponentForCache (options, write) { + // exposed by vue-loader, need to call this if cache hit because + // component lifecycle hooks will not be called. + const register = options._ssrRegister + if (write.caching && isDef(register)) { + write.componentBuffer[write.componentBuffer.length - 1].add(register) + } + return register +} + +function renderComponent (node, isRoot, context) { + const { write, next, userContext } = context + + // check cache hit + const Ctor = node.componentOptions.Ctor + const getKey = Ctor.options.serverCacheKey + const name = Ctor.options.name + const cache = context.cache + const registerComponent = registerComponentForCache(Ctor.options, write) + + if (isDef(getKey) && isDef(cache) && isDef(name)) { + const key = name + '::' + getKey(node.componentOptions.propsData) + const { has, get } = context + if (isDef(has)) { + has(key, hit => { + if (hit === true && isDef(get)) { + get(key, res => { + if (isDef(registerComponent)) { + registerComponent(userContext) + } + res.components.forEach(register => register(userContext)) + write(res.html, next) + }) + } else { + renderComponentWithCache(node, isRoot, key, context) + } + }) + } else if (isDef(get)) { + get(key, res => { + if (isDef(res)) { + if (isDef(registerComponent)) { + registerComponent(userContext) + } + res.components.forEach(register => register(userContext)) + write(res.html, next) + } else { + renderComponentWithCache(node, isRoot, key, context) + } + }) + } + } else { + if (isDef(getKey) && isUndef(cache)) { + warnOnce( + `[vue-server-renderer] Component ${ + Ctor.options.name || '(anonymous)' + } implemented serverCacheKey, ` + + 'but no cache was provided to the renderer.' + ) + } + if (isDef(getKey) && isUndef(name)) { + warnOnce( + `[vue-server-renderer] Components that implement "serverCacheKey" ` + + `must also define a unique "name" option.` + ) + } + renderComponentInner(node, isRoot, context) + } +} + +function renderComponentWithCache (node, isRoot, key, context) { + const write = context.write + write.caching = true + const buffer = write.cacheBuffer + const bufferIndex = buffer.push('') - 1 + const componentBuffer = write.componentBuffer + componentBuffer.push(new Set()) + context.renderStates.push({ + type: 'ComponentWithCache', + key, + buffer, + bufferIndex, + componentBuffer + }) + renderComponentInner(node, isRoot, context) +} + +function renderComponentInner (node, isRoot, context) { + const prevActive = context.activeInstance + // expose userContext on vnode + node.ssrContext = context.userContext + const child = context.activeInstance = createComponentInstanceForVnode( + node, + context.activeInstance + ) + normalizeRender(child) + const childNode = child._render() + childNode.parent = node + context.renderStates.push({ + type: 'Component', + prevActive + }) + renderNode(childNode, isRoot, context) +} + +function renderAsyncComponent (node, isRoot, context) { + const factory = node.asyncFactory + + const resolve = comp => { + if (comp.__esModule && comp.default) { + comp = comp.default + } + const { data, children, tag } = node.asyncMeta + const nodeContext = node.asyncMeta.context + const resolvedNode: any = createComponent( + comp, + data, + nodeContext, + children, + tag + ) + if (resolvedNode) { + if (resolvedNode.componentOptions) { + // normal component + renderComponent(resolvedNode, isRoot, context) + } else if (!Array.isArray(resolvedNode)) { + // single return node from functional component + renderNode(resolvedNode, isRoot, context) + } else { + // multiple return nodes from functional component + context.renderStates.push({ + type: 'Fragment', + children: resolvedNode, + rendered: 0, + total: resolvedNode.length + }) + context.next() + } + } else { + // invalid component, but this does not throw on the client + // so render empty comment node + context.write(`<!---->`, context.next) + } + } + + if (factory.resolved) { + resolve(factory.resolved) + return + } + + const reject = context.done + let res + try { + res = factory(resolve, reject) + } catch (e) { + reject(e) + } + if (res) { + if (typeof res.then === 'function') { + res.then(resolve, reject).catch(reject) + } else { + // new syntax in 2.3 + const comp = res.component + if (comp && typeof comp.then === 'function') { + comp.then(resolve, reject).catch(reject) + } + } + } +} + +function renderStringNode (el, context) { + const { write, next } = context + if (isUndef(el.children) || el.children.length === 0) { + write(el.open + (el.close || ''), next) + } else { + const children: Array<VNode> = el.children + context.renderStates.push({ + type: 'Element', + children, + rendered: 0, + total: children.length, + endTag: el.close + }) + write(el.open, next) + } +} + +function renderElement (el, isRoot, context) { + const { write, next } = context + + if (isTrue(isRoot)) { + if (!el.data) el.data = {} + if (!el.data.attrs) el.data.attrs = {} + el.data.attrs[SSR_ATTR] = 'true' + } + + if (el.fnOptions) { + registerComponentForCache(el.fnOptions, write) + } + + const startTag = renderStartingTag(el, context) + const endTag = `</${el.tag}>` + if (context.isUnaryTag(el.tag)) { + write(startTag, next) + } else if (isUndef(el.children) || el.children.length === 0) { + write(startTag + endTag, next) + } else { + const children: Array<VNode> = el.children + context.renderStates.push({ + type: 'Element', + children, + rendered: 0, + total: children.length, + endTag + }) + write(startTag, next) + } +} + +function hasAncestorData (node: VNode) { + const parentNode = node.parent + return isDef(parentNode) && (isDef(parentNode.data) || hasAncestorData(parentNode)) +} + +function getVShowDirectiveInfo (node: VNode): ?VNodeDirective { + let dir: VNodeDirective + let tmp + + while (isDef(node)) { + if (node.data && node.data.directives) { + tmp = node.data.directives.find(dir => dir.name === 'show') + if (tmp) { + dir = tmp + } + } + node = node.parent + } + return dir +} + +function renderStartingTag (node: VNode, context) { + let markup = `<${node.tag}` + const { directives, modules } = context + + // construct synthetic data for module processing + // because modules like style also produce code by parent VNode data + if (isUndef(node.data) && hasAncestorData(node)) { + node.data = {} + } + if (isDef(node.data)) { + // check directives + const dirs = node.data.directives + if (dirs) { + for (let i = 0; i < dirs.length; i++) { + const name = dirs[i].name + const dirRenderer = directives[name] + if (dirRenderer && name !== 'show') { + // directives mutate the node's data + // which then gets rendered by modules + dirRenderer(node, dirs[i]) + } + } + } + + // v-show directive needs to be merged from parent to child + const vshowDirectiveInfo = getVShowDirectiveInfo(node) + if (vshowDirectiveInfo) { + directives.show(node, vshowDirectiveInfo) + } + + // apply other modules + for (let i = 0; i < modules.length; i++) { + const res = modules[i](node) + if (res) { + markup += res + } + } + } + // attach scoped CSS ID + let scopeId + const activeInstance = context.activeInstance + if (isDef(activeInstance) && + activeInstance !== node.context && + isDef(scopeId = activeInstance.$options._scopeId) + ) { + markup += ` ${(scopeId: any)}` + } + if (isDef(node.fnScopeId)) { + markup += ` ${node.fnScopeId}` + } else { + while (isDef(node)) { + if (isDef(scopeId = node.context.$options._scopeId)) { + markup += ` ${scopeId}` + } + node = node.parent + } + } + return markup + '>' +} + +export function createRenderFunction ( + modules: Array<(node: VNode) => ?string>, + directives: Object, + isUnaryTag: Function, + cache: any +) { + return function render ( + component: Component, + write: (text: string, next: Function) => void, + userContext: ?Object, + done: Function + ) { + warned = Object.create(null) + const context = new RenderContext({ + activeInstance: component, + userContext, + write, done, renderNode, + isUnaryTag, modules, directives, + cache + }) + installSSRHelpers(component) + normalizeRender(component) + renderNode(component._render(), true, context) + } +} diff --git a/node_modules/vue/src/server/template-renderer/create-async-file-mapper.js b/node_modules/vue/src/server/template-renderer/create-async-file-mapper.js new file mode 100644 index 00000000..64c0647a --- /dev/null +++ b/node_modules/vue/src/server/template-renderer/create-async-file-mapper.js @@ -0,0 +1,53 @@ +/* @flow */ + +/** + * Creates a mapper that maps components used during a server-side render + * to async chunk files in the client-side build, so that we can inline them + * directly in the rendered HTML to avoid waterfall requests. + */ + +import type { ClientManifest } from './index' + +export type AsyncFileMapper = (files: Array<string>) => Array<string>; + +export function createMapper ( + clientManifest: ClientManifest +): AsyncFileMapper { + const map = createMap(clientManifest) + // map server-side moduleIds to client-side files + return function mapper (moduleIds: Array<string>): Array<string> { + const res = new Set() + for (let i = 0; i < moduleIds.length; i++) { + const mapped = map.get(moduleIds[i]) + if (mapped) { + for (let j = 0; j < mapped.length; j++) { + res.add(mapped[j]) + } + } + } + return Array.from(res) + } +} + +function createMap (clientManifest) { + const map = new Map() + Object.keys(clientManifest.modules).forEach(id => { + map.set(id, mapIdToFile(id, clientManifest)) + }) + return map +} + +function mapIdToFile (id, clientManifest) { + const files = [] + const fileIndices = clientManifest.modules[id] + if (fileIndices) { + fileIndices.forEach(index => { + const file = clientManifest.all[index] + // only include async files or non-js assets + if (clientManifest.async.indexOf(file) > -1 || !(/\.js($|\?)/.test(file))) { + files.push(file) + } + }) + } + return files +} diff --git a/node_modules/vue/src/server/template-renderer/index.js b/node_modules/vue/src/server/template-renderer/index.js new file mode 100644 index 00000000..dabc08a1 --- /dev/null +++ b/node_modules/vue/src/server/template-renderer/index.js @@ -0,0 +1,257 @@ +/* @flow */ + +const path = require('path') +const serialize = require('serialize-javascript') + +import { isJS, isCSS } from '../util' +import TemplateStream from './template-stream' +import { parseTemplate } from './parse-template' +import { createMapper } from './create-async-file-mapper' +import type { ParsedTemplate } from './parse-template' +import type { AsyncFileMapper } from './create-async-file-mapper' + +type TemplateRendererOptions = { + template: ?string; + inject?: boolean; + clientManifest?: ClientManifest; + shouldPreload?: (file: string, type: string) => boolean; + shouldPrefetch?: (file: string, type: string) => boolean; +}; + +export type ClientManifest = { + publicPath: string; + all: Array<string>; + initial: Array<string>; + async: Array<string>; + modules: { + [id: string]: Array<number>; + }, + hasNoCssVersion?: { + [file: string]: boolean; + } +}; + +type Resource = { + file: string; + extension: string; + fileWithoutQuery: string; + asType: string; +}; + +export default class TemplateRenderer { + options: TemplateRendererOptions; + inject: boolean; + parsedTemplate: ParsedTemplate | null; + publicPath: string; + clientManifest: ClientManifest; + preloadFiles: Array<Resource>; + prefetchFiles: Array<Resource>; + mapFiles: AsyncFileMapper; + + constructor (options: TemplateRendererOptions) { + this.options = options + this.inject = options.inject !== false + // if no template option is provided, the renderer is created + // as a utility object for rendering assets like preload links and scripts. + this.parsedTemplate = options.template + ? parseTemplate(options.template) + : null + + // extra functionality with client manifest + if (options.clientManifest) { + const clientManifest = this.clientManifest = options.clientManifest + this.publicPath = clientManifest.publicPath.replace(/\/$/, '') + // preload/prefetch directives + this.preloadFiles = (clientManifest.initial || []).map(normalizeFile) + this.prefetchFiles = (clientManifest.async || []).map(normalizeFile) + // initial async chunk mapping + this.mapFiles = createMapper(clientManifest) + } + } + + bindRenderFns (context: Object) { + const renderer: any = this + ;['ResourceHints', 'State', 'Scripts', 'Styles'].forEach(type => { + context[`render${type}`] = renderer[`render${type}`].bind(renderer, context) + }) + // also expose getPreloadFiles, useful for HTTP/2 push + context.getPreloadFiles = renderer.getPreloadFiles.bind(renderer, context) + } + + // render synchronously given rendered app content and render context + renderSync (content: string, context: ?Object) { + const template = this.parsedTemplate + if (!template) { + throw new Error('renderSync cannot be called without a template.') + } + context = context || {} + if (this.inject) { + return ( + template.head(context) + + (context.head || '') + + this.renderResourceHints(context) + + this.renderStyles(context) + + template.neck(context) + + content + + this.renderState(context) + + this.renderScripts(context) + + template.tail(context) + ) + } else { + return ( + template.head(context) + + template.neck(context) + + content + + template.tail(context) + ) + } + } + + renderStyles (context: Object): string { + const cssFiles = this.clientManifest + ? this.clientManifest.all.filter(isCSS) + : [] + return ( + // render links for css files + (cssFiles.length + ? cssFiles.map(file => `<link rel="stylesheet" href="${this.publicPath}/${file}">`).join('') + : '') + + // context.styles is a getter exposed by vue-style-loader which contains + // the inline component styles collected during SSR + (context.styles || '') + ) + } + + renderResourceHints (context: Object): string { + return this.renderPreloadLinks(context) + this.renderPrefetchLinks(context) + } + + getPreloadFiles (context: Object): Array<Resource> { + const usedAsyncFiles = this.getUsedAsyncFiles(context) + if (this.preloadFiles || usedAsyncFiles) { + return (this.preloadFiles || []).concat(usedAsyncFiles || []) + } else { + return [] + } + } + + renderPreloadLinks (context: Object): string { + const files = this.getPreloadFiles(context) + const shouldPreload = this.options.shouldPreload + if (files.length) { + return files.map(({ file, extension, fileWithoutQuery, asType }) => { + let extra = '' + // by default, we only preload scripts or css + if (!shouldPreload && asType !== 'script' && asType !== 'style') { + return '' + } + // user wants to explicitly control what to preload + if (shouldPreload && !shouldPreload(fileWithoutQuery, asType)) { + return '' + } + if (asType === 'font') { + extra = ` type="font/${extension}" crossorigin` + } + return `<link rel="preload" href="${ + this.publicPath}/${file + }"${ + asType !== '' ? ` as="${asType}"` : '' + }${ + extra + }>` + }).join('') + } else { + return '' + } + } + + renderPrefetchLinks (context: Object): string { + const shouldPrefetch = this.options.shouldPrefetch + if (this.prefetchFiles) { + const usedAsyncFiles = this.getUsedAsyncFiles(context) + const alreadyRendered = file => { + return usedAsyncFiles && usedAsyncFiles.some(f => f.file === file) + } + return this.prefetchFiles.map(({ file, fileWithoutQuery, asType }) => { + if (shouldPrefetch && !shouldPrefetch(fileWithoutQuery, asType)) { + return '' + } + if (alreadyRendered(file)) { + return '' + } + return `<link rel="prefetch" href="${this.publicPath}/${file}">` + }).join('') + } else { + return '' + } + } + + renderState (context: Object, options?: Object): string { + const { + contextKey = 'state', + windowKey = '__INITIAL_STATE__' + } = options || {} + const state = serialize(context[contextKey], { isJSON: true }) + const autoRemove = process.env.NODE_ENV === 'production' + ? ';(function(){var s;(s=document.currentScript||document.scripts[document.scripts.length-1]).parentNode.removeChild(s);}());' + : '' + return context[contextKey] + ? `<script>window.${windowKey}=${state}${autoRemove}</script>` + : '' + } + + renderScripts (context: Object): string { + if (this.clientManifest) { + const initial = this.preloadFiles + const async = this.getUsedAsyncFiles(context) + const needed = [initial[0]].concat(async || [], initial.slice(1)) + return needed.filter(({ file }) => isJS(file)).map(({ file }) => { + return `<script src="${this.publicPath}/${file}" defer></script>` + }).join('') + } else { + return '' + } + } + + getUsedAsyncFiles (context: Object): ?Array<Resource> { + if (!context._mappedFiles && context._registeredComponents && this.mapFiles) { + const registered = Array.from(context._registeredComponents) + context._mappedFiles = this.mapFiles(registered).map(normalizeFile) + } + return context._mappedFiles + } + + // create a transform stream + createStream (context: ?Object): TemplateStream { + if (!this.parsedTemplate) { + throw new Error('createStream cannot be called without a template.') + } + return new TemplateStream(this, this.parsedTemplate, context || {}) + } +} + +function normalizeFile (file: string): Resource { + const withoutQuery = file.replace(/\?.*/, '') + const extension = path.extname(withoutQuery).slice(1) + return { + file, + extension, + fileWithoutQuery: withoutQuery, + asType: getPreloadType(extension) + } +} + +function getPreloadType (ext: string): string { + if (ext === 'js') { + return 'script' + } else if (ext === 'css') { + return 'style' + } else if (/jpe?g|png|svg|gif|webp|ico/.test(ext)) { + return 'image' + } else if (/woff2?|ttf|otf|eot/.test(ext)) { + return 'font' + } else { + // not exhausting all possibilities here, but above covers common cases + return '' + } +} diff --git a/node_modules/vue/src/server/template-renderer/parse-template.js b/node_modules/vue/src/server/template-renderer/parse-template.js new file mode 100644 index 00000000..1ccfe899 --- /dev/null +++ b/node_modules/vue/src/server/template-renderer/parse-template.js @@ -0,0 +1,42 @@ +/* @flow */ + +const compile = require('lodash.template') +const compileOptions = { + escape: /{{([^{][\s\S]+?[^}])}}/g, + interpolate: /{{{([\s\S]+?)}}}/g +} + +export type ParsedTemplate = { + head: (data: any) => string; + neck: (data: any) => string; + tail: (data: any) => string; +}; + +export function parseTemplate ( + template: string, + contentPlaceholder?: string = '<!--vue-ssr-outlet-->' +): ParsedTemplate { + if (typeof template === 'object') { + return template + } + + let i = template.indexOf('</head>') + const j = template.indexOf(contentPlaceholder) + + if (j < 0) { + throw new Error(`Content placeholder not found in template.`) + } + + if (i < 0) { + i = template.indexOf('<body>') + if (i < 0) { + i = j + } + } + + return { + head: compile(template.slice(0, i), compileOptions), + neck: compile(template.slice(i, j), compileOptions), + tail: compile(template.slice(j + contentPlaceholder.length), compileOptions) + } +} diff --git a/node_modules/vue/src/server/template-renderer/template-stream.js b/node_modules/vue/src/server/template-renderer/template-stream.js new file mode 100644 index 00000000..ed4db78f --- /dev/null +++ b/node_modules/vue/src/server/template-renderer/template-stream.js @@ -0,0 +1,82 @@ +/* @flow */ + +const Transform = require('stream').Transform +import type TemplateRenderer from './index' +import type { ParsedTemplate } from './parse-template' + +export default class TemplateStream extends Transform { + started: boolean; + renderer: TemplateRenderer; + template: ParsedTemplate; + context: Object; + inject: boolean; + + constructor ( + renderer: TemplateRenderer, + template: ParsedTemplate, + context: Object + ) { + super() + this.started = false + this.renderer = renderer + this.template = template + this.context = context || {} + this.inject = renderer.inject + } + + _transform (data: Buffer | string, encoding: string, done: Function) { + if (!this.started) { + this.emit('beforeStart') + this.start() + } + this.push(data) + done() + } + + start () { + this.started = true + this.push(this.template.head(this.context)) + + if (this.inject) { + // inline server-rendered head meta information + if (this.context.head) { + this.push(this.context.head) + } + + // inline preload/prefetch directives for initial/async chunks + const links = this.renderer.renderResourceHints(this.context) + if (links) { + this.push(links) + } + + // CSS files and inline server-rendered CSS collected by vue-style-loader + const styles = this.renderer.renderStyles(this.context) + if (styles) { + this.push(styles) + } + } + + this.push(this.template.neck(this.context)) + } + + _flush (done: Function) { + this.emit('beforeEnd') + + if (this.inject) { + // inline initial store state + const state = this.renderer.renderState(this.context) + if (state) { + this.push(state) + } + + // embed scripts needed + const scripts = this.renderer.renderScripts(this.context) + if (scripts) { + this.push(scripts) + } + } + + this.push(this.template.tail(this.context)) + done() + } +} diff --git a/node_modules/vue/src/server/util.js b/node_modules/vue/src/server/util.js new file mode 100644 index 00000000..908f8c9d --- /dev/null +++ b/node_modules/vue/src/server/util.js @@ -0,0 +1,18 @@ +/* @flow */ + +export const isJS = (file: string): boolean => /\.js(\?[^.]+)?$/.test(file) + +export const isCSS = (file: string): boolean => /\.css(\?[^.]+)?$/.test(file) + +export function createPromiseCallback () { + let resolve, reject + const promise: Promise<string> = new Promise((_resolve, _reject) => { + resolve = _resolve + reject = _reject + }) + const cb = (err: Error, res?: string) => { + if (err) return reject(err) + resolve(res || '') + } + return { promise, cb } +} diff --git a/node_modules/vue/src/server/webpack-plugin/client.js b/node_modules/vue/src/server/webpack-plugin/client.js new file mode 100644 index 00000000..5f2cd4bd --- /dev/null +++ b/node_modules/vue/src/server/webpack-plugin/client.js @@ -0,0 +1,70 @@ +const hash = require('hash-sum') +const uniq = require('lodash.uniq') +import { isJS } from './util' + +export default class VueSSRClientPlugin { + constructor (options = {}) { + this.options = Object.assign({ + filename: 'vue-ssr-client-manifest.json' + }, options) + } + + apply (compiler) { + compiler.plugin('emit', (compilation, cb) => { + const stats = compilation.getStats().toJson() + + const allFiles = uniq(stats.assets + .map(a => a.name)) + + const initialFiles = uniq(Object.keys(stats.entrypoints) + .map(name => stats.entrypoints[name].assets) + .reduce((assets, all) => all.concat(assets), []) + .filter(isJS)) + + const asyncFiles = allFiles + .filter(isJS) + .filter(file => initialFiles.indexOf(file) < 0) + + const manifest = { + publicPath: stats.publicPath, + all: allFiles, + initial: initialFiles, + async: asyncFiles, + modules: { /* [identifier: string]: Array<index: number> */ } + } + + const assetModules = stats.modules.filter(m => m.assets.length) + const fileToIndex = file => manifest.all.indexOf(file) + stats.modules.forEach(m => { + // ignore modules duplicated in multiple chunks + if (m.chunks.length === 1) { + const cid = m.chunks[0] + const chunk = stats.chunks.find(c => c.id === cid) + if (!chunk || !chunk.files) { + return + } + const files = manifest.modules[hash(m.identifier)] = chunk.files.map(fileToIndex) + // find all asset modules associated with the same chunk + assetModules.forEach(m => { + if (m.chunks.some(id => id === cid)) { + files.push.apply(files, m.assets.map(fileToIndex)) + } + }) + } + }) + + // const debug = (file, obj) => { + // require('fs').writeFileSync(__dirname + '/' + file, JSON.stringify(obj, null, 2)) + // } + // debug('stats.json', stats) + // debug('client-manifest.json', manifest) + + const json = JSON.stringify(manifest, null, 2) + compilation.assets[this.options.filename] = { + source: () => json, + size: () => json.length + } + cb() + }) + } +} diff --git a/node_modules/vue/src/server/webpack-plugin/server.js b/node_modules/vue/src/server/webpack-plugin/server.js new file mode 100644 index 00000000..8ffa58b0 --- /dev/null +++ b/node_modules/vue/src/server/webpack-plugin/server.js @@ -0,0 +1,66 @@ +import { validate, isJS } from './util' + +export default class VueSSRServerPlugin { + constructor (options = {}) { + this.options = Object.assign({ + filename: 'vue-ssr-server-bundle.json' + }, options) + } + + apply (compiler) { + validate(compiler) + + compiler.plugin('emit', (compilation, cb) => { + const stats = compilation.getStats().toJson() + const entryName = Object.keys(stats.entrypoints)[0] + const entryInfo = stats.entrypoints[entryName] + + if (!entryInfo) { + // #5553 + return cb() + } + + const entryAssets = entryInfo.assets.filter(isJS) + + if (entryAssets.length > 1) { + throw new Error( + `Server-side bundle should have one single entry file. ` + + `Avoid using CommonsChunkPlugin in the server config.` + ) + } + + const entry = entryAssets[0] + if (!entry || typeof entry !== 'string') { + throw new Error( + `Entry "${entryName}" not found. Did you specify the correct entry option?` + ) + } + + const bundle = { + entry, + files: {}, + maps: {} + } + + stats.assets.forEach(asset => { + if (asset.name.match(/\.js$/)) { + bundle.files[asset.name] = compilation.assets[asset.name].source() + } else if (asset.name.match(/\.js\.map$/)) { + bundle.maps[asset.name.replace(/\.map$/, '')] = JSON.parse(compilation.assets[asset.name].source()) + } + // do not emit anything else for server + delete compilation.assets[asset.name] + }) + + const json = JSON.stringify(bundle, null, 2) + const filename = this.options.filename + + compilation.assets[filename] = { + source: () => json, + size: () => json.length + } + + cb() + }) + } +} diff --git a/node_modules/vue/src/server/webpack-plugin/util.js b/node_modules/vue/src/server/webpack-plugin/util.js new file mode 100644 index 00000000..d22e4b2c --- /dev/null +++ b/node_modules/vue/src/server/webpack-plugin/util.js @@ -0,0 +1,24 @@ +const { red, yellow } = require('chalk') + +const prefix = `[vue-server-renderer-webpack-plugin]` +const warn = exports.warn = msg => console.error(red(`${prefix} ${msg}\n`)) +const tip = exports.tip = msg => console.log(yellow(`${prefix} ${msg}\n`)) + +export const validate = compiler => { + if (compiler.options.target !== 'node') { + warn('webpack config `target` should be "node".') + } + + if (compiler.options.output && compiler.options.output.libraryTarget !== 'commonjs2') { + warn('webpack config `output.libraryTarget` should be "commonjs2".') + } + + if (!compiler.options.externals) { + tip( + 'It is recommended to externalize dependencies in the server build for ' + + 'better build performance.' + ) + } +} + +export { isJS, isCSS } from '../util' diff --git a/node_modules/vue/src/server/write.js b/node_modules/vue/src/server/write.js new file mode 100644 index 00000000..e6420818 --- /dev/null +++ b/node_modules/vue/src/server/write.js @@ -0,0 +1,50 @@ +/* @flow */ + +const MAX_STACK_DEPTH = 1000 +const noop = _ => _ + +const defer = typeof process !== 'undefined' && process.nextTick + ? process.nextTick + : typeof Promise !== 'undefined' + ? fn => Promise.resolve().then(fn) + : typeof setTimeout !== 'undefined' + ? setTimeout + : noop + +if (defer === noop) { + throw new Error( + 'Your JavaScript runtime does not support any asynchronous primitives ' + + 'that are required by vue-server-renderer. Please use a polyfill for ' + + 'either Promise or setTimeout.' + ) +} + +export function createWriteFunction ( + write: (text: string, next: Function) => boolean, + onError: Function +): Function { + let stackDepth = 0 + const cachedWrite = (text, next) => { + if (text && cachedWrite.caching) { + cachedWrite.cacheBuffer[cachedWrite.cacheBuffer.length - 1] += text + } + const waitForNext = write(text, next) + if (waitForNext !== true) { + if (stackDepth >= MAX_STACK_DEPTH) { + defer(() => { + try { next() } catch (e) { + onError(e) + } + }) + } else { + stackDepth++ + next() + stackDepth-- + } + } + } + cachedWrite.caching = false + cachedWrite.cacheBuffer = [] + cachedWrite.componentBuffer = [] + return cachedWrite +} diff --git a/node_modules/vue/src/sfc/parser.js b/node_modules/vue/src/sfc/parser.js new file mode 100644 index 00000000..868a43ca --- /dev/null +++ b/node_modules/vue/src/sfc/parser.js @@ -0,0 +1,116 @@ +/* @flow */ + +import deindent from 'de-indent' +import { parseHTML } from 'compiler/parser/html-parser' +import { makeMap } from 'shared/util' + +const splitRE = /\r?\n/g +const replaceRE = /./g +const isSpecialTag = makeMap('script,style,template', true) + +type Attribute = { + name: string, + value: string +}; + +/** + * Parse a single-file component (*.vue) file into an SFC Descriptor Object. + */ +export function parseComponent ( + content: string, + options?: Object = {} +): SFCDescriptor { + const sfc: SFCDescriptor = { + template: null, + script: null, + styles: [], + customBlocks: [] + } + let depth = 0 + let currentBlock: ?SFCBlock = null + + function start ( + tag: string, + attrs: Array<Attribute>, + unary: boolean, + start: number, + end: number + ) { + if (depth === 0) { + currentBlock = { + type: tag, + content: '', + start: end, + attrs: attrs.reduce((cumulated, { name, value }) => { + cumulated[name] = value || true + return cumulated + }, {}) + } + if (isSpecialTag(tag)) { + checkAttrs(currentBlock, attrs) + if (tag === 'style') { + sfc.styles.push(currentBlock) + } else { + sfc[tag] = currentBlock + } + } else { // custom blocks + sfc.customBlocks.push(currentBlock) + } + } + if (!unary) { + depth++ + } + } + + function checkAttrs (block: SFCBlock, attrs: Array<Attribute>) { + for (let i = 0; i < attrs.length; i++) { + const attr = attrs[i] + if (attr.name === 'lang') { + block.lang = attr.value + } + if (attr.name === 'scoped') { + block.scoped = true + } + if (attr.name === 'module') { + block.module = attr.value || true + } + if (attr.name === 'src') { + block.src = attr.value + } + } + } + + function end (tag: string, start: number, end: number) { + if (depth === 1 && currentBlock) { + currentBlock.end = start + let text = deindent(content.slice(currentBlock.start, currentBlock.end)) + // pad content so that linters and pre-processors can output correct + // line numbers in errors and warnings + if (currentBlock.type !== 'template' && options.pad) { + text = padContent(currentBlock, options.pad) + text + } + currentBlock.content = text + currentBlock = null + } + depth-- + } + + function padContent (block: SFCBlock, pad: true | "line" | "space") { + if (pad === 'space') { + return content.slice(0, block.start).replace(replaceRE, ' ') + } else { + const offset = content.slice(0, block.start).split(splitRE).length + const padChar = block.type === 'script' && !block.lang + ? '//\n' + : '\n' + return Array(offset).join(padChar) + } + } + + parseHTML(content, { + start, + end + }) + + return sfc +} diff --git a/node_modules/vue/src/shared/constants.js b/node_modules/vue/src/shared/constants.js new file mode 100644 index 00000000..84d019fb --- /dev/null +++ b/node_modules/vue/src/shared/constants.js @@ -0,0 +1,21 @@ +export const SSR_ATTR = 'data-server-rendered' + +export const ASSET_TYPES = [ + 'component', + 'directive', + 'filter' +] + +export const LIFECYCLE_HOOKS = [ + 'beforeCreate', + 'created', + 'beforeMount', + 'mounted', + 'beforeUpdate', + 'updated', + 'beforeDestroy', + 'destroyed', + 'activated', + 'deactivated', + 'errorCaptured' +] diff --git a/node_modules/vue/src/shared/util.js b/node_modules/vue/src/shared/util.js new file mode 100644 index 00000000..e4884734 --- /dev/null +++ b/node_modules/vue/src/shared/util.js @@ -0,0 +1,324 @@ +/* @flow */ + +export const emptyObject = Object.freeze({}) + +// these helpers produces better vm code in JS engines due to their +// explicitness and function inlining +export function isUndef (v: any): boolean %checks { + return v === undefined || v === null +} + +export function isDef (v: any): boolean %checks { + return v !== undefined && v !== null +} + +export function isTrue (v: any): boolean %checks { + return v === true +} + +export function isFalse (v: any): boolean %checks { + return v === false +} + +/** + * Check if value is primitive + */ +export function isPrimitive (value: any): boolean %checks { + return ( + typeof value === 'string' || + typeof value === 'number' || + // $flow-disable-line + typeof value === 'symbol' || + typeof value === 'boolean' + ) +} + +/** + * Quick object check - this is primarily used to tell + * Objects from primitive values when we know the value + * is a JSON-compliant type. + */ +export function isObject (obj: mixed): boolean %checks { + return obj !== null && typeof obj === 'object' +} + +/** + * Get the raw type string of a value e.g. [object Object] + */ +const _toString = Object.prototype.toString + +export function toRawType (value: any): string { + return _toString.call(value).slice(8, -1) +} + +/** + * Strict object type check. Only returns true + * for plain JavaScript objects. + */ +export function isPlainObject (obj: any): boolean { + return _toString.call(obj) === '[object Object]' +} + +export function isRegExp (v: any): boolean { + return _toString.call(v) === '[object RegExp]' +} + +/** + * Check if val is a valid array index. + */ +export function isValidArrayIndex (val: any): boolean { + const n = parseFloat(String(val)) + return n >= 0 && Math.floor(n) === n && isFinite(val) +} + +/** + * Convert a value to a string that is actually rendered. + */ +export function toString (val: any): string { + return val == null + ? '' + : typeof val === 'object' + ? JSON.stringify(val, null, 2) + : String(val) +} + +/** + * Convert a input value to a number for persistence. + * If the conversion fails, return original string. + */ +export function toNumber (val: string): number | string { + const n = parseFloat(val) + return isNaN(n) ? val : n +} + +/** + * Make a map and return a function for checking if a key + * is in that map. + */ +export function makeMap ( + str: string, + expectsLowerCase?: boolean +): (key: string) => true | void { + const map = Object.create(null) + const list: Array<string> = str.split(',') + for (let i = 0; i < list.length; i++) { + map[list[i]] = true + } + return expectsLowerCase + ? val => map[val.toLowerCase()] + : val => map[val] +} + +/** + * Check if a tag is a built-in tag. + */ +export const isBuiltInTag = makeMap('slot,component', true) + +/** + * Check if a attribute is a reserved attribute. + */ +export const isReservedAttribute = makeMap('key,ref,slot,slot-scope,is') + +/** + * Remove an item from an array + */ +export function remove (arr: Array<any>, item: any): Array<any> | void { + if (arr.length) { + const index = arr.indexOf(item) + if (index > -1) { + return arr.splice(index, 1) + } + } +} + +/** + * Check whether the object has the property. + */ +const hasOwnProperty = Object.prototype.hasOwnProperty +export function hasOwn (obj: Object | Array<*>, key: string): boolean { + return hasOwnProperty.call(obj, key) +} + +/** + * Create a cached version of a pure function. + */ +export function cached<F: Function> (fn: F): F { + const cache = Object.create(null) + return (function cachedFn (str: string) { + const hit = cache[str] + return hit || (cache[str] = fn(str)) + }: any) +} + +/** + * Camelize a hyphen-delimited string. + */ +const camelizeRE = /-(\w)/g +export const camelize = cached((str: string): string => { + return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') +}) + +/** + * Capitalize a string. + */ +export const capitalize = cached((str: string): string => { + return str.charAt(0).toUpperCase() + str.slice(1) +}) + +/** + * Hyphenate a camelCase string. + */ +const hyphenateRE = /\B([A-Z])/g +export const hyphenate = cached((str: string): string => { + return str.replace(hyphenateRE, '-$1').toLowerCase() +}) + +/** + * Simple bind polyfill for environments that do not support it... e.g. + * PhantomJS 1.x. Technically we don't need this anymore since native bind is + * now more performant in most browsers, but removing it would be breaking for + * code that was able to run in PhantomJS 1.x, so this must be kept for + * backwards compatibility. + */ + +/* istanbul ignore next */ +function polyfillBind (fn: Function, ctx: Object): Function { + function boundFn (a) { + const l = arguments.length + return l + ? l > 1 + ? fn.apply(ctx, arguments) + : fn.call(ctx, a) + : fn.call(ctx) + } + + boundFn._length = fn.length + return boundFn +} + +function nativeBind (fn: Function, ctx: Object): Function { + return fn.bind(ctx) +} + +export const bind = Function.prototype.bind + ? nativeBind + : polyfillBind + +/** + * Convert an Array-like object to a real Array. + */ +export function toArray (list: any, start?: number): Array<any> { + start = start || 0 + let i = list.length - start + const ret: Array<any> = new Array(i) + while (i--) { + ret[i] = list[i + start] + } + return ret +} + +/** + * Mix properties into target object. + */ +export function extend (to: Object, _from: ?Object): Object { + for (const key in _from) { + to[key] = _from[key] + } + return to +} + +/** + * Merge an Array of Objects into a single Object. + */ +export function toObject (arr: Array<any>): Object { + const res = {} + for (let i = 0; i < arr.length; i++) { + if (arr[i]) { + extend(res, arr[i]) + } + } + return res +} + +/** + * Perform no operation. + * Stubbing args to make Flow happy without leaving useless transpiled code + * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/) + */ +export function noop (a?: any, b?: any, c?: any) {} + +/** + * Always return false. + */ +export const no = (a?: any, b?: any, c?: any) => false + +/** + * Return same value + */ +export const identity = (_: any) => _ + +/** + * Generate a static keys string from compiler modules. + */ +export function genStaticKeys (modules: Array<ModuleOptions>): string { + return modules.reduce((keys, m) => { + return keys.concat(m.staticKeys || []) + }, []).join(',') +} + +/** + * Check if two values are loosely equal - that is, + * if they are plain objects, do they have the same shape? + */ +export function looseEqual (a: any, b: any): boolean { + if (a === b) return true + const isObjectA = isObject(a) + const isObjectB = isObject(b) + if (isObjectA && isObjectB) { + try { + const isArrayA = Array.isArray(a) + const isArrayB = Array.isArray(b) + if (isArrayA && isArrayB) { + return a.length === b.length && a.every((e, i) => { + return looseEqual(e, b[i]) + }) + } else if (!isArrayA && !isArrayB) { + const keysA = Object.keys(a) + const keysB = Object.keys(b) + return keysA.length === keysB.length && keysA.every(key => { + return looseEqual(a[key], b[key]) + }) + } else { + /* istanbul ignore next */ + return false + } + } catch (e) { + /* istanbul ignore next */ + return false + } + } else if (!isObjectA && !isObjectB) { + return String(a) === String(b) + } else { + return false + } +} + +export function looseIndexOf (arr: Array<mixed>, val: mixed): number { + for (let i = 0; i < arr.length; i++) { + if (looseEqual(arr[i], val)) return i + } + return -1 +} + +/** + * Ensure a function is called only once. + */ +export function once (fn: Function): Function { + let called = false + return function () { + if (!called) { + called = true + fn.apply(this, arguments) + } + } +} |
