modified .gitignore

This commit is contained in:
TLRZ Seyfferth
2017-09-13 10:20:31 +02:00
parent e1db4792aa
commit cf899d0675
411 changed files with 23733 additions and 67956 deletions
+166 -103
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,4 +0,0 @@
/* @flow */
export { parseComponent } from 'sfc/parser'
export { compile, compileToFunctions } from './compiler/index'
+7 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,5 +0,0 @@
/* @flow */
import Vue from './runtime/index'
export default Vue
+9 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -34,3 +34,18 @@ export const propsToAttrMap = {
htmlFor: 'for',
httpEquiv: 'http-equiv'
}
const ESC = {
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
'&': '&amp;'
}
export function escape (s: string) {
return s.replace(/[<>"&]/g, escapeChar)
}
function escapeChar (a) {
return ESC[a] || a
}
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1 +0,0 @@
export { compile } from 'weex/compiler/index'
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,7 +1,9 @@
import Richtext from './richtext'
import Transition from './transition'
import TransitionGroup from './transition-group'
export default {
Richtext,
Transition,
TransitionGroup
}
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)