modified .gitignore
This commit is contained in:
+166
-103
@@ -1,73 +1,76 @@
|
||||
/* @flow */
|
||||
|
||||
import { genHandlers } from './events'
|
||||
import { baseWarn, pluckModuleFunction } from '../helpers'
|
||||
import baseDirectives from '../directives/index'
|
||||
import { camelize, no } from 'shared/util'
|
||||
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;
|
||||
|
||||
// configurable state
|
||||
let warn
|
||||
let transforms: Array<TransformFunction>
|
||||
let dataGenFns: Array<DataGenFunction>
|
||||
let platformDirectives
|
||||
let isPlatformReservedTag
|
||||
let staticRenderFns
|
||||
let onceCount
|
||||
let currentOptions
|
||||
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
|
||||
): {
|
||||
render: string,
|
||||
staticRenderFns: Array<string>
|
||||
} {
|
||||
// save previous staticRenderFns so generate calls can be nested
|
||||
const prevStaticRenderFns: Array<string> = staticRenderFns
|
||||
const currentStaticRenderFns: Array<string> = staticRenderFns = []
|
||||
const prevOnceCount = onceCount
|
||||
onceCount = 0
|
||||
currentOptions = options
|
||||
warn = options.warn || baseWarn
|
||||
transforms = pluckModuleFunction(options.modules, 'transformCode')
|
||||
dataGenFns = pluckModuleFunction(options.modules, 'genData')
|
||||
platformDirectives = options.directives || {}
|
||||
isPlatformReservedTag = options.isReservedTag || no
|
||||
const code = ast ? genElement(ast) : '_c("div")'
|
||||
staticRenderFns = prevStaticRenderFns
|
||||
onceCount = prevOnceCount
|
||||
): CodegenResult {
|
||||
const state = new CodegenState(options)
|
||||
const code = ast ? genElement(ast, state) : '_c("div")'
|
||||
return {
|
||||
render: `with(this){return ${code}}`,
|
||||
staticRenderFns: currentStaticRenderFns
|
||||
staticRenderFns: state.staticRenderFns
|
||||
}
|
||||
}
|
||||
|
||||
function genElement (el: ASTElement): string {
|
||||
export function genElement (el: ASTElement, state: CodegenState): string {
|
||||
if (el.staticRoot && !el.staticProcessed) {
|
||||
return genStatic(el)
|
||||
return genStatic(el, state)
|
||||
} else if (el.once && !el.onceProcessed) {
|
||||
return genOnce(el)
|
||||
return genOnce(el, state)
|
||||
} else if (el.for && !el.forProcessed) {
|
||||
return genFor(el)
|
||||
return genFor(el, state)
|
||||
} else if (el.if && !el.ifProcessed) {
|
||||
return genIf(el)
|
||||
return genIf(el, state)
|
||||
} else if (el.tag === 'template' && !el.slotTarget) {
|
||||
return genChildren(el) || 'void 0'
|
||||
return genChildren(el, state) || 'void 0'
|
||||
} else if (el.tag === 'slot') {
|
||||
return genSlot(el)
|
||||
return genSlot(el, state)
|
||||
} else {
|
||||
// component or element
|
||||
let code
|
||||
if (el.component) {
|
||||
code = genComponent(el.component, el)
|
||||
code = genComponent(el.component, el, state)
|
||||
} else {
|
||||
const data = el.plain ? undefined : genData(el)
|
||||
const data = el.plain ? undefined : genData(el, state)
|
||||
|
||||
const children = el.inlineTemplate ? null : genChildren(el, true)
|
||||
const children = el.inlineTemplate ? null : genChildren(el, state, true)
|
||||
code = `_c('${el.tag}'${
|
||||
data ? `,${data}` : '' // data
|
||||
}${
|
||||
@@ -75,25 +78,25 @@ function genElement (el: ASTElement): string {
|
||||
})`
|
||||
}
|
||||
// module transforms
|
||||
for (let i = 0; i < transforms.length; i++) {
|
||||
code = transforms[i](el, code)
|
||||
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): string {
|
||||
function genStatic (el: ASTElement, state: CodegenState): string {
|
||||
el.staticProcessed = true
|
||||
staticRenderFns.push(`with(this){return ${genElement(el)}}`)
|
||||
return `_m(${staticRenderFns.length - 1}${el.staticInFor ? ',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): string {
|
||||
function genOnce (el: ASTElement, state: CodegenState): string {
|
||||
el.onceProcessed = true
|
||||
if (el.if && !el.ifProcessed) {
|
||||
return genIf(el)
|
||||
return genIf(el, state)
|
||||
} else if (el.staticInFor) {
|
||||
let key = ''
|
||||
let parent = el.parent
|
||||
@@ -105,51 +108,76 @@ function genOnce (el: ASTElement): string {
|
||||
parent = parent.parent
|
||||
}
|
||||
if (!key) {
|
||||
process.env.NODE_ENV !== 'production' && warn(
|
||||
process.env.NODE_ENV !== 'production' && state.warn(
|
||||
`v-once can only be used inside v-for that is keyed. `
|
||||
)
|
||||
return genElement(el)
|
||||
return genElement(el, state)
|
||||
}
|
||||
return `_o(${genElement(el)},${onceCount++}${key ? `,${key}` : ``})`
|
||||
return `_o(${genElement(el, state)},${state.onceId++},${key})`
|
||||
} else {
|
||||
return genStatic(el)
|
||||
return genStatic(el, state)
|
||||
}
|
||||
}
|
||||
|
||||
function genIf (el: any): string {
|
||||
export function genIf (
|
||||
el: any,
|
||||
state: CodegenState,
|
||||
altGen?: Function,
|
||||
altEmpty?: string
|
||||
): string {
|
||||
el.ifProcessed = true // avoid recursion
|
||||
return genIfConditions(el.ifConditions.slice())
|
||||
return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
|
||||
}
|
||||
|
||||
function genIfConditions (conditions: ASTIfConditions): string {
|
||||
function genIfConditions (
|
||||
conditions: ASTIfConditions,
|
||||
state: CodegenState,
|
||||
altGen?: Function,
|
||||
altEmpty?: string
|
||||
): string {
|
||||
if (!conditions.length) {
|
||||
return '_e()'
|
||||
return altEmpty || '_e()'
|
||||
}
|
||||
|
||||
const condition = conditions.shift()
|
||||
if (condition.exp) {
|
||||
return `(${condition.exp})?${genTernaryExp(condition.block)}:${genIfConditions(conditions)}`
|
||||
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 el.once ? genOnce(el) : genElement(el)
|
||||
return altGen
|
||||
? altGen(el, state)
|
||||
: el.once
|
||||
? genOnce(el, state)
|
||||
: genElement(el, state)
|
||||
}
|
||||
}
|
||||
|
||||
function genFor (el: any): string {
|
||||
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' &&
|
||||
maybeComponent(el) && el.tag !== 'slot' && el.tag !== 'template' && !el.key
|
||||
if (process.env.NODE_ENV !== 'production' &&
|
||||
state.maybeComponent(el) &&
|
||||
el.tag !== 'slot' &&
|
||||
el.tag !== 'template' &&
|
||||
!el.key
|
||||
) {
|
||||
warn(
|
||||
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.`,
|
||||
@@ -158,18 +186,18 @@ function genFor (el: any): string {
|
||||
}
|
||||
|
||||
el.forProcessed = true // avoid recursion
|
||||
return `_l((${exp}),` +
|
||||
return `${altHelper || '_l'}((${exp}),` +
|
||||
`function(${alias}${iterator1}${iterator2}){` +
|
||||
`return ${genElement(el)}` +
|
||||
`return ${(altGen || genElement)(el, state)}` +
|
||||
'})'
|
||||
}
|
||||
|
||||
function genData (el: ASTElement): string {
|
||||
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)
|
||||
const dirs = genDirectives(el, state)
|
||||
if (dirs) data += dirs + ','
|
||||
|
||||
// key
|
||||
@@ -192,8 +220,8 @@ function genData (el: ASTElement): string {
|
||||
data += `tag:"${el.tag}",`
|
||||
}
|
||||
// module data generation functions
|
||||
for (let i = 0; i < dataGenFns.length; i++) {
|
||||
data += dataGenFns[i](el)
|
||||
for (let i = 0; i < state.dataGenFns.length; i++) {
|
||||
data += state.dataGenFns[i](el)
|
||||
}
|
||||
// attributes
|
||||
if (el.attrs) {
|
||||
@@ -205,10 +233,10 @@ function genData (el: ASTElement): string {
|
||||
}
|
||||
// event handlers
|
||||
if (el.events) {
|
||||
data += `${genHandlers(el.events, false, warn)},`
|
||||
data += `${genHandlers(el.events, false, state.warn)},`
|
||||
}
|
||||
if (el.nativeEvents) {
|
||||
data += `${genHandlers(el.nativeEvents, true, warn)},`
|
||||
data += `${genHandlers(el.nativeEvents, true, state.warn)},`
|
||||
}
|
||||
// slot target
|
||||
if (el.slotTarget) {
|
||||
@@ -216,7 +244,7 @@ function genData (el: ASTElement): string {
|
||||
}
|
||||
// scoped slots
|
||||
if (el.scopedSlots) {
|
||||
data += `${genScopedSlots(el.scopedSlots)},`
|
||||
data += `${genScopedSlots(el.scopedSlots, state)},`
|
||||
}
|
||||
// component v-model
|
||||
if (el.model) {
|
||||
@@ -230,7 +258,7 @@ function genData (el: ASTElement): string {
|
||||
}
|
||||
// inline-template
|
||||
if (el.inlineTemplate) {
|
||||
const inlineTemplate = genInlineTemplate(el)
|
||||
const inlineTemplate = genInlineTemplate(el, state)
|
||||
if (inlineTemplate) {
|
||||
data += `${inlineTemplate},`
|
||||
}
|
||||
@@ -240,10 +268,14 @@ function genData (el: ASTElement): string {
|
||||
if (el.wrapData) {
|
||||
data = el.wrapData(data)
|
||||
}
|
||||
// v-on data wrap
|
||||
if (el.wrapListeners) {
|
||||
data = el.wrapListeners(data)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
function genDirectives (el: ASTElement): string | void {
|
||||
function genDirectives (el: ASTElement, state: CodegenState): string | void {
|
||||
const dirs = el.directives
|
||||
if (!dirs) return
|
||||
let res = 'directives:['
|
||||
@@ -252,11 +284,11 @@ function genDirectives (el: ASTElement): string | void {
|
||||
for (i = 0, l = dirs.length; i < l; i++) {
|
||||
dir = dirs[i]
|
||||
needRuntime = true
|
||||
const gen: DirectiveFunction = platformDirectives[dir.name] || baseDirectives[dir.name]
|
||||
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, warn)
|
||||
needRuntime = !!gen(el, dir, state.warn)
|
||||
}
|
||||
if (needRuntime) {
|
||||
hasRuntime = true
|
||||
@@ -274,15 +306,15 @@ function genDirectives (el: ASTElement): string | void {
|
||||
}
|
||||
}
|
||||
|
||||
function genInlineTemplate (el: ASTElement): ?string {
|
||||
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
|
||||
)) {
|
||||
warn('Inline-template components must have exactly one child element.')
|
||||
state.warn('Inline-template components must have exactly one child element.')
|
||||
}
|
||||
if (ast.type === 1) {
|
||||
const inlineRenderFns = generate(ast, currentOptions)
|
||||
const inlineRenderFns = generate(ast, state.options)
|
||||
return `inlineTemplate:{render:function(){${
|
||||
inlineRenderFns.render
|
||||
}},staticRenderFns:[${
|
||||
@@ -291,24 +323,37 @@ function genInlineTemplate (el: ASTElement): ?string {
|
||||
}
|
||||
}
|
||||
|
||||
function genScopedSlots (slots: { [key: string]: ASTElement }): string {
|
||||
function genScopedSlots (
|
||||
slots: { [key: string]: ASTElement },
|
||||
state: CodegenState
|
||||
): string {
|
||||
return `scopedSlots:_u([${
|
||||
Object.keys(slots).map(key => genScopedSlot(key, slots[key])).join(',')
|
||||
Object.keys(slots).map(key => {
|
||||
return genScopedSlot(key, slots[key], state)
|
||||
}).join(',')
|
||||
}])`
|
||||
}
|
||||
|
||||
function genScopedSlot (key: string, el: ASTElement) {
|
||||
function genScopedSlot (
|
||||
key: string,
|
||||
el: ASTElement,
|
||||
state: CodegenState
|
||||
): string {
|
||||
if (el.for && !el.forProcessed) {
|
||||
return genForScopedSlot(key, el)
|
||||
return genForScopedSlot(key, el, state)
|
||||
}
|
||||
return `{key:${key},fn:function(${String(el.attrsMap.scope)}){` +
|
||||
`return ${el.tag === 'template'
|
||||
? genChildren(el) || 'void 0'
|
||||
: genElement(el)
|
||||
? genChildren(el, state) || 'void 0'
|
||||
: genElement(el, state)
|
||||
}}}`
|
||||
}
|
||||
|
||||
function genForScopedSlot (key: string, el: any) {
|
||||
function genForScopedSlot (
|
||||
key: string,
|
||||
el: any,
|
||||
state: CodegenState
|
||||
): string {
|
||||
const exp = el.for
|
||||
const alias = el.alias
|
||||
const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
|
||||
@@ -316,11 +361,17 @@ function genForScopedSlot (key: string, el: any) {
|
||||
el.forProcessed = true // avoid recursion
|
||||
return `_l((${exp}),` +
|
||||
`function(${alias}${iterator1}${iterator2}){` +
|
||||
`return ${genScopedSlot(key, el)}` +
|
||||
`return ${genScopedSlot(key, el, state)}` +
|
||||
'})'
|
||||
}
|
||||
|
||||
function genChildren (el: ASTElement, checkSkip?: boolean): string | void {
|
||||
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]
|
||||
@@ -330,10 +381,13 @@ function genChildren (el: ASTElement, checkSkip?: boolean): string | void {
|
||||
el.tag !== 'template' &&
|
||||
el.tag !== 'slot'
|
||||
) {
|
||||
return genElement(el)
|
||||
return (altGenElement || genElement)(el, state)
|
||||
}
|
||||
const normalizationType = checkSkip ? getNormalizationType(children) : 0
|
||||
return `[${children.map(genNode).join(',')}]${
|
||||
const normalizationType = checkSkip
|
||||
? getNormalizationType(children, state.maybeComponent)
|
||||
: 0
|
||||
const gen = altGenNode || genNode
|
||||
return `[${children.map(c => gen(c, state)).join(',')}]${
|
||||
normalizationType ? `,${normalizationType}` : ''
|
||||
}`
|
||||
}
|
||||
@@ -343,7 +397,10 @@ function genChildren (el: ASTElement, checkSkip?: boolean): string | void {
|
||||
// 0: no normalization needed
|
||||
// 1: simple normalization needed (possible 1-level deep nested array)
|
||||
// 2: full normalization needed
|
||||
function getNormalizationType (children: Array<ASTNode>): number {
|
||||
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]
|
||||
@@ -367,28 +424,30 @@ function needsNormalization (el: ASTElement): boolean {
|
||||
return el.for !== undefined || el.tag === 'template' || el.tag === 'slot'
|
||||
}
|
||||
|
||||
function maybeComponent (el: ASTElement): boolean {
|
||||
return !isPlatformReservedTag(el.tag)
|
||||
}
|
||||
|
||||
function genNode (node: ASTNode): string {
|
||||
function genNode (node: ASTNode, state: CodegenState): string {
|
||||
if (node.type === 1) {
|
||||
return genElement(node)
|
||||
return genElement(node, state)
|
||||
} if (node.type === 3 && node.isComment) {
|
||||
return genComment(node)
|
||||
} else {
|
||||
return genText(node)
|
||||
}
|
||||
}
|
||||
|
||||
function genText (text: ASTText | ASTExpression): string {
|
||||
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))
|
||||
})`
|
||||
}
|
||||
|
||||
function genSlot (el: ASTElement): string {
|
||||
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)
|
||||
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']
|
||||
@@ -405,9 +464,13 @@ function genSlot (el: ASTElement): string {
|
||||
}
|
||||
|
||||
// componentName is el.component, take it as argument to shun flow's pessimistic refinement
|
||||
function genComponent (componentName: string, el: ASTElement): string {
|
||||
const children = el.inlineTemplate ? null : genChildren(el, true)
|
||||
return `_c(${componentName},${genData(el)}${
|
||||
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}` : ''
|
||||
})`
|
||||
}
|
||||
|
||||
+4
-2
@@ -2,8 +2,10 @@
|
||||
|
||||
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' : ''
|
||||
return `_b(${code},'${el.tag}',${dir.value},${
|
||||
dir.modifiers && dir.modifiers.prop ? 'true' : 'false'
|
||||
}${
|
||||
dir.modifiers && dir.modifiers.sync ? ',true' : ''
|
||||
})`
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -1,9 +1,11 @@
|
||||
/* @flow */
|
||||
|
||||
import on from './on'
|
||||
import bind from './bind'
|
||||
import { noop } from 'shared/util'
|
||||
|
||||
export default {
|
||||
on,
|
||||
bind,
|
||||
cloak: noop
|
||||
}
|
||||
|
||||
+1
-4
@@ -41,10 +41,7 @@ export function genAssignmentCode (
|
||||
if (modelRs.idx === null) {
|
||||
return `${value}=${assignment}`
|
||||
} else {
|
||||
return `var $$exp = ${modelRs.exp}, $$idx = ${modelRs.idx};` +
|
||||
`if (!Array.isArray($$exp)){` +
|
||||
`${value}=${assignment}}` +
|
||||
`else{$$exp.splice($$idx, 1, ${assignment})}`
|
||||
return `$set(${modelRs.exp}, ${modelRs.idx}, ${assignment})`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-142
@@ -3,11 +3,12 @@
|
||||
import { parse } from './parser/index'
|
||||
import { optimize } from './optimizer'
|
||||
import { generate } from './codegen/index'
|
||||
import { detectErrors } from './error-detector'
|
||||
import { extend, noop } from 'shared/util'
|
||||
import { warn, tip } from 'core/util/debug'
|
||||
import { createCompilerCreator } from './create-compiler'
|
||||
|
||||
function baseCompile (
|
||||
// `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 {
|
||||
@@ -19,141 +20,4 @@ function baseCompile (
|
||||
render: code.render,
|
||||
staticRenderFns: code.staticRenderFns
|
||||
}
|
||||
}
|
||||
|
||||
function makeFunction (code, errors) {
|
||||
try {
|
||||
return new Function(code)
|
||||
} catch (err) {
|
||||
errors.push({ err, code })
|
||||
return noop
|
||||
}
|
||||
}
|
||||
|
||||
export function createCompiler (baseOptions: CompilerOptions) {
|
||||
const functionCompileCache: {
|
||||
[key: string]: CompiledFunctionResult;
|
||||
} = Object.create(null)
|
||||
|
||||
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),
|
||||
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
|
||||
}
|
||||
|
||||
function compileToFunctions (
|
||||
template: string,
|
||||
options?: CompilerOptions,
|
||||
vm?: Component
|
||||
): CompiledFunctionResult {
|
||||
options = options || {}
|
||||
|
||||
/* 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 (functionCompileCache[key]) {
|
||||
return functionCompileCache[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 = makeFunction(compiled.render, fnGenErrors)
|
||||
const l = compiled.staticRenderFns.length
|
||||
res.staticRenderFns = new Array(l)
|
||||
for (let i = 0; i < l; i++) {
|
||||
res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i], 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 (functionCompileCache[key] = res)
|
||||
}
|
||||
|
||||
return {
|
||||
compile,
|
||||
compileToFunctions
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
+12
-7
@@ -55,6 +55,15 @@ function markStatic (node: ASTNode) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,17 +90,13 @@ function markStaticRoots (node: ASTNode, isInFor: boolean) {
|
||||
}
|
||||
}
|
||||
if (node.ifConditions) {
|
||||
walkThroughConditionsBlocks(node.ifConditions, isInFor)
|
||||
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
|
||||
markStaticRoots(node.ifConditions[i].block, isInFor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function walkThroughConditionsBlocks (conditionBlocks: ASTIfConditions, isInFor: boolean): void {
|
||||
for (let i = 1, len = conditionBlocks.length; i < len; i++) {
|
||||
markStaticRoots(conditionBlocks[i].block, isInFor)
|
||||
}
|
||||
}
|
||||
|
||||
function isStatic (node: ASTNode): boolean {
|
||||
if (node.type === 2) { // expression
|
||||
return false
|
||||
|
||||
+6
-4
@@ -2,8 +2,10 @@
|
||||
|
||||
let decoder
|
||||
|
||||
export function decode (html: string): string {
|
||||
decoder = decoder || document.createElement('div')
|
||||
decoder.innerHTML = html
|
||||
return decoder.textContent
|
||||
export default {
|
||||
decode (html: string): string {
|
||||
decoder = decoder || document.createElement('div')
|
||||
decoder.innerHTML = html
|
||||
return decoder.textContent
|
||||
}
|
||||
}
|
||||
|
||||
+22
-24
@@ -13,29 +13,14 @@ import { makeMap, no } from 'shared/util'
|
||||
import { isNonPhrasingTag } from 'web/compiler/util'
|
||||
|
||||
// Regular Expressions for parsing tags and attributes
|
||||
const singleAttrIdentifier = /([^\s"'<>/=]+)/
|
||||
const singleAttrAssign = /(?:=)/
|
||||
const singleAttrValues = [
|
||||
// attr value double quotes
|
||||
/"([^"]*)"+/.source,
|
||||
// attr value, single quotes
|
||||
/'([^']*)'+/.source,
|
||||
// attr value, no quotes
|
||||
/([^\s"'=<>`]+)/.source
|
||||
]
|
||||
const attribute = new RegExp(
|
||||
'^\\s*' + singleAttrIdentifier.source +
|
||||
'(?:\\s*(' + singleAttrAssign.source + ')' +
|
||||
'\\s*(?:' + singleAttrValues.join('|') + '))?'
|
||||
)
|
||||
|
||||
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 qnameCapture = `((?:${ncname}\\:)?${ncname})`
|
||||
const startTagOpen = new RegExp(`^<${qnameCapture}`)
|
||||
const startTagClose = /^\s*(\/?)>/
|
||||
const endTag = new RegExp('^<\\/' + qnameCapture + '[^>]*>')
|
||||
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`)
|
||||
const doctype = /^<!DOCTYPE [^>]+>/i
|
||||
const comment = /^<!--/
|
||||
const conditionalComment = /^<!\[/
|
||||
@@ -59,6 +44,10 @@ const decodingMap = {
|
||||
const encodedAttr = /&(?:lt|gt|quot|amp);/g
|
||||
const encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#10);/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])
|
||||
@@ -82,6 +71,9 @@ export function parseHTML (html, options) {
|
||||
const commentEnd = html.indexOf('-->')
|
||||
|
||||
if (commentEnd >= 0) {
|
||||
if (options.shouldKeepComment) {
|
||||
options.comment(html.substring(4, commentEnd))
|
||||
}
|
||||
advance(commentEnd + 3)
|
||||
continue
|
||||
}
|
||||
@@ -117,6 +109,9 @@ export function parseHTML (html, options) {
|
||||
const startTagMatch = parseStartTag()
|
||||
if (startTagMatch) {
|
||||
handleStartTag(startTagMatch)
|
||||
if (shouldIgnoreFirstNewline(lastTag, html)) {
|
||||
advance(1)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -149,16 +144,19 @@ export function parseHTML (html, options) {
|
||||
options.chars(text)
|
||||
}
|
||||
} else {
|
||||
var stackedTag = lastTag.toLowerCase()
|
||||
var reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i'))
|
||||
var endTagLength = 0
|
||||
var rest = html.replace(reStackedTag, function (all, text, endTag) {
|
||||
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')
|
||||
.replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1')
|
||||
}
|
||||
if (shouldIgnoreFirstNewline(stackedTag, text)) {
|
||||
text = text.slice(1)
|
||||
}
|
||||
if (options.chars) {
|
||||
options.chars(text)
|
||||
}
|
||||
@@ -222,7 +220,7 @@ export function parseHTML (html, options) {
|
||||
}
|
||||
}
|
||||
|
||||
const unary = isUnaryTag(tagName) || tagName === 'html' && lastTag === 'head' || !!unarySlash
|
||||
const unary = isUnaryTag(tagName) || !!unarySlash
|
||||
|
||||
const l = match.attrs.length
|
||||
const attrs = new Array(l)
|
||||
|
||||
+21
-6
@@ -1,6 +1,6 @@
|
||||
/* @flow */
|
||||
|
||||
import { decode } from 'he'
|
||||
import he from 'he'
|
||||
import { parseHTML } from './html-parser'
|
||||
import { parseText } from './text-parser'
|
||||
import { parseFilters } from './filter-parser'
|
||||
@@ -28,7 +28,7 @@ const argRE = /:(.*)$/
|
||||
const bindRE = /^:|^v-bind:/
|
||||
const modifierRE = /\.[^.]+/g
|
||||
|
||||
const decodeHTMLCached = cached(decode)
|
||||
const decodeHTMLCached = cached(he.decode)
|
||||
|
||||
// configurable state
|
||||
export let warn
|
||||
@@ -48,12 +48,15 @@ export function parse (
|
||||
options: CompilerOptions
|
||||
): ASTElement | void {
|
||||
warn = options.warn || baseWarn
|
||||
platformGetTagNamespace = options.getTagNamespace || no
|
||||
platformMustUseProp = options.mustUseProp || no
|
||||
|
||||
platformIsPreTag = options.isPreTag || no
|
||||
preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
|
||||
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 = []
|
||||
@@ -87,6 +90,7 @@ export function parse (
|
||||
isUnaryTag: options.isUnaryTag,
|
||||
canBeLeftOpenTag: options.canBeLeftOpenTag,
|
||||
shouldDecodeNewlines: options.shouldDecodeNewlines,
|
||||
shouldKeepComment: options.comments,
|
||||
start (tag, attrs, unary) {
|
||||
// check namespace.
|
||||
// inherit parent ns if there is one
|
||||
@@ -271,6 +275,13 @@ export function parse (
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
comment (text: string) {
|
||||
currentParent.children.push({
|
||||
type: 3,
|
||||
text,
|
||||
isComment: true
|
||||
})
|
||||
}
|
||||
})
|
||||
return root
|
||||
@@ -420,6 +431,8 @@ function processSlot (el) {
|
||||
const slotTarget = getBindingAttr(el, 'slot')
|
||||
if (slotTarget) {
|
||||
el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget
|
||||
// preserve slot as an attribute for native shadow DOM compat
|
||||
addAttr(el, 'slot', slotTarget)
|
||||
}
|
||||
if (el.tag === 'template') {
|
||||
el.slotScope = getAndRemoveAttr(el, 'scope')
|
||||
@@ -472,7 +485,9 @@ function processAttrs (el) {
|
||||
)
|
||||
}
|
||||
}
|
||||
if (isProp || platformMustUseProp(el.tag, el.attrsMap.type, name)) {
|
||||
if (isProp || (
|
||||
!el.component && platformMustUseProp(el.tag, el.attrsMap.type, name)
|
||||
)) {
|
||||
addProp(el, name, value)
|
||||
} else {
|
||||
addAttr(el, name, value)
|
||||
|
||||
+7
-5
@@ -5,14 +5,16 @@ import { getFirstComponentChild } from 'core/vdom/helpers/index'
|
||||
|
||||
type VNodeCache = { [key: string]: ?VNode };
|
||||
|
||||
const patternTypes: Array<Function> = [String, RegExp]
|
||||
const patternTypes: Array<Function> = [String, RegExp, Array]
|
||||
|
||||
function getComponentName (opts: ?VNodeComponentOptions): ?string {
|
||||
return opts && (opts.Ctor.options.name || opts.tag)
|
||||
}
|
||||
|
||||
function matches (pattern: string | RegExp, name: string): boolean {
|
||||
if (typeof pattern === 'string') {
|
||||
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)
|
||||
@@ -62,10 +64,10 @@ export default {
|
||||
},
|
||||
|
||||
watch: {
|
||||
include (val: string | RegExp) {
|
||||
include (val: string | RegExp | Array<string>) {
|
||||
pruneCache(this.cache, this._vnode, name => matches(val, name))
|
||||
},
|
||||
exclude (val: string | RegExp) {
|
||||
exclude (val: string | RegExp | Array<string>) {
|
||||
pruneCache(this.cache, this._vnode, name => !matches(val, name))
|
||||
}
|
||||
},
|
||||
|
||||
+6
@@ -16,6 +16,7 @@ export type Config = {
|
||||
performance: boolean;
|
||||
devtools: boolean;
|
||||
errorHandler: ?(err: Error, vm: Component, info: string) => void;
|
||||
warnHandler: ?(msg: string, vm: Component, trace: string) => void;
|
||||
ignoredElements: Array<string>;
|
||||
keyCodes: { [key: string]: number | Array<number> };
|
||||
|
||||
@@ -62,6 +63,11 @@ export default ({
|
||||
*/
|
||||
errorHandler: null,
|
||||
|
||||
/**
|
||||
* Warn handler for watcher warns
|
||||
*/
|
||||
warnHandler: null,
|
||||
|
||||
/**
|
||||
* Ignore certain custom elements
|
||||
*/
|
||||
|
||||
+4
-3
@@ -4,10 +4,11 @@ import { toArray } from '../util/index'
|
||||
|
||||
export function initUse (Vue: GlobalAPI) {
|
||||
Vue.use = function (plugin: Function | Object) {
|
||||
/* istanbul ignore if */
|
||||
if (plugin.installed) {
|
||||
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
|
||||
if (installedPlugins.indexOf(plugin) > -1) {
|
||||
return this
|
||||
}
|
||||
|
||||
// additional parameters
|
||||
const args = toArray(arguments, 1)
|
||||
args.unshift(this)
|
||||
@@ -16,7 +17,7 @@ export function initUse (Vue: GlobalAPI) {
|
||||
} else if (typeof plugin === 'function') {
|
||||
plugin.apply(null, args)
|
||||
}
|
||||
plugin.installed = true
|
||||
installedPlugins.push(plugin)
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ Object.defineProperty(Vue.prototype, '$isServer', {
|
||||
Object.defineProperty(Vue.prototype, '$ssrContext', {
|
||||
get () {
|
||||
/* istanbul ignore next */
|
||||
return this.$vnode.ssrContext
|
||||
return this.$vnode && this.$vnode.ssrContext
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
+22
-10
@@ -1,7 +1,13 @@
|
||||
/* @flow */
|
||||
|
||||
import {
|
||||
tip,
|
||||
toArray,
|
||||
hyphenate,
|
||||
handleError,
|
||||
formatComponentName
|
||||
} from '../util/index'
|
||||
import { updateListeners } from '../vdom/helpers/index'
|
||||
import { toArray, tip, hyphenate, formatComponentName } from '../util/index'
|
||||
|
||||
export function initEvents (vm: Component) {
|
||||
vm._events = Object.create(null)
|
||||
@@ -89,14 +95,16 @@ export function eventsMixin (Vue: Class<Component>) {
|
||||
vm._events[event] = null
|
||||
return vm
|
||||
}
|
||||
// specific handler
|
||||
let cb
|
||||
let i = cbs.length
|
||||
while (i--) {
|
||||
cb = cbs[i]
|
||||
if (cb === fn || cb.fn === fn) {
|
||||
cbs.splice(i, 1)
|
||||
break
|
||||
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
|
||||
@@ -121,7 +129,11 @@ export function eventsMixin (Vue: Class<Component>) {
|
||||
cbs = cbs.length > 1 ? toArray(cbs) : cbs
|
||||
const args = toArray(arguments, 1)
|
||||
for (let i = 0, l = cbs.length; i < l; i++) {
|
||||
cbs[i].apply(vm, args)
|
||||
try {
|
||||
cbs[i].apply(vm, args)
|
||||
} catch (e) {
|
||||
handleError(e, vm, `event handler for "${event}"`)
|
||||
}
|
||||
}
|
||||
}
|
||||
return vm
|
||||
|
||||
+13
-9
@@ -1,8 +1,8 @@
|
||||
/* @flow */
|
||||
|
||||
import { hasSymbol } from 'core/util/env'
|
||||
import { warn } from '../util/index'
|
||||
import { defineReactive } from '../observer/index'
|
||||
import { hasSymbol } from 'core/util/env'
|
||||
import { defineReactive, observerState } from '../observer/index'
|
||||
|
||||
export function initProvide (vm: Component) {
|
||||
const provide = vm.$options.provide
|
||||
@@ -16,6 +16,7 @@ export function initProvide (vm: Component) {
|
||||
export function initInjections (vm: Component) {
|
||||
const result = resolveInject(vm.$options.inject, vm)
|
||||
if (result) {
|
||||
observerState.shouldConvert = false
|
||||
Object.keys(result).forEach(key => {
|
||||
/* istanbul ignore else */
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
@@ -31,24 +32,24 @@ export function initInjections (vm: Component) {
|
||||
defineReactive(vm, key, result[key])
|
||||
}
|
||||
})
|
||||
observerState.shouldConvert = true
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveInject (inject: any, vm: Component): ?Object {
|
||||
if (inject) {
|
||||
// inject is :any because flow is not smart enough to figure out cached
|
||||
// isArray here
|
||||
const isArray = Array.isArray(inject)
|
||||
const result = Object.create(null)
|
||||
const keys = isArray
|
||||
? inject
|
||||
: hasSymbol
|
||||
? Reflect.ownKeys(inject)
|
||||
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 = isArray ? key : inject[key]
|
||||
const provideKey = inject[key]
|
||||
let source = vm
|
||||
while (source) {
|
||||
if (source._provided && provideKey in source._provided) {
|
||||
@@ -57,6 +58,9 @@ export function resolveInject (inject: any, vm: Component): ?Object {
|
||||
}
|
||||
source = source.$parent
|
||||
}
|
||||
if (process.env.NODE_ENV !== 'production' && !source) {
|
||||
warn(`Injection "${key}" not found`, vm)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
+20
-8
@@ -18,6 +18,7 @@ import {
|
||||
} from '../util/index'
|
||||
|
||||
export let activeInstance: any = null
|
||||
export let isUpdatingChildComponent: boolean = false
|
||||
|
||||
export function initLifecycle (vm: Component) {
|
||||
const options = vm.$options
|
||||
@@ -65,6 +66,9 @@ export function lifecycleMixin (Vue: Class<Component>) {
|
||||
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)
|
||||
@@ -129,8 +133,6 @@ export function lifecycleMixin (Vue: Class<Component>) {
|
||||
if (vm.$el) {
|
||||
vm.$el.__vue__ = null
|
||||
}
|
||||
// remove reference to DOM nodes (prevents leak)
|
||||
vm.$options._parentElm = vm.$options._refElm = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,6 +208,10 @@ export function updateChildComponent (
|
||||
parentVnode: VNode,
|
||||
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 = !!(
|
||||
@@ -217,17 +223,21 @@ export function updateChildComponent (
|
||||
|
||||
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 && parentVnode.data.attrs) || emptyObject
|
||||
vm.$listeners = listeners || emptyObject
|
||||
|
||||
// update props
|
||||
if (propsData && vm.$options.props) {
|
||||
observerState.shouldConvert = false
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
observerState.isSettingProps = true
|
||||
}
|
||||
const props = vm._props
|
||||
const propKeys = vm.$options._propKeys || []
|
||||
for (let i = 0; i < propKeys.length; i++) {
|
||||
@@ -235,12 +245,10 @@ export function updateChildComponent (
|
||||
props[key] = validateProp(key, vm.$options.props, propsData, vm)
|
||||
}
|
||||
observerState.shouldConvert = true
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
observerState.isSettingProps = false
|
||||
}
|
||||
// keep a copy of raw propsData
|
||||
vm.$options.propsData = propsData
|
||||
}
|
||||
|
||||
// update listeners
|
||||
if (listeners) {
|
||||
const oldListeners = vm.$options._parentListeners
|
||||
@@ -252,6 +260,10 @@ export function updateChildComponent (
|
||||
vm.$slots = resolveSlots(renderChildren, parentVnode.context)
|
||||
vm.$forceUpdate()
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
isUpdatingChildComponent = false
|
||||
}
|
||||
}
|
||||
|
||||
function isInInactiveTree (vm) {
|
||||
|
||||
+21
-3
@@ -1,7 +1,13 @@
|
||||
/* @flow */
|
||||
|
||||
import config from 'core/config'
|
||||
import { isObject, warn, toObject } from 'core/util/index'
|
||||
|
||||
import {
|
||||
warn,
|
||||
isObject,
|
||||
toObject,
|
||||
isReservedAttribute
|
||||
} from 'core/util/index'
|
||||
|
||||
/**
|
||||
* Runtime helper for merging v-bind="object" into a VNode's data.
|
||||
@@ -10,7 +16,8 @@ export function bindObjectProps (
|
||||
data: any,
|
||||
tag: string,
|
||||
value: any,
|
||||
asProp?: boolean
|
||||
asProp: boolean,
|
||||
isSync?: boolean
|
||||
): VNodeData {
|
||||
if (value) {
|
||||
if (!isObject(value)) {
|
||||
@@ -24,7 +31,11 @@ export function bindObjectProps (
|
||||
}
|
||||
let hash
|
||||
for (const key in value) {
|
||||
if (key === 'class' || key === 'style') {
|
||||
if (
|
||||
key === 'class' ||
|
||||
key === 'style' ||
|
||||
isReservedAttribute(key)
|
||||
) {
|
||||
hash = data
|
||||
} else {
|
||||
const type = data.attrs && data.attrs.type
|
||||
@@ -34,6 +45,13 @@ export function bindObjectProps (
|
||||
}
|
||||
if (!(key in hash)) {
|
||||
hash[key] = value[key]
|
||||
|
||||
if (isSync) {
|
||||
const on = data.on || (data.on = {})
|
||||
on[`update:${key}`] = function ($event) {
|
||||
value[key] = $event
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -7,7 +7,11 @@ import { isObject, isDef } from 'core/util/index'
|
||||
*/
|
||||
export function renderList (
|
||||
val: any,
|
||||
render: () => VNode
|
||||
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') {
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ export function renderSlot (
|
||||
if (scopedSlotFn) { // scoped slot
|
||||
props = props || {}
|
||||
if (bindObject) {
|
||||
extend(props, bindObject)
|
||||
props = extend(extend({}, bindObject), props)
|
||||
}
|
||||
return scopedSlotFn(props) || fallback
|
||||
} else {
|
||||
|
||||
+6
-1
@@ -14,10 +14,15 @@ export function resolveSlots (
|
||||
const defaultSlot = []
|
||||
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.functionalContext === context) &&
|
||||
child.data && child.data.slot != null
|
||||
data && data.slot != null
|
||||
) {
|
||||
const name = child.data.slot
|
||||
const slot = (slots[name] || (slots[name] = []))
|
||||
|
||||
+29
-3
@@ -8,7 +8,8 @@ import {
|
||||
looseEqual,
|
||||
emptyObject,
|
||||
handleError,
|
||||
looseIndexOf
|
||||
looseIndexOf,
|
||||
defineReactive
|
||||
} from '../util/index'
|
||||
|
||||
import VNode, {
|
||||
@@ -17,6 +18,8 @@ import VNode, {
|
||||
createEmptyVNode
|
||||
} from '../vdom/vnode'
|
||||
|
||||
import { isUpdatingChildComponent } from './lifecycle'
|
||||
|
||||
import { createElement } from '../vdom/create-element'
|
||||
import { renderList } from './render-helpers/render-list'
|
||||
import { renderSlot } from './render-helpers/render-slot'
|
||||
@@ -24,6 +27,7 @@ import { resolveFilter } from './render-helpers/resolve-filter'
|
||||
import { checkKeyCodes } from './render-helpers/check-keycodes'
|
||||
import { bindObjectProps } from './render-helpers/bind-object-props'
|
||||
import { renderStatic, markOnce } from './render-helpers/render-static'
|
||||
import { bindObjectListeners } from './render-helpers/bind-object-listeners'
|
||||
import { resolveSlots, resolveScopedSlots } from './render-helpers/resolve-slots'
|
||||
|
||||
export function initRender (vm: Component) {
|
||||
@@ -41,6 +45,23 @@ export function initRender (vm: Component) {
|
||||
// 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', vm.$options._parentListeners || emptyObject, () => {
|
||||
!isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
|
||||
}, true)
|
||||
} else {
|
||||
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
|
||||
defineReactive(vm, '$listeners', vm.$options._parentListeners || emptyObject, null, true)
|
||||
}
|
||||
}
|
||||
|
||||
export function renderMixin (Vue: Class<Component>) {
|
||||
@@ -57,9 +78,13 @@ export function renderMixin (Vue: Class<Component>) {
|
||||
} = vm.$options
|
||||
|
||||
if (vm._isMounted) {
|
||||
// clone slot nodes on re-renders
|
||||
// if the parent didn't update, the slot nodes will be the ones from
|
||||
// last render. They need to be cloned to ensure "freshness" for this render.
|
||||
for (const key in vm.$slots) {
|
||||
vm.$slots[key] = cloneVNodes(vm.$slots[key])
|
||||
const slot = vm.$slots[key]
|
||||
if (slot._rendered) {
|
||||
vm.$slots[key] = cloneVNodes(slot, true /* deep */)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,4 +146,5 @@ export function renderMixin (Vue: Class<Component>) {
|
||||
Vue.prototype._v = createTextVNode
|
||||
Vue.prototype._e = createEmptyVNode
|
||||
Vue.prototype._u = resolveScopedSlots
|
||||
Vue.prototype._g = bindObjectListeners
|
||||
}
|
||||
|
||||
+92
-33
@@ -3,6 +3,7 @@
|
||||
import config from '../config'
|
||||
import Dep from '../observer/dep'
|
||||
import Watcher from '../observer/watcher'
|
||||
import { isUpdatingChildComponent } from './lifecycle'
|
||||
|
||||
import {
|
||||
set,
|
||||
@@ -19,8 +20,11 @@ import {
|
||||
hasOwn,
|
||||
isReserved,
|
||||
handleError,
|
||||
nativeWatch,
|
||||
validateProp,
|
||||
isPlainObject
|
||||
isPlainObject,
|
||||
isServerRendering,
|
||||
isReservedAttribute
|
||||
} from '../util/index'
|
||||
|
||||
const sharedPropertyDefinition = {
|
||||
@@ -51,13 +55,19 @@ export function initState (vm: Component) {
|
||||
observe(vm._data = {}, true /* asRootData */)
|
||||
}
|
||||
if (opts.computed) initComputed(vm, opts.computed)
|
||||
if (opts.watch) initWatch(vm, opts.watch)
|
||||
if (opts.watch && opts.watch !== nativeWatch) {
|
||||
initWatch(vm, opts.watch)
|
||||
}
|
||||
}
|
||||
|
||||
const isReservedProp = {
|
||||
key: 1,
|
||||
ref: 1,
|
||||
slot: 1
|
||||
function checkOptionType (vm: Component, name: string) {
|
||||
const option = vm.$options[name]
|
||||
if (!isPlainObject(option)) {
|
||||
warn(
|
||||
`component option "${name}" should be an object.`,
|
||||
vm
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function initProps (vm: Component, propsOptions: Object) {
|
||||
@@ -74,14 +84,14 @@ function initProps (vm: Component, propsOptions: Object) {
|
||||
const value = validateProp(key, propsOptions, propsData, vm)
|
||||
/* istanbul ignore else */
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (isReservedProp[key] || config.isReservedAttr(key)) {
|
||||
if (isReservedAttribute(key) || config.isReservedAttr(key)) {
|
||||
warn(
|
||||
`"${key}" is a reserved attribute and cannot be used as component prop.`,
|
||||
vm
|
||||
)
|
||||
}
|
||||
defineReactive(props, key, value, () => {
|
||||
if (vm.$parent && !observerState.isSettingProps) {
|
||||
if (vm.$parent && !isUpdatingChildComponent) {
|
||||
warn(
|
||||
`Avoid mutating a prop directly since the value will be ` +
|
||||
`overwritten whenever the parent component re-renders. ` +
|
||||
@@ -120,16 +130,26 @@ function initData (vm: Component) {
|
||||
// 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--) {
|
||||
if (props && hasOwn(props, keys[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 "${keys[i]}" is already declared as a prop. ` +
|
||||
`The data property "${key}" is already declared as a prop. ` +
|
||||
`Use prop default value instead.`,
|
||||
vm
|
||||
)
|
||||
} else if (!isReserved(keys[i])) {
|
||||
proxy(vm, `_data`, keys[i])
|
||||
} else if (!isReserved(key)) {
|
||||
proxy(vm, `_data`, key)
|
||||
}
|
||||
}
|
||||
// observe data
|
||||
@@ -148,22 +168,30 @@ function getData (data: Function, vm: Component): any {
|
||||
const computedWatcherOptions = { lazy: true }
|
||||
|
||||
function initComputed (vm: Component, computed: Object) {
|
||||
process.env.NODE_ENV !== 'production' && checkOptionType(vm, 'computed')
|
||||
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]
|
||||
let getter = typeof userDef === 'function' ? userDef : userDef.get
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (getter === undefined) {
|
||||
warn(
|
||||
`No getter function has been defined for computed property "${key}".`,
|
||||
vm
|
||||
)
|
||||
getter = noop
|
||||
}
|
||||
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
|
||||
)
|
||||
}
|
||||
// create internal watcher for the computed property.
|
||||
watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)
|
||||
|
||||
// component-defined computed properties are already defined on the
|
||||
// component prototype. We only need to define computed properties defined
|
||||
@@ -180,13 +208,20 @@ function initComputed (vm: Component, computed: Object) {
|
||||
}
|
||||
}
|
||||
|
||||
export function defineComputed (target: any, key: string, userDef: Object | Function) {
|
||||
export function defineComputed (
|
||||
target: any,
|
||||
key: string,
|
||||
userDef: Object | Function
|
||||
) {
|
||||
const shouldCache = !isServerRendering()
|
||||
if (typeof userDef === 'function') {
|
||||
sharedPropertyDefinition.get = createComputedGetter(key)
|
||||
sharedPropertyDefinition.get = shouldCache
|
||||
? createComputedGetter(key)
|
||||
: userDef
|
||||
sharedPropertyDefinition.set = noop
|
||||
} else {
|
||||
sharedPropertyDefinition.get = userDef.get
|
||||
? userDef.cache !== false
|
||||
? shouldCache && userDef.cache !== false
|
||||
? createComputedGetter(key)
|
||||
: userDef.get
|
||||
: noop
|
||||
@@ -194,6 +229,15 @@ export function defineComputed (target: any, key: string, userDef: Object | Func
|
||||
? 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)
|
||||
}
|
||||
|
||||
@@ -213,28 +257,36 @@ function createComputedGetter (key) {
|
||||
}
|
||||
|
||||
function initMethods (vm: Component, methods: Object) {
|
||||
process.env.NODE_ENV !== 'production' && checkOptionType(vm, 'methods')
|
||||
const props = vm.$options.props
|
||||
for (const key in methods) {
|
||||
vm[key] = methods[key] == null ? noop : bind(methods[key], vm)
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (methods[key] == null) {
|
||||
warn(
|
||||
`method "${key}" has an undefined value in the component definition. ` +
|
||||
`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.`,
|
||||
`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) {
|
||||
process.env.NODE_ENV !== 'production' && checkOptionType(vm, 'watch')
|
||||
for (const key in watch) {
|
||||
const handler = watch[key]
|
||||
if (Array.isArray(handler)) {
|
||||
@@ -247,8 +299,12 @@ function initWatch (vm: Component, watch: Object) {
|
||||
}
|
||||
}
|
||||
|
||||
function createWatcher (vm: Component, key: string, handler: any) {
|
||||
let options
|
||||
function createWatcher (
|
||||
vm: Component,
|
||||
keyOrFn: string | Function,
|
||||
handler: any,
|
||||
options?: Object
|
||||
) {
|
||||
if (isPlainObject(handler)) {
|
||||
options = handler
|
||||
handler = handler.handler
|
||||
@@ -256,7 +312,7 @@ function createWatcher (vm: Component, key: string, handler: any) {
|
||||
if (typeof handler === 'string') {
|
||||
handler = vm[handler]
|
||||
}
|
||||
vm.$watch(key, handler, options)
|
||||
return vm.$watch(keyOrFn, handler, options)
|
||||
}
|
||||
|
||||
export function stateMixin (Vue: Class<Component>) {
|
||||
@@ -287,10 +343,13 @@ export function stateMixin (Vue: Class<Component>) {
|
||||
|
||||
Vue.prototype.$watch = function (
|
||||
expOrFn: string | Function,
|
||||
cb: 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)
|
||||
|
||||
+1
-10
@@ -23,21 +23,12 @@ export const arrayMethods = Object.create(arrayProto)
|
||||
.forEach(function (method) {
|
||||
// cache original method
|
||||
const original = arrayProto[method]
|
||||
def(arrayMethods, method, function mutator () {
|
||||
// avoid leaking arguments:
|
||||
// http://jsperf.com/closure-with-arguments
|
||||
let i = arguments.length
|
||||
const args = new Array(i)
|
||||
while (i--) {
|
||||
args[i] = arguments[i]
|
||||
}
|
||||
def(arrayMethods, method, function mutator (...args) {
|
||||
const result = original.apply(this, args)
|
||||
const ob = this.__ob__
|
||||
let inserted
|
||||
switch (method) {
|
||||
case 'push':
|
||||
inserted = args
|
||||
break
|
||||
case 'unshift':
|
||||
inserted = args
|
||||
break
|
||||
|
||||
+17
-16
@@ -4,11 +4,12 @@ import Dep from './dep'
|
||||
import { arrayMethods } from './array'
|
||||
import {
|
||||
def,
|
||||
warn,
|
||||
hasOwn,
|
||||
hasProto,
|
||||
isObject,
|
||||
isPlainObject,
|
||||
hasProto,
|
||||
hasOwn,
|
||||
warn,
|
||||
isValidArrayIndex,
|
||||
isServerRendering
|
||||
} from '../util/index'
|
||||
|
||||
@@ -21,8 +22,7 @@ const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
|
||||
* under a frozen data structure. Converting it would defeat the optimization.
|
||||
*/
|
||||
export const observerState = {
|
||||
shouldConvert: true,
|
||||
isSettingProps: false
|
||||
shouldConvert: true
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,7 +80,7 @@ export class Observer {
|
||||
* Augment an target Object or Array by intercepting
|
||||
* the prototype chain using __proto__
|
||||
*/
|
||||
function protoAugment (target, src: Object) {
|
||||
function protoAugment (target, src: Object, keys: any) {
|
||||
/* eslint-disable no-proto */
|
||||
target.__proto__ = src
|
||||
/* eslint-enable no-proto */
|
||||
@@ -132,7 +132,8 @@ export function defineReactive (
|
||||
obj: Object,
|
||||
key: string,
|
||||
val: any,
|
||||
customSetter?: Function
|
||||
customSetter?: ?Function,
|
||||
shallow?: boolean
|
||||
) {
|
||||
const dep = new Dep()
|
||||
|
||||
@@ -145,7 +146,7 @@ export function defineReactive (
|
||||
const getter = property && property.get
|
||||
const setter = property && property.set
|
||||
|
||||
let childOb = observe(val)
|
||||
let childOb = !shallow && observe(val)
|
||||
Object.defineProperty(obj, key, {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
@@ -155,9 +156,9 @@ export function defineReactive (
|
||||
dep.depend()
|
||||
if (childOb) {
|
||||
childOb.dep.depend()
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
dependArray(value)
|
||||
if (Array.isArray(value)) {
|
||||
dependArray(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
return value
|
||||
@@ -177,7 +178,7 @@ export function defineReactive (
|
||||
} else {
|
||||
val = newVal
|
||||
}
|
||||
childOb = observe(newVal)
|
||||
childOb = !shallow && observe(newVal)
|
||||
dep.notify()
|
||||
}
|
||||
})
|
||||
@@ -189,7 +190,7 @@ export function defineReactive (
|
||||
* already exist.
|
||||
*/
|
||||
export function set (target: Array<any> | Object, key: any, val: any): any {
|
||||
if (Array.isArray(target) && typeof key === 'number') {
|
||||
if (Array.isArray(target) && isValidArrayIndex(key)) {
|
||||
target.length = Math.max(target.length, key)
|
||||
target.splice(key, 1, val)
|
||||
return val
|
||||
@@ -198,7 +199,7 @@ export function set (target: Array<any> | Object, key: any, val: any): any {
|
||||
target[key] = val
|
||||
return val
|
||||
}
|
||||
const ob = (target : any).__ob__
|
||||
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 ' +
|
||||
@@ -219,11 +220,11 @@ export function set (target: Array<any> | Object, key: any, val: any): any {
|
||||
* Delete a property and trigger change if necessary.
|
||||
*/
|
||||
export function del (target: Array<any> | Object, key: any) {
|
||||
if (Array.isArray(target) && typeof key === 'number') {
|
||||
if (Array.isArray(target) && isValidArrayIndex(key)) {
|
||||
target.splice(key, 1)
|
||||
return
|
||||
}
|
||||
const ob = (target : any).__ob__
|
||||
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 ' +
|
||||
|
||||
+2
-2
@@ -81,7 +81,7 @@ function flushSchedulerQueue () {
|
||||
|
||||
// call component updated and activated hooks
|
||||
callActivatedHooks(activatedQueue)
|
||||
callUpdateHooks(updatedQueue)
|
||||
callUpdatedHooks(updatedQueue)
|
||||
|
||||
// devtool hook
|
||||
/* istanbul ignore if */
|
||||
@@ -90,7 +90,7 @@ function flushSchedulerQueue () {
|
||||
}
|
||||
}
|
||||
|
||||
function callUpdateHooks (queue) {
|
||||
function callUpdatedHooks (queue) {
|
||||
let i = queue.length
|
||||
while (i--) {
|
||||
const watcher = queue[i]
|
||||
|
||||
+15
-14
@@ -94,22 +94,23 @@ export default class Watcher {
|
||||
pushTarget(this)
|
||||
let value
|
||||
const vm = this.vm
|
||||
if (this.user) {
|
||||
try {
|
||||
value = this.getter.call(vm, vm)
|
||||
} catch (e) {
|
||||
handleError(e, vm, `getter for watcher "${this.expression}"`)
|
||||
}
|
||||
} else {
|
||||
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()
|
||||
}
|
||||
// "touch" every property so they are all tracked as
|
||||
// dependencies for deep watching
|
||||
if (this.deep) {
|
||||
traverse(value)
|
||||
}
|
||||
popTarget()
|
||||
this.cleanupDeps()
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
+6
-4
@@ -15,10 +15,12 @@ if (process.env.NODE_ENV !== 'production') {
|
||||
.replace(/[-_]/g, '')
|
||||
|
||||
warn = (msg, vm) => {
|
||||
if (hasConsole && (!config.silent)) {
|
||||
console.error(`[Vue warn]: ${msg}` + (
|
||||
vm ? generateComponentTrace(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}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-3
@@ -17,6 +17,9 @@ export const isAndroid = UA && UA.indexOf('android') > 0
|
||||
export const isIOS = UA && /iphone|ipad|ipod|ios/.test(UA)
|
||||
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 {
|
||||
@@ -26,7 +29,7 @@ if (inBrowser) {
|
||||
/* istanbul ignore next */
|
||||
supportsPassive = true
|
||||
}
|
||||
} : Object)) // https://github.com/facebook/flow/issues/285
|
||||
}: Object)) // https://github.com/facebook/flow/issues/285
|
||||
window.addEventListener('test-passive', null, opts)
|
||||
} catch (e) {}
|
||||
}
|
||||
@@ -96,13 +99,13 @@ export const nextTick = (function () {
|
||||
// "force" the microtask queue to be flushed by adding an empty timer.
|
||||
if (isIOS) setTimeout(noop)
|
||||
}
|
||||
} else if (typeof MutationObserver !== 'undefined' && (
|
||||
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
|
||||
isNative(MutationObserver) ||
|
||||
// PhantomJS and iOS 7.x
|
||||
MutationObserver.toString() === '[object MutationObserverConstructor]'
|
||||
)) {
|
||||
// use MutationObserver where native Promise is not available,
|
||||
// e.g. PhantomJS IE11, iOS7, Android 4.4
|
||||
// e.g. PhantomJS, iOS7, Android 4.4
|
||||
var counter = 1
|
||||
var observer = new MutationObserver(nextTickHandler)
|
||||
var textNode = document.createTextNode(String(counter))
|
||||
|
||||
+48
-16
@@ -2,6 +2,7 @@
|
||||
|
||||
import config from '../config'
|
||||
import { warn } from './debug'
|
||||
import { nativeWatch } from './env'
|
||||
import { set } from '../observer/index'
|
||||
|
||||
import {
|
||||
@@ -63,7 +64,7 @@ function mergeData (to: Object, from: ?Object): Object {
|
||||
/**
|
||||
* Data
|
||||
*/
|
||||
strats.data = function (
|
||||
export function mergeDataOrFn (
|
||||
parentVal: any,
|
||||
childVal: any,
|
||||
vm?: Component
|
||||
@@ -73,15 +74,6 @@ strats.data = function (
|
||||
if (!childVal) {
|
||||
return parentVal
|
||||
}
|
||||
if (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
|
||||
}
|
||||
if (!parentVal) {
|
||||
return childVal
|
||||
}
|
||||
@@ -92,8 +84,8 @@ strats.data = function (
|
||||
// it has to be a function to pass previous merges.
|
||||
return function mergedDataFn () {
|
||||
return mergeData(
|
||||
childVal.call(this),
|
||||
parentVal.call(this)
|
||||
typeof childVal === 'function' ? childVal.call(this) : childVal,
|
||||
typeof parentVal === 'function' ? parentVal.call(this) : parentVal
|
||||
)
|
||||
}
|
||||
} else if (parentVal || childVal) {
|
||||
@@ -104,7 +96,7 @@ strats.data = function (
|
||||
: childVal
|
||||
const defaultData = typeof parentVal === 'function'
|
||||
? parentVal.call(vm)
|
||||
: undefined
|
||||
: parentVal
|
||||
if (instanceData) {
|
||||
return mergeData(instanceData, defaultData)
|
||||
} else {
|
||||
@@ -114,6 +106,28 @@ strats.data = function (
|
||||
}
|
||||
}
|
||||
|
||||
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.call(this, parentVal, childVal)
|
||||
}
|
||||
|
||||
return mergeDataOrFn(parentVal, childVal, vm)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks and props are merged as arrays.
|
||||
*/
|
||||
@@ -159,6 +173,9 @@ ASSET_TYPES.forEach(function (type) {
|
||||
* another, so we merge them as arrays.
|
||||
*/
|
||||
strats.watch = function (parentVal: ?Object, childVal: ?Object): ?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 (!parentVal) return childVal
|
||||
@@ -172,7 +189,7 @@ strats.watch = function (parentVal: ?Object, childVal: ?Object): ?Object {
|
||||
}
|
||||
ret[key] = parent
|
||||
? parent.concat(child)
|
||||
: [child]
|
||||
: Array.isArray(child) ? child : [child]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
@@ -182,14 +199,15 @@ strats.watch = function (parentVal: ?Object, childVal: ?Object): ?Object {
|
||||
*/
|
||||
strats.props =
|
||||
strats.methods =
|
||||
strats.inject =
|
||||
strats.computed = function (parentVal: ?Object, childVal: ?Object): ?Object {
|
||||
if (!childVal) return Object.create(parentVal || null)
|
||||
if (!parentVal) return childVal
|
||||
const ret = Object.create(null)
|
||||
extend(ret, parentVal)
|
||||
extend(ret, childVal)
|
||||
if (childVal) extend(ret, childVal)
|
||||
return ret
|
||||
}
|
||||
strats.provide = mergeDataOrFn
|
||||
|
||||
/**
|
||||
* Default strategy.
|
||||
@@ -247,6 +265,19 @@ function normalizeProps (options: Object) {
|
||||
options.props = res
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize all injections into Object-based format
|
||||
*/
|
||||
function normalizeInject (options: Object) {
|
||||
const inject = options.inject
|
||||
if (Array.isArray(inject)) {
|
||||
const normalized = options.inject = {}
|
||||
for (let i = 0; i < inject.length; i++) {
|
||||
normalized[inject[i]] = inject[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize raw function directives into object format.
|
||||
*/
|
||||
@@ -280,6 +311,7 @@ export function mergeOptions (
|
||||
}
|
||||
|
||||
normalizeProps(child)
|
||||
normalizeInject(child)
|
||||
normalizeDirectives(child)
|
||||
const extendsFrom = child.extends
|
||||
if (extendsFrom) {
|
||||
|
||||
+14
-3
@@ -1,8 +1,14 @@
|
||||
/* @flow */
|
||||
|
||||
import { hasOwn, isObject, isPlainObject, capitalize, hyphenate } from 'shared/util'
|
||||
import { observe, observerState } from '../observer/index'
|
||||
import { warn } from './debug'
|
||||
import { observe, observerState } from '../observer/index'
|
||||
import {
|
||||
hasOwn,
|
||||
isObject,
|
||||
hyphenate,
|
||||
capitalize,
|
||||
isPlainObject
|
||||
} from 'shared/util'
|
||||
|
||||
type PropOptions = {
|
||||
type: Function | Array<Function> | null,
|
||||
@@ -139,7 +145,12 @@ function assertType (value: any, type: Function): {
|
||||
let valid
|
||||
const expectedType = getType(type)
|
||||
if (simpleCheckRE.test(expectedType)) {
|
||||
valid = typeof value === expectedType.toLowerCase()
|
||||
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') {
|
||||
|
||||
+27
-9
@@ -15,6 +15,7 @@ import {
|
||||
|
||||
import {
|
||||
resolveAsyncComponent,
|
||||
createAsyncPlaceholder,
|
||||
extractPropsFromVNodeData
|
||||
} from './helpers/index'
|
||||
|
||||
@@ -97,7 +98,7 @@ const hooksToMerge = Object.keys(componentVNodeHooks)
|
||||
|
||||
export function createComponent (
|
||||
Ctor: Class<Component> | Function | Object | void,
|
||||
data?: VNodeData,
|
||||
data: ?VNodeData,
|
||||
context: Component,
|
||||
children: ?Array<VNode>,
|
||||
tag?: string
|
||||
@@ -123,21 +124,30 @@ export function createComponent (
|
||||
}
|
||||
|
||||
// async component
|
||||
let asyncFactory
|
||||
if (isUndef(Ctor.cid)) {
|
||||
Ctor = resolveAsyncComponent(Ctor, baseCtor, context)
|
||||
asyncFactory = Ctor
|
||||
Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
|
||||
if (Ctor === undefined) {
|
||||
// return nothing if this is indeed an async component
|
||||
// wait for the callback to trigger parent update.
|
||||
return
|
||||
// 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)
|
||||
|
||||
data = data || {}
|
||||
|
||||
// transform component v-model data into props & events
|
||||
if (isDef(data.model)) {
|
||||
transformModel(Ctor.options, data)
|
||||
@@ -155,12 +165,19 @@ export function createComponent (
|
||||
// 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
|
||||
// other than props & listeners & slot
|
||||
|
||||
// work around flow
|
||||
const slot = data.slot
|
||||
data = {}
|
||||
if (slot) {
|
||||
data.slot = slot
|
||||
}
|
||||
}
|
||||
|
||||
// merge component management hooks onto the placeholder node
|
||||
@@ -171,7 +188,8 @@ export function createComponent (
|
||||
const vnode = new VNode(
|
||||
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
|
||||
data, undefined, undefined, undefined, context,
|
||||
{ Ctor, propsData, listeners, tag, children }
|
||||
{ Ctor, propsData, listeners, tag, children },
|
||||
asyncFactory
|
||||
)
|
||||
return vnode
|
||||
}
|
||||
|
||||
+15
-1
@@ -57,10 +57,24 @@ export function _createElement (
|
||||
)
|
||||
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)
|
||||
) {
|
||||
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'
|
||||
@@ -77,7 +91,7 @@ export function _createElement (
|
||||
let vnode, ns
|
||||
if (typeof tag === 'string') {
|
||||
let Ctor
|
||||
ns = config.getTagNamespace(tag)
|
||||
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
|
||||
if (config.isReservedTag(tag)) {
|
||||
// platform built-in elements
|
||||
vnode = new VNode(
|
||||
|
||||
+3
-2
@@ -8,6 +8,7 @@ import { resolveSlots } from '../instance/render-helpers/resolve-slots'
|
||||
import {
|
||||
isDef,
|
||||
camelize,
|
||||
emptyObject,
|
||||
validateProp
|
||||
} from '../util/index'
|
||||
|
||||
@@ -22,7 +23,7 @@ export function createFunctionalComponent (
|
||||
const propOptions = Ctor.options.props
|
||||
if (isDef(propOptions)) {
|
||||
for (const key in propOptions) {
|
||||
props[key] = validateProp(key, propOptions, propsData || {})
|
||||
props[key] = validateProp(key, propOptions, propsData || emptyObject)
|
||||
}
|
||||
} else {
|
||||
if (isDef(data.attrs)) mergeProps(props, data.attrs)
|
||||
@@ -37,7 +38,7 @@ export function createFunctionalComponent (
|
||||
props,
|
||||
children,
|
||||
parent: context,
|
||||
listeners: data.on || {},
|
||||
listeners: data.on || emptyObject,
|
||||
injections: resolveInject(Ctor.options.inject, context),
|
||||
slots: () => resolveSlots(children, context)
|
||||
})
|
||||
|
||||
+2
-1
@@ -1,12 +1,13 @@
|
||||
/* @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)) {
|
||||
if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -6,3 +6,4 @@ export * from './update-listeners'
|
||||
export * from './normalize-children'
|
||||
export * from './resolve-async-component'
|
||||
export * from './get-first-component-child'
|
||||
export * from './is-async-placeholder'
|
||||
|
||||
+18
@@ -9,12 +9,30 @@ import {
|
||||
isObject
|
||||
} from 'core/util/index'
|
||||
|
||||
import { createEmptyVNode } from 'core/vdom/vnode'
|
||||
|
||||
function ensureCtor (comp, base) {
|
||||
if (comp.__esModule && comp.default) {
|
||||
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>,
|
||||
|
||||
+25
-4
@@ -5,9 +5,11 @@ import { cached, isUndef } from 'shared/util'
|
||||
|
||||
const normalizeEvent = cached((name: string): {
|
||||
name: string,
|
||||
plain: boolean,
|
||||
once: boolean,
|
||||
capture: boolean,
|
||||
passive: boolean
|
||||
passive: boolean,
|
||||
handler?: Function
|
||||
} => {
|
||||
const passive = name.charAt(0) === '&'
|
||||
name = passive ? name.slice(1) : name
|
||||
@@ -15,8 +17,10 @@ const normalizeEvent = cached((name: string): {
|
||||
name = once ? name.slice(1) : name
|
||||
const capture = name.charAt(0) === '!'
|
||||
name = capture ? name.slice(1) : name
|
||||
const plain = !(passive || once || capture)
|
||||
return {
|
||||
name,
|
||||
plain,
|
||||
once,
|
||||
capture,
|
||||
passive
|
||||
@@ -27,8 +31,9 @@ export function createFnInvoker (fns: Function | Array<Function>): Function {
|
||||
function invoker () {
|
||||
const fns = invoker.fns
|
||||
if (Array.isArray(fns)) {
|
||||
for (let i = 0; i < fns.length; i++) {
|
||||
fns[i].apply(null, arguments)
|
||||
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
|
||||
@@ -39,6 +44,11 @@ export function createFnInvoker (fns: Function | Array<Function>): Function {
|
||||
return invoker
|
||||
}
|
||||
|
||||
// #6552
|
||||
function prioritizePlainEvents (a, b) {
|
||||
return a.plain ? -1 : b.plain ? 1 : 0
|
||||
}
|
||||
|
||||
export function updateListeners (
|
||||
on: Object,
|
||||
oldOn: Object,
|
||||
@@ -47,10 +57,13 @@ export function updateListeners (
|
||||
vm: Component
|
||||
) {
|
||||
let name, cur, old, event
|
||||
const toAdd = []
|
||||
let hasModifier = false
|
||||
for (name in on) {
|
||||
cur = on[name]
|
||||
old = oldOn[name]
|
||||
event = normalizeEvent(name)
|
||||
if (!event.plain) hasModifier = true
|
||||
if (isUndef(cur)) {
|
||||
process.env.NODE_ENV !== 'production' && warn(
|
||||
`Invalid handler for event "${event.name}": got ` + String(cur),
|
||||
@@ -60,12 +73,20 @@ export function updateListeners (
|
||||
if (isUndef(cur.fns)) {
|
||||
cur = on[name] = createFnInvoker(cur)
|
||||
}
|
||||
add(event.name, cur, event.once, event.capture, event.passive)
|
||||
event.handler = cur
|
||||
toAdd.push(event)
|
||||
} else if (cur !== old) {
|
||||
old.fns = cur
|
||||
on[name] = old
|
||||
}
|
||||
}
|
||||
if (toAdd.length) {
|
||||
if (hasModifier) toAdd.sort(prioritizePlainEvents)
|
||||
for (let i = 0; i < toAdd.length; i++) {
|
||||
const event = toAdd[i]
|
||||
add(event.name, event.handler, event.once, event.capture, event.passive)
|
||||
}
|
||||
}
|
||||
for (name in oldOn) {
|
||||
if (isUndef(on[name])) {
|
||||
event = normalizeEvent(name)
|
||||
|
||||
+4
-3
@@ -32,10 +32,11 @@ export function registerRef (vnode: VNodeWithData, isRemoval: ?boolean) {
|
||||
}
|
||||
} else {
|
||||
if (vnode.data.refInFor) {
|
||||
if (Array.isArray(refs[key]) && refs[key].indexOf(ref) < 0) {
|
||||
refs[key].push(ref)
|
||||
} else {
|
||||
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
|
||||
|
||||
+103
-42
@@ -6,8 +6,6 @@
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
@@ -17,6 +15,7 @@ import config from '../config'
|
||||
import { SSR_ATTR } from 'shared/constants'
|
||||
import { registerRef } from './modules/ref'
|
||||
import { activeInstance } from '../instance/lifecycle'
|
||||
import { isTextInputType } from 'web/util/element'
|
||||
|
||||
import {
|
||||
warn,
|
||||
@@ -33,22 +32,27 @@ 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)
|
||||
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)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Some browsers do not support dynamically changing type for <input>
|
||||
// so they need to be treated as different nodes
|
||||
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
|
||||
return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)
|
||||
}
|
||||
|
||||
function createKeyToOldIdx (children, beginIdx, endIdx) {
|
||||
@@ -397,10 +401,11 @@ export function createPatchFunction (backend) {
|
||||
newStartVnode = newCh[++newStartIdx]
|
||||
} else {
|
||||
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
|
||||
idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null
|
||||
idxInOld = isDef(newStartVnode.key)
|
||||
? oldKeyToIdx[newStartVnode.key]
|
||||
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
|
||||
if (isUndef(idxInOld)) { // New element
|
||||
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
|
||||
newStartVnode = newCh[++newStartIdx]
|
||||
} else {
|
||||
elmToMove = oldCh[idxInOld]
|
||||
/* istanbul ignore if */
|
||||
@@ -413,14 +418,13 @@ export function createPatchFunction (backend) {
|
||||
if (sameVnode(elmToMove, newStartVnode)) {
|
||||
patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
|
||||
oldCh[idxInOld] = undefined
|
||||
canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm)
|
||||
newStartVnode = newCh[++newStartIdx]
|
||||
canMove && nodeOps.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm)
|
||||
} else {
|
||||
// same key but different element. treat as new element
|
||||
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
|
||||
newStartVnode = newCh[++newStartIdx]
|
||||
}
|
||||
}
|
||||
newStartVnode = newCh[++newStartIdx]
|
||||
}
|
||||
}
|
||||
if (oldStartIdx > oldEndIdx) {
|
||||
@@ -431,10 +435,29 @@ export function createPatchFunction (backend) {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -444,16 +467,16 @@ export function createPatchFunction (backend) {
|
||||
vnode.key === oldVnode.key &&
|
||||
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
|
||||
) {
|
||||
vnode.elm = oldVnode.elm
|
||||
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 elm = vnode.elm = oldVnode.elm
|
||||
|
||||
const oldCh = oldVnode.children
|
||||
const ch = vnode.children
|
||||
if (isDef(data) && isPatchable(vnode)) {
|
||||
@@ -498,6 +521,11 @@ export function createPatchFunction (backend) {
|
||||
|
||||
// Note: this is a browser-only function so we can assume elms are DOM nodes.
|
||||
function hydrate (elm, vnode, insertedVnodeQueue) {
|
||||
if (isTrue(vnode.isComment) && isDef(vnode.asyncFactory)) {
|
||||
vnode.elm = elm
|
||||
vnode.isAsyncPlaceholder = true
|
||||
return true
|
||||
}
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
if (!assertNodeMatch(elm, vnode)) {
|
||||
return false
|
||||
@@ -519,27 +547,46 @@ export function createPatchFunction (backend) {
|
||||
if (!elm.hasChildNodes()) {
|
||||
createChildren(vnode, children, insertedVnodeQueue)
|
||||
} else {
|
||||
let childrenMatch = true
|
||||
let childNode = elm.firstChild
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
if (!childNode || !hydrate(childNode, children[i], insertedVnodeQueue)) {
|
||||
childrenMatch = false
|
||||
break
|
||||
// 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' &&
|
||||
!bailed
|
||||
) {
|
||||
bailed = true
|
||||
console.warn('Parent: ', elm)
|
||||
console.warn('server innerHTML: ', i)
|
||||
console.warn('client innerHTML: ', elm.innerHTML)
|
||||
}
|
||||
return false
|
||||
}
|
||||
childNode = childNode.nextSibling
|
||||
}
|
||||
// if childNode is not null, it means the actual childNodes list is
|
||||
// longer than the virtual children list.
|
||||
if (!childrenMatch || childNode) {
|
||||
if (process.env.NODE_ENV !== 'production' &&
|
||||
typeof console !== 'undefined' &&
|
||||
!bailed
|
||||
) {
|
||||
bailed = true
|
||||
console.warn('Parent: ', elm)
|
||||
console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children)
|
||||
} 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)) {
|
||||
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' &&
|
||||
!bailed
|
||||
) {
|
||||
bailed = true
|
||||
console.warn('Parent: ', elm)
|
||||
console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children)
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -630,14 +677,28 @@ export function createPatchFunction (backend) {
|
||||
// component root element replaced.
|
||||
// update parent placeholder node element, recursively
|
||||
let ancestor = vnode.parent
|
||||
const patchable = isPatchable(vnode)
|
||||
while (ancestor) {
|
||||
ancestor.elm = vnode.elm
|
||||
ancestor = ancestor.parent
|
||||
}
|
||||
if (isPatchable(vnode)) {
|
||||
for (let i = 0; i < cbs.create.length; ++i) {
|
||||
cbs.create[i](emptyNode, vnode.parent)
|
||||
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]()
|
||||
}
|
||||
}
|
||||
}
|
||||
ancestor = ancestor.parent
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-7
@@ -19,6 +19,10 @@ export default class VNode {
|
||||
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;
|
||||
|
||||
constructor (
|
||||
tag?: string,
|
||||
@@ -27,7 +31,8 @@ export default class VNode {
|
||||
text?: string,
|
||||
elm?: Node,
|
||||
context?: Component,
|
||||
componentOptions?: VNodeComponentOptions
|
||||
componentOptions?: VNodeComponentOptions,
|
||||
asyncFactory?: Function
|
||||
) {
|
||||
this.tag = tag
|
||||
this.data = data
|
||||
@@ -47,6 +52,9 @@ export default class VNode {
|
||||
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.
|
||||
@@ -56,9 +64,9 @@ export default class VNode {
|
||||
}
|
||||
}
|
||||
|
||||
export const createEmptyVNode = () => {
|
||||
export const createEmptyVNode = (text: string = '') => {
|
||||
const node = new VNode()
|
||||
node.text = ''
|
||||
node.text = text
|
||||
node.isComment = true
|
||||
return node
|
||||
}
|
||||
@@ -71,7 +79,7 @@ export function createTextVNode (val: string | number) {
|
||||
// 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 {
|
||||
export function cloneVNode (vnode: VNode, deep?: boolean): VNode {
|
||||
const cloned = new VNode(
|
||||
vnode.tag,
|
||||
vnode.data,
|
||||
@@ -79,21 +87,25 @@ export function cloneVNode (vnode: VNode): VNode {
|
||||
vnode.text,
|
||||
vnode.elm,
|
||||
vnode.context,
|
||||
vnode.componentOptions
|
||||
vnode.componentOptions,
|
||||
vnode.asyncFactory
|
||||
)
|
||||
cloned.ns = vnode.ns
|
||||
cloned.isStatic = vnode.isStatic
|
||||
cloned.key = vnode.key
|
||||
cloned.isComment = vnode.isComment
|
||||
cloned.isCloned = true
|
||||
if (deep && vnode.children) {
|
||||
cloned.children = cloneVNodes(vnode.children)
|
||||
}
|
||||
return cloned
|
||||
}
|
||||
|
||||
export function cloneVNodes (vnodes: Array<VNode>): Array<VNode> {
|
||||
export function cloneVNodes (vnodes: Array<VNode>, deep?: boolean): Array<VNode> {
|
||||
const len = vnodes.length
|
||||
const res = new Array(len)
|
||||
for (let i = 0; i < len; i++) {
|
||||
res[i] = cloneVNode(vnodes[i])
|
||||
res[i] = cloneVNode(vnodes[i], deep)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
-4
@@ -1,4 +0,0 @@
|
||||
/* @flow */
|
||||
|
||||
export { parseComponent } from 'sfc/parser'
|
||||
export { compile, compileToFunctions } from './compiler/index'
|
||||
+7
-3
@@ -40,7 +40,11 @@ export default function model (
|
||||
}
|
||||
}
|
||||
|
||||
if (tag === 'select') {
|
||||
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)
|
||||
@@ -89,7 +93,7 @@ function genCheckboxModel (
|
||||
'if(Array.isArray($$a)){' +
|
||||
`var $$v=${number ? '_n(' + valueBinding + ')' : valueBinding},` +
|
||||
'$$i=_i($$a,$$v);' +
|
||||
`if($$c){$$i<0&&(${value}=$$a.concat($$v))}` +
|
||||
`if($$el.checked){$$i<0&&(${value}=$$a.concat([$$v]))}` +
|
||||
`else{$$i>-1&&(${value}=$$a.slice(0,$$i).concat($$a.slice($$i+1)))}` +
|
||||
`}else{${genAssignmentCode(value, '$$c')}}`,
|
||||
null, true
|
||||
@@ -154,7 +158,7 @@ function genDefaultModel (
|
||||
|
||||
addProp(el, 'value', `(${value})`)
|
||||
addHandler(el, event, code, null, true)
|
||||
if (trim || number || type === 'number') {
|
||||
if (trim || number) {
|
||||
addHandler(el, 'blur', '$forceUpdate()')
|
||||
}
|
||||
}
|
||||
|
||||
+2
-25
@@ -1,31 +1,8 @@
|
||||
/* @flow */
|
||||
|
||||
import { isUnaryTag, canBeLeftOpenTag } from './util'
|
||||
import { genStaticKeys } from 'shared/util'
|
||||
import { baseOptions } from './options'
|
||||
import { createCompiler } from 'compiler/index'
|
||||
|
||||
import modules from './modules/index'
|
||||
import directives from './directives/index'
|
||||
|
||||
import {
|
||||
isPreTag,
|
||||
mustUseProp,
|
||||
isReservedTag,
|
||||
getTagNamespace
|
||||
} from '../util/index'
|
||||
|
||||
export const baseOptions: CompilerOptions = {
|
||||
expectHTML: true,
|
||||
modules,
|
||||
directives,
|
||||
isPreTag,
|
||||
isUnaryTag,
|
||||
mustUseProp,
|
||||
canBeLeftOpenTag,
|
||||
isReservedTag,
|
||||
getTagNamespace,
|
||||
staticKeys: genStaticKeys(modules)
|
||||
}
|
||||
|
||||
const { compile, compileToFunctions } = createCompiler(baseOptions)
|
||||
|
||||
export { compile, compileToFunctions }
|
||||
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
/* @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 { shouldDecodeNewlines } from './util/compat'
|
||||
import { compileToFunctions } from './compiler/index'
|
||||
|
||||
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,
|
||||
delimiters: options.delimiters
|
||||
}, this)
|
||||
options.render = render
|
||||
options.staticRenderFns = staticRenderFns
|
||||
|
||||
/* istanbul ignore if */
|
||||
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
|
||||
mark('compile end')
|
||||
measure(`${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
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
/* @flow */
|
||||
|
||||
import Vue from './runtime/index'
|
||||
|
||||
export default Vue
|
||||
+9
-1
@@ -42,12 +42,20 @@ export function removeClass (el: HTMLElement, cls: ?string) {
|
||||
} 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, ' ')
|
||||
}
|
||||
el.setAttribute('class', cur.trim())
|
||||
cur = cur.trim()
|
||||
if (cur) {
|
||||
el.setAttribute('class', cur)
|
||||
} else {
|
||||
el.removeAttribute('class')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -127,7 +127,8 @@ export default {
|
||||
if (!hasTransition) {
|
||||
return false
|
||||
}
|
||||
if (this._hasMove != null) {
|
||||
/* istanbul ignore if */
|
||||
if (this._hasMove) {
|
||||
return this._hasMove
|
||||
}
|
||||
// Detect whether an element with the move class applied has
|
||||
|
||||
+19
-5
@@ -5,7 +5,11 @@
|
||||
|
||||
import { warn } from 'core/util/index'
|
||||
import { camelize, extend, isPrimitive } from 'shared/util'
|
||||
import { mergeVNodeHook, getFirstComponentChild } from 'core/vdom/helpers/index'
|
||||
import {
|
||||
mergeVNodeHook,
|
||||
isAsyncPlaceholder,
|
||||
getFirstComponentChild
|
||||
} from 'core/vdom/helpers/index'
|
||||
|
||||
export const transitionProps = {
|
||||
name: String,
|
||||
@@ -78,13 +82,13 @@ export default {
|
||||
abstract: true,
|
||||
|
||||
render (h: Function) {
|
||||
let children: ?Array<VNode> = this.$slots.default
|
||||
let children: ?Array<VNode> = this.$options._renderChildren
|
||||
if (!children) {
|
||||
return
|
||||
}
|
||||
|
||||
// filter out text nodes (possible whitespaces)
|
||||
children = children.filter((c: VNode) => c.tag)
|
||||
children = children.filter((c: VNode) => c.tag || isAsyncPlaceholder(c))
|
||||
/* istanbul ignore if */
|
||||
if (!children.length) {
|
||||
return
|
||||
@@ -136,7 +140,9 @@ export default {
|
||||
// during entering.
|
||||
const id: string = `__transition-${this._uid}-`
|
||||
child.key = child.key == null
|
||||
? id + child.tag
|
||||
? child.isComment
|
||||
? id + 'comment'
|
||||
: id + child.tag
|
||||
: isPrimitive(child.key)
|
||||
? (String(child.key).indexOf(id) === 0 ? child.key : id + child.key)
|
||||
: child.key
|
||||
@@ -151,7 +157,12 @@ export default {
|
||||
child.data.show = true
|
||||
}
|
||||
|
||||
if (oldChild && oldChild.data && !isSameChild(child, oldChild)) {
|
||||
if (
|
||||
oldChild &&
|
||||
oldChild.data &&
|
||||
!isSameChild(child, oldChild) &&
|
||||
!isAsyncPlaceholder(oldChild)
|
||||
) {
|
||||
// replace old child transition data with fresh one
|
||||
// important for dynamic transitions!
|
||||
const oldData: Object = oldChild && (oldChild.data.transition = extend({}, data))
|
||||
@@ -165,6 +176,9 @@ export default {
|
||||
})
|
||||
return placeholder(h, rawChild)
|
||||
} else if (mode === 'in-out') {
|
||||
if (isAsyncPlaceholder(child)) {
|
||||
return oldRawChild
|
||||
}
|
||||
let delayedLeave
|
||||
const performLeave = () => { delayedLeave() }
|
||||
mergeVNodeHook(data, 'afterEnter', performLeave)
|
||||
|
||||
+26
-20
@@ -3,6 +3,7 @@
|
||||
* properties to Elements.
|
||||
*/
|
||||
|
||||
import { isTextInputType } from 'web/util/element'
|
||||
import { looseEqual, looseIndexOf } from 'shared/util'
|
||||
import { warn, isAndroid, isIE9, isIE, isEdge } from 'core/util/index'
|
||||
|
||||
@@ -20,15 +21,9 @@ if (isIE9) {
|
||||
export default {
|
||||
inserted (el, binding, vnode) {
|
||||
if (vnode.tag === 'select') {
|
||||
const cb = () => {
|
||||
setSelected(el, binding, vnode.context)
|
||||
}
|
||||
cb()
|
||||
/* istanbul ignore if */
|
||||
if (isIE || isEdge) {
|
||||
setTimeout(cb, 0)
|
||||
}
|
||||
} else if (vnode.tag === 'textarea' || el.type === 'text' || el.type === 'password') {
|
||||
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) {
|
||||
// Safari < 10.2 & UIWebView doesn't fire compositionend when
|
||||
@@ -54,17 +49,33 @@ export default {
|
||||
// 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 needReset = el.multiple
|
||||
? binding.value.some(v => hasNoMatchingOption(v, el.options))
|
||||
: binding.value !== binding.oldValue && hasNoMatchingOption(binding.value, el.options)
|
||||
if (needReset) {
|
||||
trigger(el, 'change')
|
||||
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)) {
|
||||
@@ -100,12 +111,7 @@ function setSelected (el, binding, vm) {
|
||||
}
|
||||
|
||||
function hasNoMatchingOption (value, options) {
|
||||
for (let i = 0, l = options.length; i < l; i++) {
|
||||
if (looseEqual(getValue(options[i]), value)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
return options.every(o => !looseEqual(o, value))
|
||||
}
|
||||
|
||||
function getValue (option) {
|
||||
|
||||
+2
-3
@@ -1,6 +1,5 @@
|
||||
/* @flow */
|
||||
|
||||
import { isIE9 } from 'core/util/env'
|
||||
import { enter, leave } from '../modules/transition'
|
||||
|
||||
// recursively search for possible transition defined inside the component root
|
||||
@@ -16,7 +15,7 @@ export default {
|
||||
const transition = vnode.data && vnode.data.transition
|
||||
const originalDisplay = el.__vOriginalDisplay =
|
||||
el.style.display === 'none' ? '' : el.style.display
|
||||
if (value && transition && !isIE9) {
|
||||
if (value && transition) {
|
||||
vnode.data.show = true
|
||||
enter(vnode, () => {
|
||||
el.style.display = originalDisplay
|
||||
@@ -31,7 +30,7 @@ export default {
|
||||
if (value === oldValue) return
|
||||
vnode = locateNode(vnode)
|
||||
const transition = vnode.data && vnode.data.transition
|
||||
if (transition && !isIE9) {
|
||||
if (transition) {
|
||||
vnode.data.show = true
|
||||
if (value) {
|
||||
enter(vnode, () => {
|
||||
|
||||
+10
-1
@@ -18,6 +18,10 @@ import {
|
||||
} 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
|
||||
}
|
||||
@@ -60,7 +64,12 @@ function setAttr (el: Element, key: string, value: any) {
|
||||
if (isFalsyAttrValue(value)) {
|
||||
el.removeAttribute(key)
|
||||
} else {
|
||||
el.setAttribute(key, key)
|
||||
// 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')
|
||||
|
||||
+8
-3
@@ -61,14 +61,19 @@ function shouldUpdateValue (
|
||||
}
|
||||
|
||||
function isDirty (elm: acceptValueElm, checkVal: string): boolean {
|
||||
// return true when textbox (.number and .trim) loses focus and its value is not equal to the updated value
|
||||
return document.activeElement !== elm && elm.value !== checkVal
|
||||
// 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 isInputChanged (elm: any, newVal: string): boolean {
|
||||
const value = elm.value
|
||||
const modifiers = elm._vModifiers // injected by v-model runtime
|
||||
if ((isDef(modifiers) && modifiers.number) || elm.type === 'number') {
|
||||
if (isDef(modifiers) && modifiers.number) {
|
||||
return toNumber(value) !== toNumber(newVal)
|
||||
}
|
||||
if (isDef(modifiers) && modifiers.trim) {
|
||||
|
||||
+10
-10
@@ -26,20 +26,20 @@ const setProp = (el, name, val) => {
|
||||
}
|
||||
}
|
||||
|
||||
const prefixes = ['Webkit', 'Moz', 'ms']
|
||||
const vendorNames = ['Webkit', 'Moz', 'ms']
|
||||
|
||||
let testEl
|
||||
let emptyStyle
|
||||
const normalize = cached(function (prop) {
|
||||
testEl = testEl || document.createElement('div')
|
||||
emptyStyle = emptyStyle || document.createElement('div').style
|
||||
prop = camelize(prop)
|
||||
if (prop !== 'filter' && (prop in testEl.style)) {
|
||||
if (prop !== 'filter' && (prop in emptyStyle)) {
|
||||
return prop
|
||||
}
|
||||
const upper = prop.charAt(0).toUpperCase() + prop.slice(1)
|
||||
for (let i = 0; i < prefixes.length; i++) {
|
||||
const prefixed = prefixes[i] + upper
|
||||
if (prefixed in testEl.style) {
|
||||
return prefixed
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -65,7 +65,7 @@ function updateStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) {
|
||||
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 likley wants
|
||||
// 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)
|
||||
|
||||
+5
-2
@@ -69,8 +69,11 @@ export function nextFrame (fn: Function) {
|
||||
}
|
||||
|
||||
export function addTransitionClass (el: any, cls: string) {
|
||||
(el._transitionClasses || (el._transitionClasses = [])).push(cls)
|
||||
addClass(el, cls)
|
||||
const transitionClasses = el._transitionClasses || (el._transitionClasses = [])
|
||||
if (transitionClasses.indexOf(cls) < 0) {
|
||||
transitionClasses.push(cls)
|
||||
addClass(el, cls)
|
||||
}
|
||||
}
|
||||
|
||||
export function removeTransitionClass (el: any, cls: string) {
|
||||
|
||||
-26
@@ -1,26 +0,0 @@
|
||||
/* @flow */
|
||||
|
||||
process.env.VUE_ENV = 'server'
|
||||
|
||||
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(Object.assign({}, options, {
|
||||
isUnaryTag,
|
||||
canBeLeftOpenTag,
|
||||
modules,
|
||||
// user can provide server-side implementations for custom directives
|
||||
// when creating the renderer.
|
||||
directives: Object.assign(baseDirectives, options.directives)
|
||||
}))
|
||||
}
|
||||
|
||||
export const createBundleRenderer = createBundleRendererCreator(createRenderer)
|
||||
+10
-7
@@ -1,6 +1,6 @@
|
||||
/* @flow */
|
||||
|
||||
import { escape } from 'he'
|
||||
import { escape } from '../util'
|
||||
|
||||
import {
|
||||
isDef,
|
||||
@@ -17,12 +17,15 @@ export default function renderAttrs (node: VNodeWithData): string {
|
||||
let attrs = node.data.attrs
|
||||
let res = ''
|
||||
|
||||
let parent = node.parent
|
||||
while (isDef(parent)) {
|
||||
if (isDef(parent.data) && isDef(parent.data.attrs)) {
|
||||
attrs = Object.assign({}, attrs, parent.data.attrs)
|
||||
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 = Object.assign({}, attrs, parent.data.attrs)
|
||||
}
|
||||
parent = parent.parent
|
||||
}
|
||||
parent = parent.parent
|
||||
}
|
||||
|
||||
if (isUndef(attrs)) {
|
||||
@@ -47,7 +50,7 @@ export function renderAttr (key: string, value: string): string {
|
||||
} else if (isEnumeratedAttr(key)) {
|
||||
return ` ${key}="${isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true'}"`
|
||||
} else if (!isFalsyAttrValue(value)) {
|
||||
return ` ${key}="${typeof value === 'string' ? escape(value) : value}"`
|
||||
return ` ${key}="${escape(String(value))}"`
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/* @flow */
|
||||
|
||||
import { escape } from 'he'
|
||||
import { escape } from '../util'
|
||||
import { genClassForVnode } from 'web/util/index'
|
||||
|
||||
export default function renderClass (node: VNodeWithData): ?string {
|
||||
|
||||
+3
-4
@@ -1,12 +1,11 @@
|
||||
/* @flow */
|
||||
|
||||
import { escape } from 'he'
|
||||
import { escape } from '../util'
|
||||
import { hyphenate } from 'shared/util'
|
||||
import { getStyle } from 'web/util/style'
|
||||
|
||||
function genStyleText (vnode: VNode): string {
|
||||
export function genStyle (style: Object): string {
|
||||
let styleText = ''
|
||||
const style = getStyle(vnode, false)
|
||||
for (const key in style) {
|
||||
const value = style[key]
|
||||
const hyphenatedKey = hyphenate(key)
|
||||
@@ -22,7 +21,7 @@ function genStyleText (vnode: VNode): string {
|
||||
}
|
||||
|
||||
export default function renderStyle (vnode: VNodeWithData): ?string {
|
||||
const styleText = genStyleText(vnode)
|
||||
const styleText = genStyle(getStyle(vnode, false))
|
||||
if (styleText !== '') {
|
||||
return ` style=${JSON.stringify(escape(styleText))}`
|
||||
}
|
||||
|
||||
+15
@@ -34,3 +34,18 @@ export const propsToAttrMap = {
|
||||
htmlFor: 'for',
|
||||
httpEquiv: 'http-equiv'
|
||||
}
|
||||
|
||||
const ESC = {
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
'&': '&'
|
||||
}
|
||||
|
||||
export function escape (s: string) {
|
||||
return s.replace(/[<>"&]/g, escapeChar)
|
||||
}
|
||||
|
||||
function escapeChar (a) {
|
||||
return ESC[a] || a
|
||||
}
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import { makeMap } from 'shared/util'
|
||||
export const isReservedAttr = makeMap('style,class')
|
||||
|
||||
// attributes that should be using props for binding
|
||||
const acceptValue = makeMap('input,textarea,option,select')
|
||||
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' ||
|
||||
|
||||
+34
-25
@@ -1,6 +1,6 @@
|
||||
/* @flow */
|
||||
|
||||
import { isDef, isUndef, isObject } from 'shared/util'
|
||||
import { isDef, isObject } from 'shared/util'
|
||||
|
||||
export function genClassForVnode (vnode: VNode): string {
|
||||
let data = vnode.data
|
||||
@@ -17,7 +17,7 @@ export function genClassForVnode (vnode: VNode): string {
|
||||
data = mergeClassData(data, parentNode.data)
|
||||
}
|
||||
}
|
||||
return genClassFromData(data)
|
||||
return renderClass(data.staticClass, data.class)
|
||||
}
|
||||
|
||||
function mergeClassData (child: VNodeData, parent: VNodeData): {
|
||||
@@ -32,9 +32,10 @@ function mergeClassData (child: VNodeData, parent: VNodeData): {
|
||||
}
|
||||
}
|
||||
|
||||
function genClassFromData (data: Object): string {
|
||||
const dynamicClass = data.class
|
||||
const staticClass = data.staticClass
|
||||
export function renderClass (
|
||||
staticClass: ?string,
|
||||
dynamicClass: any
|
||||
): string {
|
||||
if (isDef(staticClass) || isDef(dynamicClass)) {
|
||||
return concat(staticClass, stringifyClass(dynamicClass))
|
||||
}
|
||||
@@ -47,30 +48,38 @@ export function concat (a: ?string, b: ?string): string {
|
||||
}
|
||||
|
||||
export function stringifyClass (value: any): string {
|
||||
if (isUndef(value)) {
|
||||
return ''
|
||||
if (Array.isArray(value)) {
|
||||
return stringifyArray(value)
|
||||
}
|
||||
if (isObject(value)) {
|
||||
return stringifyObject(value)
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return value
|
||||
}
|
||||
let res = ''
|
||||
if (Array.isArray(value)) {
|
||||
let stringified
|
||||
for (let i = 0, l = value.length; i < l; i++) {
|
||||
if (isDef(value[i])) {
|
||||
if (isDef(stringified = stringifyClass(value[i])) && stringified !== '') {
|
||||
res += stringified + ' '
|
||||
}
|
||||
}
|
||||
}
|
||||
return res.slice(0, -1)
|
||||
}
|
||||
if (isObject(value)) {
|
||||
for (const key in value) {
|
||||
if (value[key]) res += key + ' '
|
||||
}
|
||||
return res.slice(0, -1)
|
||||
}
|
||||
/* 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
|
||||
}
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import { inBrowser } from 'core/util/index'
|
||||
// check whether current browser encodes a char inside attribute values
|
||||
function shouldDecode (content: string, encoded: string): boolean {
|
||||
const div = document.createElement('div')
|
||||
div.innerHTML = `<div a="${content}">`
|
||||
div.innerHTML = `<div a="${content}"/>`
|
||||
return div.innerHTML.indexOf(encoded) > 0
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -11,7 +11,7 @@ export const namespaceMap = {
|
||||
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,hr,img,li,main,ol,p,pre,ul,' +
|
||||
'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,' +
|
||||
@@ -19,7 +19,7 @@ export const isHTMLTag = makeMap(
|
||||
'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' +
|
||||
'output,progress,select,textarea,' +
|
||||
'details,dialog,menu,menuitem,summary,' +
|
||||
'content,element,shadow,template'
|
||||
'content,element,shadow,template,blockquote,iframe,tfoot'
|
||||
)
|
||||
|
||||
// this map is intentionally selective, only covering SVG elements that may
|
||||
@@ -73,3 +73,5 @@ export function isUnknownElement (tag: string): boolean {
|
||||
return (unknownElementCache[tag] = /HTMLUnknownElement/.test(el.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
export const isTextInputType = makeMap('text,number,password,search,email,tel,url')
|
||||
|
||||
-1
@@ -1 +0,0 @@
|
||||
export { compile } from 'weex/compiler/index'
|
||||
+1
-1
@@ -4,7 +4,7 @@ import { cached, camelize } from 'shared/util'
|
||||
|
||||
const normalize = cached(camelize)
|
||||
|
||||
function normalizeKeyName (str: string) : string {
|
||||
function normalizeKeyName (str: string): string {
|
||||
if (str.match(/^v\-/)) {
|
||||
return str.replace(/(v-[a-z\-]+\:)([a-z\-]+)$/i, ($, directive, prop) => {
|
||||
return directive + normalize(prop)
|
||||
|
||||
-417
@@ -1,417 +0,0 @@
|
||||
import TextNode from 'weex/runtime/text-node'
|
||||
|
||||
// this will be preserved during build
|
||||
const VueFactory = require('./factory')
|
||||
|
||||
const instances = {}
|
||||
const modules = {}
|
||||
const components = {}
|
||||
|
||||
const renderer = {
|
||||
TextNode,
|
||||
instances,
|
||||
modules,
|
||||
components
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare framework config, basically about the virtual-DOM and JS bridge.
|
||||
* @param {object} cfg
|
||||
*/
|
||||
export function init (cfg) {
|
||||
renderer.Document = cfg.Document
|
||||
renderer.Element = cfg.Element
|
||||
renderer.Comment = cfg.Comment
|
||||
renderer.sendTasks = cfg.sendTasks
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset framework config and clear all registrations.
|
||||
*/
|
||||
export function reset () {
|
||||
clear(instances)
|
||||
clear(modules)
|
||||
clear(components)
|
||||
delete renderer.Document
|
||||
delete renderer.Element
|
||||
delete renderer.Comment
|
||||
delete renderer.sendTasks
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all keys of an object.
|
||||
* @param {object} obj
|
||||
*/
|
||||
function clear (obj) {
|
||||
for (const key in obj) {
|
||||
delete obj[key]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance with id, code, config and external data.
|
||||
* @param {string} instanceId
|
||||
* @param {string} appCode
|
||||
* @param {object} config
|
||||
* @param {object} data
|
||||
* @param {object} env { info, config, services }
|
||||
*/
|
||||
export function createInstance (
|
||||
instanceId,
|
||||
appCode = '',
|
||||
config = {},
|
||||
data,
|
||||
env = {}
|
||||
) {
|
||||
// Virtual-DOM object.
|
||||
const document = new renderer.Document(instanceId, config.bundleUrl)
|
||||
|
||||
// All function/callback of parameters before sent to native
|
||||
// will be converted as an id. So `callbacks` is used to store
|
||||
// these real functions. When a callback invoked and won't be
|
||||
// called again, it should be removed from here automatically.
|
||||
const callbacks = []
|
||||
|
||||
// The latest callback id, incremental.
|
||||
const callbackId = 1
|
||||
|
||||
const instance = instances[instanceId] = {
|
||||
instanceId, config, data,
|
||||
document, callbacks, callbackId
|
||||
}
|
||||
|
||||
// Prepare native module getter and HTML5 Timer APIs.
|
||||
const moduleGetter = genModuleGetter(instanceId)
|
||||
const timerAPIs = getInstanceTimer(instanceId, moduleGetter)
|
||||
|
||||
// Prepare `weex` instance variable.
|
||||
const weexInstanceVar = {
|
||||
config,
|
||||
document,
|
||||
requireModule: moduleGetter
|
||||
}
|
||||
Object.freeze(weexInstanceVar)
|
||||
|
||||
// Each instance has a independent `Vue` module instance
|
||||
const Vue = instance.Vue = createVueModuleInstance(instanceId, moduleGetter)
|
||||
|
||||
// The function which create a closure the JS Bundle will run in.
|
||||
// It will declare some instance variables like `Vue`, HTML5 Timer APIs etc.
|
||||
const instanceVars = Object.assign({
|
||||
Vue,
|
||||
weex: weexInstanceVar,
|
||||
// deprecated
|
||||
__weex_require_module__: weexInstanceVar.requireModule // eslint-disable-line
|
||||
}, timerAPIs)
|
||||
callFunction(instanceVars, appCode)
|
||||
|
||||
// Send `createFinish` signal to native.
|
||||
renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'createFinish', args: [] }], -1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy an instance with id. It will make sure all memory of
|
||||
* this instance released and no more leaks.
|
||||
* @param {string} instanceId
|
||||
*/
|
||||
export function destroyInstance (instanceId) {
|
||||
const instance = instances[instanceId]
|
||||
if (instance && instance.app instanceof instance.Vue) {
|
||||
instance.app.$destroy()
|
||||
}
|
||||
delete instances[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.
|
||||
* @param {string} instanceId
|
||||
* @param {object} data
|
||||
*/
|
||||
export function refreshInstance (instanceId, data) {
|
||||
const instance = instances[instanceId]
|
||||
if (!instance || !(instance.app instanceof instance.Vue)) {
|
||||
return new Error(`refreshInstance: instance ${instanceId} not found!`)
|
||||
}
|
||||
for (const key in data) {
|
||||
instance.Vue.set(instance.app, key, data[key])
|
||||
}
|
||||
// Finally `refreshFinish` signal needed.
|
||||
renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'refreshFinish', args: [] }], -1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JSON object of the root element.
|
||||
* @param {string} instanceId
|
||||
*/
|
||||
export function getRoot (instanceId) {
|
||||
const instance = instances[instanceId]
|
||||
if (!instance || !(instance.app instanceof instance.Vue)) {
|
||||
return new Error(`getRoot: instance ${instanceId} not found!`)
|
||||
}
|
||||
return instance.app.$el.toJSON()
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive tasks from native. Generally there are two types of tasks:
|
||||
* 1. `fireEvent`: an device actions or user actions from native.
|
||||
* 2. `callback`: invoke function which sent to native as a parameter before.
|
||||
* @param {string} instanceId
|
||||
* @param {array} tasks
|
||||
*/
|
||||
export function receiveTasks (instanceId, tasks) {
|
||||
const instance = instances[instanceId]
|
||||
if (!instance || !(instance.app instanceof instance.Vue)) {
|
||||
return new Error(`receiveTasks: instance ${instanceId} not found!`)
|
||||
}
|
||||
const { callbacks, document } = instance
|
||||
tasks.forEach(task => {
|
||||
// `fireEvent` case: find the event target and fire.
|
||||
if (task.method === 'fireEvent') {
|
||||
const [nodeId, type, e, domChanges] = task.args
|
||||
const el = document.getRef(nodeId)
|
||||
document.fireEvent(el, type, e, domChanges)
|
||||
}
|
||||
// `callback` case: find the callback by id and call it.
|
||||
if (task.method === 'callback') {
|
||||
const [callbackId, data, ifKeepAlive] = task.args
|
||||
const callback = callbacks[callbackId]
|
||||
if (typeof callback === 'function') {
|
||||
callback(data)
|
||||
// Remove the callback from `callbacks` if it won't called again.
|
||||
if (typeof ifKeepAlive === 'undefined' || ifKeepAlive === false) {
|
||||
callbacks[callbackId] = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
// Finally `updateFinish` signal needed.
|
||||
renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'updateFinish', args: [] }], -1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Register native modules information.
|
||||
* @param {object} newModules
|
||||
*/
|
||||
export function registerModules (newModules) {
|
||||
for (const name in newModules) {
|
||||
if (!modules[name]) {
|
||||
modules[name] = {}
|
||||
}
|
||||
newModules[name].forEach(method => {
|
||||
if (typeof method === 'string') {
|
||||
modules[name][method] = true
|
||||
} else {
|
||||
modules[name][method.name] = method.args
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register native components information.
|
||||
* @param {array} newComponents
|
||||
*/
|
||||
export function registerComponents (newComponents) {
|
||||
if (Array.isArray(newComponents)) {
|
||||
newComponents.forEach(component => {
|
||||
if (!component) {
|
||||
return
|
||||
}
|
||||
if (typeof component === 'string') {
|
||||
components[component] = true
|
||||
} else if (typeof component === 'object' && typeof component.type === 'string') {
|
||||
components[component.type] = component
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a fresh instance of Vue for each Weex instance.
|
||||
*/
|
||||
function createVueModuleInstance (instanceId, moduleGetter) {
|
||||
const exports = {}
|
||||
VueFactory(exports, renderer)
|
||||
const Vue = exports.Vue
|
||||
|
||||
const instance = instances[instanceId]
|
||||
|
||||
// patch reserved tag detection to account for dynamically registered
|
||||
// components
|
||||
const isReservedTag = Vue.config.isReservedTag || (() => false)
|
||||
Vue.config.isReservedTag = name => {
|
||||
return components[name] || isReservedTag(name)
|
||||
}
|
||||
|
||||
// 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 = moduleGetter
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* @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
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate native module getter. Each native module has several
|
||||
* methods to call. And all the behaviors is instance-related. So
|
||||
* this getter will return a set of methods which additionally
|
||||
* send current instance id to native when called. Also the args
|
||||
* will be normalized into "safe" value. For example function arg
|
||||
* will be converted into a callback id.
|
||||
* @param {string} instanceId
|
||||
* @return {function}
|
||||
*/
|
||||
function genModuleGetter (instanceId) {
|
||||
const instance = instances[instanceId]
|
||||
return function (name) {
|
||||
const nativeModule = modules[name] || []
|
||||
const output = {}
|
||||
for (const methodName in nativeModule) {
|
||||
output[methodName] = (...args) => {
|
||||
const finalArgs = args.map(value => {
|
||||
return normalize(value, instance)
|
||||
})
|
||||
renderer.sendTasks(instanceId + '', [{ module: name, method: methodName, args: finalArgs }], -1)
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param {[type]} instanceId [description]
|
||||
* @param {[type]} moduleGetter [description]
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
function getInstanceTimer (instanceId, moduleGetter) {
|
||||
const instance = instances[instanceId]
|
||||
const timer = moduleGetter('timer')
|
||||
const timerAPIs = {
|
||||
setTimeout: (...args) => {
|
||||
const handler = function () {
|
||||
args[0](...args.slice(2))
|
||||
}
|
||||
timer.setTimeout(handler, args[1])
|
||||
return instance.callbackId.toString()
|
||||
},
|
||||
setInterval: (...args) => {
|
||||
const handler = function () {
|
||||
args[0](...args.slice(2))
|
||||
}
|
||||
timer.setInterval(handler, args[1])
|
||||
return instance.callbackId.toString()
|
||||
},
|
||||
clearTimeout: (n) => {
|
||||
timer.clearTimeout(n)
|
||||
},
|
||||
clearInterval: (n) => {
|
||||
timer.clearInterval(n)
|
||||
}
|
||||
}
|
||||
return timerAPIs
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a new function body with some global objects.
|
||||
* @param {object} globalObjects
|
||||
* @param {string} code
|
||||
* @return {any}
|
||||
*/
|
||||
function callFunction (globalObjects, body) {
|
||||
const globalKeys = []
|
||||
const globalValues = []
|
||||
for (const key in globalObjects) {
|
||||
globalKeys.push(key)
|
||||
globalValues.push(globalObjects[key])
|
||||
}
|
||||
globalKeys.push(body)
|
||||
|
||||
const result = new Function(...globalKeys)
|
||||
return result(...globalValues)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert all type of values into "safe" format to send to native.
|
||||
* 1. A `function` will be converted into callback id.
|
||||
* 2. An `Element` object will be converted into `ref`.
|
||||
* The `instance` param is used to generate callback id and store
|
||||
* function if necessary.
|
||||
* @param {any} v
|
||||
* @param {object} instance
|
||||
* @return {any}
|
||||
*/
|
||||
function normalize (v, instance) {
|
||||
const type = typof(v)
|
||||
|
||||
switch (type) {
|
||||
case 'undefined':
|
||||
case 'null':
|
||||
return ''
|
||||
case 'regexp':
|
||||
return v.toString()
|
||||
case 'date':
|
||||
return v.toISOString()
|
||||
case 'number':
|
||||
case 'string':
|
||||
case 'boolean':
|
||||
case 'array':
|
||||
case 'object':
|
||||
if (v instanceof renderer.Element) {
|
||||
return v.ref
|
||||
}
|
||||
return v
|
||||
case 'function':
|
||||
instance.callbacks[++instance.callbackId] = v
|
||||
return instance.callbackId.toString()
|
||||
default:
|
||||
return JSON.stringify(v)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exact type of an object by `toString()`. For example call
|
||||
* `toString()` on an array will be returned `[object Array]`.
|
||||
* @param {any} v
|
||||
* @return {string}
|
||||
*/
|
||||
function typof (v) {
|
||||
const s = Object.prototype.toString.call(v)
|
||||
return s.substring(8, s.length - 1).toLowerCase()
|
||||
}
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
// 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
|
||||
+2
@@ -1,7 +1,9 @@
|
||||
import Richtext from './richtext'
|
||||
import Transition from './transition'
|
||||
import TransitionGroup from './transition-group'
|
||||
|
||||
export default {
|
||||
Richtext,
|
||||
Transition,
|
||||
TransitionGroup
|
||||
}
|
||||
|
||||
+2
@@ -10,12 +10,14 @@ import {
|
||||
query,
|
||||
mustUseProp,
|
||||
isReservedTag,
|
||||
isRuntimeComponent,
|
||||
isUnknownElement
|
||||
} from 'weex/util/index'
|
||||
|
||||
// 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
|
||||
|
||||
+8
-3
@@ -4,21 +4,26 @@ 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,richtext,input,switch,textarea,spinner,select,' +
|
||||
'slider,slider-neighbor,indicator,trisition,trisition-group,canvas,' +
|
||||
'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 flexable than web
|
||||
// more flexible than web
|
||||
export const canBeLeftOpenTag = makeMap(
|
||||
'web,spinner,switch,video,textarea,canvas,' +
|
||||
'indicator,marquee,countdown',
|
||||
true
|
||||
)
|
||||
|
||||
export const isRuntimeComponent = makeMap(
|
||||
'richtext,trisition,trisition-group',
|
||||
true
|
||||
)
|
||||
|
||||
export const isUnaryTag = makeMap(
|
||||
'embed,img,image,input,link,meta',
|
||||
true
|
||||
|
||||
+3
-1
@@ -27,7 +27,9 @@ type RenderBundle = {
|
||||
modules?: { [filename: string]: Array<string> };
|
||||
};
|
||||
|
||||
export function createBundleRendererCreator (createRenderer: () => Renderer) {
|
||||
export function createBundleRendererCreator (
|
||||
createRenderer: (options?: RenderOptions) => Renderer
|
||||
) {
|
||||
return function createBundleRenderer (
|
||||
bundle: string | RenderBundle,
|
||||
rendererOptions?: RenderOptions = {}
|
||||
|
||||
+18
-2
@@ -115,7 +115,7 @@ export function createBundleRunner (entry, files, basedir, runInNewContext) {
|
||||
// styles injected by vue-style-loader.
|
||||
initialContext = sandbox.__VUE_SSR_CONTEXT__ = {}
|
||||
runner = evaluate(entry, sandbox)
|
||||
// On subsequent renders, __VUE_SSR_CONTEXT__ will not be avaialbe
|
||||
// On subsequent renders, __VUE_SSR_CONTEXT__ will not be available
|
||||
// to prevent cross-request pollution.
|
||||
delete sandbox.__VUE_SSR_CONTEXT__
|
||||
if (typeof runner !== 'function') {
|
||||
@@ -130,7 +130,23 @@ export function createBundleRunner (entry, files, basedir, runInNewContext) {
|
||||
if (initialContext._styles) {
|
||||
userContext._styles = deepClone(initialContext._styles)
|
||||
}
|
||||
resolve(runner(userContext))
|
||||
// #6353 after the app is resolved, if the userContext doesn't have a
|
||||
// styles property, it means the app doesn't have any lifecycle-injected
|
||||
// styles, so vue-style-loader never defined the styles getter.
|
||||
// just expose the same styles from the initialContext.
|
||||
const exposeStylesAndResolve = app => {
|
||||
if (!userContext.hasOwnProperty('styles')) {
|
||||
userContext.styles = initialContext.styles
|
||||
}
|
||||
resolve(app)
|
||||
}
|
||||
|
||||
const res = runner(userContext)
|
||||
if (typeof res.then === 'function') {
|
||||
res.then(exposeStylesAndResolve)
|
||||
} else {
|
||||
exposeStylesAndResolve(res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
/* @flow */
|
||||
|
||||
import RenderStream from './render-stream'
|
||||
import TemplateRenderer from './template-renderer/index'
|
||||
import { createWriteFunction } from './write'
|
||||
import { createRenderFunction } from './render'
|
||||
import TemplateRenderer from './template-renderer/index'
|
||||
import type { ClientManifest } from './template-renderer/index'
|
||||
|
||||
export type Renderer = {
|
||||
@@ -18,7 +18,7 @@ type RenderCache = {
|
||||
};
|
||||
|
||||
export type RenderOptions = {
|
||||
modules?: Array<(vnode: VNode) => string>;
|
||||
modules?: Array<(vnode: VNode) => ?string>;
|
||||
directives?: Object;
|
||||
isUnaryTag?: Function;
|
||||
cache?: RenderCache;
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ export class RenderContext {
|
||||
next: () => void;
|
||||
done: () => void;
|
||||
|
||||
modules: Array<() => ?string>;
|
||||
modules: Array<(node: VNode) => ?string>;
|
||||
directives: Object;
|
||||
isUnaryTag: (tag: string) => boolean;
|
||||
|
||||
|
||||
+102
-25
@@ -1,13 +1,21 @@
|
||||
/* @flow */
|
||||
|
||||
const { escape } = require('he')
|
||||
import {
|
||||
isDef,
|
||||
isUndef,
|
||||
isTrue
|
||||
} from 'shared/util'
|
||||
|
||||
import { escape } from 'web/server/util'
|
||||
import { SSR_ATTR } from 'shared/constants'
|
||||
import { RenderContext } from './render-context'
|
||||
import { compileToFunctions } from 'web/compiler/index'
|
||||
import { createComponentInstanceForVnode } from 'core/vdom/create-component'
|
||||
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 => {
|
||||
@@ -17,16 +25,13 @@ const warnOnce = msg => {
|
||||
}
|
||||
}
|
||||
|
||||
const compilationCache = Object.create(null)
|
||||
const normalizeRender = vm => {
|
||||
const { render, template } = vm.$options
|
||||
const { render, template, _scopeId } = vm.$options
|
||||
if (isUndef(render)) {
|
||||
if (template) {
|
||||
const renderFns = (
|
||||
compilationCache[template] ||
|
||||
(compilationCache[template] = compileToFunctions(template))
|
||||
)
|
||||
Object.assign(vm.$options, renderFns)
|
||||
Object.assign(vm.$options, ssrCompileToFunctions(template, {
|
||||
scopeId: _scopeId
|
||||
}))
|
||||
} else {
|
||||
throw new Error(
|
||||
`render function or template not defined in component: ${
|
||||
@@ -38,22 +43,24 @@ const normalizeRender = vm => {
|
||||
}
|
||||
|
||||
function renderNode (node, isRoot, context) {
|
||||
if (isDef(node.componentOptions)) {
|
||||
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)) {
|
||||
context.write(
|
||||
`<!--${node.text}-->`,
|
||||
context.next
|
||||
)
|
||||
} 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.raw ? node.text : escape(String(node.text)),
|
||||
context.next
|
||||
)
|
||||
context.write(`<!--${node.text}-->`, context.next)
|
||||
}
|
||||
} else {
|
||||
context.write(
|
||||
node.raw ? node.text : escape(String(node.text)),
|
||||
context.next
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,6 +168,75 @@ function renderComponentInner (node, isRoot, context) {
|
||||
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) {
|
||||
renderComponent(resolvedNode, isRoot, context)
|
||||
} else {
|
||||
reject()
|
||||
}
|
||||
}
|
||||
|
||||
const reject = err => {
|
||||
console.error(`[vue-server-renderer] error when rendering async component:\n`)
|
||||
if (err) console.error(err.stack)
|
||||
context.write(`<!--${node.text}-->`, context.next)
|
||||
}
|
||||
|
||||
if (factory.resolved) {
|
||||
resolve(factory.resolved)
|
||||
return
|
||||
}
|
||||
|
||||
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',
|
||||
rendered: 0,
|
||||
total: children.length,
|
||||
endTag: el.close, children
|
||||
})
|
||||
write(el.open, next)
|
||||
}
|
||||
}
|
||||
|
||||
function renderElement (el, isRoot, context) {
|
||||
const { write, next } = context
|
||||
|
||||
@@ -270,7 +346,7 @@ function renderStartingTag (node: VNode, context) {
|
||||
}
|
||||
|
||||
export function createRenderFunction (
|
||||
modules: Array<Function>,
|
||||
modules: Array<(node: VNode) => ?string>,
|
||||
directives: Object,
|
||||
isUnaryTag: Function,
|
||||
cache: any
|
||||
@@ -289,6 +365,7 @@ export function createRenderFunction (
|
||||
isUnaryTag, modules, directives,
|
||||
cache
|
||||
})
|
||||
installSSRHelpers(component)
|
||||
normalizeRender(component)
|
||||
renderNode(component._render(), true, context)
|
||||
}
|
||||
|
||||
+5
-5
@@ -60,7 +60,7 @@ export default class TemplateRenderer {
|
||||
if (options.clientManifest) {
|
||||
const clientManifest = this.clientManifest = options.clientManifest
|
||||
this.publicPath = clientManifest.publicPath.replace(/\/$/, '')
|
||||
// preload/prefetch drectives
|
||||
// preload/prefetch directives
|
||||
this.preloadFiles = clientManifest.initial
|
||||
this.prefetchFiles = clientManifest.async
|
||||
// initial async chunk mapping
|
||||
@@ -181,7 +181,7 @@ export default class TemplateRenderer {
|
||||
}
|
||||
return this.prefetchFiles.map(file => {
|
||||
if (!alreadyRendered(file)) {
|
||||
return `<link rel="prefetch" href="${this.publicPath}/${file}" as="script">`
|
||||
return `<link rel="prefetch" href="${this.publicPath}/${file}">`
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
@@ -209,7 +209,7 @@ export default class TemplateRenderer {
|
||||
const async = this.getUsedAsyncFiles(context)
|
||||
const needed = [initial[0]].concat(async || [], initial.slice(1))
|
||||
return needed.filter(isJS).map(file => {
|
||||
return `<script src="${this.publicPath}/${file}"></script>`
|
||||
return `<script src="${this.publicPath}/${file}" defer></script>`
|
||||
}).join('')
|
||||
} else {
|
||||
return ''
|
||||
@@ -217,7 +217,7 @@ export default class TemplateRenderer {
|
||||
}
|
||||
|
||||
getUsedAsyncFiles (context: Object): ?Array<string> {
|
||||
if (!context._mappedfiles && context._registeredComponents && this.mapFiles) {
|
||||
if (!context._mappedFiles && context._registeredComponents && this.mapFiles) {
|
||||
context._mappedFiles = this.mapFiles(Array.from(context._registeredComponents))
|
||||
}
|
||||
return context._mappedFiles
|
||||
@@ -242,7 +242,7 @@ function getPreloadType (ext: string): string {
|
||||
} else if (/woff2?|ttf|otf|eot/.test(ext)) {
|
||||
return 'font'
|
||||
} else {
|
||||
// not exhausting all possbilities here, but above covers common cases
|
||||
// not exhausting all possibilities here, but above covers common cases
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
+45
-12
@@ -17,11 +17,16 @@ export function isTrue (v: any): boolean %checks {
|
||||
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'
|
||||
return (
|
||||
typeof value === 'string' ||
|
||||
typeof value === 'number' ||
|
||||
typeof value === 'boolean'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,6 +52,14 @@ 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(val)
|
||||
return n >= 0 && Math.floor(n) === n && isFinite(val)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a value to a string that is actually rendered.
|
||||
*/
|
||||
@@ -90,6 +103,11 @@ export function makeMap (
|
||||
*/
|
||||
export const isBuiltInTag = makeMap('slot,component', true)
|
||||
|
||||
/**
|
||||
* Check if a attribute is a reserved attribute.
|
||||
*/
|
||||
export const isReservedAttribute = makeMap('key,ref,slot,is')
|
||||
|
||||
/**
|
||||
* Remove an item from an array
|
||||
*/
|
||||
@@ -139,12 +157,9 @@ export const capitalize = cached((str: string): string => {
|
||||
/**
|
||||
* Hyphenate a camelCase string.
|
||||
*/
|
||||
const hyphenateRE = /([^-])([A-Z])/g
|
||||
const hyphenateRE = /\B([A-Z])/g
|
||||
export const hyphenate = cached((str: string): string => {
|
||||
return str
|
||||
.replace(hyphenateRE, '$1-$2')
|
||||
.replace(hyphenateRE, '$1-$2')
|
||||
.toLowerCase()
|
||||
return str.replace(hyphenateRE, '-$1').toLowerCase()
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -202,13 +217,15 @@ export function toObject (arr: Array<any>): Object {
|
||||
|
||||
/**
|
||||
* 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 () {}
|
||||
export function noop (a?: any, b?: any, c?: any) {}
|
||||
|
||||
/**
|
||||
* Always return false.
|
||||
*/
|
||||
export const no = () => false
|
||||
export const no = (a?: any, b?: any, c?: any) => false
|
||||
|
||||
/**
|
||||
* Return same value
|
||||
@@ -228,15 +245,31 @@ export function genStaticKeys (modules: Array<ModuleOptions>): string {
|
||||
* Check if two values are loosely equal - that is,
|
||||
* if they are plain objects, do they have the same shape?
|
||||
*/
|
||||
export function looseEqual (a: mixed, b: mixed): boolean {
|
||||
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 {
|
||||
return JSON.stringify(a) === JSON.stringify(b)
|
||||
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) {
|
||||
// possible circular reference
|
||||
return a === b
|
||||
/* istanbul ignore next */
|
||||
return false
|
||||
}
|
||||
} else if (!isObjectA && !isObjectB) {
|
||||
return String(a) === String(b)
|
||||
|
||||
Reference in New Issue
Block a user