modified .gitignore

This commit is contained in:
TLRZ Seyfferth
2017-09-13 10:20:31 +02:00
parent e1db4792aa
commit cf899d0675
411 changed files with 23733 additions and 67956 deletions
Generated Vendored
+75 -20
View File
@@ -6,23 +6,31 @@
<a href="https://www.npmjs.com/package/vue"><img src="https://img.shields.io/npm/dm/vue.svg" alt="Downloads"></a>
<a href="https://www.npmjs.com/package/vue"><img src="https://img.shields.io/npm/v/vue.svg" alt="Version"></a>
<a href="https://www.npmjs.com/package/vue"><img src="https://img.shields.io/npm/l/vue.svg" alt="License"></a>
<a href="https://chat.vuejs.org/"><img src="https://img.shields.io/badge/chat-on%20discord-7289da.svg" alt="Chat">
<br>
<a href="https://saucelabs.com/u/vuejs"><img src="https://saucelabs.com/browser-matrix/vuejs.svg" alt="Sauce Test Status"></a>
</p>
## Supporting Vue.js
Vue.js is an MIT-licensed open source project. Its ongoing development is made possible thanks to the support by these awesome [backers](https://github.com/vuejs/vue/blob/dev/BACKERS.md). If you'd like to join them, check out [Vue.js' Patreon campaign](https://www.patreon.com/evanyou).
Vue.js is an MIT-licensed open source project. It's an independent project with its ongoing development made possible entirely thanks to the support by these awesome [backers](https://github.com/vuejs/vue/blob/dev/BACKERS.md). If you'd like to join them, please consider:
- [Become a backer or sponsor on Patreon](https://www.patreon.com/evanyou).
- [Become a backer or sponsor on OpenCollective](https://opencollective.com/vuejs).
#### What's the difference between Patreon and OpenCollective?
Funds donated via Patreon goes directly to support Evan You's full-time work on Vue.js. Funds donated via OpenCollective are managed with transparent expenses and will be used for compensating work and expenses by core team members or sponsoring community events. Your name/logo will receive proper recognition and exposure by donating on either platform.
### Sponsors via Patreon
<p align="center">
<b>Special thanks to the generous sponsorship by:</b>
<br><br>
<a href="https://stdlib.com">
<img width="240px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/stdlib.png">
</a>
<br><br>
<a href="https://www.upyun.com/?utm_source=vue&utm_medium=ad&utm_content=github">
<img width="160px" src="https://raw.githubusercontent.com/vuejs/cn.vuejs.org/master/themes/vue/source/images/upyun-small.png">
<a href="https://xiaozhuanlan.com">
<img width="160px" src="https://raw.githubusercontent.com/vuejs/cn.vuejs.org/master/themes/vue/source/images/xiaozhuanlan.png">
</a>
<br><br>
<a href="https://deepstreamhub.com" target="_blank">
@@ -53,10 +61,6 @@ Vue.js is an MIT-licensed open source project. Its ongoing development is made p
<img width="120px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/monterail.png">
</a>
<br><br>
<a href="https://www.trisoft.ro/" target="_blank">
<img width="120px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/trisoft.png">
</a>
<br><br>
<a href="https://www.2mhost.com/" target="_blank">
<img width="120px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/2mhost.png">
</a>
@@ -69,19 +73,62 @@ Vue.js is an MIT-licensed open source project. Its ongoing development is made p
<img width="120px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/tmvuejs2.png">
</a>
<br><br>
<a href="https://famebroker.com" target="_blank">
<img width="130px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/famebroker.png">
</a>
<br><br>
<a href="https://fancygrid.com" target="_blank">
<img width="120px" src="http://fancygrid.com/logo/logo.png">
</a>
<br><br>
<a href="https://component.io/" target="_blank">
<img width="130px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/component_io.png">
</a>
<br><br>
<a href="https://www.v2ex.com/t/379389" target="_blank">
<img width="130px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/v2exer.png">
</a>
</p>
### Sponsors via OpenCollective
#### Platinum
<a href="https://opencollective.com/vuejs/platinumsponsor/0/website" target="_blank"><img src="https://opencollective.com/vuejs/platinumsponsor/0/avatar.svg"></a>
#### Gold
<a href="https://opencollective.com/vuejs/goldsponsor/0/website" target="_blank"><img src="https://opencollective.com/vuejs/goldsponsor/0/avatar.svg"></a>
#### Silver
<a href="https://opencollective.com/vuejs/silversponsor/0/website" target="_blank"><img src="https://opencollective.com/vuejs/silversponsor/0/avatar.svg"></a>
#### Bronze
<a href="https://opencollective.com/vuejs/sponsor/0/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/1/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/2/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/3/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/4/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/5/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/6/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/7/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/8/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/9/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/9/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/10/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/10/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/11/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/11/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/12/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/12/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/13/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/13/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/14/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/14/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/15/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/15/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/16/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/16/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/17/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/17/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/18/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/18/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/19/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/19/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/20/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/20/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/21/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/21/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/22/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/22/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/23/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/23/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/24/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/24/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/25/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/25/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/26/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/26/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/27/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/27/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/28/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/28/avatar.svg"></a>
<a href="https://opencollective.com/vuejs/sponsor/29/website" target="_blank"><img src="https://opencollective.com/vuejs/sponsor/29/avatar.svg"></a>
## Intro
Vue.js is a library for building interactive web interfaces. It provides data-reactive components with a simple and flexible API. Core features include:
@@ -92,11 +139,15 @@ Vue.js is a library for building interactive web interfaces. It provides data-re
- [Flexible transition effect system](https://vuejs.org/guide/transitions.html)
- Fast without the need for complex optimization
Note that Vue.js only supports [ES5-compliant browsers](http://kangax.github.io/compat-table/es5/) (IE8 and below are not supported). To check out live examples and docs, visit [vuejs.org](https://vuejs.org).
Note that Vue.js only supports [ES5-compliant browsers](http://kangax.github.io/compat-table/es5/) (IE8 and below are not supported).
## Documentation
To check out live examples and docs, visit [vuejs.org](https://vuejs.org).
## Questions
For questions and support please use the [Gitter chat room](https://gitter.im/vuejs/vue) or [the official forum](http://forum.vuejs.org). The issue list of this repo is **exclusively** for bug reports and feature requests.
For questions and support please use the [the official forum](http://forum.vuejs.org) or [community chat](https://chat.vuejs.org/). The issue list of this repo is **exclusively** for bug reports and feature requests.
## Issues
@@ -106,9 +157,13 @@ Please make sure to read the [Issue Reporting Checklist](https://github.com/vuej
Please make sure to read the [Contributing Guide](https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md) before making a pull request. If you have a Vue-related project/component/tool, add it with a pull-request to [this curated list](https://github.com/vuejs/awesome-vue)!
Thank you to all the people who already contributed to Vue!
<a href="https://github.com/vuejs/vue/graphs/contributors"><img src="https://opencollective.com/vuejs/contributors.svg?width=890" /></a>
## Changelog
Details changes for each release are documented in the [release notes](https://github.com/vuejs/vue/releases).
Detailed changes for each release are documented in the [release notes](https://github.com/vuejs/vue/releases).
## Stay In Touch
+1171 -670
View File
File diff suppressed because it is too large Load Diff
+1171 -670
View File
File diff suppressed because it is too large Load Diff
+1168 -670
View File
File diff suppressed because it is too large Load Diff
+2 -4
View File
File diff suppressed because one or more lines are too long
+700 -291
View File
File diff suppressed because it is too large Load Diff
+700 -291
View File
File diff suppressed because it is too large Load Diff
+697 -291
View File
File diff suppressed because it is too large Load Diff
+2 -3
View File
File diff suppressed because one or more lines are too long
+83 -107
View File
@@ -1,151 +1,125 @@
{
"_args": [
[
{
"raw": "vue@^2.1.10",
"scope": null,
"escapedName": "vue",
"name": "vue",
"rawSpec": "^2.1.10",
"spec": ">=2.1.10 <3.0.0",
"type": "range"
},
"c:\\xampp\\htdocs\\laravel"
]
],
"_from": "vue@>=2.1.10 <3.0.0",
"_id": "vue@2.3.4",
"_inCache": true,
"_from": "vue@2.4.3",
"_id": "vue@2.4.3",
"_inBundle": false,
"_integrity": "sha512-k6zkIBR0KsE0DLUDGdRLooX/4iRUbc3T2FyrJs4YhVySbjGwS3k5c2HRCHyXo6lg1aeAF9rg3uiJDRz0J7nbDA==",
"_location": "/vue",
"_nodeVersion": "8.0.0",
"_npmOperationalInternal": {
"host": "s3://npm-registry-packages",
"tmp": "tmp/vue-2.3.4.tgz_1496897690369_0.5197095186449587"
},
"_npmUser": {
"name": "yyx990803",
"email": "yyx990803@gmail.com"
},
"_npmVersion": "5.0.0",
"_phantomChildren": {},
"_requested": {
"raw": "vue@^2.1.10",
"scope": null,
"escapedName": "vue",
"type": "version",
"registry": true,
"raw": "vue@2.4.3",
"name": "vue",
"rawSpec": "^2.1.10",
"spec": ">=2.1.10 <3.0.0",
"type": "range"
"escapedName": "vue",
"rawSpec": "2.4.3",
"saveSpec": null,
"fetchSpec": "2.4.3"
},
"_requiredBy": [
"#DEV:/"
"#DEV:/",
"#USER"
],
"_resolved": "https://registry.npmjs.org/vue/-/vue-2.3.4.tgz",
"_shasum": "5ec3b87a191da8090bbef56b7cfabd4158038171",
"_shrinkwrap": null,
"_spec": "vue@^2.1.10",
"_where": "c:\\xampp\\htdocs\\laravel",
"_resolved": "https://registry.npmjs.org/vue/-/vue-2.4.3.tgz",
"_shasum": "55fee0ec509cf2e10aa73b34b15219e92a9ab9ea",
"_spec": "vue@2.4.3",
"_where": "D:\\dev\\dp\\dp",
"author": {
"name": "Evan You"
},
"bugs": {
"url": "https://github.com/vuejs/vue/issues"
},
"dependencies": {},
"bundleDependencies": false,
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"deprecated": false,
"description": "Reactive, component-oriented view layer for modern web interfaces.",
"devDependencies": {
"babel-core": "^6.9.0",
"babel-eslint": "^7.1.0",
"babel-core": "^6.25.0",
"babel-eslint": "^7.2.3",
"babel-helper-vue-jsx-merge-props": "^2.0.2",
"babel-loader": "^6.2.4",
"babel-plugin-istanbul": "^4.0.0",
"babel-loader": "^7.0.0",
"babel-plugin-istanbul": "^4.1.4",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-vue-jsx": "^3.2.0",
"babel-preset-es2015": "^6.9.0",
"babel-plugin-transform-vue-jsx": "^3.4.3",
"babel-preset-es2015": "^6.24.1",
"babel-preset-flow-vue": "^1.0.0",
"buble": "^0.15.2",
"chalk": "^1.1.3",
"chromedriver": "^2.21.2",
"chromedriver": "^2.30.1",
"codecov.io": "^0.1.6",
"cross-spawn": "^5.0.1",
"commitizen": "^2.9.6",
"conventional-changelog": "^1.1.3",
"cross-spawn": "^5.1.0",
"cz-conventional-changelog": "^2.0.0",
"de-indent": "^1.0.2",
"es6-promise": "^4.0.5",
"eslint": "^3.10.1",
"eslint-config-vue": "^2.0.1",
"eslint-loader": "^1.3.0",
"eslint-plugin-flowtype": "^2.16.0",
"eslint-plugin-jasmine": "^2.1.0",
"eslint-plugin-vue": "^2.0.0",
"file-loader": "^0.10.1",
"flow-bin": "^0.45.0",
"es6-promise": "^4.1.0",
"eslint": "^3.0.0",
"eslint-loader": "^1.7.1",
"eslint-plugin-flowtype": "^2.34.0",
"eslint-plugin-jasmine": "^2.2.0",
"eslint-plugin-vue-libs": "^1.2.0",
"file-loader": "^0.11.2",
"flow-bin": "^0.48.0",
"hash-sum": "^1.0.2",
"he": "^1.1.0",
"http-server": "^0.9.0",
"jasmine": "^2.5.2",
"jasmine-core": "^2.5.2",
"karma": "^1.1.0",
"karma-chrome-launcher": "^2.0.0",
"karma-coverage": "^1.0.0",
"karma-firefox-launcher": "^1.0.0",
"karma-jasmine": "^1.0.2",
"karma-mocha-reporter": "^2.0.4",
"karma-phantomjs-launcher": "^1.0.0",
"he": "^1.1.1",
"http-server": "^0.10.0",
"jasmine": "^2.6.0",
"jasmine-core": "^2.6.3",
"karma": "^1.7.0",
"karma-chrome-launcher": "^2.1.1",
"karma-coverage": "^1.1.1",
"karma-firefox-launcher": "^1.0.1",
"karma-jasmine": "^1.1.0",
"karma-mocha-reporter": "^2.2.3",
"karma-phantomjs-launcher": "^1.0.4",
"karma-safari-launcher": "^1.0.0",
"karma-sauce-launcher": "^1.0.0",
"karma-sourcemap-loader": "^0.3.0",
"karma-webpack": "^2.0.1",
"lodash": "^4.17.1",
"karma-sauce-launcher": "^1.1.0",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^2.0.3",
"lodash": "^4.17.4",
"lodash.template": "^4.4.0",
"lodash.uniq": "^4.5.0",
"lru-cache": "^4.0.2",
"nightwatch": "^0.9.9",
"lru-cache": "^4.1.1",
"nightwatch": "^0.9.16",
"nightwatch-helpers": "^1.2.0",
"phantomjs-prebuilt": "^2.1.1",
"resolve": "^1.2.0",
"rollup": "^0.41.4",
"rollup-plugin-alias": "^1.2.0",
"rollup-plugin-babel": "^2.4.0",
"phantomjs-prebuilt": "^2.1.14",
"resolve": "^1.3.3",
"rollup": "^0.45.1",
"rollup-plugin-alias": "^1.3.1",
"rollup-plugin-babel": "^2.7.1",
"rollup-plugin-buble": "^0.15.0",
"rollup-plugin-commonjs": "^8.0.2",
"rollup-plugin-flow-no-whitespace": "^1.0.0",
"rollup-plugin-replace": "^1.1.0",
"rollup-watch": "^3.2.2",
"rollup-plugin-node-resolve": "^3.0.0",
"rollup-plugin-replace": "^1.1.1",
"rollup-watch": "^4.0.0",
"selenium-server": "^2.53.1",
"serialize-javascript": "^1.3.0",
"typescript": "^2.1.6",
"uglify-js": "^2.6.2",
"webpack": "^2.2.0",
"weex-js-runtime": "^0.17.0-alpha4",
"weex-vdom-tester": "^0.1.4"
},
"directories": {},
"dist": {
"integrity": "sha512-oLCxuVcVQ2inwSbS7B+zfjB6CSjgQ0yyCOzPcg7S5CXeOCbtkaiN5frR6MtwvrveqbG86OsGd9jWf6JsGyQkLw==",
"shasum": "5ec3b87a191da8090bbef56b7cfabd4158038171",
"tarball": "https://registry.npmjs.org/vue/-/vue-2.3.4.tgz"
"shelljs": "^0.7.8",
"typescript": "^2.3.4",
"uglify-js": "^3.0.15",
"webpack": "^2.6.1",
"weex-js-runtime": "^0.20.5",
"weex-vdom-tester": "^0.2.0"
},
"files": [
"src",
"dist/*.js",
"types/*.d.ts"
],
"gitHead": "1f9416d514d80a99eb45184459fdf390405967ec",
"homepage": "https://github.com/vuejs/vue#readme",
"keywords": [
"vue"
],
"license": "MIT",
"main": "dist/vue.runtime.common.js",
"maintainers": [
{
"name": "yyx990803",
"email": "yyx990803@gmail.com"
}
],
"module": "dist/vue.runtime.esm.js",
"name": "vue",
"optionalDependencies": {},
"readme": "ERROR: No README data found!",
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue.git"
@@ -155,30 +129,32 @@
"build": "node build/build.js",
"build:ssr": "npm run build -- vue.runtime.common.js,vue-server-renderer",
"build:weex": "npm run build -- weex-vue-framework,weex-template-compiler",
"commit": "git-cz",
"dev": "rollup -w -c build/config.js --environment TARGET:web-full-dev",
"dev:cjs": "rollup -w -c build/config.js --environment TARGET:web-runtime-cjs",
"dev:compiler": "rollup -w -c build/config.js --environment TARGET:web-compiler ",
"dev:esm": "rollup -w -c build/config.js --environment TARGET:web-runtime-esm",
"dev:ssr": "rollup -w -c build/config.js --environment TARGET:web-server-renderer",
"dev:test": "karma start build/karma.dev.config.js",
"dev:test": "karma start test/unit/karma.dev.config.js",
"dev:weex": "rollup -w -c build/config.js --environment TARGET:weex-framework ",
"dev:weex:compiler": "rollup -w -c build/config.js --environment TARGET:weex-compiler ",
"flow": "flow check",
"install:hooks": "ln -fs ../../build/git-hooks/pre-commit .git/hooks/pre-commit",
"lint": "eslint src build test",
"release": "bash build/release.sh",
"release:note": "node build/gen-release-note.js",
"release:weex": "bash build/release-weex.sh",
"sauce": "karma start build/karma.sauce.config.js",
"sauce": "karma start test/unit/karma.sauce.config.js",
"setup": "node build/install-hooks.js",
"test": "npm run lint && flow check && npm run test:types && npm run test:cover && npm run test:e2e -- --env phantomjs && npm run test:ssr && npm run test:weex",
"test:cover": "karma start build/karma.cover.config.js",
"test:cover": "karma start test/unit/karma.cover.config.js",
"test:e2e": "npm run build -- vue.min.js && node test/e2e/runner.js",
"test:sauce": "npm run sauce -- 0 && npm run sauce -- 1 && npm run sauce -- 2",
"test:ssr": "npm run build:ssr && jasmine JASMINE_CONFIG_PATH=test/ssr/jasmine.json",
"test:types": "tsc -p ./types/test/tsconfig.json",
"test:unit": "karma start build/karma.unit.config.js",
"test:unit": "karma start test/unit/karma.unit.config.js",
"test:weex": "npm run build:weex && jasmine JASMINE_CONFIG_PATH=test/weex/jasmine.json"
},
"typings": "types/index.d.ts",
"unpkg": "dist/vue.js",
"version": "2.3.4"
"version": "2.4.3"
}
+166 -103
View File
@@ -1,73 +1,76 @@
/* @flow */
import { genHandlers } from './events'
import { baseWarn, pluckModuleFunction } from '../helpers'
import baseDirectives from '../directives/index'
import { camelize, no } from 'shared/util'
import { camelize, no, extend } from 'shared/util'
import { baseWarn, pluckModuleFunction } from '../helpers'
type TransformFunction = (el: ASTElement, code: string) => string;
type DataGenFunction = (el: ASTElement) => string;
type DirectiveFunction = (el: ASTElement, dir: ASTDirective, warn: Function) => boolean;
// configurable state
let warn
let transforms: Array<TransformFunction>
let dataGenFns: Array<DataGenFunction>
let platformDirectives
let isPlatformReservedTag
let staticRenderFns
let onceCount
let currentOptions
export class CodegenState {
options: CompilerOptions;
warn: Function;
transforms: Array<TransformFunction>;
dataGenFns: Array<DataGenFunction>;
directives: { [key: string]: DirectiveFunction };
maybeComponent: (el: ASTElement) => boolean;
onceId: number;
staticRenderFns: Array<string>;
constructor (options: CompilerOptions) {
this.options = options
this.warn = options.warn || baseWarn
this.transforms = pluckModuleFunction(options.modules, 'transformCode')
this.dataGenFns = pluckModuleFunction(options.modules, 'genData')
this.directives = extend(extend({}, baseDirectives), options.directives)
const isReservedTag = options.isReservedTag || no
this.maybeComponent = (el: ASTElement) => !isReservedTag(el.tag)
this.onceId = 0
this.staticRenderFns = []
}
}
export type CodegenResult = {
render: string,
staticRenderFns: Array<string>
};
export function generate (
ast: ASTElement | void,
options: CompilerOptions
): {
render: string,
staticRenderFns: Array<string>
} {
// save previous staticRenderFns so generate calls can be nested
const prevStaticRenderFns: Array<string> = staticRenderFns
const currentStaticRenderFns: Array<string> = staticRenderFns = []
const prevOnceCount = onceCount
onceCount = 0
currentOptions = options
warn = options.warn || baseWarn
transforms = pluckModuleFunction(options.modules, 'transformCode')
dataGenFns = pluckModuleFunction(options.modules, 'genData')
platformDirectives = options.directives || {}
isPlatformReservedTag = options.isReservedTag || no
const code = ast ? genElement(ast) : '_c("div")'
staticRenderFns = prevStaticRenderFns
onceCount = prevOnceCount
): CodegenResult {
const state = new CodegenState(options)
const code = ast ? genElement(ast, state) : '_c("div")'
return {
render: `with(this){return ${code}}`,
staticRenderFns: currentStaticRenderFns
staticRenderFns: state.staticRenderFns
}
}
function genElement (el: ASTElement): string {
export function genElement (el: ASTElement, state: CodegenState): string {
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el)
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
return genOnce(el)
return genOnce(el, state)
} else if (el.for && !el.forProcessed) {
return genFor(el)
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {
return genIf(el)
return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget) {
return genChildren(el) || 'void 0'
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
return genSlot(el)
return genSlot(el, state)
} else {
// component or element
let code
if (el.component) {
code = genComponent(el.component, el)
code = genComponent(el.component, el, state)
} else {
const data = el.plain ? undefined : genData(el)
const data = el.plain ? undefined : genData(el, state)
const children = el.inlineTemplate ? null : genChildren(el, true)
const children = el.inlineTemplate ? null : genChildren(el, state, true)
code = `_c('${el.tag}'${
data ? `,${data}` : '' // data
}${
@@ -75,25 +78,25 @@ function genElement (el: ASTElement): string {
})`
}
// module transforms
for (let i = 0; i < transforms.length; i++) {
code = transforms[i](el, code)
for (let i = 0; i < state.transforms.length; i++) {
code = state.transforms[i](el, code)
}
return code
}
}
// hoist static sub-trees out
function genStatic (el: ASTElement): string {
function genStatic (el: ASTElement, state: CodegenState): string {
el.staticProcessed = true
staticRenderFns.push(`with(this){return ${genElement(el)}}`)
return `_m(${staticRenderFns.length - 1}${el.staticInFor ? ',true' : ''})`
state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`)
return `_m(${state.staticRenderFns.length - 1}${el.staticInFor ? ',true' : ''})`
}
// v-once
function genOnce (el: ASTElement): string {
function genOnce (el: ASTElement, state: CodegenState): string {
el.onceProcessed = true
if (el.if && !el.ifProcessed) {
return genIf(el)
return genIf(el, state)
} else if (el.staticInFor) {
let key = ''
let parent = el.parent
@@ -105,51 +108,76 @@ function genOnce (el: ASTElement): string {
parent = parent.parent
}
if (!key) {
process.env.NODE_ENV !== 'production' && warn(
process.env.NODE_ENV !== 'production' && state.warn(
`v-once can only be used inside v-for that is keyed. `
)
return genElement(el)
return genElement(el, state)
}
return `_o(${genElement(el)},${onceCount++}${key ? `,${key}` : ``})`
return `_o(${genElement(el, state)},${state.onceId++},${key})`
} else {
return genStatic(el)
return genStatic(el, state)
}
}
function genIf (el: any): string {
export function genIf (
el: any,
state: CodegenState,
altGen?: Function,
altEmpty?: string
): string {
el.ifProcessed = true // avoid recursion
return genIfConditions(el.ifConditions.slice())
return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
}
function genIfConditions (conditions: ASTIfConditions): string {
function genIfConditions (
conditions: ASTIfConditions,
state: CodegenState,
altGen?: Function,
altEmpty?: string
): string {
if (!conditions.length) {
return '_e()'
return altEmpty || '_e()'
}
const condition = conditions.shift()
if (condition.exp) {
return `(${condition.exp})?${genTernaryExp(condition.block)}:${genIfConditions(conditions)}`
return `(${condition.exp})?${
genTernaryExp(condition.block)
}:${
genIfConditions(conditions, state, altGen, altEmpty)
}`
} else {
return `${genTernaryExp(condition.block)}`
}
// v-if with v-once should generate code like (a)?_m(0):_m(1)
function genTernaryExp (el) {
return el.once ? genOnce(el) : genElement(el)
return altGen
? altGen(el, state)
: el.once
? genOnce(el, state)
: genElement(el, state)
}
}
function genFor (el: any): string {
export function genFor (
el: any,
state: CodegenState,
altGen?: Function,
altHelper?: string
): string {
const exp = el.for
const alias = el.alias
const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''
if (
process.env.NODE_ENV !== 'production' &&
maybeComponent(el) && el.tag !== 'slot' && el.tag !== 'template' && !el.key
if (process.env.NODE_ENV !== 'production' &&
state.maybeComponent(el) &&
el.tag !== 'slot' &&
el.tag !== 'template' &&
!el.key
) {
warn(
state.warn(
`<${el.tag} v-for="${alias} in ${exp}">: component lists rendered with ` +
`v-for should have explicit keys. ` +
`See https://vuejs.org/guide/list.html#key for more info.`,
@@ -158,18 +186,18 @@ function genFor (el: any): string {
}
el.forProcessed = true // avoid recursion
return `_l((${exp}),` +
return `${altHelper || '_l'}((${exp}),` +
`function(${alias}${iterator1}${iterator2}){` +
`return ${genElement(el)}` +
`return ${(altGen || genElement)(el, state)}` +
'})'
}
function genData (el: ASTElement): string {
export function genData (el: ASTElement, state: CodegenState): string {
let data = '{'
// directives first.
// directives may mutate the el's other properties before they are generated.
const dirs = genDirectives(el)
const dirs = genDirectives(el, state)
if (dirs) data += dirs + ','
// key
@@ -192,8 +220,8 @@ function genData (el: ASTElement): string {
data += `tag:"${el.tag}",`
}
// module data generation functions
for (let i = 0; i < dataGenFns.length; i++) {
data += dataGenFns[i](el)
for (let i = 0; i < state.dataGenFns.length; i++) {
data += state.dataGenFns[i](el)
}
// attributes
if (el.attrs) {
@@ -205,10 +233,10 @@ function genData (el: ASTElement): string {
}
// event handlers
if (el.events) {
data += `${genHandlers(el.events, false, warn)},`
data += `${genHandlers(el.events, false, state.warn)},`
}
if (el.nativeEvents) {
data += `${genHandlers(el.nativeEvents, true, warn)},`
data += `${genHandlers(el.nativeEvents, true, state.warn)},`
}
// slot target
if (el.slotTarget) {
@@ -216,7 +244,7 @@ function genData (el: ASTElement): string {
}
// scoped slots
if (el.scopedSlots) {
data += `${genScopedSlots(el.scopedSlots)},`
data += `${genScopedSlots(el.scopedSlots, state)},`
}
// component v-model
if (el.model) {
@@ -230,7 +258,7 @@ function genData (el: ASTElement): string {
}
// inline-template
if (el.inlineTemplate) {
const inlineTemplate = genInlineTemplate(el)
const inlineTemplate = genInlineTemplate(el, state)
if (inlineTemplate) {
data += `${inlineTemplate},`
}
@@ -240,10 +268,14 @@ function genData (el: ASTElement): string {
if (el.wrapData) {
data = el.wrapData(data)
}
// v-on data wrap
if (el.wrapListeners) {
data = el.wrapListeners(data)
}
return data
}
function genDirectives (el: ASTElement): string | void {
function genDirectives (el: ASTElement, state: CodegenState): string | void {
const dirs = el.directives
if (!dirs) return
let res = 'directives:['
@@ -252,11 +284,11 @@ function genDirectives (el: ASTElement): string | void {
for (i = 0, l = dirs.length; i < l; i++) {
dir = dirs[i]
needRuntime = true
const gen: DirectiveFunction = platformDirectives[dir.name] || baseDirectives[dir.name]
const gen: DirectiveFunction = state.directives[dir.name]
if (gen) {
// compile-time directive that manipulates AST.
// returns true if it also needs a runtime counterpart.
needRuntime = !!gen(el, dir, warn)
needRuntime = !!gen(el, dir, state.warn)
}
if (needRuntime) {
hasRuntime = true
@@ -274,15 +306,15 @@ function genDirectives (el: ASTElement): string | void {
}
}
function genInlineTemplate (el: ASTElement): ?string {
function genInlineTemplate (el: ASTElement, state: CodegenState): ?string {
const ast = el.children[0]
if (process.env.NODE_ENV !== 'production' && (
el.children.length > 1 || ast.type !== 1
)) {
warn('Inline-template components must have exactly one child element.')
state.warn('Inline-template components must have exactly one child element.')
}
if (ast.type === 1) {
const inlineRenderFns = generate(ast, currentOptions)
const inlineRenderFns = generate(ast, state.options)
return `inlineTemplate:{render:function(){${
inlineRenderFns.render
}},staticRenderFns:[${
@@ -291,24 +323,37 @@ function genInlineTemplate (el: ASTElement): ?string {
}
}
function genScopedSlots (slots: { [key: string]: ASTElement }): string {
function genScopedSlots (
slots: { [key: string]: ASTElement },
state: CodegenState
): string {
return `scopedSlots:_u([${
Object.keys(slots).map(key => genScopedSlot(key, slots[key])).join(',')
Object.keys(slots).map(key => {
return genScopedSlot(key, slots[key], state)
}).join(',')
}])`
}
function genScopedSlot (key: string, el: ASTElement) {
function genScopedSlot (
key: string,
el: ASTElement,
state: CodegenState
): string {
if (el.for && !el.forProcessed) {
return genForScopedSlot(key, el)
return genForScopedSlot(key, el, state)
}
return `{key:${key},fn:function(${String(el.attrsMap.scope)}){` +
`return ${el.tag === 'template'
? genChildren(el) || 'void 0'
: genElement(el)
? genChildren(el, state) || 'void 0'
: genElement(el, state)
}}}`
}
function genForScopedSlot (key: string, el: any) {
function genForScopedSlot (
key: string,
el: any,
state: CodegenState
): string {
const exp = el.for
const alias = el.alias
const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
@@ -316,11 +361,17 @@ function genForScopedSlot (key: string, el: any) {
el.forProcessed = true // avoid recursion
return `_l((${exp}),` +
`function(${alias}${iterator1}${iterator2}){` +
`return ${genScopedSlot(key, el)}` +
`return ${genScopedSlot(key, el, state)}` +
'})'
}
function genChildren (el: ASTElement, checkSkip?: boolean): string | void {
export function genChildren (
el: ASTElement,
state: CodegenState,
checkSkip?: boolean,
altGenElement?: Function,
altGenNode?: Function
): string | void {
const children = el.children
if (children.length) {
const el: any = children[0]
@@ -330,10 +381,13 @@ function genChildren (el: ASTElement, checkSkip?: boolean): string | void {
el.tag !== 'template' &&
el.tag !== 'slot'
) {
return genElement(el)
return (altGenElement || genElement)(el, state)
}
const normalizationType = checkSkip ? getNormalizationType(children) : 0
return `[${children.map(genNode).join(',')}]${
const normalizationType = checkSkip
? getNormalizationType(children, state.maybeComponent)
: 0
const gen = altGenNode || genNode
return `[${children.map(c => gen(c, state)).join(',')}]${
normalizationType ? `,${normalizationType}` : ''
}`
}
@@ -343,7 +397,10 @@ function genChildren (el: ASTElement, checkSkip?: boolean): string | void {
// 0: no normalization needed
// 1: simple normalization needed (possible 1-level deep nested array)
// 2: full normalization needed
function getNormalizationType (children: Array<ASTNode>): number {
function getNormalizationType (
children: Array<ASTNode>,
maybeComponent: (el: ASTElement) => boolean
): number {
let res = 0
for (let i = 0; i < children.length; i++) {
const el: ASTNode = children[i]
@@ -367,28 +424,30 @@ function needsNormalization (el: ASTElement): boolean {
return el.for !== undefined || el.tag === 'template' || el.tag === 'slot'
}
function maybeComponent (el: ASTElement): boolean {
return !isPlatformReservedTag(el.tag)
}
function genNode (node: ASTNode): string {
function genNode (node: ASTNode, state: CodegenState): string {
if (node.type === 1) {
return genElement(node)
return genElement(node, state)
} if (node.type === 3 && node.isComment) {
return genComment(node)
} else {
return genText(node)
}
}
function genText (text: ASTText | ASTExpression): string {
export function genText (text: ASTText | ASTExpression): string {
return `_v(${text.type === 2
? text.expression // no need for () because already wrapped in _s()
: transformSpecialNewlines(JSON.stringify(text.text))
})`
}
function genSlot (el: ASTElement): string {
export function genComment (comment: ASTText): string {
return `_e(${JSON.stringify(comment.text)})`
}
function genSlot (el: ASTElement, state: CodegenState): string {
const slotName = el.slotName || '"default"'
const children = genChildren(el)
const children = genChildren(el, state)
let res = `_t(${slotName}${children ? `,${children}` : ''}`
const attrs = el.attrs && `{${el.attrs.map(a => `${camelize(a.name)}:${a.value}`).join(',')}}`
const bind = el.attrsMap['v-bind']
@@ -405,9 +464,13 @@ function genSlot (el: ASTElement): string {
}
// componentName is el.component, take it as argument to shun flow's pessimistic refinement
function genComponent (componentName: string, el: ASTElement): string {
const children = el.inlineTemplate ? null : genChildren(el, true)
return `_c(${componentName},${genData(el)}${
function genComponent (
componentName: string,
el: ASTElement,
state: CodegenState
): string {
const children = el.inlineTemplate ? null : genChildren(el, state, true)
return `_c(${componentName},${genData(el, state)}${
children ? `,${children}` : ''
})`
}
+4 -2
View File
@@ -2,8 +2,10 @@
export default function bind (el: ASTElement, dir: ASTDirective) {
el.wrapData = (code: string) => {
return `_b(${code},'${el.tag}',${dir.value}${
dir.modifiers && dir.modifiers.prop ? ',true' : ''
return `_b(${code},'${el.tag}',${dir.value},${
dir.modifiers && dir.modifiers.prop ? 'true' : 'false'
}${
dir.modifiers && dir.modifiers.sync ? ',true' : ''
})`
}
}
+2
View File
@@ -1,9 +1,11 @@
/* @flow */
import on from './on'
import bind from './bind'
import { noop } from 'shared/util'
export default {
on,
bind,
cloak: noop
}
+1 -4
View File
@@ -41,10 +41,7 @@ export function genAssignmentCode (
if (modelRs.idx === null) {
return `${value}=${assignment}`
} else {
return `var $$exp = ${modelRs.exp}, $$idx = ${modelRs.idx};` +
`if (!Array.isArray($$exp)){` +
`${value}=${assignment}}` +
`else{$$exp.splice($$idx, 1, ${assignment})}`
return `$set(${modelRs.exp}, ${modelRs.idx}, ${assignment})`
}
}
+6 -142
View File
@@ -3,11 +3,12 @@
import { parse } from './parser/index'
import { optimize } from './optimizer'
import { generate } from './codegen/index'
import { detectErrors } from './error-detector'
import { extend, noop } from 'shared/util'
import { warn, tip } from 'core/util/debug'
import { createCompilerCreator } from './create-compiler'
function baseCompile (
// `createCompilerCreator` allows creating compilers that use alternative
// parser/optimizer/codegen, e.g the SSR optimizing compiler.
// Here we just export a default compiler using the default parts.
export const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
@@ -19,141 +20,4 @@ function baseCompile (
render: code.render,
staticRenderFns: code.staticRenderFns
}
}
function makeFunction (code, errors) {
try {
return new Function(code)
} catch (err) {
errors.push({ err, code })
return noop
}
}
export function createCompiler (baseOptions: CompilerOptions) {
const functionCompileCache: {
[key: string]: CompiledFunctionResult;
} = Object.create(null)
function compile (
template: string,
options?: CompilerOptions
): CompiledResult {
const finalOptions = Object.create(baseOptions)
const errors = []
const tips = []
finalOptions.warn = (msg, tip) => {
(tip ? tips : errors).push(msg)
}
if (options) {
// merge custom modules
if (options.modules) {
finalOptions.modules = (baseOptions.modules || []).concat(options.modules)
}
// merge custom directives
if (options.directives) {
finalOptions.directives = extend(
Object.create(baseOptions.directives),
options.directives
)
}
// copy other options
for (const key in options) {
if (key !== 'modules' && key !== 'directives') {
finalOptions[key] = options[key]
}
}
}
const compiled = baseCompile(template, finalOptions)
if (process.env.NODE_ENV !== 'production') {
errors.push.apply(errors, detectErrors(compiled.ast))
}
compiled.errors = errors
compiled.tips = tips
return compiled
}
function compileToFunctions (
template: string,
options?: CompilerOptions,
vm?: Component
): CompiledFunctionResult {
options = options || {}
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production') {
// detect possible CSP restriction
try {
new Function('return 1')
} catch (e) {
if (e.toString().match(/unsafe-eval|CSP/)) {
warn(
'It seems you are using the standalone build of Vue.js in an ' +
'environment with Content Security Policy that prohibits unsafe-eval. ' +
'The template compiler cannot work in this environment. Consider ' +
'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
'templates into render functions.'
)
}
}
}
// check cache
const key = options.delimiters
? String(options.delimiters) + template
: template
if (functionCompileCache[key]) {
return functionCompileCache[key]
}
// compile
const compiled = compile(template, options)
// check compilation errors/tips
if (process.env.NODE_ENV !== 'production') {
if (compiled.errors && compiled.errors.length) {
warn(
`Error compiling template:\n\n${template}\n\n` +
compiled.errors.map(e => `- ${e}`).join('\n') + '\n',
vm
)
}
if (compiled.tips && compiled.tips.length) {
compiled.tips.forEach(msg => tip(msg, vm))
}
}
// turn code into functions
const res = {}
const fnGenErrors = []
res.render = makeFunction(compiled.render, fnGenErrors)
const l = compiled.staticRenderFns.length
res.staticRenderFns = new Array(l)
for (let i = 0; i < l; i++) {
res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i], fnGenErrors)
}
// check function generation errors.
// this should only happen if there is a bug in the compiler itself.
// mostly for codegen development use
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production') {
if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) {
warn(
`Failed to generate render function:\n\n` +
fnGenErrors.map(({ err, code }) => `${err.toString()} in\n\n${code}\n`).join('\n'),
vm
)
}
}
return (functionCompileCache[key] = res)
}
return {
compile,
compileToFunctions
}
}
})
+12 -7
View File
@@ -55,6 +55,15 @@ function markStatic (node: ASTNode) {
node.static = false
}
}
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
const block = node.ifConditions[i].block
markStatic(block)
if (!block.static) {
node.static = false
}
}
}
}
}
@@ -81,17 +90,13 @@ function markStaticRoots (node: ASTNode, isInFor: boolean) {
}
}
if (node.ifConditions) {
walkThroughConditionsBlocks(node.ifConditions, isInFor)
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
markStaticRoots(node.ifConditions[i].block, isInFor)
}
}
}
}
function walkThroughConditionsBlocks (conditionBlocks: ASTIfConditions, isInFor: boolean): void {
for (let i = 1, len = conditionBlocks.length; i < len; i++) {
markStaticRoots(conditionBlocks[i].block, isInFor)
}
}
function isStatic (node: ASTNode): boolean {
if (node.type === 2) { // expression
return false
+6 -4
View File
@@ -2,8 +2,10 @@
let decoder
export function decode (html: string): string {
decoder = decoder || document.createElement('div')
decoder.innerHTML = html
return decoder.textContent
export default {
decode (html: string): string {
decoder = decoder || document.createElement('div')
decoder.innerHTML = html
return decoder.textContent
}
}
+22 -24
View File
@@ -13,29 +13,14 @@ import { makeMap, no } from 'shared/util'
import { isNonPhrasingTag } from 'web/compiler/util'
// Regular Expressions for parsing tags and attributes
const singleAttrIdentifier = /([^\s"'<>/=]+)/
const singleAttrAssign = /(?:=)/
const singleAttrValues = [
// attr value double quotes
/"([^"]*)"+/.source,
// attr value, single quotes
/'([^']*)'+/.source,
// attr value, no quotes
/([^\s"'=<>`]+)/.source
]
const attribute = new RegExp(
'^\\s*' + singleAttrIdentifier.source +
'(?:\\s*(' + singleAttrAssign.source + ')' +
'\\s*(?:' + singleAttrValues.join('|') + '))?'
)
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
// could use https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName
// but for Vue templates we can enforce a simple charset
const ncname = '[a-zA-Z_][\\w\\-\\.]*'
const qnameCapture = '((?:' + ncname + '\\:)?' + ncname + ')'
const startTagOpen = new RegExp('^<' + qnameCapture)
const qnameCapture = `((?:${ncname}\\:)?${ncname})`
const startTagOpen = new RegExp(`^<${qnameCapture}`)
const startTagClose = /^\s*(\/?)>/
const endTag = new RegExp('^<\\/' + qnameCapture + '[^>]*>')
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`)
const doctype = /^<!DOCTYPE [^>]+>/i
const comment = /^<!--/
const conditionalComment = /^<!\[/
@@ -59,6 +44,10 @@ const decodingMap = {
const encodedAttr = /&(?:lt|gt|quot|amp);/g
const encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#10);/g
// #5992
const isIgnoreNewlineTag = makeMap('pre,textarea', true)
const shouldIgnoreFirstNewline = (tag, html) => tag && isIgnoreNewlineTag(tag) && html[0] === '\n'
function decodeAttr (value, shouldDecodeNewlines) {
const re = shouldDecodeNewlines ? encodedAttrWithNewLines : encodedAttr
return value.replace(re, match => decodingMap[match])
@@ -82,6 +71,9 @@ export function parseHTML (html, options) {
const commentEnd = html.indexOf('-->')
if (commentEnd >= 0) {
if (options.shouldKeepComment) {
options.comment(html.substring(4, commentEnd))
}
advance(commentEnd + 3)
continue
}
@@ -117,6 +109,9 @@ export function parseHTML (html, options) {
const startTagMatch = parseStartTag()
if (startTagMatch) {
handleStartTag(startTagMatch)
if (shouldIgnoreFirstNewline(lastTag, html)) {
advance(1)
}
continue
}
}
@@ -149,16 +144,19 @@ export function parseHTML (html, options) {
options.chars(text)
}
} else {
var stackedTag = lastTag.toLowerCase()
var reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i'))
var endTagLength = 0
var rest = html.replace(reStackedTag, function (all, text, endTag) {
let endTagLength = 0
const stackedTag = lastTag.toLowerCase()
const reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i'))
const rest = html.replace(reStackedTag, function (all, text, endTag) {
endTagLength = endTag.length
if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') {
text = text
.replace(/<!--([\s\S]*?)-->/g, '$1')
.replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1')
}
if (shouldIgnoreFirstNewline(stackedTag, text)) {
text = text.slice(1)
}
if (options.chars) {
options.chars(text)
}
@@ -222,7 +220,7 @@ export function parseHTML (html, options) {
}
}
const unary = isUnaryTag(tagName) || tagName === 'html' && lastTag === 'head' || !!unarySlash
const unary = isUnaryTag(tagName) || !!unarySlash
const l = match.attrs.length
const attrs = new Array(l)
+21 -6
View File
@@ -1,6 +1,6 @@
/* @flow */
import { decode } from 'he'
import he from 'he'
import { parseHTML } from './html-parser'
import { parseText } from './text-parser'
import { parseFilters } from './filter-parser'
@@ -28,7 +28,7 @@ const argRE = /:(.*)$/
const bindRE = /^:|^v-bind:/
const modifierRE = /\.[^.]+/g
const decodeHTMLCached = cached(decode)
const decodeHTMLCached = cached(he.decode)
// configurable state
export let warn
@@ -48,12 +48,15 @@ export function parse (
options: CompilerOptions
): ASTElement | void {
warn = options.warn || baseWarn
platformGetTagNamespace = options.getTagNamespace || no
platformMustUseProp = options.mustUseProp || no
platformIsPreTag = options.isPreTag || no
preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
platformMustUseProp = options.mustUseProp || no
platformGetTagNamespace = options.getTagNamespace || no
transforms = pluckModuleFunction(options.modules, 'transformNode')
preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
postTransforms = pluckModuleFunction(options.modules, 'postTransformNode')
delimiters = options.delimiters
const stack = []
@@ -87,6 +90,7 @@ export function parse (
isUnaryTag: options.isUnaryTag,
canBeLeftOpenTag: options.canBeLeftOpenTag,
shouldDecodeNewlines: options.shouldDecodeNewlines,
shouldKeepComment: options.comments,
start (tag, attrs, unary) {
// check namespace.
// inherit parent ns if there is one
@@ -271,6 +275,13 @@ export function parse (
})
}
}
},
comment (text: string) {
currentParent.children.push({
type: 3,
text,
isComment: true
})
}
})
return root
@@ -420,6 +431,8 @@ function processSlot (el) {
const slotTarget = getBindingAttr(el, 'slot')
if (slotTarget) {
el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget
// preserve slot as an attribute for native shadow DOM compat
addAttr(el, 'slot', slotTarget)
}
if (el.tag === 'template') {
el.slotScope = getAndRemoveAttr(el, 'scope')
@@ -472,7 +485,9 @@ function processAttrs (el) {
)
}
}
if (isProp || platformMustUseProp(el.tag, el.attrsMap.type, name)) {
if (isProp || (
!el.component && platformMustUseProp(el.tag, el.attrsMap.type, name)
)) {
addProp(el, name, value)
} else {
addAttr(el, name, value)
+7 -5
View File
@@ -5,14 +5,16 @@ import { getFirstComponentChild } from 'core/vdom/helpers/index'
type VNodeCache = { [key: string]: ?VNode };
const patternTypes: Array<Function> = [String, RegExp]
const patternTypes: Array<Function> = [String, RegExp, Array]
function getComponentName (opts: ?VNodeComponentOptions): ?string {
return opts && (opts.Ctor.options.name || opts.tag)
}
function matches (pattern: string | RegExp, name: string): boolean {
if (typeof pattern === 'string') {
function matches (pattern: string | RegExp | Array<string>, name: string): boolean {
if (Array.isArray(pattern)) {
return pattern.indexOf(name) > -1
} else if (typeof pattern === 'string') {
return pattern.split(',').indexOf(name) > -1
} else if (isRegExp(pattern)) {
return pattern.test(name)
@@ -62,10 +64,10 @@ export default {
},
watch: {
include (val: string | RegExp) {
include (val: string | RegExp | Array<string>) {
pruneCache(this.cache, this._vnode, name => matches(val, name))
},
exclude (val: string | RegExp) {
exclude (val: string | RegExp | Array<string>) {
pruneCache(this.cache, this._vnode, name => !matches(val, name))
}
},
+6
View File
@@ -16,6 +16,7 @@ export type Config = {
performance: boolean;
devtools: boolean;
errorHandler: ?(err: Error, vm: Component, info: string) => void;
warnHandler: ?(msg: string, vm: Component, trace: string) => void;
ignoredElements: Array<string>;
keyCodes: { [key: string]: number | Array<number> };
@@ -62,6 +63,11 @@ export default ({
*/
errorHandler: null,
/**
* Warn handler for watcher warns
*/
warnHandler: null,
/**
* Ignore certain custom elements
*/
+4 -3
View File
@@ -4,10 +4,11 @@ import { toArray } from '../util/index'
export function initUse (Vue: GlobalAPI) {
Vue.use = function (plugin: Function | Object) {
/* istanbul ignore if */
if (plugin.installed) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// additional parameters
const args = toArray(arguments, 1)
args.unshift(this)
@@ -16,7 +17,7 @@ export function initUse (Vue: GlobalAPI) {
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
plugin.installed = true
installedPlugins.push(plugin)
return this
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ Object.defineProperty(Vue.prototype, '$isServer', {
Object.defineProperty(Vue.prototype, '$ssrContext', {
get () {
/* istanbul ignore next */
return this.$vnode.ssrContext
return this.$vnode && this.$vnode.ssrContext
}
})
+22 -10
View File
@@ -1,7 +1,13 @@
/* @flow */
import {
tip,
toArray,
hyphenate,
handleError,
formatComponentName
} from '../util/index'
import { updateListeners } from '../vdom/helpers/index'
import { toArray, tip, hyphenate, formatComponentName } from '../util/index'
export function initEvents (vm: Component) {
vm._events = Object.create(null)
@@ -89,14 +95,16 @@ export function eventsMixin (Vue: Class<Component>) {
vm._events[event] = null
return vm
}
// specific handler
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
if (fn) {
// specific handler
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
}
return vm
@@ -121,7 +129,11 @@ export function eventsMixin (Vue: Class<Component>) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
const args = toArray(arguments, 1)
for (let i = 0, l = cbs.length; i < l; i++) {
cbs[i].apply(vm, args)
try {
cbs[i].apply(vm, args)
} catch (e) {
handleError(e, vm, `event handler for "${event}"`)
}
}
}
return vm
+13 -9
View File
@@ -1,8 +1,8 @@
/* @flow */
import { hasSymbol } from 'core/util/env'
import { warn } from '../util/index'
import { defineReactive } from '../observer/index'
import { hasSymbol } from 'core/util/env'
import { defineReactive, observerState } from '../observer/index'
export function initProvide (vm: Component) {
const provide = vm.$options.provide
@@ -16,6 +16,7 @@ export function initProvide (vm: Component) {
export function initInjections (vm: Component) {
const result = resolveInject(vm.$options.inject, vm)
if (result) {
observerState.shouldConvert = false
Object.keys(result).forEach(key => {
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
@@ -31,24 +32,24 @@ export function initInjections (vm: Component) {
defineReactive(vm, key, result[key])
}
})
observerState.shouldConvert = true
}
}
export function resolveInject (inject: any, vm: Component): ?Object {
if (inject) {
// inject is :any because flow is not smart enough to figure out cached
// isArray here
const isArray = Array.isArray(inject)
const result = Object.create(null)
const keys = isArray
? inject
: hasSymbol
? Reflect.ownKeys(inject)
const keys = hasSymbol
? Reflect.ownKeys(inject).filter(key => {
/* istanbul ignore next */
return Object.getOwnPropertyDescriptor(inject, key).enumerable
})
: Object.keys(inject)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const provideKey = isArray ? key : inject[key]
const provideKey = inject[key]
let source = vm
while (source) {
if (source._provided && provideKey in source._provided) {
@@ -57,6 +58,9 @@ export function resolveInject (inject: any, vm: Component): ?Object {
}
source = source.$parent
}
if (process.env.NODE_ENV !== 'production' && !source) {
warn(`Injection "${key}" not found`, vm)
}
}
return result
}
+20 -8
View File
@@ -18,6 +18,7 @@ import {
} from '../util/index'
export let activeInstance: any = null
export let isUpdatingChildComponent: boolean = false
export function initLifecycle (vm: Component) {
const options = vm.$options
@@ -65,6 +66,9 @@ export function lifecycleMixin (Vue: Class<Component>) {
vm.$options._parentElm,
vm.$options._refElm
)
// no need for the ref nodes after initial patch
// this prevents keeping a detached DOM tree in memory (#5851)
vm.$options._parentElm = vm.$options._refElm = null
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
@@ -129,8 +133,6 @@ export function lifecycleMixin (Vue: Class<Component>) {
if (vm.$el) {
vm.$el.__vue__ = null
}
// remove reference to DOM nodes (prevents leak)
vm.$options._parentElm = vm.$options._refElm = null
}
}
@@ -206,6 +208,10 @@ export function updateChildComponent (
parentVnode: VNode,
renderChildren: ?Array<VNode>
) {
if (process.env.NODE_ENV !== 'production') {
isUpdatingChildComponent = true
}
// determine whether component has slot children
// we need to do this before overwriting $options._renderChildren
const hasChildren = !!(
@@ -217,17 +223,21 @@ export function updateChildComponent (
vm.$options._parentVnode = parentVnode
vm.$vnode = parentVnode // update vm's placeholder node without re-render
if (vm._vnode) { // update child tree's parent
vm._vnode.parent = parentVnode
}
vm.$options._renderChildren = renderChildren
// update $attrs and $listeners hash
// these are also reactive so they may trigger child update if the child
// used them during render
vm.$attrs = (parentVnode.data && parentVnode.data.attrs) || emptyObject
vm.$listeners = listeners || emptyObject
// update props
if (propsData && vm.$options.props) {
observerState.shouldConvert = false
if (process.env.NODE_ENV !== 'production') {
observerState.isSettingProps = true
}
const props = vm._props
const propKeys = vm.$options._propKeys || []
for (let i = 0; i < propKeys.length; i++) {
@@ -235,12 +245,10 @@ export function updateChildComponent (
props[key] = validateProp(key, vm.$options.props, propsData, vm)
}
observerState.shouldConvert = true
if (process.env.NODE_ENV !== 'production') {
observerState.isSettingProps = false
}
// keep a copy of raw propsData
vm.$options.propsData = propsData
}
// update listeners
if (listeners) {
const oldListeners = vm.$options._parentListeners
@@ -252,6 +260,10 @@ export function updateChildComponent (
vm.$slots = resolveSlots(renderChildren, parentVnode.context)
vm.$forceUpdate()
}
if (process.env.NODE_ENV !== 'production') {
isUpdatingChildComponent = false
}
}
function isInInactiveTree (vm) {
+21 -3
View File
@@ -1,7 +1,13 @@
/* @flow */
import config from 'core/config'
import { isObject, warn, toObject } from 'core/util/index'
import {
warn,
isObject,
toObject,
isReservedAttribute
} from 'core/util/index'
/**
* Runtime helper for merging v-bind="object" into a VNode's data.
@@ -10,7 +16,8 @@ export function bindObjectProps (
data: any,
tag: string,
value: any,
asProp?: boolean
asProp: boolean,
isSync?: boolean
): VNodeData {
if (value) {
if (!isObject(value)) {
@@ -24,7 +31,11 @@ export function bindObjectProps (
}
let hash
for (const key in value) {
if (key === 'class' || key === 'style') {
if (
key === 'class' ||
key === 'style' ||
isReservedAttribute(key)
) {
hash = data
} else {
const type = data.attrs && data.attrs.type
@@ -34,6 +45,13 @@ export function bindObjectProps (
}
if (!(key in hash)) {
hash[key] = value[key]
if (isSync) {
const on = data.on || (data.on = {})
on[`update:${key}`] = function ($event) {
value[key] = $event
}
}
}
}
}
+5 -1
View File
@@ -7,7 +7,11 @@ import { isObject, isDef } from 'core/util/index'
*/
export function renderList (
val: any,
render: () => VNode
render: (
val: any,
keyOrIndex: string | number,
index?: number
) => VNode
): ?Array<VNode> {
let ret: ?Array<VNode>, i, l, keys, key
if (Array.isArray(val) || typeof val === 'string') {
+1 -1
View File
@@ -15,7 +15,7 @@ export function renderSlot (
if (scopedSlotFn) { // scoped slot
props = props || {}
if (bindObject) {
extend(props, bindObject)
props = extend(extend({}, bindObject), props)
}
return scopedSlotFn(props) || fallback
} else {
+6 -1
View File
@@ -14,10 +14,15 @@ export function resolveSlots (
const defaultSlot = []
for (let i = 0, l = children.length; i < l; i++) {
const child = children[i]
const data = child.data
// remove slot attribute if the node is resolved as a Vue slot node
if (data && data.attrs && data.attrs.slot) {
delete data.attrs.slot
}
// named slots should only be respected if the vnode was rendered in the
// same context.
if ((child.context === context || child.functionalContext === context) &&
child.data && child.data.slot != null
data && data.slot != null
) {
const name = child.data.slot
const slot = (slots[name] || (slots[name] = []))
+29 -3
View File
@@ -8,7 +8,8 @@ import {
looseEqual,
emptyObject,
handleError,
looseIndexOf
looseIndexOf,
defineReactive
} from '../util/index'
import VNode, {
@@ -17,6 +18,8 @@ import VNode, {
createEmptyVNode
} from '../vdom/vnode'
import { isUpdatingChildComponent } from './lifecycle'
import { createElement } from '../vdom/create-element'
import { renderList } from './render-helpers/render-list'
import { renderSlot } from './render-helpers/render-slot'
@@ -24,6 +27,7 @@ import { resolveFilter } from './render-helpers/resolve-filter'
import { checkKeyCodes } from './render-helpers/check-keycodes'
import { bindObjectProps } from './render-helpers/bind-object-props'
import { renderStatic, markOnce } from './render-helpers/render-static'
import { bindObjectListeners } from './render-helpers/bind-object-listeners'
import { resolveSlots, resolveScopedSlots } from './render-helpers/resolve-slots'
export function initRender (vm: Component) {
@@ -41,6 +45,23 @@ export function initRender (vm: Component) {
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
// $attrs & $listeners are exposed for easier HOC creation.
// they need to be reactive so that HOCs using them are always updated
const parentData = parentVnode && parentVnode.data
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
!isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
}, true)
defineReactive(vm, '$listeners', vm.$options._parentListeners || emptyObject, () => {
!isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
}, true)
} else {
defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
defineReactive(vm, '$listeners', vm.$options._parentListeners || emptyObject, null, true)
}
}
export function renderMixin (Vue: Class<Component>) {
@@ -57,9 +78,13 @@ export function renderMixin (Vue: Class<Component>) {
} = vm.$options
if (vm._isMounted) {
// clone slot nodes on re-renders
// if the parent didn't update, the slot nodes will be the ones from
// last render. They need to be cloned to ensure "freshness" for this render.
for (const key in vm.$slots) {
vm.$slots[key] = cloneVNodes(vm.$slots[key])
const slot = vm.$slots[key]
if (slot._rendered) {
vm.$slots[key] = cloneVNodes(slot, true /* deep */)
}
}
}
@@ -121,4 +146,5 @@ export function renderMixin (Vue: Class<Component>) {
Vue.prototype._v = createTextVNode
Vue.prototype._e = createEmptyVNode
Vue.prototype._u = resolveScopedSlots
Vue.prototype._g = bindObjectListeners
}
+92 -33
View File
@@ -3,6 +3,7 @@
import config from '../config'
import Dep from '../observer/dep'
import Watcher from '../observer/watcher'
import { isUpdatingChildComponent } from './lifecycle'
import {
set,
@@ -19,8 +20,11 @@ import {
hasOwn,
isReserved,
handleError,
nativeWatch,
validateProp,
isPlainObject
isPlainObject,
isServerRendering,
isReservedAttribute
} from '../util/index'
const sharedPropertyDefinition = {
@@ -51,13 +55,19 @@ export function initState (vm: Component) {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch) initWatch(vm, opts.watch)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
const isReservedProp = {
key: 1,
ref: 1,
slot: 1
function checkOptionType (vm: Component, name: string) {
const option = vm.$options[name]
if (!isPlainObject(option)) {
warn(
`component option "${name}" should be an object.`,
vm
)
}
}
function initProps (vm: Component, propsOptions: Object) {
@@ -74,14 +84,14 @@ function initProps (vm: Component, propsOptions: Object) {
const value = validateProp(key, propsOptions, propsData, vm)
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
if (isReservedProp[key] || config.isReservedAttr(key)) {
if (isReservedAttribute(key) || config.isReservedAttr(key)) {
warn(
`"${key}" is a reserved attribute and cannot be used as component prop.`,
vm
)
}
defineReactive(props, key, value, () => {
if (vm.$parent && !observerState.isSettingProps) {
if (vm.$parent && !isUpdatingChildComponent) {
warn(
`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
@@ -120,16 +130,26 @@ function initData (vm: Component) {
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
if (props && hasOwn(props, keys[i])) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
`Method "${key}" has already been defined as a data property.`,
vm
)
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
`The data property "${keys[i]}" is already declared as a prop. ` +
`The data property "${key}" is already declared as a prop. ` +
`Use prop default value instead.`,
vm
)
} else if (!isReserved(keys[i])) {
proxy(vm, `_data`, keys[i])
} else if (!isReserved(key)) {
proxy(vm, `_data`, key)
}
}
// observe data
@@ -148,22 +168,30 @@ function getData (data: Function, vm: Component): any {
const computedWatcherOptions = { lazy: true }
function initComputed (vm: Component, computed: Object) {
process.env.NODE_ENV !== 'production' && checkOptionType(vm, 'computed')
const watchers = vm._computedWatchers = Object.create(null)
// computed properties are just getters during SSR
const isSSR = isServerRendering()
for (const key in computed) {
const userDef = computed[key]
let getter = typeof userDef === 'function' ? userDef : userDef.get
if (process.env.NODE_ENV !== 'production') {
if (getter === undefined) {
warn(
`No getter function has been defined for computed property "${key}".`,
vm
)
getter = noop
}
const getter = typeof userDef === 'function' ? userDef : userDef.get
if (process.env.NODE_ENV !== 'production' && getter == null) {
warn(
`Getter is missing for computed property "${key}".`,
vm
)
}
if (!isSSR) {
// create internal watcher for the computed property.
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
)
}
// create internal watcher for the computed property.
watchers[key] = new Watcher(vm, getter, noop, computedWatcherOptions)
// component-defined computed properties are already defined on the
// component prototype. We only need to define computed properties defined
@@ -180,13 +208,20 @@ function initComputed (vm: Component, computed: Object) {
}
}
export function defineComputed (target: any, key: string, userDef: Object | Function) {
export function defineComputed (
target: any,
key: string,
userDef: Object | Function
) {
const shouldCache = !isServerRendering()
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = createComputedGetter(key)
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: userDef
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get
? userDef.cache !== false
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: userDef.get
: noop
@@ -194,6 +229,15 @@ export function defineComputed (target: any, key: string, userDef: Object | Func
? userDef.set
: noop
}
if (process.env.NODE_ENV !== 'production' &&
sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
`Computed property "${key}" was assigned to but it has no setter.`,
this
)
}
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
@@ -213,28 +257,36 @@ function createComputedGetter (key) {
}
function initMethods (vm: Component, methods: Object) {
process.env.NODE_ENV !== 'production' && checkOptionType(vm, 'methods')
const props = vm.$options.props
for (const key in methods) {
vm[key] = methods[key] == null ? noop : bind(methods[key], vm)
if (process.env.NODE_ENV !== 'production') {
if (methods[key] == null) {
warn(
`method "${key}" has an undefined value in the component definition. ` +
`Method "${key}" has an undefined value in the component definition. ` +
`Did you reference the function correctly?`,
vm
)
}
if (props && hasOwn(props, key)) {
warn(
`method "${key}" has already been defined as a prop.`,
`Method "${key}" has already been defined as a prop.`,
vm
)
}
if ((key in vm) && isReserved(key)) {
warn(
`Method "${key}" conflicts with an existing Vue instance method. ` +
`Avoid defining component methods that start with _ or $.`
)
}
}
vm[key] = methods[key] == null ? noop : bind(methods[key], vm)
}
}
function initWatch (vm: Component, watch: Object) {
process.env.NODE_ENV !== 'production' && checkOptionType(vm, 'watch')
for (const key in watch) {
const handler = watch[key]
if (Array.isArray(handler)) {
@@ -247,8 +299,12 @@ function initWatch (vm: Component, watch: Object) {
}
}
function createWatcher (vm: Component, key: string, handler: any) {
let options
function createWatcher (
vm: Component,
keyOrFn: string | Function,
handler: any,
options?: Object
) {
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
@@ -256,7 +312,7 @@ function createWatcher (vm: Component, key: string, handler: any) {
if (typeof handler === 'string') {
handler = vm[handler]
}
vm.$watch(key, handler, options)
return vm.$watch(keyOrFn, handler, options)
}
export function stateMixin (Vue: Class<Component>) {
@@ -287,10 +343,13 @@ export function stateMixin (Vue: Class<Component>) {
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
+1 -10
View File
@@ -23,21 +23,12 @@ export const arrayMethods = Object.create(arrayProto)
.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator () {
// avoid leaking arguments:
// http://jsperf.com/closure-with-arguments
let i = arguments.length
const args = new Array(i)
while (i--) {
args[i] = arguments[i]
}
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
inserted = args
break
case 'unshift':
inserted = args
break
+17 -16
View File
@@ -4,11 +4,12 @@ import Dep from './dep'
import { arrayMethods } from './array'
import {
def,
warn,
hasOwn,
hasProto,
isObject,
isPlainObject,
hasProto,
hasOwn,
warn,
isValidArrayIndex,
isServerRendering
} from '../util/index'
@@ -21,8 +22,7 @@ const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
* under a frozen data structure. Converting it would defeat the optimization.
*/
export const observerState = {
shouldConvert: true,
isSettingProps: false
shouldConvert: true
}
/**
@@ -80,7 +80,7 @@ export class Observer {
* Augment an target Object or Array by intercepting
* the prototype chain using __proto__
*/
function protoAugment (target, src: Object) {
function protoAugment (target, src: Object, keys: any) {
/* eslint-disable no-proto */
target.__proto__ = src
/* eslint-enable no-proto */
@@ -132,7 +132,8 @@ export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: Function
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
@@ -145,7 +146,7 @@ export function defineReactive (
const getter = property && property.get
const setter = property && property.set
let childOb = observe(val)
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
@@ -155,9 +156,9 @@ export function defineReactive (
dep.depend()
if (childOb) {
childOb.dep.depend()
}
if (Array.isArray(value)) {
dependArray(value)
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
@@ -177,7 +178,7 @@ export function defineReactive (
} else {
val = newVal
}
childOb = observe(newVal)
childOb = !shallow && observe(newVal)
dep.notify()
}
})
@@ -189,7 +190,7 @@ export function defineReactive (
* already exist.
*/
export function set (target: Array<any> | Object, key: any, val: any): any {
if (Array.isArray(target) && typeof key === 'number') {
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
@@ -198,7 +199,7 @@ export function set (target: Array<any> | Object, key: any, val: any): any {
target[key] = val
return val
}
const ob = (target : any).__ob__
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
@@ -219,11 +220,11 @@ export function set (target: Array<any> | Object, key: any, val: any): any {
* Delete a property and trigger change if necessary.
*/
export function del (target: Array<any> | Object, key: any) {
if (Array.isArray(target) && typeof key === 'number') {
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}
const ob = (target : any).__ob__
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid deleting properties on a Vue instance or its root $data ' +
+2 -2
View File
@@ -81,7 +81,7 @@ function flushSchedulerQueue () {
// call component updated and activated hooks
callActivatedHooks(activatedQueue)
callUpdateHooks(updatedQueue)
callUpdatedHooks(updatedQueue)
// devtool hook
/* istanbul ignore if */
@@ -90,7 +90,7 @@ function flushSchedulerQueue () {
}
}
function callUpdateHooks (queue) {
function callUpdatedHooks (queue) {
let i = queue.length
while (i--) {
const watcher = queue[i]
+15 -14
View File
@@ -94,22 +94,23 @@ export default class Watcher {
pushTarget(this)
let value
const vm = this.vm
if (this.user) {
try {
value = this.getter.call(vm, vm)
} catch (e) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
}
} else {
try {
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
return value
}
+6 -4
View File
@@ -15,10 +15,12 @@ if (process.env.NODE_ENV !== 'production') {
.replace(/[-_]/g, '')
warn = (msg, vm) => {
if (hasConsole && (!config.silent)) {
console.error(`[Vue warn]: ${msg}` + (
vm ? generateComponentTrace(vm) : ''
))
const trace = vm ? generateComponentTrace(vm) : ''
if (config.warnHandler) {
config.warnHandler.call(null, msg, vm, trace)
} else if (hasConsole && (!config.silent)) {
console.error(`[Vue warn]: ${msg}${trace}`)
}
}
+6 -3
View File
@@ -17,6 +17,9 @@ export const isAndroid = UA && UA.indexOf('android') > 0
export const isIOS = UA && /iphone|ipad|ipod|ios/.test(UA)
export const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge
// Firefox has a "watch" function on Object.prototype...
export const nativeWatch = ({}).watch
export let supportsPassive = false
if (inBrowser) {
try {
@@ -26,7 +29,7 @@ if (inBrowser) {
/* istanbul ignore next */
supportsPassive = true
}
} : Object)) // https://github.com/facebook/flow/issues/285
}: Object)) // https://github.com/facebook/flow/issues/285
window.addEventListener('test-passive', null, opts)
} catch (e) {}
}
@@ -96,13 +99,13 @@ export const nextTick = (function () {
// "force" the microtask queue to be flushed by adding an empty timer.
if (isIOS) setTimeout(noop)
}
} else if (typeof MutationObserver !== 'undefined' && (
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
// use MutationObserver where native Promise is not available,
// e.g. PhantomJS IE11, iOS7, Android 4.4
// e.g. PhantomJS, iOS7, Android 4.4
var counter = 1
var observer = new MutationObserver(nextTickHandler)
var textNode = document.createTextNode(String(counter))
+48 -16
View File
@@ -2,6 +2,7 @@
import config from '../config'
import { warn } from './debug'
import { nativeWatch } from './env'
import { set } from '../observer/index'
import {
@@ -63,7 +64,7 @@ function mergeData (to: Object, from: ?Object): Object {
/**
* Data
*/
strats.data = function (
export function mergeDataOrFn (
parentVal: any,
childVal: any,
vm?: Component
@@ -73,15 +74,6 @@ strats.data = function (
if (!childVal) {
return parentVal
}
if (typeof childVal !== 'function') {
process.env.NODE_ENV !== 'production' && warn(
'The "data" option should be a function ' +
'that returns a per-instance value in component ' +
'definitions.',
vm
)
return parentVal
}
if (!parentVal) {
return childVal
}
@@ -92,8 +84,8 @@ strats.data = function (
// it has to be a function to pass previous merges.
return function mergedDataFn () {
return mergeData(
childVal.call(this),
parentVal.call(this)
typeof childVal === 'function' ? childVal.call(this) : childVal,
typeof parentVal === 'function' ? parentVal.call(this) : parentVal
)
}
} else if (parentVal || childVal) {
@@ -104,7 +96,7 @@ strats.data = function (
: childVal
const defaultData = typeof parentVal === 'function'
? parentVal.call(vm)
: undefined
: parentVal
if (instanceData) {
return mergeData(instanceData, defaultData)
} else {
@@ -114,6 +106,28 @@ strats.data = function (
}
}
strats.data = function (
parentVal: any,
childVal: any,
vm?: Component
): ?Function {
if (!vm) {
if (childVal && typeof childVal !== 'function') {
process.env.NODE_ENV !== 'production' && warn(
'The "data" option should be a function ' +
'that returns a per-instance value in component ' +
'definitions.',
vm
)
return parentVal
}
return mergeDataOrFn.call(this, parentVal, childVal)
}
return mergeDataOrFn(parentVal, childVal, vm)
}
/**
* Hooks and props are merged as arrays.
*/
@@ -159,6 +173,9 @@ ASSET_TYPES.forEach(function (type) {
* another, so we merge them as arrays.
*/
strats.watch = function (parentVal: ?Object, childVal: ?Object): ?Object {
// work around Firefox's Object.prototype.watch...
if (parentVal === nativeWatch) parentVal = undefined
if (childVal === nativeWatch) childVal = undefined
/* istanbul ignore if */
if (!childVal) return Object.create(parentVal || null)
if (!parentVal) return childVal
@@ -172,7 +189,7 @@ strats.watch = function (parentVal: ?Object, childVal: ?Object): ?Object {
}
ret[key] = parent
? parent.concat(child)
: [child]
: Array.isArray(child) ? child : [child]
}
return ret
}
@@ -182,14 +199,15 @@ strats.watch = function (parentVal: ?Object, childVal: ?Object): ?Object {
*/
strats.props =
strats.methods =
strats.inject =
strats.computed = function (parentVal: ?Object, childVal: ?Object): ?Object {
if (!childVal) return Object.create(parentVal || null)
if (!parentVal) return childVal
const ret = Object.create(null)
extend(ret, parentVal)
extend(ret, childVal)
if (childVal) extend(ret, childVal)
return ret
}
strats.provide = mergeDataOrFn
/**
* Default strategy.
@@ -247,6 +265,19 @@ function normalizeProps (options: Object) {
options.props = res
}
/**
* Normalize all injections into Object-based format
*/
function normalizeInject (options: Object) {
const inject = options.inject
if (Array.isArray(inject)) {
const normalized = options.inject = {}
for (let i = 0; i < inject.length; i++) {
normalized[inject[i]] = inject[i]
}
}
}
/**
* Normalize raw function directives into object format.
*/
@@ -280,6 +311,7 @@ export function mergeOptions (
}
normalizeProps(child)
normalizeInject(child)
normalizeDirectives(child)
const extendsFrom = child.extends
if (extendsFrom) {
+14 -3
View File
@@ -1,8 +1,14 @@
/* @flow */
import { hasOwn, isObject, isPlainObject, capitalize, hyphenate } from 'shared/util'
import { observe, observerState } from '../observer/index'
import { warn } from './debug'
import { observe, observerState } from '../observer/index'
import {
hasOwn,
isObject,
hyphenate,
capitalize,
isPlainObject
} from 'shared/util'
type PropOptions = {
type: Function | Array<Function> | null,
@@ -139,7 +145,12 @@ function assertType (value: any, type: Function): {
let valid
const expectedType = getType(type)
if (simpleCheckRE.test(expectedType)) {
valid = typeof value === expectedType.toLowerCase()
const t = typeof value
valid = t === expectedType.toLowerCase()
// for primitive wrapper objects
if (!valid && t === 'object') {
valid = value instanceof type
}
} else if (expectedType === 'Object') {
valid = isPlainObject(value)
} else if (expectedType === 'Array') {
+27 -9
View File
@@ -15,6 +15,7 @@ import {
import {
resolveAsyncComponent,
createAsyncPlaceholder,
extractPropsFromVNodeData
} from './helpers/index'
@@ -97,7 +98,7 @@ const hooksToMerge = Object.keys(componentVNodeHooks)
export function createComponent (
Ctor: Class<Component> | Function | Object | void,
data?: VNodeData,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag?: string
@@ -123,21 +124,30 @@ export function createComponent (
}
// async component
let asyncFactory
if (isUndef(Ctor.cid)) {
Ctor = resolveAsyncComponent(Ctor, baseCtor, context)
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
if (Ctor === undefined) {
// return nothing if this is indeed an async component
// wait for the callback to trigger parent update.
return
// return a placeholder node for async component, which is rendered
// as a comment node but preserves all the raw information for the node.
// the information will be used for async server-rendering and hydration.
return createAsyncPlaceholder(
asyncFactory,
data,
context,
children,
tag
)
}
}
data = data || {}
// resolve constructor options in case global mixins are applied after
// component constructor creation
resolveConstructorOptions(Ctor)
data = data || {}
// transform component v-model data into props & events
if (isDef(data.model)) {
transformModel(Ctor.options, data)
@@ -155,12 +165,19 @@ export function createComponent (
// child component listeners instead of DOM listeners
const listeners = data.on
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn
if (isTrue(Ctor.options.abstract)) {
// abstract components do not keep anything
// other than props & listeners
// other than props & listeners & slot
// work around flow
const slot = data.slot
data = {}
if (slot) {
data.slot = slot
}
}
// merge component management hooks onto the placeholder node
@@ -171,7 +188,8 @@ export function createComponent (
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children }
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
return vnode
}
+15 -1
View File
@@ -57,10 +57,24 @@ export function _createElement (
)
return createEmptyVNode()
}
// object syntax in v-bind
if (isDef(data) && isDef(data.is)) {
tag = data.is
}
if (!tag) {
// in case of component :is set to falsy value
return createEmptyVNode()
}
// warn against non-primitive key
if (process.env.NODE_ENV !== 'production' &&
isDef(data) && isDef(data.key) && !isPrimitive(data.key)
) {
warn(
'Avoid using non-primitive value as key, ' +
'use string/number value instead.',
context
)
}
// support single function children as default scoped slot
if (Array.isArray(children) &&
typeof children[0] === 'function'
@@ -77,7 +91,7 @@ export function _createElement (
let vnode, ns
if (typeof tag === 'string') {
let Ctor
ns = config.getTagNamespace(tag)
ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
if (config.isReservedTag(tag)) {
// platform built-in elements
vnode = new VNode(
+3 -2
View File
@@ -8,6 +8,7 @@ import { resolveSlots } from '../instance/render-helpers/resolve-slots'
import {
isDef,
camelize,
emptyObject,
validateProp
} from '../util/index'
@@ -22,7 +23,7 @@ export function createFunctionalComponent (
const propOptions = Ctor.options.props
if (isDef(propOptions)) {
for (const key in propOptions) {
props[key] = validateProp(key, propOptions, propsData || {})
props[key] = validateProp(key, propOptions, propsData || emptyObject)
}
} else {
if (isDef(data.attrs)) mergeProps(props, data.attrs)
@@ -37,7 +38,7 @@ export function createFunctionalComponent (
props,
children,
parent: context,
listeners: data.on || {},
listeners: data.on || emptyObject,
injections: resolveInject(Ctor.options.inject, context),
slots: () => resolveSlots(children, context)
})
+2 -1
View File
@@ -1,12 +1,13 @@
/* @flow */
import { isDef } from 'shared/util'
import { isAsyncPlaceholder } from './is-async-placeholder'
export function getFirstComponentChild (children: ?Array<VNode>): ?VNode {
if (Array.isArray(children)) {
for (let i = 0; i < children.length; i++) {
const c = children[i]
if (isDef(c) && isDef(c.componentOptions)) {
if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
return c
}
}
+1
View File
@@ -6,3 +6,4 @@ export * from './update-listeners'
export * from './normalize-children'
export * from './resolve-async-component'
export * from './get-first-component-child'
export * from './is-async-placeholder'
+18
View File
@@ -9,12 +9,30 @@ import {
isObject
} from 'core/util/index'
import { createEmptyVNode } from 'core/vdom/vnode'
function ensureCtor (comp, base) {
if (comp.__esModule && comp.default) {
comp = comp.default
}
return isObject(comp)
? base.extend(comp)
: comp
}
export function createAsyncPlaceholder (
factory: Function,
data: ?VNodeData,
context: Component,
children: ?Array<VNode>,
tag: ?string
): VNode {
const node = createEmptyVNode()
node.asyncFactory = factory
node.asyncMeta = { data, context, children, tag }
return node
}
export function resolveAsyncComponent (
factory: Function,
baseCtor: Class<Component>,
+25 -4
View File
@@ -5,9 +5,11 @@ import { cached, isUndef } from 'shared/util'
const normalizeEvent = cached((name: string): {
name: string,
plain: boolean,
once: boolean,
capture: boolean,
passive: boolean
passive: boolean,
handler?: Function
} => {
const passive = name.charAt(0) === '&'
name = passive ? name.slice(1) : name
@@ -15,8 +17,10 @@ const normalizeEvent = cached((name: string): {
name = once ? name.slice(1) : name
const capture = name.charAt(0) === '!'
name = capture ? name.slice(1) : name
const plain = !(passive || once || capture)
return {
name,
plain,
once,
capture,
passive
@@ -27,8 +31,9 @@ export function createFnInvoker (fns: Function | Array<Function>): Function {
function invoker () {
const fns = invoker.fns
if (Array.isArray(fns)) {
for (let i = 0; i < fns.length; i++) {
fns[i].apply(null, arguments)
const cloned = fns.slice()
for (let i = 0; i < cloned.length; i++) {
cloned[i].apply(null, arguments)
}
} else {
// return handler return value for single handlers
@@ -39,6 +44,11 @@ export function createFnInvoker (fns: Function | Array<Function>): Function {
return invoker
}
// #6552
function prioritizePlainEvents (a, b) {
return a.plain ? -1 : b.plain ? 1 : 0
}
export function updateListeners (
on: Object,
oldOn: Object,
@@ -47,10 +57,13 @@ export function updateListeners (
vm: Component
) {
let name, cur, old, event
const toAdd = []
let hasModifier = false
for (name in on) {
cur = on[name]
old = oldOn[name]
event = normalizeEvent(name)
if (!event.plain) hasModifier = true
if (isUndef(cur)) {
process.env.NODE_ENV !== 'production' && warn(
`Invalid handler for event "${event.name}": got ` + String(cur),
@@ -60,12 +73,20 @@ export function updateListeners (
if (isUndef(cur.fns)) {
cur = on[name] = createFnInvoker(cur)
}
add(event.name, cur, event.once, event.capture, event.passive)
event.handler = cur
toAdd.push(event)
} else if (cur !== old) {
old.fns = cur
on[name] = old
}
}
if (toAdd.length) {
if (hasModifier) toAdd.sort(prioritizePlainEvents)
for (let i = 0; i < toAdd.length; i++) {
const event = toAdd[i]
add(event.name, event.handler, event.once, event.capture, event.passive)
}
}
for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name)
+4 -3
View File
@@ -32,10 +32,11 @@ export function registerRef (vnode: VNodeWithData, isRemoval: ?boolean) {
}
} else {
if (vnode.data.refInFor) {
if (Array.isArray(refs[key]) && refs[key].indexOf(ref) < 0) {
refs[key].push(ref)
} else {
if (!Array.isArray(refs[key])) {
refs[key] = [ref]
} else if (refs[key].indexOf(ref) < 0) {
// $flow-disable-line
refs[key].push(ref)
}
} else {
refs[key] = ref
+103 -42
View File
@@ -6,8 +6,6 @@
*
* modified by Evan You (@yyx990803)
*
/*
* Not type-checking this because this file is perf-critical and the cost
* of making flow understand it is not worth it.
*/
@@ -17,6 +15,7 @@ import config from '../config'
import { SSR_ATTR } from 'shared/constants'
import { registerRef } from './modules/ref'
import { activeInstance } from '../instance/lifecycle'
import { isTextInputType } from 'web/util/element'
import {
warn,
@@ -33,22 +32,27 @@ const hooks = ['create', 'activate', 'update', 'remove', 'destroy']
function sameVnode (a, b) {
return (
a.key === b.key &&
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
a.key === b.key && (
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
) || (
isTrue(a.isAsyncPlaceholder) &&
a.asyncFactory === b.asyncFactory &&
isUndef(b.asyncFactory.error)
)
)
)
}
// Some browsers do not support dynamically changing type for <input>
// so they need to be treated as different nodes
function sameInputType (a, b) {
if (a.tag !== 'input') return true
let i
const typeA = isDef(i = a.data) && isDef(i = i.attrs) && i.type
const typeB = isDef(i = b.data) && isDef(i = i.attrs) && i.type
return typeA === typeB
return typeA === typeB || isTextInputType(typeA) && isTextInputType(typeB)
}
function createKeyToOldIdx (children, beginIdx, endIdx) {
@@ -397,10 +401,11 @@ export function createPatchFunction (backend) {
newStartVnode = newCh[++newStartIdx]
} else {
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
newStartVnode = newCh[++newStartIdx]
} else {
elmToMove = oldCh[idxInOld]
/* istanbul ignore if */
@@ -413,14 +418,13 @@ export function createPatchFunction (backend) {
if (sameVnode(elmToMove, newStartVnode)) {
patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm)
newStartVnode = newCh[++newStartIdx]
canMove && nodeOps.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm)
} else {
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
newStartVnode = newCh[++newStartIdx]
}
}
newStartVnode = newCh[++newStartIdx]
}
}
if (oldStartIdx > oldEndIdx) {
@@ -431,10 +435,29 @@ export function createPatchFunction (backend) {
}
}
function findIdxInOld (node, oldCh, start, end) {
for (let i = start; i < end; i++) {
const c = oldCh[i]
if (isDef(c) && sameVnode(node, c)) return i
}
}
function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
if (oldVnode === vnode) {
return
}
const elm = vnode.elm = oldVnode.elm
if (isTrue(oldVnode.isAsyncPlaceholder)) {
if (isDef(vnode.asyncFactory.resolved)) {
hydrate(oldVnode.elm, vnode, insertedVnodeQueue)
} else {
vnode.isAsyncPlaceholder = true
}
return
}
// reuse element for static trees.
// note we only do this if the vnode is cloned -
// if the new node is not cloned it means the render functions have been
@@ -444,16 +467,16 @@ export function createPatchFunction (backend) {
vnode.key === oldVnode.key &&
(isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
) {
vnode.elm = oldVnode.elm
vnode.componentInstance = oldVnode.componentInstance
return
}
let i
const data = vnode.data
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
i(oldVnode, vnode)
}
const elm = vnode.elm = oldVnode.elm
const oldCh = oldVnode.children
const ch = vnode.children
if (isDef(data) && isPatchable(vnode)) {
@@ -498,6 +521,11 @@ export function createPatchFunction (backend) {
// Note: this is a browser-only function so we can assume elms are DOM nodes.
function hydrate (elm, vnode, insertedVnodeQueue) {
if (isTrue(vnode.isComment) && isDef(vnode.asyncFactory)) {
vnode.elm = elm
vnode.isAsyncPlaceholder = true
return true
}
if (process.env.NODE_ENV !== 'production') {
if (!assertNodeMatch(elm, vnode)) {
return false
@@ -519,27 +547,46 @@ export function createPatchFunction (backend) {
if (!elm.hasChildNodes()) {
createChildren(vnode, children, insertedVnodeQueue)
} else {
let childrenMatch = true
let childNode = elm.firstChild
for (let i = 0; i < children.length; i++) {
if (!childNode || !hydrate(childNode, children[i], insertedVnodeQueue)) {
childrenMatch = false
break
// v-html and domProps: innerHTML
if (isDef(i = data) && isDef(i = i.domProps) && isDef(i = i.innerHTML)) {
if (i !== elm.innerHTML) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' &&
typeof console !== 'undefined' &&
!bailed
) {
bailed = true
console.warn('Parent: ', elm)
console.warn('server innerHTML: ', i)
console.warn('client innerHTML: ', elm.innerHTML)
}
return false
}
childNode = childNode.nextSibling
}
// if childNode is not null, it means the actual childNodes list is
// longer than the virtual children list.
if (!childrenMatch || childNode) {
if (process.env.NODE_ENV !== 'production' &&
typeof console !== 'undefined' &&
!bailed
) {
bailed = true
console.warn('Parent: ', elm)
console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children)
} else {
// iterate and compare children lists
let childrenMatch = true
let childNode = elm.firstChild
for (let i = 0; i < children.length; i++) {
if (!childNode || !hydrate(childNode, children[i], insertedVnodeQueue)) {
childrenMatch = false
break
}
childNode = childNode.nextSibling
}
// if childNode is not null, it means the actual childNodes list is
// longer than the virtual children list.
if (!childrenMatch || childNode) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' &&
typeof console !== 'undefined' &&
!bailed
) {
bailed = true
console.warn('Parent: ', elm)
console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children)
}
return false
}
return false
}
}
}
@@ -630,14 +677,28 @@ export function createPatchFunction (backend) {
// component root element replaced.
// update parent placeholder node element, recursively
let ancestor = vnode.parent
const patchable = isPatchable(vnode)
while (ancestor) {
ancestor.elm = vnode.elm
ancestor = ancestor.parent
}
if (isPatchable(vnode)) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, vnode.parent)
for (let i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i](ancestor)
}
ancestor.elm = vnode.elm
if (patchable) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, ancestor)
}
// #6513
// invoke insert hooks that may have been merged by create hooks.
// e.g. for directives that uses the "inserted" hook.
const insert = ancestor.data.hook.insert
if (insert.merged) {
// start at index 1 to avoid re-invoking component mounted hook
for (let i = 1; i < insert.fns.length; i++) {
insert.fns[i]()
}
}
}
ancestor = ancestor.parent
}
}
+19 -7
View File
@@ -19,6 +19,10 @@ export default class VNode {
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
asyncFactory: Function | void; // async component factory function
asyncMeta: Object | void;
isAsyncPlaceholder: boolean;
ssrContext: Object | void;
constructor (
tag?: string,
@@ -27,7 +31,8 @@ export default class VNode {
text?: string,
elm?: Node,
context?: Component,
componentOptions?: VNodeComponentOptions
componentOptions?: VNodeComponentOptions,
asyncFactory?: Function
) {
this.tag = tag
this.data = data
@@ -47,6 +52,9 @@ export default class VNode {
this.isComment = false
this.isCloned = false
this.isOnce = false
this.asyncFactory = asyncFactory
this.asyncMeta = undefined
this.isAsyncPlaceholder = false
}
// DEPRECATED: alias for componentInstance for backwards compat.
@@ -56,9 +64,9 @@ export default class VNode {
}
}
export const createEmptyVNode = () => {
export const createEmptyVNode = (text: string = '') => {
const node = new VNode()
node.text = ''
node.text = text
node.isComment = true
return node
}
@@ -71,7 +79,7 @@ export function createTextVNode (val: string | number) {
// used for static nodes and slot nodes because they may be reused across
// multiple renders, cloning them avoids errors when DOM manipulations rely
// on their elm reference.
export function cloneVNode (vnode: VNode): VNode {
export function cloneVNode (vnode: VNode, deep?: boolean): VNode {
const cloned = new VNode(
vnode.tag,
vnode.data,
@@ -79,21 +87,25 @@ export function cloneVNode (vnode: VNode): VNode {
vnode.text,
vnode.elm,
vnode.context,
vnode.componentOptions
vnode.componentOptions,
vnode.asyncFactory
)
cloned.ns = vnode.ns
cloned.isStatic = vnode.isStatic
cloned.key = vnode.key
cloned.isComment = vnode.isComment
cloned.isCloned = true
if (deep && vnode.children) {
cloned.children = cloneVNodes(vnode.children)
}
return cloned
}
export function cloneVNodes (vnodes: Array<VNode>): Array<VNode> {
export function cloneVNodes (vnodes: Array<VNode>, deep?: boolean): Array<VNode> {
const len = vnodes.length
const res = new Array(len)
for (let i = 0; i < len; i++) {
res[i] = cloneVNode(vnodes[i])
res[i] = cloneVNode(vnodes[i], deep)
}
return res
}
-4
View File
@@ -1,4 +0,0 @@
/* @flow */
export { parseComponent } from 'sfc/parser'
export { compile, compileToFunctions } from './compiler/index'
+7 -3
View File
@@ -40,7 +40,11 @@ export default function model (
}
}
if (tag === 'select') {
if (el.component) {
genComponentModel(el, value, modifiers)
// component v-model doesn't need extra runtime
return false
} else if (tag === 'select') {
genSelect(el, value, modifiers)
} else if (tag === 'input' && type === 'checkbox') {
genCheckboxModel(el, value, modifiers)
@@ -89,7 +93,7 @@ function genCheckboxModel (
'if(Array.isArray($$a)){' +
`var $$v=${number ? '_n(' + valueBinding + ')' : valueBinding},` +
'$$i=_i($$a,$$v);' +
`if($$c){$$i<0&&(${value}=$$a.concat($$v))}` +
`if($$el.checked){$$i<0&&(${value}=$$a.concat([$$v]))}` +
`else{$$i>-1&&(${value}=$$a.slice(0,$$i).concat($$a.slice($$i+1)))}` +
`}else{${genAssignmentCode(value, '$$c')}}`,
null, true
@@ -154,7 +158,7 @@ function genDefaultModel (
addProp(el, 'value', `(${value})`)
addHandler(el, event, code, null, true)
if (trim || number || type === 'number') {
if (trim || number) {
addHandler(el, 'blur', '$forceUpdate()')
}
}
+2 -25
View File
@@ -1,31 +1,8 @@
/* @flow */
import { isUnaryTag, canBeLeftOpenTag } from './util'
import { genStaticKeys } from 'shared/util'
import { baseOptions } from './options'
import { createCompiler } from 'compiler/index'
import modules from './modules/index'
import directives from './directives/index'
import {
isPreTag,
mustUseProp,
isReservedTag,
getTagNamespace
} from '../util/index'
export const baseOptions: CompilerOptions = {
expectHTML: true,
modules,
directives,
isPreTag,
isUnaryTag,
mustUseProp,
canBeLeftOpenTag,
isReservedTag,
getTagNamespace,
staticKeys: genStaticKeys(modules)
}
const { compile, compileToFunctions } = createCompiler(baseOptions)
export { compile, compileToFunctions }
-98
View File
@@ -1,98 +0,0 @@
/* @flow */
import config from 'core/config'
import { warn, cached } from 'core/util/index'
import { mark, measure } from 'core/util/perf'
import Vue from './runtime/index'
import { query } from './util/index'
import { shouldDecodeNewlines } from './util/compat'
import { compileToFunctions } from './compiler/index'
const idToTemplate = cached(id => {
const el = query(id)
return el && el.innerHTML
})
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && query(el)
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
process.env.NODE_ENV !== 'production' && warn(
`Do not mount Vue to <html> or <body> - mount to normal elements instead.`
)
return this
}
const options = this.$options
// resolve template/el and convert to render function
if (!options.render) {
let template = options.template
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template)
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`,
this
)
}
}
} else if (template.nodeType) {
template = template.innerHTML
} else {
if (process.env.NODE_ENV !== 'production') {
warn('invalid template option:' + template, this)
}
return this
}
} else if (el) {
template = getOuterHTML(el)
}
if (template) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile')
}
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
delimiters: options.delimiters
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
mark('compile end')
measure(`${this._name} compile`, 'compile', 'compile end')
}
}
}
return mount.call(this, el, hydrating)
}
/**
* Get outerHTML of elements, taking care
* of SVG elements in IE as well.
*/
function getOuterHTML (el: Element): string {
if (el.outerHTML) {
return el.outerHTML
} else {
const container = document.createElement('div')
container.appendChild(el.cloneNode(true))
return container.innerHTML
}
}
Vue.compile = compileToFunctions
export default Vue
-5
View File
@@ -1,5 +0,0 @@
/* @flow */
import Vue from './runtime/index'
export default Vue
+9 -1
View File
@@ -42,12 +42,20 @@ export function removeClass (el: HTMLElement, cls: ?string) {
} else {
el.classList.remove(cls)
}
if (!el.classList.length) {
el.removeAttribute('class')
}
} else {
let cur = ` ${el.getAttribute('class') || ''} `
const tar = ' ' + cls + ' '
while (cur.indexOf(tar) >= 0) {
cur = cur.replace(tar, ' ')
}
el.setAttribute('class', cur.trim())
cur = cur.trim()
if (cur) {
el.setAttribute('class', cur)
} else {
el.removeAttribute('class')
}
}
}
+2 -1
View File
@@ -127,7 +127,8 @@ export default {
if (!hasTransition) {
return false
}
if (this._hasMove != null) {
/* istanbul ignore if */
if (this._hasMove) {
return this._hasMove
}
// Detect whether an element with the move class applied has
+19 -5
View File
@@ -5,7 +5,11 @@
import { warn } from 'core/util/index'
import { camelize, extend, isPrimitive } from 'shared/util'
import { mergeVNodeHook, getFirstComponentChild } from 'core/vdom/helpers/index'
import {
mergeVNodeHook,
isAsyncPlaceholder,
getFirstComponentChild
} from 'core/vdom/helpers/index'
export const transitionProps = {
name: String,
@@ -78,13 +82,13 @@ export default {
abstract: true,
render (h: Function) {
let children: ?Array<VNode> = this.$slots.default
let children: ?Array<VNode> = this.$options._renderChildren
if (!children) {
return
}
// filter out text nodes (possible whitespaces)
children = children.filter((c: VNode) => c.tag)
children = children.filter((c: VNode) => c.tag || isAsyncPlaceholder(c))
/* istanbul ignore if */
if (!children.length) {
return
@@ -136,7 +140,9 @@ export default {
// during entering.
const id: string = `__transition-${this._uid}-`
child.key = child.key == null
? id + child.tag
? child.isComment
? id + 'comment'
: id + child.tag
: isPrimitive(child.key)
? (String(child.key).indexOf(id) === 0 ? child.key : id + child.key)
: child.key
@@ -151,7 +157,12 @@ export default {
child.data.show = true
}
if (oldChild && oldChild.data && !isSameChild(child, oldChild)) {
if (
oldChild &&
oldChild.data &&
!isSameChild(child, oldChild) &&
!isAsyncPlaceholder(oldChild)
) {
// replace old child transition data with fresh one
// important for dynamic transitions!
const oldData: Object = oldChild && (oldChild.data.transition = extend({}, data))
@@ -165,6 +176,9 @@ export default {
})
return placeholder(h, rawChild)
} else if (mode === 'in-out') {
if (isAsyncPlaceholder(child)) {
return oldRawChild
}
let delayedLeave
const performLeave = () => { delayedLeave() }
mergeVNodeHook(data, 'afterEnter', performLeave)
+26 -20
View File
@@ -3,6 +3,7 @@
* properties to Elements.
*/
import { isTextInputType } from 'web/util/element'
import { looseEqual, looseIndexOf } from 'shared/util'
import { warn, isAndroid, isIE9, isIE, isEdge } from 'core/util/index'
@@ -20,15 +21,9 @@ if (isIE9) {
export default {
inserted (el, binding, vnode) {
if (vnode.tag === 'select') {
const cb = () => {
setSelected(el, binding, vnode.context)
}
cb()
/* istanbul ignore if */
if (isIE || isEdge) {
setTimeout(cb, 0)
}
} else if (vnode.tag === 'textarea' || el.type === 'text' || el.type === 'password') {
setSelected(el, binding, vnode.context)
el._vOptions = [].map.call(el.options, getValue)
} else if (vnode.tag === 'textarea' || isTextInputType(el.type)) {
el._vModifiers = binding.modifiers
if (!binding.modifiers.lazy) {
// Safari < 10.2 & UIWebView doesn't fire compositionend when
@@ -54,17 +49,33 @@ export default {
// it's possible that the value is out-of-sync with the rendered options.
// detect such cases and filter out values that no longer has a matching
// option in the DOM.
const needReset = el.multiple
? binding.value.some(v => hasNoMatchingOption(v, el.options))
: binding.value !== binding.oldValue && hasNoMatchingOption(binding.value, el.options)
if (needReset) {
trigger(el, 'change')
const prevOptions = el._vOptions
const curOptions = el._vOptions = [].map.call(el.options, getValue)
if (curOptions.some((o, i) => !looseEqual(o, prevOptions[i]))) {
// trigger change event if
// no matching option found for at least one value
const needReset = el.multiple
? binding.value.some(v => hasNoMatchingOption(v, curOptions))
: binding.value !== binding.oldValue && hasNoMatchingOption(binding.value, curOptions)
if (needReset) {
trigger(el, 'change')
}
}
}
}
}
function setSelected (el, binding, vm) {
actuallySetSelected(el, binding, vm)
/* istanbul ignore if */
if (isIE || isEdge) {
setTimeout(() => {
actuallySetSelected(el, binding, vm)
}, 0)
}
}
function actuallySetSelected (el, binding, vm) {
const value = binding.value
const isMultiple = el.multiple
if (isMultiple && !Array.isArray(value)) {
@@ -100,12 +111,7 @@ function setSelected (el, binding, vm) {
}
function hasNoMatchingOption (value, options) {
for (let i = 0, l = options.length; i < l; i++) {
if (looseEqual(getValue(options[i]), value)) {
return false
}
}
return true
return options.every(o => !looseEqual(o, value))
}
function getValue (option) {
+2 -3
View File
@@ -1,6 +1,5 @@
/* @flow */
import { isIE9 } from 'core/util/env'
import { enter, leave } from '../modules/transition'
// recursively search for possible transition defined inside the component root
@@ -16,7 +15,7 @@ export default {
const transition = vnode.data && vnode.data.transition
const originalDisplay = el.__vOriginalDisplay =
el.style.display === 'none' ? '' : el.style.display
if (value && transition && !isIE9) {
if (value && transition) {
vnode.data.show = true
enter(vnode, () => {
el.style.display = originalDisplay
@@ -31,7 +30,7 @@ export default {
if (value === oldValue) return
vnode = locateNode(vnode)
const transition = vnode.data && vnode.data.transition
if (transition && !isIE9) {
if (transition) {
vnode.data.show = true
if (value) {
enter(vnode, () => {
+10 -1
View File
@@ -18,6 +18,10 @@ import {
} from 'web/util/index'
function updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) {
const opts = vnode.componentOptions
if (isDef(opts) && opts.Ctor.options.inheritAttrs === false) {
return
}
if (isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs)) {
return
}
@@ -60,7 +64,12 @@ function setAttr (el: Element, key: string, value: any) {
if (isFalsyAttrValue(value)) {
el.removeAttribute(key)
} else {
el.setAttribute(key, key)
// technically allowfullscreen is a boolean attribute for <iframe>,
// but Flash expects a value of "true" when used on <embed> tag
value = key === 'allowfullscreen' && el.tagName === 'EMBED'
? 'true'
: key
el.setAttribute(key, value)
}
} else if (isEnumeratedAttr(key)) {
el.setAttribute(key, isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true')
+8 -3
View File
@@ -61,14 +61,19 @@ function shouldUpdateValue (
}
function isDirty (elm: acceptValueElm, checkVal: string): boolean {
// return true when textbox (.number and .trim) loses focus and its value is not equal to the updated value
return document.activeElement !== elm && elm.value !== checkVal
// return true when textbox (.number and .trim) loses focus and its value is
// not equal to the updated value
let notInFocus = true
// #6157
// work around IE bug when accessing document.activeElement in an iframe
try { notInFocus = document.activeElement !== elm } catch (e) {}
return notInFocus && elm.value !== checkVal
}
function isInputChanged (elm: any, newVal: string): boolean {
const value = elm.value
const modifiers = elm._vModifiers // injected by v-model runtime
if ((isDef(modifiers) && modifiers.number) || elm.type === 'number') {
if (isDef(modifiers) && modifiers.number) {
return toNumber(value) !== toNumber(newVal)
}
if (isDef(modifiers) && modifiers.trim) {
+10 -10
View File
@@ -26,20 +26,20 @@ const setProp = (el, name, val) => {
}
}
const prefixes = ['Webkit', 'Moz', 'ms']
const vendorNames = ['Webkit', 'Moz', 'ms']
let testEl
let emptyStyle
const normalize = cached(function (prop) {
testEl = testEl || document.createElement('div')
emptyStyle = emptyStyle || document.createElement('div').style
prop = camelize(prop)
if (prop !== 'filter' && (prop in testEl.style)) {
if (prop !== 'filter' && (prop in emptyStyle)) {
return prop
}
const upper = prop.charAt(0).toUpperCase() + prop.slice(1)
for (let i = 0; i < prefixes.length; i++) {
const prefixed = prefixes[i] + upper
if (prefixed in testEl.style) {
return prefixed
const capName = prop.charAt(0).toUpperCase() + prop.slice(1)
for (let i = 0; i < vendorNames.length; i++) {
const name = vendorNames[i] + capName
if (name in emptyStyle) {
return name
}
}
})
@@ -65,7 +65,7 @@ function updateStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) {
const style = normalizeStyleBinding(vnode.data.style) || {}
// store normalized style under a different key for next diff
// make sure to clone it if it's reactive, since the user likley wants
// make sure to clone it if it's reactive, since the user likely wants
// to mutate it.
vnode.data.normalizedStyle = isDef(style.__ob__)
? extend({}, style)
+5 -2
View File
@@ -69,8 +69,11 @@ export function nextFrame (fn: Function) {
}
export function addTransitionClass (el: any, cls: string) {
(el._transitionClasses || (el._transitionClasses = [])).push(cls)
addClass(el, cls)
const transitionClasses = el._transitionClasses || (el._transitionClasses = [])
if (transitionClasses.indexOf(cls) < 0) {
transitionClasses.push(cls)
addClass(el, cls)
}
}
export function removeTransitionClass (el: any, cls: string) {
-26
View File
@@ -1,26 +0,0 @@
/* @flow */
process.env.VUE_ENV = 'server'
import modules from './server/modules/index'
import baseDirectives from './server/directives/index'
import { isUnaryTag, canBeLeftOpenTag } from './compiler/util'
import { createRenderer as _createRenderer } from 'server/create-renderer'
import { createBundleRendererCreator } from 'server/bundle-renderer/create-bundle-renderer'
export function createRenderer (options?: Object = {}): {
renderToString: Function,
renderToStream: Function
} {
return _createRenderer(Object.assign({}, options, {
isUnaryTag,
canBeLeftOpenTag,
modules,
// user can provide server-side implementations for custom directives
// when creating the renderer.
directives: Object.assign(baseDirectives, options.directives)
}))
}
export const createBundleRenderer = createBundleRendererCreator(createRenderer)
+10 -7
View File
@@ -1,6 +1,6 @@
/* @flow */
import { escape } from 'he'
import { escape } from '../util'
import {
isDef,
@@ -17,12 +17,15 @@ export default function renderAttrs (node: VNodeWithData): string {
let attrs = node.data.attrs
let res = ''
let parent = node.parent
while (isDef(parent)) {
if (isDef(parent.data) && isDef(parent.data.attrs)) {
attrs = Object.assign({}, attrs, parent.data.attrs)
const opts = node.parent && node.parent.componentOptions
if (isUndef(opts) || opts.Ctor.options.inheritAttrs !== false) {
let parent = node.parent
while (isDef(parent)) {
if (isDef(parent.data) && isDef(parent.data.attrs)) {
attrs = Object.assign({}, attrs, parent.data.attrs)
}
parent = parent.parent
}
parent = parent.parent
}
if (isUndef(attrs)) {
@@ -47,7 +50,7 @@ export function renderAttr (key: string, value: string): string {
} else if (isEnumeratedAttr(key)) {
return ` ${key}="${isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true'}"`
} else if (!isFalsyAttrValue(value)) {
return ` ${key}="${typeof value === 'string' ? escape(value) : value}"`
return ` ${key}="${escape(String(value))}"`
}
return ''
}
+1 -1
View File
@@ -1,6 +1,6 @@
/* @flow */
import { escape } from 'he'
import { escape } from '../util'
import { genClassForVnode } from 'web/util/index'
export default function renderClass (node: VNodeWithData): ?string {
+3 -4
View File
@@ -1,12 +1,11 @@
/* @flow */
import { escape } from 'he'
import { escape } from '../util'
import { hyphenate } from 'shared/util'
import { getStyle } from 'web/util/style'
function genStyleText (vnode: VNode): string {
export function genStyle (style: Object): string {
let styleText = ''
const style = getStyle(vnode, false)
for (const key in style) {
const value = style[key]
const hyphenatedKey = hyphenate(key)
@@ -22,7 +21,7 @@ function genStyleText (vnode: VNode): string {
}
export default function renderStyle (vnode: VNodeWithData): ?string {
const styleText = genStyleText(vnode)
const styleText = genStyle(getStyle(vnode, false))
if (styleText !== '') {
return ` style=${JSON.stringify(escape(styleText))}`
}
+15
View File
@@ -34,3 +34,18 @@ export const propsToAttrMap = {
htmlFor: 'for',
httpEquiv: 'http-equiv'
}
const ESC = {
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
'&': '&amp;'
}
export function escape (s: string) {
return s.replace(/[<>"&]/g, escapeChar)
}
function escapeChar (a) {
return ESC[a] || a
}
+1 -1
View File
@@ -7,7 +7,7 @@ import { makeMap } from 'shared/util'
export const isReservedAttr = makeMap('style,class')
// attributes that should be using props for binding
const acceptValue = makeMap('input,textarea,option,select')
const acceptValue = makeMap('input,textarea,option,select,progress')
export const mustUseProp = (tag: string, type: ?string, attr: string): boolean => {
return (
(attr === 'value' && acceptValue(tag)) && type !== 'button' ||
+34 -25
View File
@@ -1,6 +1,6 @@
/* @flow */
import { isDef, isUndef, isObject } from 'shared/util'
import { isDef, isObject } from 'shared/util'
export function genClassForVnode (vnode: VNode): string {
let data = vnode.data
@@ -17,7 +17,7 @@ export function genClassForVnode (vnode: VNode): string {
data = mergeClassData(data, parentNode.data)
}
}
return genClassFromData(data)
return renderClass(data.staticClass, data.class)
}
function mergeClassData (child: VNodeData, parent: VNodeData): {
@@ -32,9 +32,10 @@ function mergeClassData (child: VNodeData, parent: VNodeData): {
}
}
function genClassFromData (data: Object): string {
const dynamicClass = data.class
const staticClass = data.staticClass
export function renderClass (
staticClass: ?string,
dynamicClass: any
): string {
if (isDef(staticClass) || isDef(dynamicClass)) {
return concat(staticClass, stringifyClass(dynamicClass))
}
@@ -47,30 +48,38 @@ export function concat (a: ?string, b: ?string): string {
}
export function stringifyClass (value: any): string {
if (isUndef(value)) {
return ''
if (Array.isArray(value)) {
return stringifyArray(value)
}
if (isObject(value)) {
return stringifyObject(value)
}
if (typeof value === 'string') {
return value
}
let res = ''
if (Array.isArray(value)) {
let stringified
for (let i = 0, l = value.length; i < l; i++) {
if (isDef(value[i])) {
if (isDef(stringified = stringifyClass(value[i])) && stringified !== '') {
res += stringified + ' '
}
}
}
return res.slice(0, -1)
}
if (isObject(value)) {
for (const key in value) {
if (value[key]) res += key + ' '
}
return res.slice(0, -1)
}
/* istanbul ignore next */
return ''
}
function stringifyArray (value: Array<any>): string {
let res = ''
let stringified
for (let i = 0, l = value.length; i < l; i++) {
if (isDef(stringified = stringifyClass(value[i])) && stringified !== '') {
if (res) res += ' '
res += stringified
}
}
return res
}
function stringifyObject (value: Object): string {
let res = ''
for (const key in value) {
if (value[key]) {
if (res) res += ' '
res += key
}
}
return res
}
+1 -1
View File
@@ -5,7 +5,7 @@ import { inBrowser } from 'core/util/index'
// check whether current browser encodes a char inside attribute values
function shouldDecode (content: string, encoded: string): boolean {
const div = document.createElement('div')
div.innerHTML = `<div a="${content}">`
div.innerHTML = `<div a="${content}"/>`
return div.innerHTML.indexOf(encoded) > 0
}
+4 -2
View File
@@ -11,7 +11,7 @@ export const namespaceMap = {
export const isHTMLTag = makeMap(
'html,body,base,head,link,meta,style,title,' +
'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' +
'div,dd,dl,dt,figcaption,figure,hr,img,li,main,ol,p,pre,ul,' +
'div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,' +
'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' +
's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' +
'embed,object,param,source,canvas,script,noscript,del,ins,' +
@@ -19,7 +19,7 @@ export const isHTMLTag = makeMap(
'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' +
'output,progress,select,textarea,' +
'details,dialog,menu,menuitem,summary,' +
'content,element,shadow,template'
'content,element,shadow,template,blockquote,iframe,tfoot'
)
// this map is intentionally selective, only covering SVG elements that may
@@ -73,3 +73,5 @@ export function isUnknownElement (tag: string): boolean {
return (unknownElementCache[tag] = /HTMLUnknownElement/.test(el.toString()))
}
}
export const isTextInputType = makeMap('text,number,password,search,email,tel,url')
-1
View File
@@ -1 +0,0 @@
export { compile } from 'weex/compiler/index'
+1 -1
View File
@@ -4,7 +4,7 @@ import { cached, camelize } from 'shared/util'
const normalize = cached(camelize)
function normalizeKeyName (str: string) : string {
function normalizeKeyName (str: string): string {
if (str.match(/^v\-/)) {
return str.replace(/(v-[a-z\-]+\:)([a-z\-]+)$/i, ($, directive, prop) => {
return directive + normalize(prop)
-417
View File
@@ -1,417 +0,0 @@
import TextNode from 'weex/runtime/text-node'
// this will be preserved during build
const VueFactory = require('./factory')
const instances = {}
const modules = {}
const components = {}
const renderer = {
TextNode,
instances,
modules,
components
}
/**
* Prepare framework config, basically about the virtual-DOM and JS bridge.
* @param {object} cfg
*/
export function init (cfg) {
renderer.Document = cfg.Document
renderer.Element = cfg.Element
renderer.Comment = cfg.Comment
renderer.sendTasks = cfg.sendTasks
}
/**
* Reset framework config and clear all registrations.
*/
export function reset () {
clear(instances)
clear(modules)
clear(components)
delete renderer.Document
delete renderer.Element
delete renderer.Comment
delete renderer.sendTasks
}
/**
* Delete all keys of an object.
* @param {object} obj
*/
function clear (obj) {
for (const key in obj) {
delete obj[key]
}
}
/**
* Create an instance with id, code, config and external data.
* @param {string} instanceId
* @param {string} appCode
* @param {object} config
* @param {object} data
* @param {object} env { info, config, services }
*/
export function createInstance (
instanceId,
appCode = '',
config = {},
data,
env = {}
) {
// Virtual-DOM object.
const document = new renderer.Document(instanceId, config.bundleUrl)
// All function/callback of parameters before sent to native
// will be converted as an id. So `callbacks` is used to store
// these real functions. When a callback invoked and won't be
// called again, it should be removed from here automatically.
const callbacks = []
// The latest callback id, incremental.
const callbackId = 1
const instance = instances[instanceId] = {
instanceId, config, data,
document, callbacks, callbackId
}
// Prepare native module getter and HTML5 Timer APIs.
const moduleGetter = genModuleGetter(instanceId)
const timerAPIs = getInstanceTimer(instanceId, moduleGetter)
// Prepare `weex` instance variable.
const weexInstanceVar = {
config,
document,
requireModule: moduleGetter
}
Object.freeze(weexInstanceVar)
// Each instance has a independent `Vue` module instance
const Vue = instance.Vue = createVueModuleInstance(instanceId, moduleGetter)
// The function which create a closure the JS Bundle will run in.
// It will declare some instance variables like `Vue`, HTML5 Timer APIs etc.
const instanceVars = Object.assign({
Vue,
weex: weexInstanceVar,
// deprecated
__weex_require_module__: weexInstanceVar.requireModule // eslint-disable-line
}, timerAPIs)
callFunction(instanceVars, appCode)
// Send `createFinish` signal to native.
renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'createFinish', args: [] }], -1)
}
/**
* Destroy an instance with id. It will make sure all memory of
* this instance released and no more leaks.
* @param {string} instanceId
*/
export function destroyInstance (instanceId) {
const instance = instances[instanceId]
if (instance && instance.app instanceof instance.Vue) {
instance.app.$destroy()
}
delete instances[instanceId]
}
/**
* Refresh an instance with id and new top-level component data.
* It will use `Vue.set` on all keys of the new data. So it's better
* define all possible meaningful keys when instance created.
* @param {string} instanceId
* @param {object} data
*/
export function refreshInstance (instanceId, data) {
const instance = instances[instanceId]
if (!instance || !(instance.app instanceof instance.Vue)) {
return new Error(`refreshInstance: instance ${instanceId} not found!`)
}
for (const key in data) {
instance.Vue.set(instance.app, key, data[key])
}
// Finally `refreshFinish` signal needed.
renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'refreshFinish', args: [] }], -1)
}
/**
* Get the JSON object of the root element.
* @param {string} instanceId
*/
export function getRoot (instanceId) {
const instance = instances[instanceId]
if (!instance || !(instance.app instanceof instance.Vue)) {
return new Error(`getRoot: instance ${instanceId} not found!`)
}
return instance.app.$el.toJSON()
}
/**
* Receive tasks from native. Generally there are two types of tasks:
* 1. `fireEvent`: an device actions or user actions from native.
* 2. `callback`: invoke function which sent to native as a parameter before.
* @param {string} instanceId
* @param {array} tasks
*/
export function receiveTasks (instanceId, tasks) {
const instance = instances[instanceId]
if (!instance || !(instance.app instanceof instance.Vue)) {
return new Error(`receiveTasks: instance ${instanceId} not found!`)
}
const { callbacks, document } = instance
tasks.forEach(task => {
// `fireEvent` case: find the event target and fire.
if (task.method === 'fireEvent') {
const [nodeId, type, e, domChanges] = task.args
const el = document.getRef(nodeId)
document.fireEvent(el, type, e, domChanges)
}
// `callback` case: find the callback by id and call it.
if (task.method === 'callback') {
const [callbackId, data, ifKeepAlive] = task.args
const callback = callbacks[callbackId]
if (typeof callback === 'function') {
callback(data)
// Remove the callback from `callbacks` if it won't called again.
if (typeof ifKeepAlive === 'undefined' || ifKeepAlive === false) {
callbacks[callbackId] = undefined
}
}
}
})
// Finally `updateFinish` signal needed.
renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'updateFinish', args: [] }], -1)
}
/**
* Register native modules information.
* @param {object} newModules
*/
export function registerModules (newModules) {
for (const name in newModules) {
if (!modules[name]) {
modules[name] = {}
}
newModules[name].forEach(method => {
if (typeof method === 'string') {
modules[name][method] = true
} else {
modules[name][method.name] = method.args
}
})
}
}
/**
* Register native components information.
* @param {array} newComponents
*/
export function registerComponents (newComponents) {
if (Array.isArray(newComponents)) {
newComponents.forEach(component => {
if (!component) {
return
}
if (typeof component === 'string') {
components[component] = true
} else if (typeof component === 'object' && typeof component.type === 'string') {
components[component.type] = component
}
})
}
}
/**
* Create a fresh instance of Vue for each Weex instance.
*/
function createVueModuleInstance (instanceId, moduleGetter) {
const exports = {}
VueFactory(exports, renderer)
const Vue = exports.Vue
const instance = instances[instanceId]
// patch reserved tag detection to account for dynamically registered
// components
const isReservedTag = Vue.config.isReservedTag || (() => false)
Vue.config.isReservedTag = name => {
return components[name] || isReservedTag(name)
}
// expose weex-specific info
Vue.prototype.$instanceId = instanceId
Vue.prototype.$document = instance.document
// expose weex native module getter on subVue prototype so that
// vdom runtime modules can access native modules via vnode.context
Vue.prototype.$requireWeexModule = moduleGetter
// Hack `Vue` behavior to handle instance information and data
// before root component created.
Vue.mixin({
beforeCreate () {
const options = this.$options
// root component (vm)
if (options.el) {
// set external data of instance
const dataOption = options.data
const internalData = (typeof dataOption === 'function' ? dataOption() : dataOption) || {}
options.data = Object.assign(internalData, instance.data)
// record instance by id
instance.app = this
}
}
})
/**
* @deprecated Just instance variable `weex.config`
* Get instance config.
* @return {object}
*/
Vue.prototype.$getConfig = function () {
if (instance.app instanceof Vue) {
return instance.config
}
}
return Vue
}
/**
* Generate native module getter. Each native module has several
* methods to call. And all the behaviors is instance-related. So
* this getter will return a set of methods which additionally
* send current instance id to native when called. Also the args
* will be normalized into "safe" value. For example function arg
* will be converted into a callback id.
* @param {string} instanceId
* @return {function}
*/
function genModuleGetter (instanceId) {
const instance = instances[instanceId]
return function (name) {
const nativeModule = modules[name] || []
const output = {}
for (const methodName in nativeModule) {
output[methodName] = (...args) => {
const finalArgs = args.map(value => {
return normalize(value, instance)
})
renderer.sendTasks(instanceId + '', [{ module: name, method: methodName, args: finalArgs }], -1)
}
}
return output
}
}
/**
* Generate HTML5 Timer APIs. An important point is that the callback
* will be converted into callback id when sent to native. So the
* framework can make sure no side effect of the callback happened after
* an instance destroyed.
* @param {[type]} instanceId [description]
* @param {[type]} moduleGetter [description]
* @return {[type]} [description]
*/
function getInstanceTimer (instanceId, moduleGetter) {
const instance = instances[instanceId]
const timer = moduleGetter('timer')
const timerAPIs = {
setTimeout: (...args) => {
const handler = function () {
args[0](...args.slice(2))
}
timer.setTimeout(handler, args[1])
return instance.callbackId.toString()
},
setInterval: (...args) => {
const handler = function () {
args[0](...args.slice(2))
}
timer.setInterval(handler, args[1])
return instance.callbackId.toString()
},
clearTimeout: (n) => {
timer.clearTimeout(n)
},
clearInterval: (n) => {
timer.clearInterval(n)
}
}
return timerAPIs
}
/**
* Call a new function body with some global objects.
* @param {object} globalObjects
* @param {string} code
* @return {any}
*/
function callFunction (globalObjects, body) {
const globalKeys = []
const globalValues = []
for (const key in globalObjects) {
globalKeys.push(key)
globalValues.push(globalObjects[key])
}
globalKeys.push(body)
const result = new Function(...globalKeys)
return result(...globalValues)
}
/**
* Convert all type of values into "safe" format to send to native.
* 1. A `function` will be converted into callback id.
* 2. An `Element` object will be converted into `ref`.
* The `instance` param is used to generate callback id and store
* function if necessary.
* @param {any} v
* @param {object} instance
* @return {any}
*/
function normalize (v, instance) {
const type = typof(v)
switch (type) {
case 'undefined':
case 'null':
return ''
case 'regexp':
return v.toString()
case 'date':
return v.toISOString()
case 'number':
case 'string':
case 'boolean':
case 'array':
case 'object':
if (v instanceof renderer.Element) {
return v.ref
}
return v
case 'function':
instance.callbacks[++instance.callbackId] = v
return instance.callbackId.toString()
default:
return JSON.stringify(v)
}
}
/**
* Get the exact type of an object by `toString()`. For example call
* `toString()` on an array will be returned `[object Array]`.
* @param {any} v
* @return {string}
*/
function typof (v) {
const s = Object.prototype.toString.call(v)
return s.substring(8, s.length - 1).toLowerCase()
}
-6
View File
@@ -1,6 +0,0 @@
// this entry is built and wrapped with a factory function
// used to generate a fresh copy of Vue for every Weex instance.
import Vue from './runtime/index'
exports.Vue = Vue
+2
View File
@@ -1,7 +1,9 @@
import Richtext from './richtext'
import Transition from './transition'
import TransitionGroup from './transition-group'
export default {
Richtext,
Transition,
TransitionGroup
}
+2
View File
@@ -10,12 +10,14 @@ import {
query,
mustUseProp,
isReservedTag,
isRuntimeComponent,
isUnknownElement
} from 'weex/util/index'
// install platform specific utils
Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isRuntimeComponent = isRuntimeComponent
Vue.config.isUnknownElement = isUnknownElement
// install platform runtime directives and components
+8 -3
View File
@@ -4,21 +4,26 @@ import { makeMap } from 'shared/util'
export const isReservedTag = makeMap(
'template,script,style,element,content,slot,link,meta,svg,view,' +
'a,div,img,image,text,span,richtext,input,switch,textarea,spinner,select,' +
'slider,slider-neighbor,indicator,trisition,trisition-group,canvas,' +
'a,div,img,image,text,span,input,switch,textarea,spinner,select,' +
'slider,slider-neighbor,indicator,canvas,' +
'list,cell,header,loading,loading-indicator,refresh,scrollable,scroller,' +
'video,web,embed,tabbar,tabheader,datepicker,timepicker,marquee,countdown',
true
)
// Elements that you can, intentionally, leave open (and which close themselves)
// more flexable than web
// more flexible than web
export const canBeLeftOpenTag = makeMap(
'web,spinner,switch,video,textarea,canvas,' +
'indicator,marquee,countdown',
true
)
export const isRuntimeComponent = makeMap(
'richtext,trisition,trisition-group',
true
)
export const isUnaryTag = makeMap(
'embed,img,image,input,link,meta',
true
+3 -1
View File
@@ -27,7 +27,9 @@ type RenderBundle = {
modules?: { [filename: string]: Array<string> };
};
export function createBundleRendererCreator (createRenderer: () => Renderer) {
export function createBundleRendererCreator (
createRenderer: (options?: RenderOptions) => Renderer
) {
return function createBundleRenderer (
bundle: string | RenderBundle,
rendererOptions?: RenderOptions = {}
+18 -2
View File
@@ -115,7 +115,7 @@ export function createBundleRunner (entry, files, basedir, runInNewContext) {
// styles injected by vue-style-loader.
initialContext = sandbox.__VUE_SSR_CONTEXT__ = {}
runner = evaluate(entry, sandbox)
// On subsequent renders, __VUE_SSR_CONTEXT__ will not be avaialbe
// On subsequent renders, __VUE_SSR_CONTEXT__ will not be available
// to prevent cross-request pollution.
delete sandbox.__VUE_SSR_CONTEXT__
if (typeof runner !== 'function') {
@@ -130,7 +130,23 @@ export function createBundleRunner (entry, files, basedir, runInNewContext) {
if (initialContext._styles) {
userContext._styles = deepClone(initialContext._styles)
}
resolve(runner(userContext))
// #6353 after the app is resolved, if the userContext doesn't have a
// styles property, it means the app doesn't have any lifecycle-injected
// styles, so vue-style-loader never defined the styles getter.
// just expose the same styles from the initialContext.
const exposeStylesAndResolve = app => {
if (!userContext.hasOwnProperty('styles')) {
userContext.styles = initialContext.styles
}
resolve(app)
}
const res = runner(userContext)
if (typeof res.then === 'function') {
res.then(exposeStylesAndResolve)
} else {
exposeStylesAndResolve(res)
}
})
}
}
+2 -2
View File
@@ -1,9 +1,9 @@
/* @flow */
import RenderStream from './render-stream'
import TemplateRenderer from './template-renderer/index'
import { createWriteFunction } from './write'
import { createRenderFunction } from './render'
import TemplateRenderer from './template-renderer/index'
import type { ClientManifest } from './template-renderer/index'
export type Renderer = {
@@ -18,7 +18,7 @@ type RenderCache = {
};
export type RenderOptions = {
modules?: Array<(vnode: VNode) => string>;
modules?: Array<(vnode: VNode) => ?string>;
directives?: Object;
isUnaryTag?: Function;
cache?: RenderCache;
+1 -1
View File
@@ -28,7 +28,7 @@ export class RenderContext {
next: () => void;
done: () => void;
modules: Array<() => ?string>;
modules: Array<(node: VNode) => ?string>;
directives: Object;
isUnaryTag: (tag: string) => boolean;
+102 -25
View File
@@ -1,13 +1,21 @@
/* @flow */
const { escape } = require('he')
import {
isDef,
isUndef,
isTrue
} from 'shared/util'
import { escape } from 'web/server/util'
import { SSR_ATTR } from 'shared/constants'
import { RenderContext } from './render-context'
import { compileToFunctions } from 'web/compiler/index'
import { createComponentInstanceForVnode } from 'core/vdom/create-component'
import { ssrCompileToFunctions } from 'web/server/compiler'
import { installSSRHelpers } from './optimizing-compiler/runtime-helpers'
import { isDef, isUndef, isTrue } from 'shared/util'
import {
createComponent,
createComponentInstanceForVnode
} from 'core/vdom/create-component'
let warned = Object.create(null)
const warnOnce = msg => {
@@ -17,16 +25,13 @@ const warnOnce = msg => {
}
}
const compilationCache = Object.create(null)
const normalizeRender = vm => {
const { render, template } = vm.$options
const { render, template, _scopeId } = vm.$options
if (isUndef(render)) {
if (template) {
const renderFns = (
compilationCache[template] ||
(compilationCache[template] = compileToFunctions(template))
)
Object.assign(vm.$options, renderFns)
Object.assign(vm.$options, ssrCompileToFunctions(template, {
scopeId: _scopeId
}))
} else {
throw new Error(
`render function or template not defined in component: ${
@@ -38,22 +43,24 @@ const normalizeRender = vm => {
}
function renderNode (node, isRoot, context) {
if (isDef(node.componentOptions)) {
if (node.isString) {
renderStringNode(node, context)
} else if (isDef(node.componentOptions)) {
renderComponent(node, isRoot, context)
} else {
if (isDef(node.tag)) {
renderElement(node, isRoot, context)
} else if (isTrue(node.isComment)) {
context.write(
`<!--${node.text}-->`,
context.next
)
} else if (isDef(node.tag)) {
renderElement(node, isRoot, context)
} else if (isTrue(node.isComment)) {
if (isDef(node.asyncFactory)) {
// async component
renderAsyncComponent(node, isRoot, context)
} else {
context.write(
node.raw ? node.text : escape(String(node.text)),
context.next
)
context.write(`<!--${node.text}-->`, context.next)
}
} else {
context.write(
node.raw ? node.text : escape(String(node.text)),
context.next
)
}
}
@@ -161,6 +168,75 @@ function renderComponentInner (node, isRoot, context) {
renderNode(childNode, isRoot, context)
}
function renderAsyncComponent (node, isRoot, context) {
const factory = node.asyncFactory
const resolve = comp => {
if (comp.__esModule && comp.default) {
comp = comp.default
}
const { data, children, tag } = node.asyncMeta
const nodeContext = node.asyncMeta.context
const resolvedNode: any = createComponent(
comp,
data,
nodeContext,
children,
tag
)
if (resolvedNode) {
renderComponent(resolvedNode, isRoot, context)
} else {
reject()
}
}
const reject = err => {
console.error(`[vue-server-renderer] error when rendering async component:\n`)
if (err) console.error(err.stack)
context.write(`<!--${node.text}-->`, context.next)
}
if (factory.resolved) {
resolve(factory.resolved)
return
}
let res
try {
res = factory(resolve, reject)
} catch (e) {
reject(e)
}
if (res) {
if (typeof res.then === 'function') {
res.then(resolve, reject).catch(reject)
} else {
// new syntax in 2.3
const comp = res.component
if (comp && typeof comp.then === 'function') {
comp.then(resolve, reject).catch(reject)
}
}
}
}
function renderStringNode (el, context) {
const { write, next } = context
if (isUndef(el.children) || el.children.length === 0) {
write(el.open + (el.close || ''), next)
} else {
const children: Array<VNode> = el.children
context.renderStates.push({
type: 'Element',
rendered: 0,
total: children.length,
endTag: el.close, children
})
write(el.open, next)
}
}
function renderElement (el, isRoot, context) {
const { write, next } = context
@@ -270,7 +346,7 @@ function renderStartingTag (node: VNode, context) {
}
export function createRenderFunction (
modules: Array<Function>,
modules: Array<(node: VNode) => ?string>,
directives: Object,
isUnaryTag: Function,
cache: any
@@ -289,6 +365,7 @@ export function createRenderFunction (
isUnaryTag, modules, directives,
cache
})
installSSRHelpers(component)
normalizeRender(component)
renderNode(component._render(), true, context)
}
+5 -5
View File
@@ -60,7 +60,7 @@ export default class TemplateRenderer {
if (options.clientManifest) {
const clientManifest = this.clientManifest = options.clientManifest
this.publicPath = clientManifest.publicPath.replace(/\/$/, '')
// preload/prefetch drectives
// preload/prefetch directives
this.preloadFiles = clientManifest.initial
this.prefetchFiles = clientManifest.async
// initial async chunk mapping
@@ -181,7 +181,7 @@ export default class TemplateRenderer {
}
return this.prefetchFiles.map(file => {
if (!alreadyRendered(file)) {
return `<link rel="prefetch" href="${this.publicPath}/${file}" as="script">`
return `<link rel="prefetch" href="${this.publicPath}/${file}">`
} else {
return ''
}
@@ -209,7 +209,7 @@ export default class TemplateRenderer {
const async = this.getUsedAsyncFiles(context)
const needed = [initial[0]].concat(async || [], initial.slice(1))
return needed.filter(isJS).map(file => {
return `<script src="${this.publicPath}/${file}"></script>`
return `<script src="${this.publicPath}/${file}" defer></script>`
}).join('')
} else {
return ''
@@ -217,7 +217,7 @@ export default class TemplateRenderer {
}
getUsedAsyncFiles (context: Object): ?Array<string> {
if (!context._mappedfiles && context._registeredComponents && this.mapFiles) {
if (!context._mappedFiles && context._registeredComponents && this.mapFiles) {
context._mappedFiles = this.mapFiles(Array.from(context._registeredComponents))
}
return context._mappedFiles
@@ -242,7 +242,7 @@ function getPreloadType (ext: string): string {
} else if (/woff2?|ttf|otf|eot/.test(ext)) {
return 'font'
} else {
// not exhausting all possbilities here, but above covers common cases
// not exhausting all possibilities here, but above covers common cases
return ''
}
}
+45 -12
View File
@@ -17,11 +17,16 @@ export function isTrue (v: any): boolean %checks {
export function isFalse (v: any): boolean %checks {
return v === false
}
/**
* Check if value is primitive
*/
export function isPrimitive (value: any): boolean %checks {
return typeof value === 'string' || typeof value === 'number'
return (
typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'boolean'
)
}
/**
@@ -47,6 +52,14 @@ export function isRegExp (v: any): boolean {
return _toString.call(v) === '[object RegExp]'
}
/**
* Check if val is a valid array index.
*/
export function isValidArrayIndex (val: any): boolean {
const n = parseFloat(val)
return n >= 0 && Math.floor(n) === n && isFinite(val)
}
/**
* Convert a value to a string that is actually rendered.
*/
@@ -90,6 +103,11 @@ export function makeMap (
*/
export const isBuiltInTag = makeMap('slot,component', true)
/**
* Check if a attribute is a reserved attribute.
*/
export const isReservedAttribute = makeMap('key,ref,slot,is')
/**
* Remove an item from an array
*/
@@ -139,12 +157,9 @@ export const capitalize = cached((str: string): string => {
/**
* Hyphenate a camelCase string.
*/
const hyphenateRE = /([^-])([A-Z])/g
const hyphenateRE = /\B([A-Z])/g
export const hyphenate = cached((str: string): string => {
return str
.replace(hyphenateRE, '$1-$2')
.replace(hyphenateRE, '$1-$2')
.toLowerCase()
return str.replace(hyphenateRE, '-$1').toLowerCase()
})
/**
@@ -202,13 +217,15 @@ export function toObject (arr: Array<any>): Object {
/**
* Perform no operation.
* Stubbing args to make Flow happy without leaving useless transpiled code
* with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/)
*/
export function noop () {}
export function noop (a?: any, b?: any, c?: any) {}
/**
* Always return false.
*/
export const no = () => false
export const no = (a?: any, b?: any, c?: any) => false
/**
* Return same value
@@ -228,15 +245,31 @@ export function genStaticKeys (modules: Array<ModuleOptions>): string {
* Check if two values are loosely equal - that is,
* if they are plain objects, do they have the same shape?
*/
export function looseEqual (a: mixed, b: mixed): boolean {
export function looseEqual (a: any, b: any): boolean {
if (a === b) return true
const isObjectA = isObject(a)
const isObjectB = isObject(b)
if (isObjectA && isObjectB) {
try {
return JSON.stringify(a) === JSON.stringify(b)
const isArrayA = Array.isArray(a)
const isArrayB = Array.isArray(b)
if (isArrayA && isArrayB) {
return a.length === b.length && a.every((e, i) => {
return looseEqual(e, b[i])
})
} else if (!isArrayA && !isArrayB) {
const keysA = Object.keys(a)
const keysB = Object.keys(b)
return keysA.length === keysB.length && keysA.every(key => {
return looseEqual(a[key], b[key])
})
} else {
/* istanbul ignore next */
return false
}
} catch (e) {
// possible circular reference
return a === b
/* istanbul ignore next */
return false
}
} else if (!isObjectA && !isObjectB) {
return String(a) === String(b)
+13 -5
View File
@@ -6,10 +6,15 @@ type Constructor = {
}
export type Component = typeof Vue | ComponentOptions<Vue> | FunctionalComponentOptions;
interface EsModuleComponent {
default: Component
}
export type AsyncComponent = (
resolve: (component: Component) => void,
reject: (reason?: any) => void
) => Promise<Component> | Component | void;
) => Promise<Component | EsModuleComponent> | Component | void;
export interface ComponentOptions<V extends Vue> {
data?: Object | ((this: V) => Object);
@@ -42,7 +47,7 @@ export interface ComponentOptions<V extends Vue> {
filters?: { [key: string]: Function };
provide?: Object | (() => Object);
inject?: { [key: string]: string | symbol } | Array<string>;
inject?: { [key: string]: string | symbol } | string[];
model?: {
prop?: string;
@@ -54,13 +59,16 @@ export interface ComponentOptions<V extends Vue> {
name?: string;
extends?: ComponentOptions<Vue> | typeof Vue;
delimiters?: [string, string];
comments?: boolean;
inheritAttrs?: boolean;
}
export interface FunctionalComponentOptions {
props?: string[] | { [key: string]: PropOptions | Constructor | Constructor[] };
functional: boolean;
render(this: never, createElement: CreateElement, context: RenderContext): VNode;
name?: string;
props?: string[] | { [key: string]: PropOptions | Constructor | Constructor[] };
inject?: { [key: string]: string | symbol } | string[];
functional: boolean;
render(this: never, createElement: CreateElement, context: RenderContext): VNode | void;
}
export interface RenderContext {
+6
View File
@@ -42,7 +42,11 @@ export declare class Vue {
readonly $slots: { [key: string]: VNode[] };
readonly $scopedSlots: { [key: string]: ScopedSlot };
readonly $isServer: boolean;
readonly $ssrContext: any;
readonly $props: any;
readonly $vnode: VNode;
readonly $attrs: { [key: string] : string };
readonly $listeners: { [key: string]: Function | Array<Function> };
$mount(elementOrSelector?: Element | String, hydrating?: boolean): this;
$forceUpdate(): void;
@@ -74,6 +78,7 @@ export declare class Vue {
productionTip: boolean;
performance: boolean;
errorHandler(err: Error, vm: Vue, info: string): void;
warnHandler(msg: string, vm: Vue, trace: string): void;
ignoredElements: string[];
keyCodes: { [key: string]: number };
}
@@ -94,6 +99,7 @@ export declare class Vue {
static component(id: string, definition?: Component | AsyncComponent): typeof Vue;
static use<T>(plugin: PluginObject<T> | PluginFunction<T>, options?: T): void;
static use(plugin: PluginObject<any> | PluginFunction<any>, ...options: any[]): void;
static mixin(mixin: typeof Vue | ComponentOptions<Vue>): void;
static compile(template: string): {
render(createElement: typeof Vue.prototype.$createElement): VNode;