mirror of
https://github.com/vuejs/vue.git
synced 2024-11-21 20:28:54 +00:00
init
This commit is contained in:
commit
a879ec06ef
10
.babelrc
Normal file
10
.babelrc
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"env": {
|
||||
"development": {
|
||||
"presets": ["es2015", "stage-2"]
|
||||
},
|
||||
"production": {
|
||||
"presets": ["es2015-rollup", "stage-2"]
|
||||
}
|
||||
}
|
||||
}
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
npm-debug.log
|
||||
explorations
|
||||
TODOs.md
|
124
build/build.js
Normal file
124
build/build.js
Normal file
@ -0,0 +1,124 @@
|
||||
var fs = require('fs')
|
||||
var zlib = require('zlib')
|
||||
var rollup = require('rollup')
|
||||
var uglify = require('uglify-js')
|
||||
var babel = require('rollup-plugin-babel')
|
||||
var node = require('rollup-plugin-node-resolve')
|
||||
var commonjs = require('rollup-plugin-commonjs')
|
||||
var replace = require('rollup-plugin-replace')
|
||||
var version = process.env.VERSION || require('../package.json').version
|
||||
|
||||
var banner =
|
||||
'/*!\n' +
|
||||
' * Vue.js v' + version + '\n' +
|
||||
' * (c) ' + new Date().getFullYear() + ' Evan You\n' +
|
||||
' * Released under the MIT License.\n' +
|
||||
' */'
|
||||
|
||||
// update main file
|
||||
var main = fs
|
||||
.readFileSync('src/index.js', 'utf-8')
|
||||
.replace(/Vue\.version = '[\d\.]+'/, "Vue.version = '" + version + "'")
|
||||
fs.writeFileSync('src/index.js', main)
|
||||
|
||||
var plugins = [
|
||||
node(),
|
||||
commonjs({
|
||||
include: 'node_modules/**'
|
||||
}),
|
||||
babel({
|
||||
exclude: 'node_modules/**'
|
||||
})
|
||||
]
|
||||
|
||||
// CommonJS build.
|
||||
// this is used as the "main" field in package.json
|
||||
// and used by bundlers like Webpack and Browserify.
|
||||
rollup.rollup({
|
||||
entry: 'src/index.js',
|
||||
plugins: plugins
|
||||
})
|
||||
.then(function (bundle) {
|
||||
return write('dist/vue.common.js', bundle.generate({
|
||||
format: 'cjs',
|
||||
banner: banner
|
||||
}).code)
|
||||
})
|
||||
// Standalone Dev Build
|
||||
.then(function () {
|
||||
return rollup.rollup({
|
||||
entry: 'src/index.js',
|
||||
plugins: [
|
||||
replace({
|
||||
'process.env.NODE_ENV': "'development'"
|
||||
})
|
||||
].concat(plugins)
|
||||
})
|
||||
.then(function (bundle) {
|
||||
return write('dist/vue.js', bundle.generate({
|
||||
format: 'umd',
|
||||
banner: banner,
|
||||
moduleName: 'Vue'
|
||||
}).code)
|
||||
})
|
||||
})
|
||||
.then(function () {
|
||||
// Standalone Production Build
|
||||
return rollup.rollup({
|
||||
entry: 'src/index.js',
|
||||
plugins: [
|
||||
replace({
|
||||
'process.env.NODE_ENV': "'production'"
|
||||
})
|
||||
].concat(plugins)
|
||||
})
|
||||
.then(function (bundle) {
|
||||
var code = bundle.generate({
|
||||
format: 'umd',
|
||||
moduleName: 'Vue'
|
||||
}).code
|
||||
var minified = banner + '\n' + uglify.minify(code, {
|
||||
fromString: true,
|
||||
output: {
|
||||
ascii_only: true
|
||||
}
|
||||
}).code
|
||||
return write('dist/vue.min.js', minified)
|
||||
})
|
||||
.then(zip)
|
||||
})
|
||||
.catch(logError)
|
||||
|
||||
function write (dest, code) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
fs.writeFile(dest, code, function (err) {
|
||||
if (err) return reject(err)
|
||||
console.log(blue(dest) + ' ' + getSize(code))
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function zip () {
|
||||
return new Promise(function (resolve, reject) {
|
||||
fs.readFile('dist/vue.min.js', function (err, buf) {
|
||||
if (err) return reject(err)
|
||||
zlib.gzip(buf, function (err, buf) {
|
||||
if (err) return reject(err)
|
||||
write('dist/vue.min.js.gz', buf).then(resolve)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getSize (code) {
|
||||
return (code.length / 1024).toFixed(2) + 'kb'
|
||||
}
|
||||
|
||||
function logError (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
function blue (str) {
|
||||
return '\x1b[1m\x1b[34m' + str + '\x1b[39m\x1b[22m'
|
||||
}
|
38
package.json
Normal file
38
package.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "vue-lite",
|
||||
"version": "1.0.0",
|
||||
"description": "Lighter-weight Vue on virtual dom",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "webpack --watch",
|
||||
"test": "mocha",
|
||||
"build": "NODE_ENV=production node build/build.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vuejs/vue-lite.git"
|
||||
},
|
||||
"keywords": [
|
||||
"vue"
|
||||
],
|
||||
"author": "Evan You",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vuejs/vue-lite/issues"
|
||||
},
|
||||
"homepage": "https://github.com/vuejs/vue-lite#readme",
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.0.0",
|
||||
"babel-loader": "^6.0.0",
|
||||
"babel-preset-es2015": "^6.0.0",
|
||||
"babel-preset-es2015-rollup": "^1.1.1",
|
||||
"babel-preset-stage-2": "^6.0.0",
|
||||
"rollup": "^0.25.8",
|
||||
"rollup-plugin-babel": "^2.4.0",
|
||||
"rollup-plugin-commonjs": "^2.2.1",
|
||||
"rollup-plugin-node-resolve": "^1.5.0",
|
||||
"rollup-plugin-replace": "^1.1.0",
|
||||
"uglify-js": "^2.6.2",
|
||||
"webpack": "^1.12.14"
|
||||
}
|
||||
}
|
129
src/compiler/codegen.js
Normal file
129
src/compiler/codegen.js
Normal file
@ -0,0 +1,129 @@
|
||||
import { parseText } from './text-parser'
|
||||
|
||||
const bindRE = /^:|^v-bind:/
|
||||
const onRE = /^@|^v-on:/
|
||||
const mustUsePropsRE = /^(value|selected|checked|muted)$/
|
||||
|
||||
export function generate (ast) {
|
||||
const code = genElement(ast)
|
||||
return new Function (`with (this) { return ${code}}`)
|
||||
}
|
||||
|
||||
function genElement (el, key) {
|
||||
let exp
|
||||
if (exp = getAttr(el, 'v-for')) {
|
||||
return genFor(el, exp)
|
||||
} else if (exp = getAttr(el, 'v-if')) {
|
||||
return genIf(el, exp)
|
||||
} else if (el.tag === 'template') {
|
||||
return genChildren(el)
|
||||
} else {
|
||||
return `__h__('${ el.tag }', ${ genData(el, key) }, ${ genChildren(el) })`
|
||||
}
|
||||
}
|
||||
|
||||
function genIf (el, exp) {
|
||||
return `(${ exp }) ? ${ genElement(el) } : ''`
|
||||
}
|
||||
|
||||
function genFor (el, exp) {
|
||||
const inMatch = exp.match(/([a-zA-Z_][\w]*)\s+(?:in|of)\s+(.*)/)
|
||||
if (!inMatch) {
|
||||
throw new Error('Invalid v-for expression: '+ exp)
|
||||
}
|
||||
const alias = inMatch[1].trim()
|
||||
exp = inMatch[2].trim()
|
||||
const key = el.attrsMap['track-by'] || 'undefined'
|
||||
return `(${ exp }).map(function (${ alias }, $index) {return ${ genElement(el, key) }})`
|
||||
}
|
||||
|
||||
function genData (el, key) {
|
||||
if (!el.attrs.length) {
|
||||
return '{}'
|
||||
}
|
||||
let data = key ? `{key:${ key },` : `{`
|
||||
if (el.attrsMap[':class'] || el.attrsMap['class']) {
|
||||
data += `class: _renderClass(${ el.attrsMap[':class'] }, "${ el.attrsMap['class'] || '' }"),`
|
||||
}
|
||||
let attrs = `attrs:{`
|
||||
let props = `props:{`
|
||||
let hasAttrs = false
|
||||
let hasProps = false
|
||||
for (let i = 0, l = el.attrs.length; i < l; i++) {
|
||||
let attr = el.attrs[i]
|
||||
let name = attr.name
|
||||
if (bindRE.test(name)) {
|
||||
name = name.replace(bindRE, '')
|
||||
if (name === 'class') {
|
||||
continue
|
||||
} else if (name === 'style') {
|
||||
data += `style: ${ attr.value },`
|
||||
} else if (mustUsePropsRE.test(name)) {
|
||||
hasProps = true
|
||||
props += `"${ name }": (${ attr.value }),`
|
||||
} else {
|
||||
hasAttrs = true
|
||||
attrs += `"${ name }": (${ attr.value }),`
|
||||
}
|
||||
} else if (onRE.test(name)) {
|
||||
name = name.replace(onRE, '')
|
||||
// TODO
|
||||
} else if (name !== 'class') {
|
||||
hasAttrs = true
|
||||
attrs += `"${ name }": (${ JSON.stringify(attr.value) }),`
|
||||
}
|
||||
}
|
||||
if (hasAttrs) {
|
||||
data += attrs.slice(0, -1) + '},'
|
||||
}
|
||||
if (hasProps) {
|
||||
data += props.slice(0, -1) + '},'
|
||||
}
|
||||
return data.replace(/,$/, '') + '}'
|
||||
}
|
||||
|
||||
function genChildren (el) {
|
||||
if (!el.children.length) {
|
||||
return 'undefined'
|
||||
}
|
||||
return '__flatten__([' + el.children.map(genNode).join(',') + '])'
|
||||
}
|
||||
|
||||
function genNode (node) {
|
||||
if (node.tag) {
|
||||
return genElement(node)
|
||||
} else {
|
||||
return genText(node)
|
||||
}
|
||||
}
|
||||
|
||||
function genText (text) {
|
||||
if (text === ' ') {
|
||||
return '" "'
|
||||
} else {
|
||||
const exp = parseText(text)
|
||||
if (exp) {
|
||||
return 'String(' + escapeNewlines(exp) + ')'
|
||||
} else {
|
||||
return escapeNewlines(JSON.stringify(text))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function escapeNewlines (str) {
|
||||
return str.replace(/\n/g, '\\n')
|
||||
}
|
||||
|
||||
function getAttr (el, attr) {
|
||||
let val
|
||||
if (val = el.attrsMap[attr]) {
|
||||
el.attrsMap[attr] = null
|
||||
for (let i = 0, l = el.attrs.length; i < l; i++) {
|
||||
if (el.attrs[i].name === attr) {
|
||||
el.attrs.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return val
|
||||
}
|
389
src/compiler/html-parser.js
Normal file
389
src/compiler/html-parser.js
Normal file
@ -0,0 +1,389 @@
|
||||
/**
|
||||
* Convert HTML string to AST
|
||||
*
|
||||
* @param {String} html
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
export function parse (html) {
|
||||
let root
|
||||
let currentParent
|
||||
let stack = []
|
||||
HTMLParser(html, {
|
||||
html5: true,
|
||||
start (tag, attrs, unary) {
|
||||
let element = {
|
||||
tag,
|
||||
attrs,
|
||||
attrsMap: makeAttrsMap(attrs),
|
||||
parent: currentParent,
|
||||
children: []
|
||||
}
|
||||
if (!root) {
|
||||
root = element
|
||||
}
|
||||
if (currentParent) {
|
||||
currentParent.children.push(element)
|
||||
}
|
||||
if (!unary) {
|
||||
currentParent = element
|
||||
stack.push(element)
|
||||
}
|
||||
},
|
||||
end () {
|
||||
stack.length -= 1
|
||||
currentParent = stack[stack.length - 1]
|
||||
},
|
||||
chars (text) {
|
||||
text = currentParent.tag === 'pre'
|
||||
? text
|
||||
: text.trim() ? text : ' '
|
||||
currentParent.children.push(text)
|
||||
},
|
||||
comment () {
|
||||
// noop
|
||||
}
|
||||
})
|
||||
return root
|
||||
}
|
||||
|
||||
function makeAttrsMap (attrs) {
|
||||
const map = {}
|
||||
for (let i = 0, l = attrs.length; i < l; i++) {
|
||||
map[attrs[i].name] = attrs[i].value
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
/*!
|
||||
* HTML Parser By John Resig (ejohn.org)
|
||||
* Modified by Juriy "kangax" Zaytsev
|
||||
* Original code by Erik Arvidsson, Mozilla Public License
|
||||
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
||||
*/
|
||||
|
||||
/*
|
||||
* // Use like so:
|
||||
* HTMLParser(htmlString, {
|
||||
* start: function(tag, attrs, unary) {},
|
||||
* end: function(tag) {},
|
||||
* chars: function(text) {},
|
||||
* comment: function(text) {}
|
||||
* });
|
||||
*/
|
||||
|
||||
/* global ActiveXObject, DOMDocument */
|
||||
|
||||
function makeMap(values) {
|
||||
values = values.split(/,/)
|
||||
var map = {}
|
||||
values.forEach(function(value) {
|
||||
map[value] = 1
|
||||
})
|
||||
return function(value) {
|
||||
return map[value.toLowerCase()] === 1
|
||||
}
|
||||
}
|
||||
|
||||
// Regular Expressions for parsing tags and attributes
|
||||
var singleAttrIdentifier = /([^\s"'<>\/=]+)/,
|
||||
singleAttrAssign = /=/,
|
||||
singleAttrAssigns = [singleAttrAssign],
|
||||
singleAttrValues = [
|
||||
// attr value double quotes
|
||||
/"([^"]*)"+/.source,
|
||||
// attr value, single quotes
|
||||
/'([^']*)'+/.source,
|
||||
// attr value, no quotes
|
||||
/([^\s"'=<>`]+)/.source
|
||||
],
|
||||
qnameCapture = (function() {
|
||||
// could use https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName
|
||||
// but for Vue templates we can enforce a simple charset
|
||||
var ncname = '[a-zA-Z_][\\w\\-\\.]*'
|
||||
return '((?:' + ncname + '\\:)?' + ncname + ')'
|
||||
})(),
|
||||
startTagOpen = new RegExp('^<' + qnameCapture),
|
||||
startTagClose = /^\s*(\/?)>/,
|
||||
endTag = new RegExp('^<\\/' + qnameCapture + '[^>]*>'),
|
||||
doctype = /^<!DOCTYPE [^>]+>/i
|
||||
|
||||
var IS_REGEX_CAPTURING_BROKEN = false
|
||||
'x'.replace(/x(.)?/g, function(m, g) {
|
||||
IS_REGEX_CAPTURING_BROKEN = g === ''
|
||||
})
|
||||
|
||||
// Empty Elements
|
||||
var empty = makeMap('area,base,basefont,br,col,embed,frame,hr,img,input,isindex,keygen,link,meta,param,source,track,wbr')
|
||||
|
||||
// Inline Elements
|
||||
var inline = makeMap('a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,noscript,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,svg,textarea,tt,u,var')
|
||||
|
||||
// Elements that you can, intentionally, leave open
|
||||
// (and which close themselves)
|
||||
var closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source')
|
||||
|
||||
// Attributes that have their values filled in disabled='disabled'
|
||||
var fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected')
|
||||
|
||||
// Special Elements (can contain anything)
|
||||
var special = makeMap('script,style')
|
||||
|
||||
// HTML5 tags https://html.spec.whatwg.org/multipage/indices.html#elements-3
|
||||
// Phrasing Content https://html.spec.whatwg.org/multipage/dom.html#phrasing-content
|
||||
var nonPhrasing = makeMap('address,article,aside,base,blockquote,body,caption,col,colgroup,dd,details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,title,tr,track')
|
||||
|
||||
var reCache = {}
|
||||
|
||||
function attrForHandler(handler) {
|
||||
var pattern = singleAttrIdentifier.source +
|
||||
'(?:\\s*(' + joinSingleAttrAssigns(handler) + ')' +
|
||||
'\\s*(?:' + singleAttrValues.join('|') + '))?'
|
||||
return new RegExp('^\\s*' + pattern)
|
||||
}
|
||||
|
||||
function joinSingleAttrAssigns(handler) {
|
||||
return singleAttrAssigns.map(function(assign) {
|
||||
return '(?:' + assign.source + ')'
|
||||
}).join('|')
|
||||
}
|
||||
|
||||
export default function HTMLParser(html, handler) {
|
||||
var stack = [], lastTag
|
||||
var attribute = attrForHandler(handler)
|
||||
var last, prevTag, nextTag
|
||||
while (html) {
|
||||
last = html
|
||||
// Make sure we're not in a script or style element
|
||||
if (!lastTag || !special(lastTag)) {
|
||||
var textEnd = html.indexOf('<')
|
||||
if (textEnd === 0) {
|
||||
// Comment:
|
||||
if (/^<!--/.test(html)) {
|
||||
var commentEnd = html.indexOf('-->')
|
||||
|
||||
if (commentEnd >= 0) {
|
||||
if (handler.comment) {
|
||||
handler.comment(html.substring(4, commentEnd))
|
||||
}
|
||||
html = html.substring(commentEnd + 3)
|
||||
prevTag = ''
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
|
||||
if (/^<!\[/.test(html)) {
|
||||
var conditionalEnd = html.indexOf(']>')
|
||||
|
||||
if (conditionalEnd >= 0) {
|
||||
if (handler.comment) {
|
||||
handler.comment(html.substring(2, conditionalEnd + 1), true /* non-standard */)
|
||||
}
|
||||
html = html.substring(conditionalEnd + 2)
|
||||
prevTag = ''
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Doctype:
|
||||
var doctypeMatch = html.match(doctype)
|
||||
if (doctypeMatch) {
|
||||
if (handler.doctype) {
|
||||
handler.doctype(doctypeMatch[0])
|
||||
}
|
||||
html = html.substring(doctypeMatch[0].length)
|
||||
prevTag = ''
|
||||
continue
|
||||
}
|
||||
|
||||
// End tag:
|
||||
var endTagMatch = html.match(endTag)
|
||||
if (endTagMatch) {
|
||||
html = html.substring(endTagMatch[0].length)
|
||||
endTagMatch[0].replace(endTag, parseEndTag)
|
||||
prevTag = '/' + endTagMatch[1].toLowerCase()
|
||||
continue
|
||||
}
|
||||
|
||||
// Start tag:
|
||||
var startTagMatch = parseStartTag(html)
|
||||
if (startTagMatch) {
|
||||
html = startTagMatch.rest
|
||||
handleStartTag(startTagMatch)
|
||||
prevTag = startTagMatch.tagName.toLowerCase()
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
var text
|
||||
if (textEnd >= 0) {
|
||||
text = html.substring(0, textEnd)
|
||||
html = html.substring(textEnd)
|
||||
}
|
||||
else {
|
||||
text = html
|
||||
html = ''
|
||||
}
|
||||
|
||||
// next tag
|
||||
var nextTagMatch = parseStartTag(html)
|
||||
if (nextTagMatch) {
|
||||
nextTag = nextTagMatch.tagName
|
||||
}
|
||||
else {
|
||||
nextTagMatch = html.match(endTag)
|
||||
if (nextTagMatch) {
|
||||
nextTag = '/' + nextTagMatch[1]
|
||||
}
|
||||
else {
|
||||
nextTag = ''
|
||||
}
|
||||
}
|
||||
|
||||
if (handler.chars) {
|
||||
handler.chars(text, prevTag, nextTag)
|
||||
}
|
||||
prevTag = ''
|
||||
|
||||
}
|
||||
else {
|
||||
var stackedTag = lastTag.toLowerCase()
|
||||
var reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)</' + stackedTag + '[^>]*>', 'i'))
|
||||
|
||||
html = html.replace(reStackedTag, function(all, text) {
|
||||
if (stackedTag !== 'script' && stackedTag !== 'style' && stackedTag !== 'noscript') {
|
||||
text = text
|
||||
.replace(/<!--([\s\S]*?)-->/g, '$1')
|
||||
.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, '$1')
|
||||
}
|
||||
|
||||
if (handler.chars) {
|
||||
handler.chars(text)
|
||||
}
|
||||
|
||||
return ''
|
||||
})
|
||||
|
||||
parseEndTag('</' + stackedTag + '>', stackedTag)
|
||||
}
|
||||
|
||||
if (html === last) {
|
||||
throw new Error('Parse Error: ' + html)
|
||||
}
|
||||
}
|
||||
|
||||
if (!handler.partialMarkup) {
|
||||
// Clean up any remaining tags
|
||||
parseEndTag()
|
||||
}
|
||||
|
||||
function parseStartTag(input) {
|
||||
var start = input.match(startTagOpen)
|
||||
if (start) {
|
||||
var match = {
|
||||
tagName: start[1],
|
||||
attrs: []
|
||||
}
|
||||
input = input.slice(start[0].length)
|
||||
var end, attr
|
||||
while (!(end = input.match(startTagClose)) && (attr = input.match(attribute))) {
|
||||
input = input.slice(attr[0].length)
|
||||
match.attrs.push(attr)
|
||||
}
|
||||
if (end) {
|
||||
match.unarySlash = end[1]
|
||||
match.rest = input.slice(end[0].length)
|
||||
return match
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleStartTag(match) {
|
||||
var tagName = match.tagName
|
||||
var unarySlash = match.unarySlash
|
||||
|
||||
if (handler.html5 && lastTag === 'p' && nonPhrasing(tagName)) {
|
||||
parseEndTag('', lastTag)
|
||||
}
|
||||
|
||||
if (!handler.html5) {
|
||||
while (lastTag && inline(lastTag)) {
|
||||
parseEndTag('', lastTag)
|
||||
}
|
||||
}
|
||||
|
||||
if (closeSelf(tagName) && lastTag === tagName) {
|
||||
parseEndTag('', tagName)
|
||||
}
|
||||
|
||||
var unary = empty(tagName) || tagName === 'html' && lastTag === 'head' || !!unarySlash
|
||||
|
||||
var attrs = match.attrs.map(function(args) {
|
||||
// hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778
|
||||
if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) {
|
||||
if (args[3] === '') { delete args[3] }
|
||||
if (args[4] === '') { delete args[4] }
|
||||
if (args[5] === '') { delete args[5] }
|
||||
}
|
||||
return {
|
||||
name: args[1],
|
||||
value: args[3] || args[4] || (args[5] && fillAttrs(args[5]) ? name : '')
|
||||
}
|
||||
})
|
||||
|
||||
if (!unary) {
|
||||
stack.push({ tag: tagName, attrs: attrs })
|
||||
lastTag = tagName
|
||||
unarySlash = ''
|
||||
}
|
||||
|
||||
if (handler.start) {
|
||||
handler.start(tagName, attrs, unary, unarySlash)
|
||||
}
|
||||
}
|
||||
|
||||
function parseEndTag(tag, tagName) {
|
||||
var pos
|
||||
|
||||
// Find the closest opened tag of the same type
|
||||
if (tagName) {
|
||||
var needle = tagName.toLowerCase()
|
||||
for (pos = stack.length - 1; pos >= 0; pos--) {
|
||||
if (stack[pos].tag.toLowerCase() === needle) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// If no tag name is provided, clean shop
|
||||
else {
|
||||
pos = 0
|
||||
}
|
||||
|
||||
if (pos >= 0) {
|
||||
// Close all the open elements, up the stack
|
||||
for (var i = stack.length - 1; i >= pos; i--) {
|
||||
if (handler.end) {
|
||||
handler.end(stack[i].tag, stack[i].attrs, i > pos || !tag)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the open elements from the stack
|
||||
stack.length = pos
|
||||
lastTag = pos && stack[pos - 1].tag
|
||||
}
|
||||
else if (tagName.toLowerCase() === 'br') {
|
||||
if (handler.start) {
|
||||
handler.start(tagName, [], true, '')
|
||||
}
|
||||
}
|
||||
else if (tagName.toLowerCase() === 'p') {
|
||||
if (handler.start) {
|
||||
handler.start(tagName, [], false, '', true)
|
||||
}
|
||||
if (handler.end) {
|
||||
handler.end(tagName, [])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
src/compiler/index.js
Normal file
10
src/compiler/index.js
Normal file
@ -0,0 +1,10 @@
|
||||
import { parse } from './html-parser'
|
||||
import { generate } from './codegen'
|
||||
|
||||
const cache = Object.create(null)
|
||||
|
||||
export function compile (html) {
|
||||
html = html.trim()
|
||||
const hit = cache[html]
|
||||
return hit || (cache[html] = generate(parse(html)))
|
||||
}
|
27
src/compiler/text-parser.js
Normal file
27
src/compiler/text-parser.js
Normal file
@ -0,0 +1,27 @@
|
||||
const tagRE = /\{\{((?:.|\\n)+?)\}\}/g
|
||||
|
||||
export function parseText (text) {
|
||||
if (!tagRE.test(text)) {
|
||||
return null
|
||||
}
|
||||
var tokens = []
|
||||
var lastIndex = tagRE.lastIndex = 0
|
||||
var match, index, value
|
||||
/* eslint-disable no-cond-assign */
|
||||
while (match = tagRE.exec(text)) {
|
||||
/* eslint-enable no-cond-assign */
|
||||
index = match.index
|
||||
// push text token
|
||||
if (index > lastIndex) {
|
||||
tokens.push(JSON.stringify(text.slice(lastIndex, index)))
|
||||
}
|
||||
// tag token
|
||||
value = match[1]
|
||||
tokens.push('(' + match[1].trim() + ')')
|
||||
lastIndex = index + match[0].length
|
||||
}
|
||||
if (lastIndex < text.length) {
|
||||
tokens.push(JSON.stringify(text.slice(lastIndex)))
|
||||
}
|
||||
return tokens.join('+')
|
||||
}
|
79
src/config.js
Normal file
79
src/config.js
Normal file
@ -0,0 +1,79 @@
|
||||
export default {
|
||||
|
||||
/**
|
||||
* Whether to print debug messages.
|
||||
* Also enables stack trace for warnings.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
|
||||
debug: false,
|
||||
|
||||
/**
|
||||
* Whether to suppress warnings.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
|
||||
silent: false,
|
||||
|
||||
/**
|
||||
* Whether to use async rendering.
|
||||
*/
|
||||
|
||||
async: true,
|
||||
|
||||
/**
|
||||
* Whether to warn against errors caught when evaluating
|
||||
* expressions.
|
||||
*/
|
||||
|
||||
warnExpressionErrors: true,
|
||||
|
||||
/**
|
||||
* Whether to allow devtools inspection.
|
||||
* Disabled by default in production builds.
|
||||
*/
|
||||
|
||||
devtools: process.env.NODE_ENV !== 'production',
|
||||
|
||||
/**
|
||||
* Internal flag to indicate the delimiters have been
|
||||
* changed.
|
||||
*
|
||||
* @type {Boolean}
|
||||
*/
|
||||
|
||||
_delimitersChanged: true,
|
||||
|
||||
/**
|
||||
* List of asset types that a component can own.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
|
||||
_assetTypes: [
|
||||
'component',
|
||||
'directive',
|
||||
'elementDirective',
|
||||
'filter',
|
||||
'transition',
|
||||
'partial'
|
||||
],
|
||||
|
||||
/**
|
||||
* prop binding modes
|
||||
*/
|
||||
|
||||
_propBindingModes: {
|
||||
ONE_WAY: 0,
|
||||
TWO_WAY: 1,
|
||||
ONE_TIME: 2
|
||||
},
|
||||
|
||||
/**
|
||||
* Max circular updates allowed in a batcher flush cycle.
|
||||
*/
|
||||
|
||||
_maxUpdateCount: 100
|
||||
}
|
3
src/index.js
Normal file
3
src/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import Vue from './instance/index'
|
||||
|
||||
export default Vue
|
1
src/index.umd.js
Normal file
1
src/index.umd.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./index')['default']
|
85
src/instance/index.js
Normal file
85
src/instance/index.js
Normal file
@ -0,0 +1,85 @@
|
||||
import { compile } from '../compiler/index'
|
||||
import { observe } from '../observer/index'
|
||||
import Watcher from '../observer/watcher'
|
||||
import { h, patch } from '../vdom/index'
|
||||
import { nextTick, isReserved, getOuterHTML } from '../util/index'
|
||||
|
||||
export default class Component {
|
||||
constructor (options) {
|
||||
this.$options = options
|
||||
this._data = options.data
|
||||
const el = this._el = document.querySelector(options.el)
|
||||
const render = compile(getOuterHTML(el))
|
||||
this._el.innerHTML = ''
|
||||
Object.keys(options.data).forEach(key => this._proxy(key))
|
||||
if (options.methods) {
|
||||
Object.keys(options.methods).forEach(key => {
|
||||
this[key] = options.methods[key].bind(this)
|
||||
})
|
||||
}
|
||||
this._ob = observe(options.data)
|
||||
this._watchers = []
|
||||
this._watcher = new Watcher(this, render, this._update)
|
||||
this._update(this._watcher.value)
|
||||
}
|
||||
|
||||
_update (vtree) {
|
||||
if (!this._tree) {
|
||||
patch(this._el, vtree)
|
||||
} else {
|
||||
patch(this._tree, vtree)
|
||||
}
|
||||
this._tree = vtree
|
||||
}
|
||||
|
||||
_renderClass (dynamic, cls) {
|
||||
dynamic = dynamic
|
||||
? typeof dynamic === 'string'
|
||||
? dynamic
|
||||
: Object.keys(dynamic).filter(key => dynamic[key]).join(' ')
|
||||
: ''
|
||||
return cls
|
||||
? cls + (dynamic ? ' ' + dynamic : '')
|
||||
: dynamic
|
||||
}
|
||||
|
||||
__flatten__ (arr) {
|
||||
var res = []
|
||||
for (var i = 0, l = arr.length; i < l; i++) {
|
||||
var e = arr[i]
|
||||
if (Array.isArray(e)) {
|
||||
for (var j = 0, k = e.length; j < k; j++) {
|
||||
if (e[j]) {
|
||||
res.push(e[j])
|
||||
}
|
||||
}
|
||||
} else if (e) {
|
||||
res.push(e)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
_proxy (key) {
|
||||
if (!isReserved(key)) {
|
||||
// need to store ref to self here
|
||||
// because these getter/setters might
|
||||
// be called by child scopes via
|
||||
// prototype inheritance.
|
||||
var self = this
|
||||
Object.defineProperty(self, key, {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: function proxyGetter () {
|
||||
return self._data[key]
|
||||
},
|
||||
set: function proxySetter (val) {
|
||||
self._data[key] = val
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.prototype.__h__ = h
|
||||
Component.nextTick = nextTick
|
88
src/observer/array.js
Normal file
88
src/observer/array.js
Normal file
@ -0,0 +1,88 @@
|
||||
import { def } from '../util/index'
|
||||
|
||||
const arrayProto = Array.prototype
|
||||
export const arrayMethods = Object.create(arrayProto)
|
||||
|
||||
/**
|
||||
* Intercept mutating methods and emit events
|
||||
*/
|
||||
|
||||
;[
|
||||
'push',
|
||||
'pop',
|
||||
'shift',
|
||||
'unshift',
|
||||
'splice',
|
||||
'sort',
|
||||
'reverse'
|
||||
]
|
||||
.forEach(function (method) {
|
||||
// cache original method
|
||||
var original = arrayProto[method]
|
||||
def(arrayMethods, method, function mutator () {
|
||||
// avoid leaking arguments:
|
||||
// http://jsperf.com/closure-with-arguments
|
||||
var i = arguments.length
|
||||
var args = new Array(i)
|
||||
while (i--) {
|
||||
args[i] = arguments[i]
|
||||
}
|
||||
var result = original.apply(this, args)
|
||||
var ob = this.__ob__
|
||||
var inserted
|
||||
switch (method) {
|
||||
case 'push':
|
||||
inserted = args
|
||||
break
|
||||
case 'unshift':
|
||||
inserted = args
|
||||
break
|
||||
case 'splice':
|
||||
inserted = args.slice(2)
|
||||
break
|
||||
}
|
||||
if (inserted) ob.observeArray(inserted)
|
||||
// notify change
|
||||
ob.dep.notify()
|
||||
return result
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Swap the element at the given index with a new value
|
||||
* and emits corresponding event.
|
||||
*
|
||||
* @param {Number} index
|
||||
* @param {*} val
|
||||
* @return {*} - replaced element
|
||||
*/
|
||||
|
||||
def(
|
||||
arrayProto,
|
||||
'$set',
|
||||
function $set (index, val) {
|
||||
if (index >= this.length) {
|
||||
this.length = Number(index) + 1
|
||||
}
|
||||
return this.splice(index, 1, val)[0]
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Convenience method to remove the element at given index or target element reference.
|
||||
*
|
||||
* @param {*} item
|
||||
*/
|
||||
|
||||
def(
|
||||
arrayProto,
|
||||
'$remove',
|
||||
function $remove (item) {
|
||||
/* istanbul ignore if */
|
||||
if (!this.length) return
|
||||
var index = this.indexOf(item)
|
||||
if (index > -1) {
|
||||
return this.splice(index, 1)
|
||||
}
|
||||
}
|
||||
)
|
107
src/observer/batcher.js
Normal file
107
src/observer/batcher.js
Normal file
@ -0,0 +1,107 @@
|
||||
import config from '../config'
|
||||
import {
|
||||
warn,
|
||||
nextTick,
|
||||
devtools
|
||||
} from '../util/index'
|
||||
|
||||
// we have two separate queues: one for directive updates
|
||||
// and one for user watcher registered via $watch().
|
||||
// we want to guarantee directive updates to be called
|
||||
// before user watchers so that when user watchers are
|
||||
// triggered, the DOM would have already been in updated
|
||||
// state.
|
||||
|
||||
var queueIndex
|
||||
var queue = []
|
||||
var userQueue = []
|
||||
var has = {}
|
||||
var circular = {}
|
||||
var waiting = false
|
||||
var internalQueueDepleted = false
|
||||
|
||||
/**
|
||||
* Reset the batcher's state.
|
||||
*/
|
||||
|
||||
function resetBatcherState () {
|
||||
queue = []
|
||||
userQueue = []
|
||||
has = {}
|
||||
circular = {}
|
||||
waiting = internalQueueDepleted = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush both queues and run the watchers.
|
||||
*/
|
||||
|
||||
function flushBatcherQueue () {
|
||||
runBatcherQueue(queue)
|
||||
internalQueueDepleted = true
|
||||
runBatcherQueue(userQueue)
|
||||
resetBatcherState()
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the watchers in a single queue.
|
||||
*
|
||||
* @param {Array} queue
|
||||
*/
|
||||
|
||||
function runBatcherQueue (queue) {
|
||||
// do not cache length because more watchers might be pushed
|
||||
// as we run existing watchers
|
||||
for (queueIndex = 0; queueIndex < queue.length; queueIndex++) {
|
||||
var watcher = queue[queueIndex]
|
||||
var id = watcher.id
|
||||
has[id] = null
|
||||
watcher.run()
|
||||
// in dev build, check and stop circular updates.
|
||||
if (process.env.NODE_ENV !== 'production' && has[id] != null) {
|
||||
circular[id] = (circular[id] || 0) + 1
|
||||
if (circular[id] > config._maxUpdateCount) {
|
||||
warn(
|
||||
'You may have an infinite update loop for watcher ' +
|
||||
'with expression "' + watcher.expression + '"',
|
||||
watcher.vm
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a watcher into the watcher queue.
|
||||
* Jobs with duplicate IDs will be skipped unless it's
|
||||
* pushed when the queue is being flushed.
|
||||
*
|
||||
* @param {Watcher} watcher
|
||||
* properties:
|
||||
* - {Number} id
|
||||
* - {Function} run
|
||||
*/
|
||||
|
||||
export function pushWatcher (watcher) {
|
||||
var id = watcher.id
|
||||
if (has[id] == null) {
|
||||
if (internalQueueDepleted && !watcher.user) {
|
||||
// an internal watcher triggered by a user watcher...
|
||||
// let's run it immediately after current user watcher is done.
|
||||
userQueue.splice(queueIndex + 1, 0, watcher)
|
||||
} else {
|
||||
// push watcher into appropriate queue
|
||||
var q = watcher.user
|
||||
? userQueue
|
||||
: queue
|
||||
has[id] = q.length
|
||||
q.push(watcher)
|
||||
// queue the flush
|
||||
if (!waiting) {
|
||||
waiting = true
|
||||
nextTick(flushBatcherQueue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
58
src/observer/dep.js
Normal file
58
src/observer/dep.js
Normal file
@ -0,0 +1,58 @@
|
||||
let uid = 0
|
||||
|
||||
/**
|
||||
* A dep is an observable that can have multiple
|
||||
* directives subscribing to it.
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
|
||||
export default function Dep () {
|
||||
this.id = uid++
|
||||
this.subs = []
|
||||
}
|
||||
|
||||
// the current target watcher being evaluated.
|
||||
// this is globally unique because there could be only one
|
||||
// watcher being evaluated at any time.
|
||||
Dep.target = null
|
||||
|
||||
/**
|
||||
* Add a directive subscriber.
|
||||
*
|
||||
* @param {Directive} sub
|
||||
*/
|
||||
|
||||
Dep.prototype.addSub = function (sub) {
|
||||
this.subs.push(sub)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a directive subscriber.
|
||||
*
|
||||
* @param {Directive} sub
|
||||
*/
|
||||
|
||||
Dep.prototype.removeSub = function (sub) {
|
||||
this.subs.$remove(sub)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add self as a dependency to the target watcher.
|
||||
*/
|
||||
|
||||
Dep.prototype.depend = function () {
|
||||
Dep.target.addDep(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all subscribers of a new value.
|
||||
*/
|
||||
|
||||
Dep.prototype.notify = function () {
|
||||
// stablize the subscriber list first
|
||||
var subs = this.subs.slice()
|
||||
for (var i = 0, l = subs.length; i < l; i++) {
|
||||
subs[i].update()
|
||||
}
|
||||
}
|
240
src/observer/index.js
Normal file
240
src/observer/index.js
Normal file
@ -0,0 +1,240 @@
|
||||
import Dep from './dep'
|
||||
import { arrayMethods } from './array'
|
||||
import {
|
||||
def,
|
||||
isArray,
|
||||
isPlainObject,
|
||||
hasProto,
|
||||
hasOwn
|
||||
} from '../util/index'
|
||||
|
||||
const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
|
||||
|
||||
/**
|
||||
* By default, when a reactive property is set, the new value is
|
||||
* also converted to become reactive. However in certain cases, e.g.
|
||||
* v-for scope alias and props, we don't want to force conversion
|
||||
* because the value may be a nested value under a frozen data structure.
|
||||
*
|
||||
* So whenever we want to set a reactive property without forcing
|
||||
* conversion on the new value, we wrap that call inside this function.
|
||||
*/
|
||||
|
||||
let shouldConvert = true
|
||||
export function withoutConversion (fn) {
|
||||
shouldConvert = false
|
||||
fn()
|
||||
shouldConvert = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Observer class that are attached to each observed
|
||||
* object. Once attached, the observer converts target
|
||||
* object's property keys into getter/setters that
|
||||
* collect dependencies and dispatches updates.
|
||||
*
|
||||
* @param {Array|Object} value
|
||||
* @constructor
|
||||
*/
|
||||
|
||||
export function Observer (value) {
|
||||
this.value = value
|
||||
this.dep = new Dep()
|
||||
def(value, '__ob__', this)
|
||||
if (isArray(value)) {
|
||||
var augment = hasProto
|
||||
? protoAugment
|
||||
: copyAugment
|
||||
augment(value, arrayMethods, arrayKeys)
|
||||
this.observeArray(value)
|
||||
} else {
|
||||
this.walk(value)
|
||||
}
|
||||
}
|
||||
|
||||
// Instance methods
|
||||
|
||||
/**
|
||||
* Walk through each property and convert them into
|
||||
* getter/setters. This method should only be called when
|
||||
* value type is Object.
|
||||
*
|
||||
* @param {Object} obj
|
||||
*/
|
||||
|
||||
Observer.prototype.walk = function (obj) {
|
||||
var keys = Object.keys(obj)
|
||||
for (var i = 0, l = keys.length; i < l; i++) {
|
||||
this.convert(keys[i], obj[keys[i]])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Observe a list of Array items.
|
||||
*
|
||||
* @param {Array} items
|
||||
*/
|
||||
|
||||
Observer.prototype.observeArray = function (items) {
|
||||
for (var i = 0, l = items.length; i < l; i++) {
|
||||
observe(items[i])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a property into getter/setter so we can emit
|
||||
* the events when the property is accessed/changed.
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {*} val
|
||||
*/
|
||||
|
||||
Observer.prototype.convert = function (key, val) {
|
||||
defineReactive(this.value, key, val)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an owner vm, so that when $set/$delete mutations
|
||||
* happen we can notify owner vms to proxy the keys and
|
||||
* digest the watchers. This is only called when the object
|
||||
* is observed as an instance's root $data.
|
||||
*
|
||||
* @param {Vue} vm
|
||||
*/
|
||||
|
||||
Observer.prototype.addVm = function (vm) {
|
||||
(this.vms || (this.vms = [])).push(vm)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an owner vm. This is called when the object is
|
||||
* swapped out as an instance's $data object.
|
||||
*
|
||||
* @param {Vue} vm
|
||||
*/
|
||||
|
||||
Observer.prototype.removeVm = function (vm) {
|
||||
this.vms.$remove(vm)
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
/**
|
||||
* Augment an target Object or Array by intercepting
|
||||
* the prototype chain using __proto__
|
||||
*
|
||||
* @param {Object|Array} target
|
||||
* @param {Object} src
|
||||
*/
|
||||
|
||||
function protoAugment (target, src) {
|
||||
/* eslint-disable no-proto */
|
||||
target.__proto__ = src
|
||||
/* eslint-enable no-proto */
|
||||
}
|
||||
|
||||
/**
|
||||
* Augment an target Object or Array by defining
|
||||
* hidden properties.
|
||||
*
|
||||
* @param {Object|Array} target
|
||||
* @param {Object} proto
|
||||
*/
|
||||
|
||||
function copyAugment (target, src, keys) {
|
||||
for (var i = 0, l = keys.length; i < l; i++) {
|
||||
var key = keys[i]
|
||||
def(target, key, src[key])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to create an observer instance for a value,
|
||||
* returns the new observer if successfully observed,
|
||||
* or the existing observer if the value already has one.
|
||||
*
|
||||
* @param {*} value
|
||||
* @param {Vue} [vm]
|
||||
* @return {Observer|undefined}
|
||||
* @static
|
||||
*/
|
||||
|
||||
export function observe (value, vm) {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return
|
||||
}
|
||||
var ob
|
||||
if (
|
||||
hasOwn(value, '__ob__') &&
|
||||
value.__ob__ instanceof Observer
|
||||
) {
|
||||
ob = value.__ob__
|
||||
} else if (
|
||||
shouldConvert &&
|
||||
(isArray(value) || isPlainObject(value)) &&
|
||||
Object.isExtensible(value) &&
|
||||
!value._isVue
|
||||
) {
|
||||
ob = new Observer(value)
|
||||
}
|
||||
if (ob && vm) {
|
||||
ob.addVm(vm)
|
||||
}
|
||||
return ob
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a reactive property on an Object.
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @param {String} key
|
||||
* @param {*} val
|
||||
*/
|
||||
|
||||
export function defineReactive (obj, key, val) {
|
||||
var dep = new Dep()
|
||||
|
||||
var property = Object.getOwnPropertyDescriptor(obj, key)
|
||||
if (property && property.configurable === false) {
|
||||
return
|
||||
}
|
||||
|
||||
// cater for pre-defined getter/setters
|
||||
var getter = property && property.get
|
||||
var setter = property && property.set
|
||||
|
||||
var childOb = observe(val)
|
||||
Object.defineProperty(obj, key, {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
get: function reactiveGetter () {
|
||||
var value = getter ? getter.call(obj) : val
|
||||
if (Dep.target) {
|
||||
dep.depend()
|
||||
if (childOb) {
|
||||
childOb.dep.depend()
|
||||
}
|
||||
if (isArray(value)) {
|
||||
for (var e, i = 0, l = value.length; i < l; i++) {
|
||||
e = value[i]
|
||||
e && e.__ob__ && e.__ob__.dep.depend()
|
||||
}
|
||||
}
|
||||
}
|
||||
return value
|
||||
},
|
||||
set: function reactiveSetter (newVal) {
|
||||
var value = getter ? getter.call(obj) : val
|
||||
if (newVal === value) {
|
||||
return
|
||||
}
|
||||
if (setter) {
|
||||
setter.call(obj, newVal)
|
||||
} else {
|
||||
val = newVal
|
||||
}
|
||||
childOb = observe(newVal)
|
||||
dep.notify()
|
||||
}
|
||||
})
|
||||
}
|
319
src/observer/watcher.js
Normal file
319
src/observer/watcher.js
Normal file
@ -0,0 +1,319 @@
|
||||
import config from '../config'
|
||||
import Dep from './dep'
|
||||
import { pushWatcher } from './batcher'
|
||||
import {
|
||||
extend,
|
||||
warn,
|
||||
isArray,
|
||||
isObject,
|
||||
nextTick
|
||||
} from '../util/index'
|
||||
|
||||
let uid = 0
|
||||
|
||||
/**
|
||||
* A watcher parses an expression, collects dependencies,
|
||||
* and fires callback when the expression value changes.
|
||||
* This is used for both the $watch() api and directives.
|
||||
*
|
||||
* @param {Vue} vm
|
||||
* @param {String|Function} expOrFn
|
||||
* @param {Function} cb
|
||||
* @param {Object} options
|
||||
* - {Array} filters
|
||||
* - {Boolean} twoWay
|
||||
* - {Boolean} deep
|
||||
* - {Boolean} user
|
||||
* - {Boolean} sync
|
||||
* - {Boolean} lazy
|
||||
* - {Function} [preProcess]
|
||||
* - {Function} [postProcess]
|
||||
* @constructor
|
||||
*/
|
||||
|
||||
export default function Watcher (vm, expOrFn, cb, options) {
|
||||
// mix in options
|
||||
if (options) {
|
||||
extend(this, options)
|
||||
}
|
||||
var isFn = typeof expOrFn === 'function'
|
||||
this.vm = vm
|
||||
vm._watchers.push(this)
|
||||
this.expression = expOrFn
|
||||
this.cb = cb
|
||||
this.id = ++uid // uid for batching
|
||||
this.active = true
|
||||
this.dirty = this.lazy // for lazy watchers
|
||||
this.deps = []
|
||||
this.newDeps = []
|
||||
this.depIds = Object.create(null)
|
||||
this.newDepIds = null
|
||||
this.prevError = null // for async error stacks
|
||||
// parse expression for getter/setter
|
||||
if (isFn) {
|
||||
this.getter = expOrFn
|
||||
this.setter = undefined
|
||||
} else {
|
||||
warn('vue-lite only supports watching functions.')
|
||||
}
|
||||
this.value = this.lazy
|
||||
? undefined
|
||||
: this.get()
|
||||
// state for avoiding false triggers for deep and Array
|
||||
// watchers during vm._digest()
|
||||
this.queued = this.shallow = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the getter, and re-collect dependencies.
|
||||
*/
|
||||
|
||||
Watcher.prototype.get = function () {
|
||||
this.beforeGet()
|
||||
var scope = this.scope || this.vm
|
||||
var value
|
||||
try {
|
||||
value = this.getter.call(scope, scope)
|
||||
} catch (e) {
|
||||
if (
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
config.warnExpressionErrors
|
||||
) {
|
||||
warn(
|
||||
'Error when evaluating expression ' +
|
||||
'"' + this.expression + '": ' + e.toString(),
|
||||
this.vm
|
||||
)
|
||||
}
|
||||
}
|
||||
// "touch" every property so they are all tracked as
|
||||
// dependencies for deep watching
|
||||
if (this.deep) {
|
||||
traverse(value)
|
||||
}
|
||||
if (this.preProcess) {
|
||||
value = this.preProcess(value)
|
||||
}
|
||||
if (this.filters) {
|
||||
value = scope._applyFilters(value, null, this.filters, false)
|
||||
}
|
||||
if (this.postProcess) {
|
||||
value = this.postProcess(value)
|
||||
}
|
||||
this.afterGet()
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the corresponding value with the setter.
|
||||
*
|
||||
* @param {*} value
|
||||
*/
|
||||
|
||||
Watcher.prototype.set = function (value) {
|
||||
var scope = this.scope || this.vm
|
||||
if (this.filters) {
|
||||
value = scope._applyFilters(
|
||||
value, this.value, this.filters, true)
|
||||
}
|
||||
try {
|
||||
this.setter.call(scope, scope, value)
|
||||
} catch (e) {
|
||||
if (
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
config.warnExpressionErrors
|
||||
) {
|
||||
warn(
|
||||
'Error when evaluating setter ' +
|
||||
'"' + this.expression + '": ' + e.toString(),
|
||||
this.vm
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare for dependency collection.
|
||||
*/
|
||||
|
||||
Watcher.prototype.beforeGet = function () {
|
||||
Dep.target = this
|
||||
this.newDepIds = Object.create(null)
|
||||
this.newDeps.length = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a dependency to this directive.
|
||||
*
|
||||
* @param {Dep} dep
|
||||
*/
|
||||
|
||||
Watcher.prototype.addDep = function (dep) {
|
||||
var id = dep.id
|
||||
if (!this.newDepIds[id]) {
|
||||
this.newDepIds[id] = true
|
||||
this.newDeps.push(dep)
|
||||
if (!this.depIds[id]) {
|
||||
dep.addSub(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up for dependency collection.
|
||||
*/
|
||||
|
||||
Watcher.prototype.afterGet = function () {
|
||||
Dep.target = null
|
||||
var i = this.deps.length
|
||||
while (i--) {
|
||||
var dep = this.deps[i]
|
||||
if (!this.newDepIds[dep.id]) {
|
||||
dep.removeSub(this)
|
||||
}
|
||||
}
|
||||
this.depIds = this.newDepIds
|
||||
var tmp = this.deps
|
||||
this.deps = this.newDeps
|
||||
this.newDeps = tmp
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscriber interface.
|
||||
* Will be called when a dependency changes.
|
||||
*
|
||||
* @param {Boolean} shallow
|
||||
*/
|
||||
|
||||
Watcher.prototype.update = function (shallow) {
|
||||
if (this.lazy) {
|
||||
this.dirty = true
|
||||
} else if (this.sync || !config.async) {
|
||||
this.run()
|
||||
} else {
|
||||
// if queued, only overwrite shallow with non-shallow,
|
||||
// but not the other way around.
|
||||
this.shallow = this.queued
|
||||
? shallow
|
||||
? this.shallow
|
||||
: false
|
||||
: !!shallow
|
||||
this.queued = true
|
||||
// record before-push error stack in debug mode
|
||||
/* istanbul ignore if */
|
||||
if (process.env.NODE_ENV !== 'production' && config.debug) {
|
||||
this.prevError = new Error('[vue] async stack trace')
|
||||
}
|
||||
pushWatcher(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batcher job interface.
|
||||
* Will be called by the batcher.
|
||||
*/
|
||||
|
||||
Watcher.prototype.run = function () {
|
||||
if (this.active) {
|
||||
var value = this.get()
|
||||
if (
|
||||
value !== this.value ||
|
||||
// Deep watchers and watchers on Object/Arrays should fire even
|
||||
// when the value is the same, because the value may
|
||||
// have mutated; but only do so if this is a
|
||||
// non-shallow update (caused by a vm digest).
|
||||
((isObject(value) || this.deep) && !this.shallow)
|
||||
) {
|
||||
// set new value
|
||||
var oldValue = this.value
|
||||
this.value = value
|
||||
// in debug + async mode, when a watcher callbacks
|
||||
// throws, we also throw the saved before-push error
|
||||
// so the full cross-tick stack trace is available.
|
||||
var prevError = this.prevError
|
||||
/* istanbul ignore if */
|
||||
if (process.env.NODE_ENV !== 'production' &&
|
||||
config.debug && prevError) {
|
||||
this.prevError = null
|
||||
try {
|
||||
this.cb.call(this.vm, value, oldValue)
|
||||
} catch (e) {
|
||||
nextTick(function () {
|
||||
throw prevError
|
||||
}, 0)
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
this.cb.call(this.vm, value, oldValue)
|
||||
}
|
||||
}
|
||||
this.queued = this.shallow = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the value of the watcher.
|
||||
* This only gets called for lazy watchers.
|
||||
*/
|
||||
|
||||
Watcher.prototype.evaluate = function () {
|
||||
// avoid overwriting another watcher that is being
|
||||
// collected.
|
||||
var current = Dep.target
|
||||
this.value = this.get()
|
||||
this.dirty = false
|
||||
Dep.target = current
|
||||
}
|
||||
|
||||
/**
|
||||
* Depend on all deps collected by this watcher.
|
||||
*/
|
||||
|
||||
Watcher.prototype.depend = function () {
|
||||
var i = this.deps.length
|
||||
while (i--) {
|
||||
this.deps[i].depend()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove self from all dependencies' subcriber list.
|
||||
*/
|
||||
|
||||
Watcher.prototype.teardown = function () {
|
||||
if (this.active) {
|
||||
// remove self from vm's watcher list
|
||||
// this is a somewhat expensive operation so we skip it
|
||||
// if the vm is being destroyed or is performing a v-for
|
||||
// re-render (the watcher list is then filtered by v-for).
|
||||
if (!this.vm._isBeingDestroyed && !this.vm._vForRemoving) {
|
||||
this.vm._watchers.$remove(this)
|
||||
}
|
||||
var i = this.deps.length
|
||||
while (i--) {
|
||||
this.deps[i].removeSub(this)
|
||||
}
|
||||
this.active = false
|
||||
this.vm = this.cb = this.value = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recrusively traverse an object to evoke all converted
|
||||
* getters, so that every nested property inside the object
|
||||
* is collected as a "deep" dependency.
|
||||
*
|
||||
* @param {*} val
|
||||
*/
|
||||
|
||||
function traverse (val) {
|
||||
var i, keys
|
||||
if (isArray(val)) {
|
||||
i = val.length
|
||||
while (i--) traverse(val[i])
|
||||
} else if (isObject(val)) {
|
||||
keys = Object.keys(val)
|
||||
i = keys.length
|
||||
while (i--) traverse(val[keys[i]])
|
||||
}
|
||||
}
|
89
src/util/component.js
Normal file
89
src/util/component.js
Normal file
@ -0,0 +1,89 @@
|
||||
import { warn } from './debug'
|
||||
import { resolveAsset } from './options'
|
||||
import { getAttr, getBindAttr } from './dom'
|
||||
|
||||
export const commonTagRE = /^(div|p|span|img|a|b|i|br|ul|ol|li|h1|h2|h3|h4|h5|h6|code|pre|table|th|td|tr|form|label|input|select|option|nav|article|section|header|footer)$/i
|
||||
export const reservedTagRE = /^(slot|partial|component)$/i
|
||||
|
||||
let isUnknownElement
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
isUnknownElement = function (el, tag) {
|
||||
if (tag.indexOf('-') > -1) {
|
||||
// http://stackoverflow.com/a/28210364/1070244
|
||||
return (
|
||||
el.constructor === window.HTMLUnknownElement ||
|
||||
el.constructor === window.HTMLElement
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
/HTMLUnknownElement/.test(el.toString()) &&
|
||||
// Chrome returns unknown for several HTML5 elements.
|
||||
// https://code.google.com/p/chromium/issues/detail?id=540526
|
||||
!/^(data|time|rtc|rb)$/.test(tag)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an element is a component, if yes return its
|
||||
* component id.
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {Object} options
|
||||
* @return {Object|undefined}
|
||||
*/
|
||||
|
||||
export function checkComponentAttr (el, options) {
|
||||
var tag = el.tagName.toLowerCase()
|
||||
var hasAttrs = el.hasAttributes()
|
||||
if (!commonTagRE.test(tag) && !reservedTagRE.test(tag)) {
|
||||
if (resolveAsset(options, 'components', tag)) {
|
||||
return { id: tag }
|
||||
} else {
|
||||
var is = hasAttrs && getIsBinding(el)
|
||||
if (is) {
|
||||
return is
|
||||
} else if (process.env.NODE_ENV !== 'production') {
|
||||
var expectedTag =
|
||||
options._componentNameMap &&
|
||||
options._componentNameMap[tag]
|
||||
if (expectedTag) {
|
||||
warn(
|
||||
'Unknown custom element: <' + tag + '> - ' +
|
||||
'did you mean <' + expectedTag + '>? ' +
|
||||
'HTML is case-insensitive, remember to use kebab-case in templates.'
|
||||
)
|
||||
} else if (isUnknownElement(el, tag)) {
|
||||
warn(
|
||||
'Unknown custom element: <' + tag + '> - did you ' +
|
||||
'register the component correctly? For recursive components, ' +
|
||||
'make sure to provide the "name" option.'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (hasAttrs) {
|
||||
return getIsBinding(el)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get "is" binding from an element.
|
||||
*
|
||||
* @param {Element} el
|
||||
* @return {Object|undefined}
|
||||
*/
|
||||
|
||||
function getIsBinding (el) {
|
||||
// dynamic syntax
|
||||
var exp = getAttr(el, 'is')
|
||||
if (exp != null) {
|
||||
return { id: exp }
|
||||
} else {
|
||||
exp = getBindAttr(el, 'is')
|
||||
if (exp != null) {
|
||||
return { id: exp, dynamic: true }
|
||||
}
|
||||
}
|
||||
}
|
24
src/util/debug.js
Normal file
24
src/util/debug.js
Normal file
@ -0,0 +1,24 @@
|
||||
import config from '../config'
|
||||
import { hyphenate } from './lang'
|
||||
|
||||
let warn
|
||||
let formatComponentName
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const hasConsole = typeof console !== 'undefined'
|
||||
|
||||
warn = (msg, vm) => {
|
||||
if (hasConsole && (!config.silent)) {
|
||||
console.error('[Vue warn]: ' + msg + (vm ? formatComponentName(vm) : ''))
|
||||
}
|
||||
}
|
||||
|
||||
formatComponentName = vm => {
|
||||
var name = vm._isVue ? vm.$options.name : vm.name
|
||||
return name
|
||||
? ' (found in component: <' + hyphenate(name) + '>)'
|
||||
: ''
|
||||
}
|
||||
}
|
||||
|
||||
export { warn }
|
388
src/util/dom.js
Normal file
388
src/util/dom.js
Normal file
@ -0,0 +1,388 @@
|
||||
import config from '../config'
|
||||
import { isIE9 } from './env'
|
||||
import { warn } from './debug'
|
||||
import { camelize } from './lang'
|
||||
|
||||
/**
|
||||
* Query an element selector if it's not an element already.
|
||||
*
|
||||
* @param {String|Element} el
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
export function query (el) {
|
||||
if (typeof el === 'string') {
|
||||
var selector = el
|
||||
el = document.querySelector(el)
|
||||
if (!el) {
|
||||
process.env.NODE_ENV !== 'production' && warn(
|
||||
'Cannot find element: ' + selector
|
||||
)
|
||||
}
|
||||
}
|
||||
return el
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a node is in the document.
|
||||
* Note: document.documentElement.contains should work here
|
||||
* but always returns false for comment nodes in phantomjs,
|
||||
* making unit tests difficult. This is fixed by doing the
|
||||
* contains() check on the node's parentNode instead of
|
||||
* the node itself.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
export function inDoc (node) {
|
||||
var doc = document.documentElement
|
||||
var parent = node && node.parentNode
|
||||
return doc === node ||
|
||||
doc === parent ||
|
||||
!!(parent && parent.nodeType === 1 && (doc.contains(parent)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get and remove an attribute from a node.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @param {String} _attr
|
||||
*/
|
||||
|
||||
export function getAttr (node, _attr) {
|
||||
var val = node.getAttribute(_attr)
|
||||
if (val !== null) {
|
||||
node.removeAttribute(_attr)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an attribute with colon or v-bind: prefix.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @param {String} name
|
||||
* @return {String|null}
|
||||
*/
|
||||
|
||||
export function getBindAttr (node, name) {
|
||||
var val = getAttr(node, ':' + name)
|
||||
if (val === null) {
|
||||
val = getAttr(node, 'v-bind:' + name)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the presence of a bind attribute.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @param {String} name
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
export function hasBindAttr (node, name) {
|
||||
return node.hasAttribute(name) ||
|
||||
node.hasAttribute(':' + name) ||
|
||||
node.hasAttribute('v-bind:' + name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert el before target
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {Element} target
|
||||
*/
|
||||
|
||||
export function before (el, target) {
|
||||
target.parentNode.insertBefore(el, target)
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert el after target
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {Element} target
|
||||
*/
|
||||
|
||||
export function after (el, target) {
|
||||
if (target.nextSibling) {
|
||||
before(el, target.nextSibling)
|
||||
} else {
|
||||
target.parentNode.appendChild(el)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove el from DOM
|
||||
*
|
||||
* @param {Element} el
|
||||
*/
|
||||
|
||||
export function remove (el) {
|
||||
el.parentNode.removeChild(el)
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend el to target
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {Element} target
|
||||
*/
|
||||
|
||||
export function prepend (el, target) {
|
||||
if (target.firstChild) {
|
||||
before(el, target.firstChild)
|
||||
} else {
|
||||
target.appendChild(el)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace target with el
|
||||
*
|
||||
* @param {Element} target
|
||||
* @param {Element} el
|
||||
*/
|
||||
|
||||
export function replace (target, el) {
|
||||
var parent = target.parentNode
|
||||
if (parent) {
|
||||
parent.replaceChild(el, target)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add event listener shorthand.
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {String} event
|
||||
* @param {Function} cb
|
||||
* @param {Boolean} [useCapture]
|
||||
*/
|
||||
|
||||
export function on (el, event, cb, useCapture) {
|
||||
el.addEventListener(event, cb, useCapture)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove event listener shorthand.
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {String} event
|
||||
* @param {Function} cb
|
||||
*/
|
||||
|
||||
export function off (el, event, cb) {
|
||||
el.removeEventListener(event, cb)
|
||||
}
|
||||
|
||||
/**
|
||||
* For IE9 compat: when both class and :class are present
|
||||
* getAttribute('class') returns wrong value...
|
||||
*
|
||||
* @param {Element} el
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
function getClass (el) {
|
||||
var classname = el.className
|
||||
if (typeof classname === 'object') {
|
||||
classname = classname.baseVal || ''
|
||||
}
|
||||
return classname
|
||||
}
|
||||
|
||||
/**
|
||||
* In IE9, setAttribute('class') will result in empty class
|
||||
* if the element also has the :class attribute; However in
|
||||
* PhantomJS, setting `className` does not work on SVG elements...
|
||||
* So we have to do a conditional check here.
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {String} cls
|
||||
*/
|
||||
|
||||
export function setClass (el, cls) {
|
||||
/* istanbul ignore if */
|
||||
if (isIE9 && !/svg$/.test(el.namespaceURI)) {
|
||||
el.className = cls
|
||||
} else {
|
||||
el.setAttribute('class', cls)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add class with compatibility for IE & SVG
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {String} cls
|
||||
*/
|
||||
|
||||
export function addClass (el, cls) {
|
||||
if (el.classList) {
|
||||
el.classList.add(cls)
|
||||
} else {
|
||||
var cur = ' ' + getClass(el) + ' '
|
||||
if (cur.indexOf(' ' + cls + ' ') < 0) {
|
||||
setClass(el, (cur + cls).trim())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove class with compatibility for IE & SVG
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {String} cls
|
||||
*/
|
||||
|
||||
export function removeClass (el, cls) {
|
||||
if (el.classList) {
|
||||
el.classList.remove(cls)
|
||||
} else {
|
||||
var cur = ' ' + getClass(el) + ' '
|
||||
var tar = ' ' + cls + ' '
|
||||
while (cur.indexOf(tar) >= 0) {
|
||||
cur = cur.replace(tar, ' ')
|
||||
}
|
||||
setClass(el, cur.trim())
|
||||
}
|
||||
if (!el.className) {
|
||||
el.removeAttribute('class')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract raw content inside an element into a temporary
|
||||
* container div
|
||||
*
|
||||
* @param {Element} el
|
||||
* @param {Boolean} asFragment
|
||||
* @return {Element|DocumentFragment}
|
||||
*/
|
||||
|
||||
export function extractContent (el, asFragment) {
|
||||
var child
|
||||
var rawContent
|
||||
/* istanbul ignore if */
|
||||
if (isTemplate(el) && isFragment(el.content)) {
|
||||
el = el.content
|
||||
}
|
||||
if (el.hasChildNodes()) {
|
||||
trimNode(el)
|
||||
rawContent = asFragment
|
||||
? document.createDocumentFragment()
|
||||
: document.createElement('div')
|
||||
/* eslint-disable no-cond-assign */
|
||||
while (child = el.firstChild) {
|
||||
/* eslint-enable no-cond-assign */
|
||||
rawContent.appendChild(child)
|
||||
}
|
||||
}
|
||||
return rawContent
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim possible empty head/tail text and comment
|
||||
* nodes inside a parent.
|
||||
*
|
||||
* @param {Node} node
|
||||
*/
|
||||
|
||||
export function trimNode (node) {
|
||||
var child
|
||||
/* eslint-disable no-sequences */
|
||||
while (child = node.firstChild, isTrimmable(child)) {
|
||||
node.removeChild(child)
|
||||
}
|
||||
while (child = node.lastChild, isTrimmable(child)) {
|
||||
node.removeChild(child)
|
||||
}
|
||||
/* eslint-enable no-sequences */
|
||||
}
|
||||
|
||||
function isTrimmable (node) {
|
||||
return node && (
|
||||
(node.nodeType === 3 && !node.data.trim()) ||
|
||||
node.nodeType === 8
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an element is a template tag.
|
||||
* Note if the template appears inside an SVG its tagName
|
||||
* will be in lowercase.
|
||||
*
|
||||
* @param {Element} el
|
||||
*/
|
||||
|
||||
export function isTemplate (el) {
|
||||
return el.tagName &&
|
||||
el.tagName.toLowerCase() === 'template'
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an "anchor" for performing dom insertion/removals.
|
||||
* This is used in a number of scenarios:
|
||||
* - fragment instance
|
||||
* - v-html
|
||||
* - v-if
|
||||
* - v-for
|
||||
* - component
|
||||
*
|
||||
* @param {String} content
|
||||
* @param {Boolean} persist - IE trashes empty textNodes on
|
||||
* cloneNode(true), so in certain
|
||||
* cases the anchor needs to be
|
||||
* non-empty to be persisted in
|
||||
* templates.
|
||||
* @return {Comment|Text}
|
||||
*/
|
||||
|
||||
export function createAnchor (content, persist) {
|
||||
var anchor = config.debug
|
||||
? document.createComment(content)
|
||||
: document.createTextNode(persist ? ' ' : '')
|
||||
anchor.__v_anchor = true
|
||||
return anchor
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a component ref attribute that starts with $.
|
||||
*
|
||||
* @param {Element} node
|
||||
* @return {String|undefined}
|
||||
*/
|
||||
|
||||
var refRE = /^v-ref:/
|
||||
export function findRef (node) {
|
||||
if (node.hasAttributes()) {
|
||||
var attrs = node.attributes
|
||||
for (var i = 0, l = attrs.length; i < l; i++) {
|
||||
var name = attrs[i].name
|
||||
if (refRE.test(name)) {
|
||||
return camelize(name.replace(refRE, ''))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get outerHTML of elements, taking care
|
||||
* of SVG elements in IE as well.
|
||||
*
|
||||
* @param {Element} el
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
export function getOuterHTML (el) {
|
||||
if (el.outerHTML) {
|
||||
return el.outerHTML
|
||||
} else {
|
||||
var container = document.createElement('div')
|
||||
container.appendChild(el.cloneNode(true))
|
||||
return container.innerHTML
|
||||
}
|
||||
}
|
106
src/util/env.js
Normal file
106
src/util/env.js
Normal file
@ -0,0 +1,106 @@
|
||||
/* global MutationObserver */
|
||||
|
||||
// can we use __proto__?
|
||||
export const hasProto = '__proto__' in {}
|
||||
|
||||
// Browser environment sniffing
|
||||
export const inBrowser =
|
||||
typeof window !== 'undefined' &&
|
||||
Object.prototype.toString.call(window) !== '[object Object]'
|
||||
|
||||
// detect devtools
|
||||
export const devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__
|
||||
|
||||
// UA sniffing for working around browser-specific quirks
|
||||
const UA = inBrowser && window.navigator.userAgent.toLowerCase()
|
||||
export const isIE9 = UA && UA.indexOf('msie 9.0') > 0
|
||||
export const isAndroid = UA && UA.indexOf('android') > 0
|
||||
|
||||
let transitionProp
|
||||
let transitionEndEvent
|
||||
let animationProp
|
||||
let animationEndEvent
|
||||
|
||||
// Transition property/event sniffing
|
||||
if (inBrowser && !isIE9) {
|
||||
const isWebkitTrans =
|
||||
window.ontransitionend === undefined &&
|
||||
window.onwebkittransitionend !== undefined
|
||||
const isWebkitAnim =
|
||||
window.onanimationend === undefined &&
|
||||
window.onwebkitanimationend !== undefined
|
||||
transitionProp = isWebkitTrans
|
||||
? 'WebkitTransition'
|
||||
: 'transition'
|
||||
transitionEndEvent = isWebkitTrans
|
||||
? 'webkitTransitionEnd'
|
||||
: 'transitionend'
|
||||
animationProp = isWebkitAnim
|
||||
? 'WebkitAnimation'
|
||||
: 'animation'
|
||||
animationEndEvent = isWebkitAnim
|
||||
? 'webkitAnimationEnd'
|
||||
: 'animationend'
|
||||
}
|
||||
|
||||
export {
|
||||
transitionProp,
|
||||
transitionEndEvent,
|
||||
animationProp,
|
||||
animationEndEvent
|
||||
}
|
||||
|
||||
/**
|
||||
* Defer a task to execute it asynchronously. Ideally this
|
||||
* should be executed as a microtask, so we leverage
|
||||
* MutationObserver if it's available, and fallback to
|
||||
* setTimeout(0).
|
||||
*
|
||||
* @param {Function} cb
|
||||
* @param {Object} ctx
|
||||
*/
|
||||
|
||||
export const nextTick = (function () {
|
||||
var callbacks = []
|
||||
var pending = false
|
||||
var timerFunc
|
||||
function nextTickHandler () {
|
||||
pending = false
|
||||
var copies = callbacks.slice(0)
|
||||
callbacks = []
|
||||
for (var i = 0; i < copies.length; i++) {
|
||||
copies[i]()
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore if */
|
||||
if (typeof MutationObserver !== 'undefined') {
|
||||
var counter = 1
|
||||
var observer = new MutationObserver(nextTickHandler)
|
||||
var textNode = document.createTextNode(counter)
|
||||
observer.observe(textNode, {
|
||||
characterData: true
|
||||
})
|
||||
timerFunc = function () {
|
||||
counter = (counter + 1) % 2
|
||||
textNode.data = counter
|
||||
}
|
||||
} else {
|
||||
// webpack attempts to inject a shim for setImmediate
|
||||
// if it is used as a global, so we have to work around that to
|
||||
// avoid bundling unnecessary code.
|
||||
const context = inBrowser
|
||||
? window
|
||||
: typeof global !== 'undefined' ? global : {}
|
||||
timerFunc = context.setImmediate || setTimeout
|
||||
}
|
||||
return function (cb, ctx) {
|
||||
var func = ctx
|
||||
? function () { cb.call(ctx) }
|
||||
: cb
|
||||
callbacks.push(func)
|
||||
if (pending) return
|
||||
pending = true
|
||||
timerFunc(nextTickHandler, 0)
|
||||
}
|
||||
})()
|
7
src/util/index.js
Normal file
7
src/util/index.js
Normal file
@ -0,0 +1,7 @@
|
||||
export * from './lang'
|
||||
export * from './env'
|
||||
export * from './dom'
|
||||
export * from './options'
|
||||
export * from './component'
|
||||
export * from './debug'
|
||||
export { defineReactive } from '../observer/index'
|
407
src/util/lang.js
Normal file
407
src/util/lang.js
Normal file
@ -0,0 +1,407 @@
|
||||
export function isPrimitive (s) {
|
||||
return typeof s === 'string' || typeof s === 'number'
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a property on an object. Adds the new property and
|
||||
* triggers change notification if the property doesn't
|
||||
* already exist.
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @param {String} key
|
||||
* @param {*} val
|
||||
* @public
|
||||
*/
|
||||
|
||||
export function set (obj, key, val) {
|
||||
if (hasOwn(obj, key)) {
|
||||
obj[key] = val
|
||||
return
|
||||
}
|
||||
if (obj._isVue) {
|
||||
set(obj._data, key, val)
|
||||
return
|
||||
}
|
||||
var ob = obj.__ob__
|
||||
if (!ob) {
|
||||
obj[key] = val
|
||||
return
|
||||
}
|
||||
ob.convert(key, val)
|
||||
ob.dep.notify()
|
||||
if (ob.vms) {
|
||||
var i = ob.vms.length
|
||||
while (i--) {
|
||||
var vm = ob.vms[i]
|
||||
vm._proxy(key)
|
||||
vm._digest()
|
||||
}
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a property and trigger change if necessary.
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @param {String} key
|
||||
*/
|
||||
|
||||
export function del (obj, key) {
|
||||
if (!hasOwn(obj, key)) {
|
||||
return
|
||||
}
|
||||
delete obj[key]
|
||||
var ob = obj.__ob__
|
||||
if (!ob) {
|
||||
return
|
||||
}
|
||||
ob.dep.notify()
|
||||
if (ob.vms) {
|
||||
var i = ob.vms.length
|
||||
while (i--) {
|
||||
var vm = ob.vms[i]
|
||||
vm._unproxy(key)
|
||||
vm._digest()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var hasOwnProperty = Object.prototype.hasOwnProperty
|
||||
/**
|
||||
* Check whether the object has the property.
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @param {String} key
|
||||
* @return {Boolean}
|
||||
*/
|
||||
export function hasOwn (obj, key) {
|
||||
return hasOwnProperty.call(obj, key)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an expression is a literal value.
|
||||
*
|
||||
* @param {String} exp
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
var literalValueRE = /^\s?(true|false|-?[\d\.]+|'[^']*'|"[^"]*")\s?$/
|
||||
export function isLiteral (exp) {
|
||||
return literalValueRE.test(exp)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string starts with $ or _
|
||||
*
|
||||
* @param {String} str
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
export function isReserved (str) {
|
||||
var c = (str + '').charCodeAt(0)
|
||||
return c === 0x24 || c === 0x5F
|
||||
}
|
||||
|
||||
/**
|
||||
* Guard text output, make sure undefined outputs
|
||||
* empty string
|
||||
*
|
||||
* @param {*} value
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
export function _toString (value) {
|
||||
return value == null
|
||||
? ''
|
||||
: value.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Check and convert possible numeric strings to numbers
|
||||
* before setting back to data
|
||||
*
|
||||
* @param {*} value
|
||||
* @return {*|Number}
|
||||
*/
|
||||
|
||||
export function toNumber (value) {
|
||||
if (typeof value !== 'string') {
|
||||
return value
|
||||
} else {
|
||||
var parsed = Number(value)
|
||||
return isNaN(parsed)
|
||||
? value
|
||||
: parsed
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert string boolean literals into real booleans.
|
||||
*
|
||||
* @param {*} value
|
||||
* @return {*|Boolean}
|
||||
*/
|
||||
|
||||
export function toBoolean (value) {
|
||||
return value === 'true'
|
||||
? true
|
||||
: value === 'false'
|
||||
? false
|
||||
: value
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip quotes from a string
|
||||
*
|
||||
* @param {String} str
|
||||
* @return {String | false}
|
||||
*/
|
||||
|
||||
export function stripQuotes (str) {
|
||||
var a = str.charCodeAt(0)
|
||||
var b = str.charCodeAt(str.length - 1)
|
||||
return a === b && (a === 0x22 || a === 0x27)
|
||||
? str.slice(1, -1)
|
||||
: str
|
||||
}
|
||||
|
||||
/**
|
||||
* Camelize a hyphen-delmited string.
|
||||
*
|
||||
* @param {String} str
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
var camelizeRE = /-(\w)/g
|
||||
export function camelize (str) {
|
||||
return str.replace(camelizeRE, toUpper)
|
||||
}
|
||||
|
||||
function toUpper (_, c) {
|
||||
return c ? c.toUpperCase() : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* Hyphenate a camelCase string.
|
||||
*
|
||||
* @param {String} str
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
var hyphenateRE = /([a-z\d])([A-Z])/g
|
||||
export function hyphenate (str) {
|
||||
return str
|
||||
.replace(hyphenateRE, '$1-$2')
|
||||
.toLowerCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts hyphen/underscore/slash delimitered names into
|
||||
* camelized classNames.
|
||||
*
|
||||
* e.g. my-component => MyComponent
|
||||
* some_else => SomeElse
|
||||
* some/comp => SomeComp
|
||||
*
|
||||
* @param {String} str
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
var classifyRE = /(?:^|[-_\/])(\w)/g
|
||||
export function classify (str) {
|
||||
return str.replace(classifyRE, toUpper)
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple bind, faster than native
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @param {Object} ctx
|
||||
* @return {Function}
|
||||
*/
|
||||
|
||||
export function bind (fn, ctx) {
|
||||
return function (a) {
|
||||
var l = arguments.length
|
||||
return l
|
||||
? l > 1
|
||||
? fn.apply(ctx, arguments)
|
||||
: fn.call(ctx, a)
|
||||
: fn.call(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an Array-like object to a real Array.
|
||||
*
|
||||
* @param {Array-like} list
|
||||
* @param {Number} [start] - start index
|
||||
* @return {Array}
|
||||
*/
|
||||
|
||||
export function toArray (list, start) {
|
||||
start = start || 0
|
||||
var i = list.length - start
|
||||
var ret = new Array(i)
|
||||
while (i--) {
|
||||
ret[i] = list[i + start]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Mix properties into target object.
|
||||
*
|
||||
* @param {Object} to
|
||||
* @param {Object} from
|
||||
*/
|
||||
|
||||
export function extend (to, from) {
|
||||
var keys = Object.keys(from)
|
||||
var i = keys.length
|
||||
while (i--) {
|
||||
to[keys[i]] = from[keys[i]]
|
||||
}
|
||||
return to
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick object check - this is primarily used to tell
|
||||
* Objects from primitive values when we know the value
|
||||
* is a JSON-compliant type.
|
||||
*
|
||||
* @param {*} obj
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
export function isObject (obj) {
|
||||
return obj !== null && typeof obj === 'object'
|
||||
}
|
||||
|
||||
/**
|
||||
* Strict object type check. Only returns true
|
||||
* for plain JavaScript objects.
|
||||
*
|
||||
* @param {*} obj
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
var toString = Object.prototype.toString
|
||||
var OBJECT_STRING = '[object Object]'
|
||||
export function isPlainObject (obj) {
|
||||
return toString.call(obj) === OBJECT_STRING
|
||||
}
|
||||
|
||||
/**
|
||||
* Array type check.
|
||||
*
|
||||
* @param {*} obj
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
export const isArray = Array.isArray
|
||||
|
||||
/**
|
||||
* Define a property.
|
||||
*
|
||||
* @param {Object} obj
|
||||
* @param {String} key
|
||||
* @param {*} val
|
||||
* @param {Boolean} [enumerable]
|
||||
*/
|
||||
|
||||
export function def (obj, key, val, enumerable) {
|
||||
Object.defineProperty(obj, key, {
|
||||
value: val,
|
||||
enumerable: !!enumerable,
|
||||
writable: true,
|
||||
configurable: true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Debounce a function so it only gets called after the
|
||||
* input stops arriving after the given wait period.
|
||||
*
|
||||
* @param {Function} func
|
||||
* @param {Number} wait
|
||||
* @return {Function} - the debounced function
|
||||
*/
|
||||
|
||||
export function debounce (func, wait) {
|
||||
var timeout, args, context, timestamp, result
|
||||
var later = function () {
|
||||
var last = Date.now() - timestamp
|
||||
if (last < wait && last >= 0) {
|
||||
timeout = setTimeout(later, wait - last)
|
||||
} else {
|
||||
timeout = null
|
||||
result = func.apply(context, args)
|
||||
if (!timeout) context = args = null
|
||||
}
|
||||
}
|
||||
return function () {
|
||||
context = this
|
||||
args = arguments
|
||||
timestamp = Date.now()
|
||||
if (!timeout) {
|
||||
timeout = setTimeout(later, wait)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manual indexOf because it's slightly faster than
|
||||
* native.
|
||||
*
|
||||
* @param {Array} arr
|
||||
* @param {*} obj
|
||||
*/
|
||||
|
||||
export function indexOf (arr, obj) {
|
||||
var i = arr.length
|
||||
while (i--) {
|
||||
if (arr[i] === obj) return i
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a cancellable version of an async callback.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Function}
|
||||
*/
|
||||
|
||||
export function cancellable (fn) {
|
||||
var cb = function () {
|
||||
if (!cb.cancelled) {
|
||||
return fn.apply(this, arguments)
|
||||
}
|
||||
}
|
||||
cb.cancel = function () {
|
||||
cb.cancelled = true
|
||||
}
|
||||
return cb
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if two values are loosely equal - that is,
|
||||
* if they are plain objects, do they have the same shape?
|
||||
*
|
||||
* @param {*} a
|
||||
* @param {*} b
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
export function looseEqual (a, b) {
|
||||
/* eslint-disable eqeqeq */
|
||||
return a == b || (
|
||||
isObject(a) && isObject(b)
|
||||
? JSON.stringify(a) === JSON.stringify(b)
|
||||
: false
|
||||
)
|
||||
/* eslint-enable eqeqeq */
|
||||
}
|
381
src/util/options.js
Normal file
381
src/util/options.js
Normal file
@ -0,0 +1,381 @@
|
||||
import Vue from '../instance/index'
|
||||
import config from '../config'
|
||||
import {
|
||||
extend,
|
||||
set,
|
||||
isObject,
|
||||
isArray,
|
||||
isPlainObject,
|
||||
hasOwn,
|
||||
camelize,
|
||||
hyphenate
|
||||
} from './lang'
|
||||
import { warn } from './debug'
|
||||
import { commonTagRE, reservedTagRE } from './component'
|
||||
|
||||
/**
|
||||
* Option overwriting strategies are functions that handle
|
||||
* how to merge a parent option value and a child option
|
||||
* value into the final value.
|
||||
*
|
||||
* All strategy functions follow the same signature:
|
||||
*
|
||||
* @param {*} parentVal
|
||||
* @param {*} childVal
|
||||
* @param {Vue} [vm]
|
||||
*/
|
||||
|
||||
var strats = config.optionMergeStrategies = Object.create(null)
|
||||
|
||||
/**
|
||||
* Helper that recursively merges two data objects together.
|
||||
*/
|
||||
|
||||
function mergeData (to, from) {
|
||||
var key, toVal, fromVal
|
||||
for (key in from) {
|
||||
toVal = to[key]
|
||||
fromVal = from[key]
|
||||
if (!hasOwn(to, key)) {
|
||||
set(to, key, fromVal)
|
||||
} else if (isObject(toVal) && isObject(fromVal)) {
|
||||
mergeData(toVal, fromVal)
|
||||
}
|
||||
}
|
||||
return to
|
||||
}
|
||||
|
||||
/**
|
||||
* Data
|
||||
*/
|
||||
|
||||
strats.data = function (parentVal, childVal, vm) {
|
||||
if (!vm) {
|
||||
// in a Vue.extend merge, both should be functions
|
||||
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
|
||||
}
|
||||
// when parentVal & childVal are both present,
|
||||
// we need to return a function that returns the
|
||||
// merged result of both functions... no need to
|
||||
// check if parentVal is a function here because
|
||||
// it has to be a function to pass previous merges.
|
||||
return function mergedDataFn () {
|
||||
return mergeData(
|
||||
childVal.call(this),
|
||||
parentVal.call(this)
|
||||
)
|
||||
}
|
||||
} else if (parentVal || childVal) {
|
||||
return function mergedInstanceDataFn () {
|
||||
// instance merge
|
||||
var instanceData = typeof childVal === 'function'
|
||||
? childVal.call(vm)
|
||||
: childVal
|
||||
var defaultData = typeof parentVal === 'function'
|
||||
? parentVal.call(vm)
|
||||
: undefined
|
||||
if (instanceData) {
|
||||
return mergeData(instanceData, defaultData)
|
||||
} else {
|
||||
return defaultData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* El
|
||||
*/
|
||||
|
||||
strats.el = function (parentVal, childVal, vm) {
|
||||
if (!vm && childVal && typeof childVal !== 'function') {
|
||||
process.env.NODE_ENV !== 'production' && warn(
|
||||
'The "el" option should be a function ' +
|
||||
'that returns a per-instance value in component ' +
|
||||
'definitions.',
|
||||
vm
|
||||
)
|
||||
return
|
||||
}
|
||||
var ret = childVal || parentVal
|
||||
// invoke the element factory if this is instance merge
|
||||
return vm && typeof ret === 'function'
|
||||
? ret.call(vm)
|
||||
: ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks and param attributes are merged as arrays.
|
||||
*/
|
||||
|
||||
strats.init =
|
||||
strats.created =
|
||||
strats.ready =
|
||||
strats.attached =
|
||||
strats.detached =
|
||||
strats.beforeCompile =
|
||||
strats.compiled =
|
||||
strats.beforeDestroy =
|
||||
strats.destroyed =
|
||||
strats.activate = function (parentVal, childVal) {
|
||||
return childVal
|
||||
? parentVal
|
||||
? parentVal.concat(childVal)
|
||||
: isArray(childVal)
|
||||
? childVal
|
||||
: [childVal]
|
||||
: parentVal
|
||||
}
|
||||
|
||||
/**
|
||||
* Assets
|
||||
*
|
||||
* When a vm is present (instance creation), we need to do
|
||||
* a three-way merge between constructor options, instance
|
||||
* options and parent options.
|
||||
*/
|
||||
|
||||
function mergeAssets (parentVal, childVal) {
|
||||
var res = Object.create(parentVal)
|
||||
return childVal
|
||||
? extend(res, guardArrayAssets(childVal))
|
||||
: res
|
||||
}
|
||||
|
||||
config._assetTypes.forEach(function (type) {
|
||||
strats[type + 's'] = mergeAssets
|
||||
})
|
||||
|
||||
/**
|
||||
* Events & Watchers.
|
||||
*
|
||||
* Events & watchers hashes should not overwrite one
|
||||
* another, so we merge them as arrays.
|
||||
*/
|
||||
|
||||
strats.watch =
|
||||
strats.events = function (parentVal, childVal) {
|
||||
if (!childVal) return parentVal
|
||||
if (!parentVal) return childVal
|
||||
var ret = {}
|
||||
extend(ret, parentVal)
|
||||
for (var key in childVal) {
|
||||
var parent = ret[key]
|
||||
var child = childVal[key]
|
||||
if (parent && !isArray(parent)) {
|
||||
parent = [parent]
|
||||
}
|
||||
ret[key] = parent
|
||||
? parent.concat(child)
|
||||
: [child]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Other object hashes.
|
||||
*/
|
||||
|
||||
strats.props =
|
||||
strats.methods =
|
||||
strats.computed = function (parentVal, childVal) {
|
||||
if (!childVal) return parentVal
|
||||
if (!parentVal) return childVal
|
||||
var ret = Object.create(null)
|
||||
extend(ret, parentVal)
|
||||
extend(ret, childVal)
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Default strategy.
|
||||
*/
|
||||
|
||||
var defaultStrat = function (parentVal, childVal) {
|
||||
return childVal === undefined
|
||||
? parentVal
|
||||
: childVal
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure component options get converted to actual
|
||||
* constructors.
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
|
||||
function guardComponents (options) {
|
||||
if (options.components) {
|
||||
var components = options.components =
|
||||
guardArrayAssets(options.components)
|
||||
var ids = Object.keys(components)
|
||||
var def
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
var map = options._componentNameMap = {}
|
||||
}
|
||||
for (var i = 0, l = ids.length; i < l; i++) {
|
||||
var key = ids[i]
|
||||
if (commonTagRE.test(key) || reservedTagRE.test(key)) {
|
||||
process.env.NODE_ENV !== 'production' && warn(
|
||||
'Do not use built-in or reserved HTML elements as component ' +
|
||||
'id: ' + key
|
||||
)
|
||||
continue
|
||||
}
|
||||
// record a all lowercase <-> kebab-case mapping for
|
||||
// possible custom element case error warning
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
map[key.replace(/-/g, '').toLowerCase()] = hyphenate(key)
|
||||
}
|
||||
def = components[key]
|
||||
if (isPlainObject(def)) {
|
||||
components[key] = Vue.extend(def)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure all props option syntax are normalized into the
|
||||
* Object-based format.
|
||||
*
|
||||
* @param {Object} options
|
||||
*/
|
||||
|
||||
function guardProps (options) {
|
||||
var props = options.props
|
||||
var i, val
|
||||
if (isArray(props)) {
|
||||
options.props = {}
|
||||
i = props.length
|
||||
while (i--) {
|
||||
val = props[i]
|
||||
if (typeof val === 'string') {
|
||||
options.props[val] = null
|
||||
} else if (val.name) {
|
||||
options.props[val.name] = val
|
||||
}
|
||||
}
|
||||
} else if (isPlainObject(props)) {
|
||||
var keys = Object.keys(props)
|
||||
i = keys.length
|
||||
while (i--) {
|
||||
val = props[keys[i]]
|
||||
if (typeof val === 'function') {
|
||||
props[keys[i]] = { type: val }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Guard an Array-format assets option and converted it
|
||||
* into the key-value Object format.
|
||||
*
|
||||
* @param {Object|Array} assets
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function guardArrayAssets (assets) {
|
||||
if (isArray(assets)) {
|
||||
var res = {}
|
||||
var i = assets.length
|
||||
var asset
|
||||
while (i--) {
|
||||
asset = assets[i]
|
||||
var id = typeof asset === 'function'
|
||||
? ((asset.options && asset.options.name) || asset.id)
|
||||
: (asset.name || asset.id)
|
||||
if (!id) {
|
||||
process.env.NODE_ENV !== 'production' && warn(
|
||||
'Array-syntax assets must provide a "name" or "id" field.'
|
||||
)
|
||||
} else {
|
||||
res[id] = asset
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
return assets
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge two option objects into a new one.
|
||||
* Core utility used in both instantiation and inheritance.
|
||||
*
|
||||
* @param {Object} parent
|
||||
* @param {Object} child
|
||||
* @param {Vue} [vm] - if vm is present, indicates this is
|
||||
* an instantiation merge.
|
||||
*/
|
||||
|
||||
export function mergeOptions (parent, child, vm) {
|
||||
guardComponents(child)
|
||||
guardProps(child)
|
||||
var options = {}
|
||||
var key
|
||||
if (child.mixins) {
|
||||
for (var i = 0, l = child.mixins.length; i < l; i++) {
|
||||
parent = mergeOptions(parent, child.mixins[i], vm)
|
||||
}
|
||||
}
|
||||
for (key in parent) {
|
||||
mergeField(key)
|
||||
}
|
||||
for (key in child) {
|
||||
if (!hasOwn(parent, key)) {
|
||||
mergeField(key)
|
||||
}
|
||||
}
|
||||
function mergeField (key) {
|
||||
var strat = strats[key] || defaultStrat
|
||||
options[key] = strat(parent[key], child[key], vm, key)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an asset.
|
||||
* This function is used because child instances need access
|
||||
* to assets defined in its ancestor chain.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {String} type
|
||||
* @param {String} id
|
||||
* @param {Boolean} warnMissing
|
||||
* @return {Object|Function}
|
||||
*/
|
||||
|
||||
export function resolveAsset (options, type, id, warnMissing) {
|
||||
/* istanbul ignore if */
|
||||
if (typeof id !== 'string') {
|
||||
return
|
||||
}
|
||||
var assets = options[type]
|
||||
var camelizedId
|
||||
var res = assets[id] ||
|
||||
// camelCase ID
|
||||
assets[camelizedId = camelize(id)] ||
|
||||
// Pascal Case ID
|
||||
assets[camelizedId.charAt(0).toUpperCase() + camelizedId.slice(1)]
|
||||
if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
|
||||
warn(
|
||||
'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
|
||||
options
|
||||
)
|
||||
}
|
||||
return res
|
||||
}
|
39
src/vdom/dom.js
Normal file
39
src/vdom/dom.js
Normal file
@ -0,0 +1,39 @@
|
||||
export function createElement(tagName){
|
||||
return document.createElement(tagName)
|
||||
}
|
||||
|
||||
export function createElementNS(namespaceURI, qualifiedName){
|
||||
return document.createElementNS(namespaceURI, qualifiedName)
|
||||
}
|
||||
|
||||
export function createTextNode(text){
|
||||
return document.createTextNode(text)
|
||||
}
|
||||
|
||||
export function insertBefore(parentNode, newNode, referenceNode){
|
||||
parentNode.insertBefore(newNode, referenceNode)
|
||||
}
|
||||
|
||||
export function removeChild(node, child){
|
||||
node.removeChild(child)
|
||||
}
|
||||
|
||||
export function appendChild(node, child){
|
||||
node.appendChild(child)
|
||||
}
|
||||
|
||||
export function parentNode(node){
|
||||
return node.parentElement
|
||||
}
|
||||
|
||||
export function nextSibling(node){
|
||||
return node.nextSibling
|
||||
}
|
||||
|
||||
export function tagName(node){
|
||||
return node.tagName
|
||||
}
|
||||
|
||||
export function setTextContent(node, text){
|
||||
node.textContent = text
|
||||
}
|
33
src/vdom/h.js
Normal file
33
src/vdom/h.js
Normal file
@ -0,0 +1,33 @@
|
||||
import VNode from './vnode'
|
||||
import { isPrimitive, isArray } from '../util/index'
|
||||
|
||||
function addNS(data, children) {
|
||||
data.ns = 'http://www.w3.org/2000/svg'
|
||||
if (children !== undefined) {
|
||||
for (var i = 0; i < children.length; ++i) {
|
||||
addNS(children[i].data, children[i].children)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default function h (tag, b, c) {
|
||||
var data = {}, children, text, i
|
||||
if (arguments.length === 3) {
|
||||
data = b
|
||||
if (isArray(c)) { children = c }
|
||||
else if (isPrimitive(c)) { text = c }
|
||||
} else if (arguments.length === 2) {
|
||||
if (isArray(b)) { children = b }
|
||||
else if (isPrimitive(b)) { text = b }
|
||||
else { data = b }
|
||||
}
|
||||
if (isArray(children)) {
|
||||
for (i = 0; i < children.length; ++i) {
|
||||
if (isPrimitive(children[i])) children[i] = VNode(undefined, undefined, undefined, children[i])
|
||||
}
|
||||
}
|
||||
if (tag === 'svg') {
|
||||
addNS(data, children)
|
||||
}
|
||||
return VNode(tag, data, children, text, undefined)
|
||||
}
|
17
src/vdom/index.js
Normal file
17
src/vdom/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
import createPatchFunction from './patch'
|
||||
import h from './h'
|
||||
import _class from './modules/class'
|
||||
import style from './modules/style'
|
||||
import props from './modules/props'
|
||||
import attrs from './modules/attrs'
|
||||
import events from './modules/events'
|
||||
|
||||
const patch = createPatchFunction([
|
||||
_class, // makes it easy to toggle classes
|
||||
props,
|
||||
style,
|
||||
attrs,
|
||||
events
|
||||
])
|
||||
|
||||
export { patch, h }
|
42
src/vdom/modules/attrs.js
Normal file
42
src/vdom/modules/attrs.js
Normal file
@ -0,0 +1,42 @@
|
||||
var booleanAttrs = ["allowfullscreen", "async", "autofocus", "autoplay", "checked", "compact", "controls", "declare",
|
||||
"default", "defaultchecked", "defaultmuted", "defaultselected", "defer", "disabled", "draggable",
|
||||
"enabled", "formnovalidate", "hidden", "indeterminate", "inert", "ismap", "itemscope", "loop", "multiple",
|
||||
"muted", "nohref", "noresize", "noshade", "novalidate", "nowrap", "open", "pauseonexit", "readonly",
|
||||
"required", "reversed", "scoped", "seamless", "selected", "sortable", "spellcheck", "translate",
|
||||
"truespeed", "typemustmatch", "visible"]
|
||||
|
||||
var booleanAttrsDict = {}
|
||||
for(var i=0, len = booleanAttrs.length; i < len; i++) {
|
||||
booleanAttrsDict[booleanAttrs[i]] = true
|
||||
}
|
||||
|
||||
function updateAttrs(oldVnode, vnode) {
|
||||
var key, cur, old, elm = vnode.elm,
|
||||
oldAttrs = oldVnode.data.attrs || {}, attrs = vnode.data.attrs || {}
|
||||
|
||||
// update modified attributes, add new attributes
|
||||
for (key in attrs) {
|
||||
cur = attrs[key]
|
||||
old = oldAttrs[key]
|
||||
if (old !== cur) {
|
||||
// TODO: add support to namespaced attributes (setAttributeNS)
|
||||
if(!cur && booleanAttrsDict[key])
|
||||
elm.removeAttribute(key)
|
||||
else
|
||||
elm.setAttribute(key, cur)
|
||||
}
|
||||
}
|
||||
//remove removed attributes
|
||||
// use `in` operator since the previous `for` iteration uses it (.i.e. add even attributes with undefined value)
|
||||
// the other option is to remove all attributes with value == undefined
|
||||
for (key in oldAttrs) {
|
||||
if (!(key in attrs)) {
|
||||
elm.removeAttribute(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
create: updateAttrs,
|
||||
update: updateAttrs
|
||||
}
|
12
src/vdom/modules/class.js
Normal file
12
src/vdom/modules/class.js
Normal file
@ -0,0 +1,12 @@
|
||||
import { setClass } from '../../util/index'
|
||||
|
||||
function updateClass (oldVnode, vnode) {
|
||||
if (vnode.data.class !== undefined) {
|
||||
setClass(vnode.elm, vnode.data.class || '')
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
init: updateClass,
|
||||
update: updateClass
|
||||
}
|
42
src/vdom/modules/events.js
Normal file
42
src/vdom/modules/events.js
Normal file
@ -0,0 +1,42 @@
|
||||
function arrInvoker(arr) {
|
||||
return function() {
|
||||
// Special case when length is two, for performance
|
||||
arr.length === 2 ? arr[0](arr[1]) : arr[0].apply(undefined, arr.slice(1))
|
||||
}
|
||||
}
|
||||
|
||||
function fnInvoker(o) {
|
||||
return function(ev) { o.fn(ev) }
|
||||
}
|
||||
|
||||
function updateEventListeners(oldVnode, vnode) {
|
||||
var name, cur, old, elm = vnode.elm,
|
||||
oldOn = oldVnode.data.on || {}, on = vnode.data.on
|
||||
if (!on) return
|
||||
for (name in on) {
|
||||
cur = on[name]
|
||||
old = oldOn[name]
|
||||
if (old === undefined) {
|
||||
if (Array.isArray(cur)) {
|
||||
elm.addEventListener(name, arrInvoker(cur))
|
||||
} else {
|
||||
cur = {fn: cur}
|
||||
on[name] = cur
|
||||
elm.addEventListener(name, fnInvoker(cur))
|
||||
}
|
||||
} else if (Array.isArray(old)) {
|
||||
// Deliberately modify old array since it's captured in closure created with `arrInvoker`
|
||||
old.length = cur.length
|
||||
for (var i = 0; i < old.length; ++i) old[i] = cur[i]
|
||||
on[name] = old
|
||||
} else {
|
||||
old.fn = cur
|
||||
on[name] = old
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
create: updateEventListeners,
|
||||
update: updateEventListeners
|
||||
}
|
21
src/vdom/modules/props.js
Normal file
21
src/vdom/modules/props.js
Normal file
@ -0,0 +1,21 @@
|
||||
function updateProps(oldVnode, vnode) {
|
||||
var key, cur, old, elm = vnode.elm,
|
||||
oldProps = oldVnode.data.props || {}, props = vnode.data.props || {}
|
||||
for (key in oldProps) {
|
||||
if (!props[key]) {
|
||||
delete elm[key]
|
||||
}
|
||||
}
|
||||
for (key in props) {
|
||||
cur = props[key]
|
||||
old = oldProps[key]
|
||||
if (old !== cur && (key !== 'value' || elm[key] !== cur)) {
|
||||
elm[key] = cur
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
create: updateProps,
|
||||
update: updateProps
|
||||
}
|
73
src/vdom/modules/style.js
Normal file
73
src/vdom/modules/style.js
Normal file
@ -0,0 +1,73 @@
|
||||
// TODO:
|
||||
// - remove animation related bits
|
||||
// - include prefix sniffing of v-bind:style
|
||||
|
||||
var raf = (typeof window !== 'undefined' && window.requestAnimationFrame) || setTimeout
|
||||
var nextFrame = function(fn) { raf(function() { raf(fn) }) }
|
||||
|
||||
function setNextFrame(obj, prop, val) {
|
||||
nextFrame(function() { obj[prop] = val })
|
||||
}
|
||||
|
||||
function updateStyle(oldVnode, vnode) {
|
||||
var cur, name, elm = vnode.elm,
|
||||
oldStyle = oldVnode.data.style || {},
|
||||
style = vnode.data.style || {},
|
||||
oldHasDel = 'delayed' in oldStyle
|
||||
for (name in oldStyle) {
|
||||
if (!style[name]) {
|
||||
elm.style[name] = ''
|
||||
}
|
||||
}
|
||||
for (name in style) {
|
||||
cur = style[name]
|
||||
if (name === 'delayed') {
|
||||
for (name in style.delayed) {
|
||||
cur = style.delayed[name]
|
||||
if (!oldHasDel || cur !== oldStyle.delayed[name]) {
|
||||
setNextFrame(elm.style, name, cur)
|
||||
}
|
||||
}
|
||||
} else if (name !== 'remove' && cur !== oldStyle[name]) {
|
||||
elm.style[name] = cur
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyDestroyStyle(vnode) {
|
||||
var style, name, elm = vnode.elm, s = vnode.data.style
|
||||
if (!s || !(style = s.destroy)) return
|
||||
for (name in style) {
|
||||
elm.style[name] = style[name]
|
||||
}
|
||||
}
|
||||
|
||||
function applyRemoveStyle(vnode, rm) {
|
||||
var s = vnode.data.style
|
||||
if (!s || !s.remove) {
|
||||
rm()
|
||||
return
|
||||
}
|
||||
var name, elm = vnode.elm, idx, i = 0, maxDur = 0,
|
||||
compStyle, style = s.remove, amount = 0, applied = []
|
||||
for (name in style) {
|
||||
applied.push(name)
|
||||
elm.style[name] = style[name]
|
||||
}
|
||||
compStyle = getComputedStyle(elm)
|
||||
var props = compStyle['transition-property'].split(', ')
|
||||
for (; i < props.length; ++i) {
|
||||
if(applied.indexOf(props[i]) !== -1) amount++
|
||||
}
|
||||
elm.addEventListener('transitionend', function(ev) {
|
||||
if (ev.target === elm) --amount
|
||||
if (amount === 0) rm()
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
create: updateStyle,
|
||||
update: updateStyle,
|
||||
destroy: applyDestroyStyle,
|
||||
remove: applyRemoveStyle
|
||||
}
|
258
src/vdom/patch.js
Normal file
258
src/vdom/patch.js
Normal file
@ -0,0 +1,258 @@
|
||||
import VNode from './vnode'
|
||||
import * as dom from './dom'
|
||||
import { isPrimitive } from '../util/index'
|
||||
|
||||
const emptyNode = VNode('', {}, [], undefined, undefined)
|
||||
const hooks = ['create', 'update', 'remove', 'destroy', 'pre', 'post']
|
||||
|
||||
function isUndef (s) {
|
||||
return s === undefined
|
||||
}
|
||||
|
||||
function isDef (s) {
|
||||
return s !== undefined
|
||||
}
|
||||
|
||||
function sameVnode (vnode1, vnode2) {
|
||||
return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel
|
||||
}
|
||||
|
||||
function createKeyToOldIdx (children, beginIdx, endIdx) {
|
||||
var i, map = {}, key
|
||||
for (i = beginIdx; i <= endIdx; ++i) {
|
||||
key = children[i].key
|
||||
if (isDef(key)) map[key] = i
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
export default function createPatchFunction (modules, api) {
|
||||
var i, j, cbs = {}
|
||||
|
||||
if (isUndef(api)) api = dom
|
||||
|
||||
for (i = 0; i < hooks.length; ++i) {
|
||||
cbs[hooks[i]] = []
|
||||
for (j = 0; j < modules.length; ++j) {
|
||||
if (modules[j][hooks[i]] !== undefined) cbs[hooks[i]].push(modules[j][hooks[i]])
|
||||
}
|
||||
}
|
||||
|
||||
function emptyNodeAt (elm) {
|
||||
return VNode(api.tagName(elm).toLowerCase(), {}, [], undefined, elm)
|
||||
}
|
||||
|
||||
function createRmCb (childElm, listeners) {
|
||||
return function() {
|
||||
if (--listeners === 0) {
|
||||
var parent = api.parentNode(childElm)
|
||||
api.removeChild(parent, childElm)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createElm (vnode, insertedVnodeQueue) {
|
||||
var i, thunk, data = vnode.data
|
||||
if (isDef(data)) {
|
||||
if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode)
|
||||
if (isDef(i = data.vnode)) {
|
||||
thunk = vnode
|
||||
vnode = i
|
||||
}
|
||||
}
|
||||
var elm, children = vnode.children, tag = vnode.sel
|
||||
if (isDef(tag)) {
|
||||
elm = vnode.elm = isDef(data) && isDef(i = data.ns)
|
||||
? api.createElementNS(i, tag)
|
||||
: api.createElement(tag)
|
||||
if (Array.isArray(children)) {
|
||||
for (i = 0; i < children.length; ++i) {
|
||||
api.appendChild(elm, createElm(children[i], insertedVnodeQueue))
|
||||
}
|
||||
} else if (isPrimitive(vnode.text)) {
|
||||
api.appendChild(elm, api.createTextNode(vnode.text))
|
||||
}
|
||||
for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode)
|
||||
i = vnode.data.hook // Reuse variable
|
||||
if (isDef(i)) {
|
||||
if (i.create) i.create(emptyNode, vnode)
|
||||
if (i.insert) insertedVnodeQueue.push(vnode)
|
||||
}
|
||||
} else {
|
||||
elm = vnode.elm = api.createTextNode(vnode.text)
|
||||
}
|
||||
if (isDef(thunk)) thunk.elm = vnode.elm
|
||||
return vnode.elm
|
||||
}
|
||||
|
||||
function addVnodes (parentElm, before, vnodes, startIdx, endIdx, insertedVnodeQueue) {
|
||||
for (; startIdx <= endIdx; ++startIdx) {
|
||||
api.insertBefore(parentElm, createElm(vnodes[startIdx], insertedVnodeQueue), before)
|
||||
}
|
||||
}
|
||||
|
||||
function invokeDestroyHook (vnode) {
|
||||
var i, j, data = vnode.data
|
||||
if (isDef(data)) {
|
||||
if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)
|
||||
for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)
|
||||
if (isDef(i = vnode.children)) {
|
||||
for (j = 0; j < vnode.children.length; ++j) {
|
||||
invokeDestroyHook(vnode.children[j])
|
||||
}
|
||||
}
|
||||
if (isDef(i = data.vnode)) invokeDestroyHook(i)
|
||||
}
|
||||
}
|
||||
|
||||
function removeVnodes (parentElm, vnodes, startIdx, endIdx) {
|
||||
for (; startIdx <= endIdx; ++startIdx) {
|
||||
var i, listeners, rm, ch = vnodes[startIdx]
|
||||
if (isDef(ch)) {
|
||||
if (isDef(ch.sel)) {
|
||||
invokeDestroyHook(ch)
|
||||
listeners = cbs.remove.length + 1
|
||||
rm = createRmCb(ch.elm, listeners)
|
||||
for (i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm)
|
||||
if (isDef(i = ch.data) && isDef(i = i.hook) && isDef(i = i.remove)) {
|
||||
i(ch, rm)
|
||||
} else {
|
||||
rm()
|
||||
}
|
||||
} else { // Text node
|
||||
api.removeChild(parentElm, ch.elm)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue) {
|
||||
var oldStartIdx = 0, newStartIdx = 0
|
||||
var oldEndIdx = oldCh.length - 1
|
||||
var oldStartVnode = oldCh[0]
|
||||
var oldEndVnode = oldCh[oldEndIdx]
|
||||
var newEndIdx = newCh.length - 1
|
||||
var newStartVnode = newCh[0]
|
||||
var newEndVnode = newCh[newEndIdx]
|
||||
var oldKeyToIdx, idxInOld, elmToMove, before
|
||||
|
||||
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
|
||||
if (isUndef(oldStartVnode)) {
|
||||
oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
|
||||
} else if (isUndef(oldEndVnode)) {
|
||||
oldEndVnode = oldCh[--oldEndIdx]
|
||||
} else if (sameVnode(oldStartVnode, newStartVnode)) {
|
||||
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
|
||||
oldStartVnode = oldCh[++oldStartIdx]
|
||||
newStartVnode = newCh[++newStartIdx]
|
||||
} else if (sameVnode(oldEndVnode, newEndVnode)) {
|
||||
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
|
||||
oldEndVnode = oldCh[--oldEndIdx]
|
||||
newEndVnode = newCh[--newEndIdx]
|
||||
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
|
||||
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
|
||||
api.insertBefore(parentElm, oldStartVnode.elm, api.nextSibling(oldEndVnode.elm))
|
||||
oldStartVnode = oldCh[++oldStartIdx]
|
||||
newEndVnode = newCh[--newEndIdx]
|
||||
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
|
||||
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
|
||||
api.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
|
||||
oldEndVnode = oldCh[--oldEndIdx]
|
||||
newStartVnode = newCh[++newStartIdx]
|
||||
} else {
|
||||
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
|
||||
idxInOld = oldKeyToIdx[newStartVnode.key]
|
||||
if (isUndef(idxInOld)) { // New element
|
||||
api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm)
|
||||
newStartVnode = newCh[++newStartIdx]
|
||||
} else {
|
||||
elmToMove = oldCh[idxInOld]
|
||||
patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
|
||||
oldCh[idxInOld] = undefined
|
||||
api.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm)
|
||||
newStartVnode = newCh[++newStartIdx]
|
||||
}
|
||||
}
|
||||
}
|
||||
if (oldStartIdx > oldEndIdx) {
|
||||
before = isUndef(newCh[newEndIdx+1]) ? null : newCh[newEndIdx+1].elm
|
||||
addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
|
||||
} else if (newStartIdx > newEndIdx) {
|
||||
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
|
||||
}
|
||||
}
|
||||
|
||||
function patchVnode (oldVnode, vnode, insertedVnodeQueue) {
|
||||
var i, hook
|
||||
if (isDef(i = vnode.data) && isDef(hook = i.hook) && isDef(i = hook.prepatch)) {
|
||||
i(oldVnode, vnode)
|
||||
}
|
||||
if (isDef(i = oldVnode.data) && isDef(i = i.vnode)) oldVnode = i
|
||||
if (isDef(i = vnode.data) && isDef(i = i.vnode)) {
|
||||
patchVnode(oldVnode, i, insertedVnodeQueue)
|
||||
vnode.elm = i.elm
|
||||
return
|
||||
}
|
||||
var elm = vnode.elm = oldVnode.elm, oldCh = oldVnode.children, ch = vnode.children
|
||||
if (oldVnode === vnode) return
|
||||
if (!sameVnode(oldVnode, vnode)) {
|
||||
var parentElm = api.parentNode(oldVnode.elm)
|
||||
elm = createElm(vnode, insertedVnodeQueue)
|
||||
api.insertBefore(parentElm, elm, oldVnode.elm)
|
||||
removeVnodes(parentElm, [oldVnode], 0, 0)
|
||||
return
|
||||
}
|
||||
if (isDef(vnode.data)) {
|
||||
for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
|
||||
i = vnode.data.hook
|
||||
if (isDef(i) && isDef(i = i.update)) i(oldVnode, vnode)
|
||||
}
|
||||
if (isUndef(vnode.text)) {
|
||||
if (isDef(oldCh) && isDef(ch)) {
|
||||
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue)
|
||||
} else if (isDef(ch)) {
|
||||
if (isDef(oldVnode.text)) api.setTextContent(elm, '')
|
||||
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
|
||||
} else if (isDef(oldCh)) {
|
||||
removeVnodes(elm, oldCh, 0, oldCh.length - 1)
|
||||
} else if (isDef(oldVnode.text)) {
|
||||
api.setTextContent(elm, '')
|
||||
}
|
||||
} else if (oldVnode.text !== vnode.text) {
|
||||
api.setTextContent(elm, vnode.text)
|
||||
}
|
||||
if (isDef(hook) && isDef(i = hook.postpatch)) {
|
||||
i(oldVnode, vnode)
|
||||
}
|
||||
}
|
||||
|
||||
return function patch (oldVnode, vnode) {
|
||||
var i, elm, parent
|
||||
var insertedVnodeQueue = []
|
||||
for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]()
|
||||
|
||||
if (isUndef(oldVnode.sel)) {
|
||||
oldVnode = emptyNodeAt(oldVnode)
|
||||
}
|
||||
|
||||
if (sameVnode(oldVnode, vnode)) {
|
||||
patchVnode(oldVnode, vnode, insertedVnodeQueue)
|
||||
} else {
|
||||
elm = oldVnode.elm
|
||||
parent = api.parentNode(elm)
|
||||
|
||||
createElm(vnode, insertedVnodeQueue)
|
||||
|
||||
if (parent !== null) {
|
||||
api.insertBefore(parent, vnode.elm, api.nextSibling(elm))
|
||||
removeVnodes(parent, [oldVnode], 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < insertedVnodeQueue.length; ++i) {
|
||||
insertedVnodeQueue[i].data.hook.insert(insertedVnodeQueue[i])
|
||||
}
|
||||
for (i = 0; i < cbs.post.length; ++i) cbs.post[i]()
|
||||
return vnode
|
||||
}
|
||||
}
|
4
src/vdom/vnode.js
Normal file
4
src/vdom/vnode.js
Normal file
@ -0,0 +1,4 @@
|
||||
export default function VNode (sel, data, children, text, elm) {
|
||||
const key = data === undefined ? undefined : data.key
|
||||
return { sel, data, children, text, elm, key }
|
||||
}
|
16
webpack.config.js
Normal file
16
webpack.config.js
Normal file
@ -0,0 +1,16 @@
|
||||
var path = require('path')
|
||||
|
||||
module.exports = {
|
||||
entry: path.resolve(__dirname, 'src/index.umd.js'),
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'vue.js',
|
||||
library: 'Vue',
|
||||
libraryTarget: 'umd'
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{ test: /\.js/, loader: 'babel', exclude: /node_modules/ }
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user