nav tabs on admin dashboard

This commit is contained in:
2019-03-07 00:20:34 -06:00
parent f73d6ae228
commit e4f473f376
11661 changed files with 216240 additions and 1544253 deletions

196
node_modules/vue/README.md generated vendored
View File

@@ -1,19 +1,19 @@
<p align="center"><a href="https://vuejs.org" target="_blank" rel="noopener noreferrer"><img width="100" src="https://vuejs.org/images/logo.png" alt="Vue logo"></a></p>
<p align="center">
<a href="https://circleci.com/gh/vuejs/vue/tree/dev"><img src="https://img.shields.io/circleci/project/vuejs/vue/dev.svg" alt="Build Status"></a>
<a href="https://circleci.com/gh/vuejs/vue/tree/dev"><img src="https://img.shields.io/circleci/project/github/vuejs/vue/dev.svg" alt="Build Status"></a>
<a href="https://codecov.io/github/vuejs/vue?branch=dev"><img src="https://img.shields.io/codecov/c/github/vuejs/vue/dev.svg" alt="Coverage Status"></a>
<a href="https://npmcharts.com/compare/vue?minimal=true"><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"></a>
<br>
<a href="https://saucelabs.com/u/vuejs"><img src="https://saucelabs.com/browser-matrix/vuejs.svg" alt="Sauce Test Status"></a>
<a href="https://app.saucelabs.com/builds/50f8372d79f743a3b25fb6ca4851ca4c"><img src="https://app.saucelabs.com/buildstatus/vuejs" alt="Build Status"></a>
</p>
<h2 align="center">Supporting Vue.js</h2>
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:
Vue.js is an MIT-licensed open source project with its ongoing development made possible entirely by the support of 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 Open Collective](https://opencollective.com/vuejs).
@@ -25,50 +25,51 @@ Funds donated via Patreon go directly to support Evan You's full-time work on Vu
<h3 align="center">Special Sponsors</h3>
<!--special start-->
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://stdlib.com" target="_blank" rel="noopener noreferrer">
<img width="222px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/stdlib.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://www.bitsrc.io/?utm_source=vue&utm_medium=vue&utm_campaign=vue&utm_term=vue&utm_content=vue" target="_blank" rel="noopener noreferrer">
<img height="90px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/bit-wide.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://xiaozhuanlan.com" target="_blank" rel="noopener noreferrer">
<img width="222px" src="https://raw.githubusercontent.com/vuejs/cn.vuejs.org/master/themes/vue/source/images/xiaozhuanlan.png">
</a>
</td>
</tr><tr></tr>
</tbody>
</table>
<p align="center">
<a href="https://stdlib.com/" target="_blank">
<img width="260px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/stdlib.png">
</a>
</p>
<!--special end-->
<h3 align="center">Sponsors via Patreon</h3>
<h4 align="center">Platinum</h4>
<h3 align="center">Platinum Sponsors</h3>
<!--platinum start-->
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="http://tooltwist.com/" target="_blank" rel="noopener noreferrer">
<a href="https://moduscreate.com/?utm_source=Vue&utm_medium=Partnership&utm_campaign=VueShip" target="_blank">
<img width="222px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/modus.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://www.bitsrc.io/?utm_source=vue&utm_medium=vue&utm_campaign=vue&utm_term=vue&utm_content=vue" target="_blank">
<img width="222px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/bit.png">
</a>
</td>
<td align="center" valign="middle">
<a href="http://tooltwist.com/" target="_blank">
<img width="222px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/tooltwist.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://vueschool.io/?utm_source=Vuejs.org&utm_medium=Banner&utm_campaign=Sponsored%20Banner&utm_content=V1" target="_blank" rel="noopener noreferrer">
<a href="https://vueschool.io/?utm_source=Vuejs.org&utm_medium=Banner&utm_campaign=Sponsored%20Banner&utm_content=V1" target="_blank">
<img width="222px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/vueschool.png">
</a>
</td>
</tr><tr></tr>
<tr>
<td align="center" valign="middle">
<a href="https://www.datacamp.com/careers?utm_source=vuejs&utm_medium=sidebar" target="_blank" rel="noopener noreferrer">
<img width="222px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/datacamp.png">
<a href="https://vehikl.com/" target="_blank">
<img width="222px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/vehikl.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://www.nativescript.org/vue?utm_source=vue-js-org&utm_medium=website&utm_campaign=nativescript-awareness" target="_blank">
<img width="222px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/nativescript.png">
</a>
</td>
</tr><tr></tr>
@@ -76,84 +77,151 @@ Funds donated via Patreon go directly to support Evan You's full-time work on Vu
</table>
<!--platinum end-->
<h4 align="center">Gold</h4>
<!--special-china start-->
<h3 align="center">Platinum Sponsors (China)</h3>
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="http://www.dcloud.io/?hmsr=vuejsorg&hmpl=&hmcu=&hmkw=&hmci=" target="_blank">
<img width="177px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/dcloud.gif">
</a>
</td>
</tr><tr></tr>
</tbody>
</table>
<!--special-china end-->
<h3 align="center">Gold Sponsors</h4>
<!--gold start-->
<table>
<tbody>
<tr>
<td align="center" valign="middle">
<a href="https://laravel.com" target="_blank" rel="noopener noreferrer">
<a href="https://www.vuemastery.com/" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/vuemastery.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://laravel.com" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/laravel.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://htmlburger.com" target="_blank" rel="noopener noreferrer">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/htmlburger.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://chaitin.cn/en/" target="_blank" rel="noopener noreferrer">
<a href="https://chaitin.cn/en/" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/chaitin.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://anymod.com" target="_blank" rel="noopener noreferrer">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/anymod.png">
<a href="https://htmlburger.com" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/html_burger.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://www.frontenddeveloperlove.com/" target="_blank" rel="noopener noreferrer">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/frontend-love.png">
<a href="https://www.frontenddeveloperlove.com/" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/frontend_love.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://onsen.io/vue/" target="_blank" rel="noopener noreferrer">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/onsen-ui.png">
<a href="https://onsen.io/vue/" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/onsen_ui.png">
</a>
</td>
</tr><tr></tr>
<tr>
<td align="center" valign="middle">
<a href="https://vuetifyjs.com" target="_blank" rel="noopener noreferrer">
<a href="https://vuetifyjs.com" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/vuetify.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://neds.com.au/" target="_blank" rel="noopener noreferrer">
<a href="https://neds.com.au/" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/neds.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://hackr.io/tutorials/learn-vue-js" target="_blank" rel="noopener noreferrer">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/hackr-io.png">
<a href="https://icons8.com/" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/icons_8.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://icons8.com/" target="_blank" rel="noopener noreferrer">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/icons8.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://vuejobs.com/?ref=vuejs" target="_blank" rel="noopener noreferrer">
<a href="https://vuejobs.com/?ref=vuejs" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/vuejobs.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://leanpub.com/vuejs2" target="_blank" rel="noopener noreferrer">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/tmvuejs2.png">
<a href="https://www.valuecoders.com" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/valuecoders.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://tidelift.com/subscription/npm/vue" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/tidelift.png">
</a>
</td>
</tr><tr></tr>
<tr>
<td align="center" valign="middle">
<a href="https://www.bmqb.com/jobs" target="_blank" rel="noopener noreferrer">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/bmqb.png">
<a href="https://webdock.io/en" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/webdock.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://codepilot.ai" target="_blank" rel="noopener noreferrer">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/codepilot.png">
<a href="http://www.syncfusion.com/?utm_source=vuejs&utm_medium=list&utm_campaign=vuejsjslistcy19" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/syncfusion.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://opteo.com/vue" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/opteo.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://devsquad.com/" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/devsquad.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://www.inkoop.in/" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/inkoop.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://www.firesticktricks.com/" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/firestick_tricks.png">
</a>
</td>
</tr><tr></tr>
<tr>
<td align="center" valign="middle">
<a href="https://github.com/marcus-hiles" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/marcus_hiles.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://intygrate.com/" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/intygrate.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://isleofcode.com/" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/isle_of_code.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://yakaz.com/" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/yakaz.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://www.bacancytechnology.com" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/bacancy_technology.png">
</a>
</td>
<td align="center" valign="middle">
<a href="https://passionatepeople.io/" target="_blank">
<img width="148px" src="https://raw.githubusercontent.com/vuejs/vuejs.org/master/themes/vue/source/images/passionate_people.png">
</a>
</td>
</tr><tr></tr>
@@ -210,7 +278,7 @@ Vue.js supports all browsers that are [ES5-compliant](http://kangax.github.io/co
[vue-router-status]: https://img.shields.io/npm/v/vue-router.svg
[vuex-status]: https://img.shields.io/npm/v/vuex.svg
[vue-cli-status]: https://img.shields.io/npm/v/vue-cli.svg
[vue-cli-status]: https://img.shields.io/npm/v/@vue/cli.svg
[vue-loader-status]: https://img.shields.io/npm/v/vue-loader.svg
[vue-server-renderer-status]: https://img.shields.io/npm/v/vue-server-renderer.svg
[vue-class-component-status]: https://img.shields.io/npm/v/vue-class-component.svg
@@ -219,7 +287,7 @@ Vue.js supports all browsers that are [ES5-compliant](http://kangax.github.io/co
[vue-router-package]: https://npmjs.com/package/vue-router
[vuex-package]: https://npmjs.com/package/vuex
[vue-cli-package]: https://npmjs.com/package/vue-cli
[vue-cli-package]: https://npmjs.com/package/@vue/cli
[vue-loader-package]: https://npmjs.com/package/vue-loader
[vue-server-renderer-package]: https://npmjs.com/package/vue-server-renderer
[vue-class-component-package]: https://npmjs.com/package/vue-class-component
@@ -232,7 +300,7 @@ To check out [live examples](https://vuejs.org/v2/examples/) and docs, visit [vu
## Questions
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.
For questions and support please use [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

2
node_modules/vue/dist/README.md generated vendored
View File

@@ -40,7 +40,7 @@ module.exports = {
}
}
}
````
```
#### Rollup

10961
node_modules/vue/dist/vue.common.js generated vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

4546
node_modules/vue/dist/vue.esm.js generated vendored

File diff suppressed because it is too large Load Diff

21599
node_modules/vue/dist/vue.js generated vendored

File diff suppressed because it is too large Load Diff

6
node_modules/vue/dist/vue.min.js generated vendored

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

15658
node_modules/vue/dist/vue.runtime.js generated vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

93
node_modules/vue/package.json generated vendored
View File

@@ -1,26 +1,26 @@
{
"_from": "vue@^2.5.7",
"_id": "vue@2.5.17",
"_from": "vue@^2.6.8",
"_id": "vue@2.6.8",
"_inBundle": false,
"_integrity": "sha512-mFbcWoDIJi0w0Za4emyLiW72Jae0yjANHbCVquMKijcavBGypqlF7zHRgMa5k4sesdv7hv2rB4JPdZfR+TPfhQ==",
"_integrity": "sha512-+vp9lEC2Kt3yom673pzg1J7T1NVGuGzO9j8Wxno+rQN2WYVBX2pyo/RGQ3fXCLh2Pk76Skw/laAPCuBuEQ4diw==",
"_location": "/vue",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "vue@^2.5.7",
"raw": "vue@^2.6.8",
"name": "vue",
"escapedName": "vue",
"rawSpec": "^2.5.7",
"rawSpec": "^2.6.8",
"saveSpec": null,
"fetchSpec": "^2.5.7"
"fetchSpec": "^2.6.8"
},
"_requiredBy": [
"#DEV:/"
],
"_resolved": "https://registry.npmjs.org/vue/-/vue-2.5.17.tgz",
"_shasum": "0f8789ad718be68ca1872629832ed533589c6ada",
"_spec": "vue@^2.5.7",
"_resolved": "https://registry.npmjs.org/vue/-/vue-2.6.8.tgz",
"_shasum": "f21cbc536bfc14f7d1d792a137bb12f69e60ea91",
"_spec": "vue@^2.6.8",
"_where": "C:\\xampp\\htdocs\\w4rpservices",
"author": {
"name": "Evan You"
@@ -37,43 +37,44 @@
"deprecated": false,
"description": "Reactive, component-oriented view layer for modern web interfaces.",
"devDependencies": {
"@types/node": "^8.0.33",
"@types/webpack": "^3.0.13",
"@babel/core": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.1.0",
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/plugin-syntax-jsx": "^7.0.0",
"@babel/plugin-transform-flow-strip-types": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/register": "^7.0.0",
"@types/node": "^10.12.18",
"@types/webpack": "^4.4.22",
"acorn": "^5.2.1",
"babel-core": "^6.25.0",
"babel-eslint": "^8.0.3",
"babel-helper-vue-jsx-merge-props": "^2.0.2",
"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.4.3",
"babel-preset-es2015": "^6.24.1",
"babel-eslint": "^10.0.1",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"babel-loader": "^8.0.4",
"babel-plugin-istanbul": "^5.1.0",
"babel-plugin-transform-vue-jsx": "^4.0.1",
"babel-preset-flow-vue": "^1.0.0",
"buble": "^0.19.3",
"chalk": "^2.3.0",
"chromedriver": "^2.30.1",
"chromedriver": "^2.45.0",
"codecov": "^3.0.0",
"commitizen": "^2.9.6",
"conventional-changelog": "^1.1.3",
"cross-spawn": "^5.1.0",
"cross-spawn": "^6.0.5",
"cz-conventional-changelog": "^2.0.0",
"de-indent": "^1.0.2",
"es6-promise": "^4.1.0",
"escodegen": "^1.8.1",
"eslint": "^4.13.1",
"eslint-loader": "^1.7.1",
"eslint": "^5.7.0",
"eslint-plugin-flowtype": "^2.34.0",
"eslint-plugin-jasmine": "^2.8.4",
"eslint-plugin-vue-libs": "^2.0.1",
"file-loader": "^1.1.5",
"file-loader": "^3.0.1",
"flow-bin": "^0.61.0",
"hash-sum": "^1.0.2",
"he": "^1.1.1",
"http-server": "^0.11.1",
"jasmine": "^2.99.0",
"jasmine-core": "^2.99.0",
"karma": "^2.0.0",
"karma": "^3.1.1",
"karma-chrome-launcher": "^2.1.1",
"karma-coverage": "^1.1.1",
"karma-firefox-launcher": "^1.0.1",
@@ -81,36 +82,35 @@
"karma-mocha-reporter": "^2.2.3",
"karma-phantomjs-launcher": "^1.0.4",
"karma-safari-launcher": "^1.0.0",
"karma-sauce-launcher": "^1.1.0",
"karma-sauce-launcher": "^2.0.2",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^2.0.3",
"lint-staged": "^7.0.0",
"karma-webpack": "^4.0.0-rc.2",
"lint-staged": "^8.0.0",
"lodash": "^4.17.4",
"lodash.template": "^4.4.0",
"lodash.uniq": "^4.5.0",
"lru-cache": "^4.1.1",
"lru-cache": "^5.1.1",
"nightwatch": "^0.9.16",
"nightwatch-helpers": "^1.2.0",
"phantomjs-prebuilt": "^2.1.14",
"puppeteer": "^1.11.0",
"resolve": "^1.3.3",
"rollup": "^0.54.1",
"rollup": "^1.0.0",
"rollup-plugin-alias": "^1.3.1",
"rollup-plugin-babel": "^3.0.2",
"rollup-plugin-buble": "^0.19.2",
"rollup-plugin-commonjs": "^8.0.0",
"rollup-plugin-buble": "^0.19.6",
"rollup-plugin-commonjs": "^9.2.0",
"rollup-plugin-flow-no-whitespace": "^1.0.0",
"rollup-plugin-node-resolve": "^3.0.0",
"rollup-plugin-node-resolve": "^4.0.0",
"rollup-plugin-replace": "^2.0.0",
"rollup-watch": "^4.0.0",
"selenium-server": "^2.53.1",
"serialize-javascript": "^1.3.0",
"shelljs": "^0.8.1",
"typescript": "^2.7.1",
"uglify-js": "^3.0.15",
"webpack": "^3.11.0",
"terser": "^3.10.2",
"typescript": "^3.1.3",
"webpack": "~4.28.4",
"weex-js-runtime": "^0.23.6",
"weex-styler": "^0.3.0",
"yorkie": "^1.0.1"
"yorkie": "^2.0.0"
},
"files": [
"src",
@@ -147,7 +147,7 @@
"build:weex": "npm run build -- weex",
"commit": "git-cz",
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",
"dev:cjs": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-cjs",
"dev:cjs": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-cjs-dev",
"dev:compiler": "rollup -w -c scripts/config.js --environment TARGET:web-compiler ",
"dev:esm": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-esm",
"dev:ssr": "rollup -w -c scripts/config.js --environment TARGET:web-server-renderer",
@@ -156,7 +156,7 @@
"dev:weex:compiler": "rollup -w -c scripts/config.js --environment TARGET:weex-compiler ",
"dev:weex:factory": "rollup -w -c scripts/config.js --environment TARGET:weex-factory",
"flow": "flow check",
"lint": "eslint --fix src scripts test",
"lint": "eslint src scripts test",
"release": "bash scripts/release.sh",
"release:note": "node scripts/gen-release-note.js",
"release:weex": "bash scripts/release-weex.sh",
@@ -165,12 +165,13 @@
"test:cover": "karma start test/unit/karma.cover.config.js",
"test:e2e": "npm run build -- web-full-prod,web-server-basic-renderer && 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:ssr": "npm run build:ssr && jasmine JASMINE_CONFIG_PATH=test/ssr/jasmine.js",
"test:types": "tsc -p ./types/test/tsconfig.json",
"test:unit": "karma start test/unit/karma.unit.config.js",
"test:weex": "npm run build:weex && jasmine JASMINE_CONFIG_PATH=test/weex/jasmine.json"
"test:weex": "npm run build:weex && jasmine JASMINE_CONFIG_PATH=test/weex/jasmine.js"
},
"sideEffects": false,
"typings": "types/index.d.ts",
"unpkg": "dist/vue.js",
"version": "2.5.17"
"version": "2.6.8"
}

View File

@@ -1,6 +1,7 @@
/* @flow */
const fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/
const fnInvokeRE = /\([^)]*?\);*$/
const simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/
// KeyboardEvent.keyCode aliases
@@ -18,16 +19,19 @@ const keyCodes: { [key: string]: number | Array<number> } = {
// KeyboardEvent.key aliases
const keyNames: { [key: string]: string | Array<string> } = {
esc: 'Escape',
// #7880: IE11 and Edge use `Esc` for Escape key name.
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
space: ' ',
// #9112: IE11 uses `Spacebar` for Space key name.
space: [' ', 'Spacebar'],
// #7806: IE11 uses key names without `Arrow` prefix for arrow keys.
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
'delete': ['Backspace', 'Delete']
// #9112: IE11 uses `Del` for Delete key name.
'delete': ['Backspace', 'Delete', 'Del']
}
// #4868: modifiers that prevent the execution of the listener
@@ -50,14 +54,25 @@ const modifierCode: { [key: string]: string } = {
export function genHandlers (
events: ASTElementHandlers,
isNative: boolean,
warn: Function
isNative: boolean
): string {
let res = isNative ? 'nativeOn:{' : 'on:{'
const prefix = isNative ? 'nativeOn:' : 'on:'
let staticHandlers = ``
let dynamicHandlers = ``
for (const name in events) {
res += `"${name}":${genHandler(name, events[name])},`
const handlerCode = genHandler(events[name])
if (events[name] && events[name].dynamic) {
dynamicHandlers += `${name},${handlerCode},`
} else {
staticHandlers += `"${name}":${handlerCode},`
}
}
staticHandlers = `{${staticHandlers.slice(0, -1)}}`
if (dynamicHandlers) {
return prefix + `_d(${staticHandlers},[${dynamicHandlers.slice(0, -1)}])`
} else {
return prefix + staticHandlers
}
return res.slice(0, -1) + '}'
}
// Generate handler code with binding params on Weex
@@ -78,20 +93,18 @@ function genWeexHandler (params: Array<any>, handlerCode: string) {
'}'
}
function genHandler (
name: string,
handler: ASTElementHandler | Array<ASTElementHandler>
): string {
function genHandler (handler: ASTElementHandler | Array<ASTElementHandler>): string {
if (!handler) {
return 'function(){}'
}
if (Array.isArray(handler)) {
return `[${handler.map(handler => genHandler(name, handler)).join(',')}]`
return `[${handler.map(handler => genHandler(handler)).join(',')}]`
}
const isMethodPath = simplePathRE.test(handler.value)
const isFunctionExpression = fnExpRE.test(handler.value)
const isFunctionInvocation = simplePathRE.test(handler.value.replace(fnInvokeRE, ''))
if (!handler.modifiers) {
if (isMethodPath || isFunctionExpression) {
@@ -101,7 +114,9 @@ function genHandler (
if (__WEEX__ && handler.params) {
return genWeexHandler(handler.params, handler.value)
}
return `function($event){${handler.value}}` // inline statement
return `function($event){${
isFunctionInvocation ? `return ${handler.value}` : handler.value
}}` // inline statement
} else {
let code = ''
let genModifierCode = ''
@@ -136,7 +151,9 @@ function genHandler (
? `return ${handler.value}($event)`
: isFunctionExpression
? `return (${handler.value})($event)`
: handler.value
: isFunctionInvocation
? `return ${handler.value}`
: handler.value
/* istanbul ignore if */
if (__WEEX__ && handler.params) {
return genWeexHandler(handler.params, code + handlerCode)
@@ -146,7 +163,13 @@ function genHandler (
}
function genKeyFilter (keys: Array<string>): string {
return `if(!('button' in $event)&&${keys.map(genFilterCode).join('&&')})return null;`
return (
// make sure the key filters only apply to KeyboardEvents
// #9441: can't use 'keyCode' in $event because Chrome autofill fires fake
// key events that do not have keyCode property...
`if(!$event.type.indexOf('key')&&` +
`${keys.map(genFilterCode).join('&&')})return null;`
)
}
function genFilterCode (key: string): string {

View File

@@ -4,6 +4,7 @@ import { genHandlers } from './events'
import baseDirectives from '../directives/index'
import { camelize, no, extend } from 'shared/util'
import { baseWarn, pluckModuleFunction } from '../helpers'
import { emptySlotScopeToken } from '../parser/index'
type TransformFunction = (el: ASTElement, code: string) => string;
type DataGenFunction = (el: ASTElement) => string;
@@ -18,6 +19,7 @@ export class CodegenState {
maybeComponent: (el: ASTElement) => boolean;
onceId: number;
staticRenderFns: Array<string>;
pre: boolean;
constructor (options: CompilerOptions) {
this.options = options
@@ -26,9 +28,10 @@ export class CodegenState {
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.maybeComponent = (el: ASTElement) => !!el.component || !isReservedTag(el.tag)
this.onceId = 0
this.staticRenderFns = []
this.pre = false
}
}
@@ -50,6 +53,10 @@ export function generate (
}
export function genElement (el: ASTElement, state: CodegenState): string {
if (el.parent) {
el.pre = el.pre || el.parent.pre
}
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el, state)
} else if (el.once && !el.onceProcessed) {
@@ -58,7 +65,7 @@ export function genElement (el: ASTElement, state: CodegenState): string {
return genFor(el, state)
} else if (el.if && !el.ifProcessed) {
return genIf(el, state)
} else if (el.tag === 'template' && !el.slotTarget) {
} else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
return genChildren(el, state) || 'void 0'
} else if (el.tag === 'slot') {
return genSlot(el, state)
@@ -68,7 +75,10 @@ export function genElement (el: ASTElement, state: CodegenState): string {
if (el.component) {
code = genComponent(el.component, el, state)
} else {
const data = el.plain ? undefined : genData(el, state)
let data
if (!el.plain || (el.pre && state.maybeComponent(el))) {
data = genData(el, state)
}
const children = el.inlineTemplate ? null : genChildren(el, state, true)
code = `_c('${el.tag}'${
@@ -88,7 +98,15 @@ export function genElement (el: ASTElement, state: CodegenState): string {
// hoist static sub-trees out
function genStatic (el: ASTElement, state: CodegenState): string {
el.staticProcessed = true
// Some elements (templates) need to behave differently inside of a v-pre
// node. All pre nodes are static roots, so we can use this as a location to
// wrap a state change and reset it upon exiting the pre node.
const originalPreState = state.pre
if (el.pre) {
state.pre = el.pre
}
state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`)
state.pre = originalPreState
return `_m(${
state.staticRenderFns.length - 1
}${
@@ -113,7 +131,8 @@ function genOnce (el: ASTElement, state: CodegenState): string {
}
if (!key) {
process.env.NODE_ENV !== 'production' && state.warn(
`v-once can only be used inside v-for that is keyed. `
`v-once can only be used inside v-for that is keyed. `,
el.rawAttrsMap['v-once']
)
return genElement(el, state)
}
@@ -185,6 +204,7 @@ export function genFor (
`<${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.`,
el.rawAttrsMap['v-for'],
true /* tip */
)
}
@@ -229,18 +249,18 @@ export function genData (el: ASTElement, state: CodegenState): string {
}
// attributes
if (el.attrs) {
data += `attrs:{${genProps(el.attrs)}},`
data += `attrs:${genProps(el.attrs)},`
}
// DOM props
if (el.props) {
data += `domProps:{${genProps(el.props)}},`
data += `domProps:${genProps(el.props)},`
}
// event handlers
if (el.events) {
data += `${genHandlers(el.events, false, state.warn)},`
data += `${genHandlers(el.events, false)},`
}
if (el.nativeEvents) {
data += `${genHandlers(el.nativeEvents, true, state.warn)},`
data += `${genHandlers(el.nativeEvents, true)},`
}
// slot target
// only for non-scoped slots
@@ -249,7 +269,7 @@ export function genData (el: ASTElement, state: CodegenState): string {
}
// scoped slots
if (el.scopedSlots) {
data += `${genScopedSlots(el.scopedSlots, state)},`
data += `${genScopedSlots(el, el.scopedSlots, state)},`
}
// component v-model
if (el.model) {
@@ -269,6 +289,12 @@ export function genData (el: ASTElement, state: CodegenState): string {
}
}
data = data.replace(/,$/, '') + '}'
// v-bind dynamic argument wrap
// v-bind with dynamic arguments must be applied using the same v-bind object
// merge helper so that class/style/mustUseProp attrs are handled correctly.
if (el.dynamicAttrs) {
data = `_b(${data},"${el.tag}",${genProps(el.dynamicAttrs)})`
}
// v-bind data wrap
if (el.wrapData) {
data = el.wrapData(data)
@@ -300,7 +326,7 @@ function genDirectives (el: ASTElement, state: CodegenState): string | void {
res += `{name:"${dir.name}",rawName:"${dir.rawName}"${
dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : ''
}${
dir.arg ? `,arg:"${dir.arg}"` : ''
dir.arg ? `,arg:${dir.isDynamicArg ? dir.arg : `"${dir.arg}"`}` : ''
}${
dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : ''
}},`
@@ -316,9 +342,12 @@ function genInlineTemplate (el: ASTElement, state: CodegenState): ?string {
if (process.env.NODE_ENV !== 'production' && (
el.children.length !== 1 || ast.type !== 1
)) {
state.warn('Inline-template components must have exactly one child element.')
state.warn(
'Inline-template components must have exactly one child element.',
{ start: el.start }
)
}
if (ast.type === 1) {
if (ast && ast.type === 1) {
const inlineRenderFns = generate(ast, state.options)
return `inlineTemplate:{render:function(){${
inlineRenderFns.render
@@ -329,48 +358,106 @@ function genInlineTemplate (el: ASTElement, state: CodegenState): ?string {
}
function genScopedSlots (
el: ASTElement,
slots: { [key: string]: ASTElement },
state: CodegenState
): string {
return `scopedSlots:_u([${
Object.keys(slots).map(key => {
return genScopedSlot(key, slots[key], state)
}).join(',')
}])`
// by default scoped slots are considered "stable", this allows child
// components with only scoped slots to skip forced updates from parent.
// but in some cases we have to bail-out of this optimization
// for example if the slot contains dynamic names, has v-if or v-for on them...
let needsForceUpdate = el.for || Object.keys(slots).some(key => {
const slot = slots[key]
return (
slot.slotTargetDynamic ||
slot.if ||
slot.for ||
containsSlotChild(slot) // is passing down slot from parent which may be dynamic
)
})
// #9534: if a component with scoped slots is inside a conditional branch,
// it's possible for the same component to be reused but with different
// compiled slot content. To avoid that, we generate a unique key based on
// the generated code of all the slot contents.
let needsKey = !!el.if
// OR when it is inside another scoped slot or v-for (the reactivity may be
// disconnected due to the intermediate scope variable)
// #9438, #9506
// TODO: this can be further optimized by properly analyzing in-scope bindings
// and skip force updating ones that do not actually use scope variables.
if (!needsForceUpdate) {
let parent = el.parent
while (parent) {
if (
(parent.slotScope && parent.slotScope !== emptySlotScopeToken) ||
parent.for
) {
needsForceUpdate = true
break
}
if (parent.if) {
needsKey = true
}
parent = parent.parent
}
}
const generatedSlots = Object.keys(slots)
.map(key => genScopedSlot(slots[key], state))
.join(',')
return `scopedSlots:_u([${generatedSlots}]${
needsForceUpdate ? `,null,true` : ``
}${
!needsForceUpdate && needsKey ? `,null,false,${hash(generatedSlots)}` : ``
})`
}
function hash(str) {
let hash = 5381
let i = str.length
while(i) {
hash = (hash * 33) ^ str.charCodeAt(--i)
}
return hash >>> 0
}
function containsSlotChild (el: ASTNode): boolean {
if (el.type === 1) {
if (el.tag === 'slot') {
return true
}
return el.children.some(containsSlotChild)
}
return false
}
function genScopedSlot (
key: string,
el: ASTElement,
state: CodegenState
): string {
if (el.for && !el.forProcessed) {
return genForScopedSlot(key, el, state)
const isLegacySyntax = el.attrsMap['slot-scope']
if (el.if && !el.ifProcessed && !isLegacySyntax) {
return genIf(el, state, genScopedSlot, `null`)
}
const fn = `function(${String(el.slotScope)}){` +
if (el.for && !el.forProcessed) {
return genFor(el, state, genScopedSlot)
}
const slotScope = el.slotScope === emptySlotScopeToken
? ``
: String(el.slotScope)
const fn = `function(${slotScope}){` +
`return ${el.tag === 'template'
? el.if
? `${el.if}?${genChildren(el, state) || 'undefined'}:undefined`
? el.if && isLegacySyntax
? `(${el.if})?${genChildren(el, state) || 'undefined'}:undefined`
: genChildren(el, state) || 'undefined'
: genElement(el, state)
}}`
return `{key:${key},fn:${fn}}`
}
function genForScopedSlot (
key: string,
el: any,
state: CodegenState
): string {
const exp = el.for
const alias = el.alias
const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''
el.forProcessed = true // avoid recursion
return `_l((${exp}),` +
`function(${alias}${iterator1}${iterator2}){` +
`return ${genScopedSlot(key, el, state)}` +
'})'
// reverse proxy v-slot without scope on this.$slots
const reverseProxy = slotScope ? `` : `,proxy:true`
return `{key:${el.slotTarget || `"default"`},fn:${fn}${reverseProxy}}`
}
export function genChildren (
@@ -389,7 +476,10 @@ export function genChildren (
el.tag !== 'template' &&
el.tag !== 'slot'
) {
return (altGenElement || genElement)(el, state)
const normalizationType = checkSkip
? state.maybeComponent(el) ? `,1` : `,0`
: ``
return `${(altGenElement || genElement)(el, state)}${normalizationType}`
}
const normalizationType = checkSkip
? getNormalizationType(children, state.maybeComponent)
@@ -435,7 +525,7 @@ function needsNormalization (el: ASTElement): boolean {
function genNode (node: ASTNode, state: CodegenState): string {
if (node.type === 1) {
return genElement(node, state)
} if (node.type === 3 && node.isComment) {
} else if (node.type === 3 && node.isComment) {
return genComment(node)
} else {
return genText(node)
@@ -457,7 +547,14 @@ function genSlot (el: ASTElement, state: CodegenState): string {
const slotName = el.slotName || '"default"'
const children = genChildren(el, state)
let res = `_t(${slotName}${children ? `,${children}` : ''}`
const attrs = el.attrs && `{${el.attrs.map(a => `${camelize(a.name)}:${a.value}`).join(',')}}`
const attrs = el.attrs || el.dynamicAttrs
? genProps((el.attrs || []).concat(el.dynamicAttrs || []).map(attr => ({
// slot props are camelized
name: camelize(attr.name),
value: attr.value,
dynamic: attr.dynamic
})))
: null
const bind = el.attrsMap['v-bind']
if ((attrs || bind) && !children) {
res += `,null`
@@ -483,18 +580,26 @@ function genComponent (
})`
}
function genProps (props: Array<{ name: string, value: any }>): string {
let res = ''
function genProps (props: Array<ASTAttr>): string {
let staticProps = ``
let dynamicProps = ``
for (let i = 0; i < props.length; i++) {
const prop = props[i]
/* istanbul ignore if */
if (__WEEX__) {
res += `"${prop.name}":${generateValue(prop.value)},`
const value = __WEEX__
? generateValue(prop.value)
: transformSpecialNewlines(prop.value)
if (prop.dynamic) {
dynamicProps += `${prop.name},${value},`
} else {
res += `"${prop.name}":${transformSpecialNewlines(prop.value)},`
staticProps += `"${prop.name}":${value},`
}
}
return res.slice(0, -1)
staticProps = `{${staticProps.slice(0, -1)}}`
if (dynamicProps) {
return `_d(${staticProps},[${dynamicProps.slice(0, -1)}])`
} else {
return staticProps
}
}
/* istanbul ignore next */

View File

@@ -13,11 +13,29 @@ export function createCompilerCreator (baseCompile: Function): Function {
const finalOptions = Object.create(baseOptions)
const errors = []
const tips = []
finalOptions.warn = (msg, tip) => {
let warn = (msg, range, tip) => {
(tip ? tips : errors).push(msg)
}
if (options) {
if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
// $flow-disable-line
const leadingSpaceLength = template.match(/^\s*/)[0].length
warn = (msg, range, tip) => {
const data: WarningMessage = { msg }
if (range) {
if (range.start != null) {
data.start = range.start + leadingSpaceLength
}
if (range.end != null) {
data.end = range.end + leadingSpaceLength
}
}
(tip ? tips : errors).push(data)
}
}
// merge custom modules
if (options.modules) {
finalOptions.modules =
@@ -38,9 +56,11 @@ export function createCompilerCreator (baseCompile: Function): Function {
}
}
const compiled = baseCompile(template, finalOptions)
finalOptions.warn = warn
const compiled = baseCompile(template.trim(), finalOptions)
if (process.env.NODE_ENV !== 'production') {
errors.push.apply(errors, detectErrors(compiled.ast))
detectErrors(compiled.ast, warn)
}
compiled.errors = errors
compiled.tips = tips

View File

@@ -25,7 +25,7 @@ export function genComponentModel (
el.model = {
value: `(${value})`,
expression: `"${value}"`,
expression: JSON.stringify(value),
callback: `function (${baseValueExpression}) {${assignment}}`
}
}

View File

@@ -2,6 +2,8 @@
import { dirRE, onRE } from './parser/index'
type Range = { start?: number, end?: number };
// these keywords should not appear inside expressions, but operators like
// typeof, instanceof and in are allowed
const prohibitedKeywordRE = new RegExp('\\b' + (
@@ -19,89 +21,92 @@ const unaryOperatorsRE = new RegExp('\\b' + (
const stripStringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g
// detect problematic expressions in a template
export function detectErrors (ast: ?ASTNode): Array<string> {
const errors: Array<string> = []
export function detectErrors (ast: ?ASTNode, warn: Function) {
if (ast) {
checkNode(ast, errors)
checkNode(ast, warn)
}
return errors
}
function checkNode (node: ASTNode, errors: Array<string>) {
function checkNode (node: ASTNode, warn: Function) {
if (node.type === 1) {
for (const name in node.attrsMap) {
if (dirRE.test(name)) {
const value = node.attrsMap[name]
if (value) {
const range = node.rawAttrsMap[name]
if (name === 'v-for') {
checkFor(node, `v-for="${value}"`, errors)
checkFor(node, `v-for="${value}"`, warn, range)
} else if (onRE.test(name)) {
checkEvent(value, `${name}="${value}"`, errors)
checkEvent(value, `${name}="${value}"`, warn, range)
} else {
checkExpression(value, `${name}="${value}"`, errors)
checkExpression(value, `${name}="${value}"`, warn, range)
}
}
}
}
if (node.children) {
for (let i = 0; i < node.children.length; i++) {
checkNode(node.children[i], errors)
checkNode(node.children[i], warn)
}
}
} else if (node.type === 2) {
checkExpression(node.expression, node.text, errors)
checkExpression(node.expression, node.text, warn, node)
}
}
function checkEvent (exp: string, text: string, errors: Array<string>) {
function checkEvent (exp: string, text: string, warn: Function, range?: Range) {
const stipped = exp.replace(stripStringRE, '')
const keywordMatch: any = stipped.match(unaryOperatorsRE)
if (keywordMatch && stipped.charAt(keywordMatch.index - 1) !== '$') {
errors.push(
warn(
`avoid using JavaScript unary operator as property name: ` +
`"${keywordMatch[0]}" in expression ${text.trim()}`
`"${keywordMatch[0]}" in expression ${text.trim()}`,
range
)
}
checkExpression(exp, text, errors)
checkExpression(exp, text, warn, range)
}
function checkFor (node: ASTElement, text: string, errors: Array<string>) {
checkExpression(node.for || '', text, errors)
checkIdentifier(node.alias, 'v-for alias', text, errors)
checkIdentifier(node.iterator1, 'v-for iterator', text, errors)
checkIdentifier(node.iterator2, 'v-for iterator', text, errors)
function checkFor (node: ASTElement, text: string, warn: Function, range?: Range) {
checkExpression(node.for || '', text, warn, range)
checkIdentifier(node.alias, 'v-for alias', text, warn, range)
checkIdentifier(node.iterator1, 'v-for iterator', text, warn, range)
checkIdentifier(node.iterator2, 'v-for iterator', text, warn, range)
}
function checkIdentifier (
ident: ?string,
type: string,
text: string,
errors: Array<string>
warn: Function,
range?: Range
) {
if (typeof ident === 'string') {
try {
new Function(`var ${ident}=_`)
} catch (e) {
errors.push(`invalid ${type} "${ident}" in expression: ${text.trim()}`)
warn(`invalid ${type} "${ident}" in expression: ${text.trim()}`, range)
}
}
}
function checkExpression (exp: string, text: string, errors: Array<string>) {
function checkExpression (exp: string, text: string, warn: Function, range?: Range) {
try {
new Function(`return ${exp}`)
} catch (e) {
const keywordMatch = exp.replace(stripStringRE, '').match(prohibitedKeywordRE)
if (keywordMatch) {
errors.push(
warn(
`avoid using JavaScript keyword as property name: ` +
`"${keywordMatch[0]}"\n Raw expression: ${text.trim()}`
`"${keywordMatch[0]}"\n Raw expression: ${text.trim()}`,
range
)
} else {
errors.push(
warn(
`invalid expression: ${e.message} in\n\n` +
` ${exp}\n\n` +
` Raw expression: ${text.trim()}\n`
` Raw expression: ${text.trim()}\n`,
range
)
}
}

View File

@@ -3,9 +3,13 @@
import { emptyObject } from 'shared/util'
import { parseFilters } from './parser/filter-parser'
export function baseWarn (msg: string) {
type Range = { start?: number, end?: number };
/* eslint-disable no-unused-vars */
export function baseWarn (msg: string, range?: Range) {
console.error(`[Vue compiler]: ${msg}`)
}
/* eslint-enable no-unused-vars */
export function pluckModuleFunction<F: Function> (
modules: ?Array<Object>,
@@ -16,20 +20,23 @@ export function pluckModuleFunction<F: Function> (
: []
}
export function addProp (el: ASTElement, name: string, value: string) {
(el.props || (el.props = [])).push({ name, value })
export function addProp (el: ASTElement, name: string, value: string, range?: Range, dynamic?: boolean) {
(el.props || (el.props = [])).push(rangeSetItem({ name, value, dynamic }, range))
el.plain = false
}
export function addAttr (el: ASTElement, name: string, value: any) {
(el.attrs || (el.attrs = [])).push({ name, value })
export function addAttr (el: ASTElement, name: string, value: any, range?: Range, dynamic?: boolean) {
const attrs = dynamic
? (el.dynamicAttrs || (el.dynamicAttrs = []))
: (el.attrs || (el.attrs = []))
attrs.push(rangeSetItem({ name, value, dynamic }, range))
el.plain = false
}
// add a raw attr (use this in preTransforms)
export function addRawAttr (el: ASTElement, name: string, value: any) {
export function addRawAttr (el: ASTElement, name: string, value: any, range?: Range) {
el.attrsMap[name] = value
el.attrsList.push({ name, value })
el.attrsList.push(rangeSetItem({ name, value }, range))
}
export function addDirective (
@@ -38,19 +45,36 @@ export function addDirective (
rawName: string,
value: string,
arg: ?string,
modifiers: ?ASTModifiers
isDynamicArg: boolean,
modifiers: ?ASTModifiers,
range?: Range
) {
(el.directives || (el.directives = [])).push({ name, rawName, value, arg, modifiers })
(el.directives || (el.directives = [])).push(rangeSetItem({
name,
rawName,
value,
arg,
isDynamicArg,
modifiers
}, range))
el.plain = false
}
function prependModifierMarker (symbol: string, name: string, dynamic?: boolean): string {
return dynamic
? `_p(${name},"${symbol}")`
: symbol + name // mark the event as captured
}
export function addHandler (
el: ASTElement,
name: string,
value: string,
modifiers: ?ASTModifiers,
important?: boolean,
warn?: Function
warn?: ?Function,
range?: Range,
dynamic?: boolean
) {
modifiers = modifiers || emptyObject
// warn prevent and passive modifier
@@ -61,37 +85,44 @@ export function addHandler (
) {
warn(
'passive and prevent can\'t be used together. ' +
'Passive handler can\'t prevent default event.'
'Passive handler can\'t prevent default event.',
range
)
}
// check capture modifier
if (modifiers.capture) {
delete modifiers.capture
name = '!' + name // mark the event as captured
}
if (modifiers.once) {
delete modifiers.once
name = '~' + name // mark the event as once
}
/* istanbul ignore if */
if (modifiers.passive) {
delete modifiers.passive
name = '&' + name // mark the event as passive
}
// normalize click.right and click.middle since they don't actually fire
// this is technically browser-specific, but at least for now browsers are
// the only target envs that have right/middle clicks.
if (name === 'click') {
if (modifiers.right) {
if (modifiers.right) {
if (dynamic) {
name = `(${name})==='click'?'contextmenu':(${name})`
} else if (name === 'click') {
name = 'contextmenu'
delete modifiers.right
} else if (modifiers.middle) {
}
} else if (modifiers.middle) {
if (dynamic) {
name = `(${name})==='click'?'mouseup':(${name})`
} else if (name === 'click') {
name = 'mouseup'
}
}
// check capture modifier
if (modifiers.capture) {
delete modifiers.capture
name = prependModifierMarker('!', name, dynamic)
}
if (modifiers.once) {
delete modifiers.once
name = prependModifierMarker('~', name, dynamic)
}
/* istanbul ignore if */
if (modifiers.passive) {
delete modifiers.passive
name = prependModifierMarker('&', name, dynamic)
}
let events
if (modifiers.native) {
delete modifiers.native
@@ -100,9 +131,7 @@ export function addHandler (
events = el.events || (el.events = {})
}
const newHandler: any = {
value: value.trim()
}
const newHandler: any = rangeSetItem({ value: value.trim(), dynamic }, range)
if (modifiers !== emptyObject) {
newHandler.modifiers = modifiers
}
@@ -120,6 +149,15 @@ export function addHandler (
el.plain = false
}
export function getRawBindingAttr (
el: ASTElement,
name: string
) {
return el.rawAttrsMap[':' + name] ||
el.rawAttrsMap['v-bind:' + name] ||
el.rawAttrsMap[name]
}
export function getBindingAttr (
el: ASTElement,
name: string,
@@ -162,3 +200,32 @@ export function getAndRemoveAttr (
}
return val
}
export function getAndRemoveAttrByRegex (
el: ASTElement,
name: RegExp
) {
const list = el.attrsList
for (let i = 0, l = list.length; i < l; i++) {
const attr = list[i]
if (name.test(attr.name)) {
list.splice(i, 1)
return attr
}
}
}
function rangeSetItem (
item: any,
range?: { start?: number, end?: number }
) {
if (range) {
if (range.start != null) {
item.start = range.start
}
if (range.end != null) {
item.end = range.end
}
}
return item
}

View File

@@ -30,7 +30,7 @@ export function optimize (root: ?ASTElement, options: CompilerOptions) {
function genStaticKeys (keys: string): Function {
return makeMap(
'type,tag,attrsList,attrsMap,plain,parent,children,attrs' +
'type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap' +
(keys ? ',' + keys : '')
)
}

View File

@@ -11,12 +11,12 @@
import { makeMap, no } from 'shared/util'
import { isNonPhrasingTag } from 'web/compiler/util'
import { unicodeRegExp } from 'core/util/lang'
// Regular Expressions for parsing tags and attributes
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
// could use https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-QName
// but for Vue templates we can enforce a simple charset
const ncname = '[a-zA-Z_][\\w\\-\\.]*'
const dynamicArgAttribute = /^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z${unicodeRegExp.source}]*`
const qnameCapture = `((?:${ncname}\\:)?${ncname})`
const startTagOpen = new RegExp(`^<${qnameCapture}`)
const startTagClose = /^\s*(\/?)>/
@@ -26,11 +26,6 @@ const doctype = /^<!DOCTYPE [^>]+>/i
const comment = /^<!\--/
const conditionalComment = /^<!\[/
let IS_REGEX_CAPTURING_BROKEN = false
'x'.replace(/x(.)?/g, function (m, g) {
IS_REGEX_CAPTURING_BROKEN = g === ''
})
// Special Elements (can contain anything)
export const isPlainTextElement = makeMap('script,style,textarea', true)
const reCache = {}
@@ -41,10 +36,11 @@ const decodingMap = {
'&quot;': '"',
'&amp;': '&',
'&#10;': '\n',
'&#9;': '\t'
'&#9;': '\t',
'&#39;': "'"
}
const encodedAttr = /&(?:lt|gt|quot|amp);/g
const encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#10|#9);/g
const encodedAttr = /&(?:lt|gt|quot|amp|#39);/g
const encodedAttrWithNewLines = /&(?:lt|gt|quot|amp|#39|#10|#9);/g
// #5992
const isIgnoreNewlineTag = makeMap('pre,textarea', true)
@@ -74,7 +70,7 @@ export function parseHTML (html, options) {
if (commentEnd >= 0) {
if (options.shouldKeepComment) {
options.comment(html.substring(4, commentEnd))
options.comment(html.substring(4, commentEnd), index, index + commentEnd + 3)
}
advance(commentEnd + 3)
continue
@@ -111,7 +107,7 @@ export function parseHTML (html, options) {
const startTagMatch = parseStartTag()
if (startTagMatch) {
handleStartTag(startTagMatch)
if (shouldIgnoreFirstNewline(lastTag, html)) {
if (shouldIgnoreFirstNewline(startTagMatch.tagName, html)) {
advance(1)
}
continue
@@ -134,16 +130,18 @@ export function parseHTML (html, options) {
rest = html.slice(textEnd)
}
text = html.substring(0, textEnd)
advance(textEnd)
}
if (textEnd < 0) {
text = html
html = ''
}
if (text) {
advance(text.length)
}
if (options.chars && text) {
options.chars(text)
options.chars(text, index - text.length, index)
}
} else {
let endTagLength = 0
@@ -172,7 +170,7 @@ export function parseHTML (html, options) {
if (html === last) {
options.chars && options.chars(html)
if (process.env.NODE_ENV !== 'production' && !stack.length && options.warn) {
options.warn(`Mal-formatted tag at end of template: "${html}"`)
options.warn(`Mal-formatted tag at end of template: "${html}"`, { start: index + html.length })
}
break
}
@@ -196,8 +194,10 @@ export function parseHTML (html, options) {
}
advance(start[0].length)
let end, attr
while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
while (!(end = html.match(startTagClose)) && (attr = html.match(dynamicArgAttribute) || html.match(attribute))) {
attr.start = index
advance(attr[0].length)
attr.end = index
match.attrs.push(attr)
}
if (end) {
@@ -228,12 +228,6 @@ export function parseHTML (html, options) {
const attrs = new Array(l)
for (let i = 0; i < l; i++) {
const args = match.attrs[i]
// hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778
if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) {
if (args[3] === '') { delete args[3] }
if (args[4] === '') { delete args[4] }
if (args[5] === '') { delete args[5] }
}
const value = args[3] || args[4] || args[5] || ''
const shouldDecodeNewlines = tagName === 'a' && args[1] === 'href'
? options.shouldDecodeNewlinesForHref
@@ -242,10 +236,14 @@ export function parseHTML (html, options) {
name: args[1],
value: decodeAttr(value, shouldDecodeNewlines)
}
if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
attrs[i].start = args.start + args[0].match(/^\s*/).length
attrs[i].end = args.end
}
}
if (!unary) {
stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs })
stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs, start: match.start, end: match.end })
lastTag = tagName
}
@@ -259,12 +257,9 @@ export function parseHTML (html, options) {
if (start == null) start = index
if (end == null) end = index
if (tagName) {
lowerCasedTagName = tagName.toLowerCase()
}
// Find the closest opened tag of the same type
if (tagName) {
lowerCasedTagName = tagName.toLowerCase()
for (pos = stack.length - 1; pos >= 0; pos--) {
if (stack[pos].lowerCasedTag === lowerCasedTagName) {
break
@@ -283,7 +278,8 @@ export function parseHTML (html, options) {
options.warn
) {
options.warn(
`tag <${stack[i].tag}> has no matching end tag.`
`tag <${stack[i].tag}> has no matching end tag.`,
{ start: stack[i].start, end: stack[i].end }
)
}
if (options.end) {

View File

@@ -5,7 +5,7 @@ import { parseHTML } from './html-parser'
import { parseText } from './text-parser'
import { parseFilters } from './filter-parser'
import { genAssignmentCode } from '../directives/model'
import { extend, cached, no, camelize } from 'shared/util'
import { extend, cached, no, camelize, hyphenate } from 'shared/util'
import { isIE, isEdge, isServerRendering } from 'core/util/env'
import {
@@ -16,21 +16,36 @@ import {
addDirective,
getBindingAttr,
getAndRemoveAttr,
pluckModuleFunction
getRawBindingAttr,
pluckModuleFunction,
getAndRemoveAttrByRegex
} from '../helpers'
export const onRE = /^@|^v-on:/
export const dirRE = /^v-|^@|^:/
export const forAliasRE = /([^]*?)\s+(?:in|of)\s+([^]*)/
export const dirRE = process.env.VBIND_PROP_SHORTHAND
? /^v-|^@|^:|^\./
: /^v-|^@|^:/
export const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
export const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/
const stripParensRE = /^\(|\)$/g
const dynamicArgRE = /^\[.*\]$/
const argRE = /:(.*)$/
export const bindRE = /^:|^v-bind:/
const modifierRE = /\.[^.]+/g
export const bindRE = /^:|^\.|^v-bind:/
const propBindRE = /^\./
const modifierRE = /\.[^.\]]+(?=[^\]]*$)/g
const slotRE = /^v-slot(:|$)|^#/
const lineBreakRE = /[\r\n]/
const whitespaceRE = /\s+/g
const invalidAttributeRE = /[\s"'<>\/=]/
const decodeHTMLCached = cached(he.decode)
export const emptySlotScopeToken = `_empty_`
// configurable state
export let warn: any
let delimiters
@@ -40,12 +55,11 @@ let postTransforms
let platformIsPreTag
let platformMustUseProp
let platformGetTagNamespace
type Attr = { name: string; value: string };
let maybeComponent
export function createASTElement (
tag: string,
attrs: Array<Attr>,
attrs: Array<ASTAttr>,
parent: ASTElement | void
): ASTElement {
return {
@@ -53,6 +67,7 @@ export function createASTElement (
tag,
attrsList: attrs,
attrsMap: makeAttrsMap(attrs),
rawAttrsMap: {},
parent,
children: []
}
@@ -70,6 +85,8 @@ export function parse (
platformIsPreTag = options.isPreTag || no
platformMustUseProp = options.mustUseProp || no
platformGetTagNamespace = options.getTagNamespace || no
const isReservedTag = options.isReservedTag || no
maybeComponent = (el: ASTElement) => !!el.component || !isReservedTag(el.tag)
transforms = pluckModuleFunction(options.modules, 'transformNode')
preTransforms = pluckModuleFunction(options.modules, 'preTransformNode')
@@ -79,20 +96,67 @@ export function parse (
const stack = []
const preserveWhitespace = options.preserveWhitespace !== false
const whitespaceOption = options.whitespace
let root
let currentParent
let inVPre = false
let inPre = false
let warned = false
function warnOnce (msg) {
function warnOnce (msg, range) {
if (!warned) {
warned = true
warn(msg)
warn(msg, range)
}
}
function closeElement (element) {
trimEndingWhitespace(element)
if (!inVPre && !element.processed) {
element = processElement(element, options)
}
// tree management
if (!stack.length && element !== root) {
// allow root elements with v-if, v-else-if and v-else
if (root.if && (element.elseif || element.else)) {
if (process.env.NODE_ENV !== 'production') {
checkRootConstraints(element)
}
addIfCondition(root, {
exp: element.elseif,
block: element
})
} else if (process.env.NODE_ENV !== 'production') {
warnOnce(
`Component template should contain exactly one root element. ` +
`If you are using v-if on multiple elements, ` +
`use v-else-if to chain them instead.`,
{ start: element.start }
)
}
}
if (currentParent && !element.forbidden) {
if (element.elseif || element.else) {
processIfConditions(element, currentParent)
} else {
if (element.slotScope) {
// scoped slot
// keep it in the children list so that v-else(-if) conditions can
// find it as the prev node.
const name = element.slotTarget || '"default"'
;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element
}
currentParent.children.push(element)
element.parent = currentParent
}
}
// final children cleanup
// filter out scoped slots
element.children = element.children.filter(c => !(c: any).slotScope)
// remove trailing whitespace node again
trimEndingWhitespace(element)
// check pre state
if (element.pre) {
inVPre = false
@@ -106,6 +170,37 @@ export function parse (
}
}
function trimEndingWhitespace (el) {
// remove trailing whitespace node
if (!inPre) {
let lastNode
while (
(lastNode = el.children[el.children.length - 1]) &&
lastNode.type === 3 &&
lastNode.text === ' '
) {
el.children.pop()
}
}
}
function checkRootConstraints (el) {
if (el.tag === 'slot' || el.tag === 'template') {
warnOnce(
`Cannot use <${el.tag}> as component root element because it may ` +
'contain multiple nodes.',
{ start: el.start }
)
}
if (el.attrsMap.hasOwnProperty('v-for')) {
warnOnce(
'Cannot use v-for on stateful component root element because ' +
'it renders multiple elements.',
el.rawAttrsMap['v-for']
)
}
}
parseHTML(template, {
warn,
expectHTML: options.expectHTML,
@@ -114,7 +209,8 @@ export function parse (
shouldDecodeNewlines: options.shouldDecodeNewlines,
shouldDecodeNewlinesForHref: options.shouldDecodeNewlinesForHref,
shouldKeepComment: options.comments,
start (tag, attrs, unary) {
outputSourceRange: options.outputSourceRange,
start (tag, attrs, unary, start, end) {
// check namespace.
// inherit parent ns if there is one
const ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag)
@@ -130,12 +226,36 @@ export function parse (
element.ns = ns
}
if (process.env.NODE_ENV !== 'production') {
if (options.outputSourceRange) {
element.start = start
element.end = end
element.rawAttrsMap = element.attrsList.reduce((cumulated, attr) => {
cumulated[attr.name] = attr
return cumulated
}, {})
}
attrs.forEach(attr => {
if (invalidAttributeRE.test(attr.name)) {
warn(
`Invalid dynamic argument expression: attribute names cannot contain ` +
`spaces, quotes, <, >, / or =.`,
{
start: attr.start + attr.name.indexOf(`[`),
end: attr.start + attr.name.length
}
)
}
})
}
if (isForbiddenTag(element) && !isServerRendering()) {
element.forbidden = true
process.env.NODE_ENV !== 'production' && warn(
'Templates should only be responsible for mapping the state to the ' +
'UI. Avoid placing tags with side-effects in your templates, such as ' +
`<${tag}>` + ', as they will not be parsed.'
`<${tag}>` + ', as they will not be parsed.',
{ start: element.start }
)
}
@@ -160,59 +280,15 @@ export function parse (
processFor(element)
processIf(element)
processOnce(element)
// element-scope stuff
processElement(element, options)
}
function checkRootConstraints (el) {
if (process.env.NODE_ENV !== 'production') {
if (el.tag === 'slot' || el.tag === 'template') {
warnOnce(
`Cannot use <${el.tag}> as component root element because it may ` +
'contain multiple nodes.'
)
}
if (el.attrsMap.hasOwnProperty('v-for')) {
warnOnce(
'Cannot use v-for on stateful component root element because ' +
'it renders multiple elements.'
)
}
}
}
// tree management
if (!root) {
root = element
checkRootConstraints(root)
} else if (!stack.length) {
// allow root elements with v-if, v-else-if and v-else
if (root.if && (element.elseif || element.else)) {
checkRootConstraints(element)
addIfCondition(root, {
exp: element.elseif,
block: element
})
} else if (process.env.NODE_ENV !== 'production') {
warnOnce(
`Component template should contain exactly one root element. ` +
`If you are using v-if on multiple elements, ` +
`use v-else-if to chain them instead.`
)
}
}
if (currentParent && !element.forbidden) {
if (element.elseif || element.else) {
processIfConditions(element, currentParent)
} else if (element.slotScope) { // scoped slot
currentParent.plain = false
const name = element.slotTarget || '"default"'
;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element
} else {
currentParent.children.push(element)
element.parent = currentParent
if (process.env.NODE_ENV !== 'production') {
checkRootConstraints(root)
}
}
if (!unary) {
currentParent = element
stack.push(element)
@@ -221,29 +297,29 @@ export function parse (
}
},
end () {
// remove trailing whitespace
end (tag, start, end) {
const element = stack[stack.length - 1]
const lastNode = element.children[element.children.length - 1]
if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) {
element.children.pop()
}
// pop stack
stack.length -= 1
currentParent = stack[stack.length - 1]
if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
element.end = end
}
closeElement(element)
},
chars (text: string) {
chars (text: string, start: number, end: number) {
if (!currentParent) {
if (process.env.NODE_ENV !== 'production') {
if (text === template) {
warnOnce(
'Component template requires a root element, rather than just text.'
'Component template requires a root element, rather than just text.',
{ start }
)
} else if ((text = text.trim())) {
warnOnce(
`text "${text}" outside root element will be ignored.`
`text "${text}" outside root element will be ignored.`,
{ start }
)
}
}
@@ -258,33 +334,66 @@ export function parse (
return
}
const children = currentParent.children
text = inPre || text.trim()
? isTextTag(currentParent) ? text : decodeHTMLCached(text)
// only preserve whitespace if its not right after a starting tag
: preserveWhitespace && children.length ? ' ' : ''
if (inPre || text.trim()) {
text = isTextTag(currentParent) ? text : decodeHTMLCached(text)
} else if (!children.length) {
// remove the whitespace-only node right after an opening tag
text = ''
} else if (whitespaceOption) {
if (whitespaceOption === 'condense') {
// in condense mode, remove the whitespace node if it contains
// line break, otherwise condense to a single space
text = lineBreakRE.test(text) ? '' : ' '
} else {
text = ' '
}
} else {
text = preserveWhitespace ? ' ' : ''
}
if (text) {
if (whitespaceOption === 'condense') {
// condense consecutive whitespaces into single space
text = text.replace(whitespaceRE, ' ')
}
let res
let child: ?ASTNode
if (!inVPre && text !== ' ' && (res = parseText(text, delimiters))) {
children.push({
child = {
type: 2,
expression: res.expression,
tokens: res.tokens,
text
})
}
} else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
children.push({
child = {
type: 3,
text
})
}
}
if (child) {
if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
child.start = start
child.end = end
}
children.push(child)
}
}
},
comment (text: string) {
currentParent.children.push({
type: 3,
text,
isComment: true
})
comment (text: string, start, end) {
// adding anyting as a sibling to the root node is forbidden
// comments should still be allowed, but ignored
if (currentParent) {
const child: ASTText = {
type: 3,
text,
isComment: true
}
if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
child.start = start
child.end = end
}
currentParent.children.push(child)
}
}
})
return root
@@ -297,13 +406,18 @@ function processPre (el) {
}
function processRawAttrs (el) {
const l = el.attrsList.length
if (l) {
const attrs = el.attrs = new Array(l)
for (let i = 0; i < l; i++) {
const list = el.attrsList
const len = list.length
if (len) {
const attrs: Array<ASTAttr> = el.attrs = new Array(len)
for (let i = 0; i < len; i++) {
attrs[i] = {
name: el.attrsList[i].name,
value: JSON.stringify(el.attrsList[i].value)
name: list[i].name,
value: JSON.stringify(list[i].value)
}
if (list[i].start != null) {
attrs[i].start = list[i].start
attrs[i].end = list[i].end
}
}
} else if (!el.pre) {
@@ -312,27 +426,53 @@ function processRawAttrs (el) {
}
}
export function processElement (element: ASTElement, options: CompilerOptions) {
export function processElement (
element: ASTElement,
options: CompilerOptions
) {
processKey(element)
// determine whether this is a plain element after
// removing structural attributes
element.plain = !element.key && !element.attrsList.length
element.plain = (
!element.key &&
!element.scopedSlots &&
!element.attrsList.length
)
processRef(element)
processSlot(element)
processSlotContent(element)
processSlotOutlet(element)
processComponent(element)
for (let i = 0; i < transforms.length; i++) {
element = transforms[i](element, options) || element
}
processAttrs(element)
return element
}
function processKey (el) {
const exp = getBindingAttr(el, 'key')
if (exp) {
if (process.env.NODE_ENV !== 'production' && el.tag === 'template') {
warn(`<template> cannot be keyed. Place the key on real elements instead.`)
if (process.env.NODE_ENV !== 'production') {
if (el.tag === 'template') {
warn(
`<template> cannot be keyed. Place the key on real elements instead.`,
getRawBindingAttr(el, 'key')
)
}
if (el.for) {
const iterator = el.iterator2 || el.iterator1
const parent = el.parent
if (iterator && iterator === exp && parent && parent.tag === 'transition-group') {
warn(
`Do not use v-for index as key on <transition-group> children, ` +
`this is the same as not using keys.`,
getRawBindingAttr(el, 'key'),
true /* tip */
)
}
}
}
el.key = exp
}
@@ -354,7 +494,8 @@ export function processFor (el: ASTElement) {
extend(el, res)
} else if (process.env.NODE_ENV !== 'production') {
warn(
`Invalid v-for expression: ${exp}`
`Invalid v-for expression: ${exp}`,
el.rawAttrsMap['v-for']
)
}
}
@@ -375,7 +516,7 @@ export function parseFor (exp: string): ?ForParseResult {
const alias = inMatch[1].trim().replace(stripParensRE, '')
const iteratorMatch = alias.match(forIteratorRE)
if (iteratorMatch) {
res.alias = alias.replace(forIteratorRE, '')
res.alias = alias.replace(forIteratorRE, '').trim()
res.iterator1 = iteratorMatch[1].trim()
if (iteratorMatch[2]) {
res.iterator2 = iteratorMatch[2].trim()
@@ -415,7 +556,8 @@ function processIfConditions (el, parent) {
} else if (process.env.NODE_ENV !== 'production') {
warn(
`v-${el.elseif ? ('else-if="' + el.elseif + '"') : 'else'} ` +
`used on element <${el.tag}> without corresponding v-if.`
`used on element <${el.tag}> without corresponding v-if.`,
el.rawAttrsMap[el.elseif ? 'v-else-if' : 'v-else']
)
}
}
@@ -429,7 +571,8 @@ function findPrevElement (children: Array<any>): ASTElement | void {
if (process.env.NODE_ENV !== 'production' && children[i].text !== ' ') {
warn(
`text "${children[i].text.trim()}" between v-if and v-else(-if) ` +
`will be ignored.`
`will be ignored.`,
children[i]
)
}
children.pop()
@@ -451,52 +594,154 @@ function processOnce (el) {
}
}
function processSlot (el) {
// handle content being passed to a component as slot,
// e.g. <template slot="xxx">, <div slot-scope="xxx">
function processSlotContent (el) {
let slotScope
if (el.tag === 'template') {
slotScope = getAndRemoveAttr(el, 'scope')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && slotScope) {
warn(
`the "scope" attribute for scoped slots have been deprecated and ` +
`replaced by "slot-scope" since 2.5. The new "slot-scope" attribute ` +
`can also be used on plain elements in addition to <template> to ` +
`denote scoped slots.`,
el.rawAttrsMap['scope'],
true
)
}
el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope')
} else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && el.attrsMap['v-for']) {
warn(
`Ambiguous combined usage of slot-scope and v-for on <${el.tag}> ` +
`(v-for takes higher priority). Use a wrapper <template> for the ` +
`scoped slot to make it clearer.`,
el.rawAttrsMap['slot-scope'],
true
)
}
el.slotScope = slotScope
}
// slot="xxx"
const slotTarget = getBindingAttr(el, 'slot')
if (slotTarget) {
el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget
el.slotTargetDynamic = !!(el.attrsMap[':slot'] || el.attrsMap['v-bind:slot'])
// preserve slot as an attribute for native shadow DOM compat
// only for non-scoped slots.
if (el.tag !== 'template' && !el.slotScope) {
addAttr(el, 'slot', slotTarget, getRawBindingAttr(el, 'slot'))
}
}
// 2.6 v-slot syntax
if (process.env.NEW_SLOT_SYNTAX) {
if (el.tag === 'template') {
// v-slot on <template>
const slotBinding = getAndRemoveAttrByRegex(el, slotRE)
if (slotBinding) {
if (process.env.NODE_ENV !== 'production') {
if (el.slotTarget || el.slotScope) {
warn(
`Unexpected mixed usage of different slot syntaxes.`,
el
)
}
if (el.parent && !maybeComponent(el.parent)) {
warn(
`<template v-slot> can only appear at the root level inside ` +
`the receiving the component`,
el
)
}
}
const { name, dynamic } = getSlotName(slotBinding)
el.slotTarget = name
el.slotTargetDynamic = dynamic
el.slotScope = slotBinding.value || emptySlotScopeToken // force it into a scoped slot for perf
}
} else {
// v-slot on component, denotes default slot
const slotBinding = getAndRemoveAttrByRegex(el, slotRE)
if (slotBinding) {
if (process.env.NODE_ENV !== 'production') {
if (!maybeComponent(el)) {
warn(
`v-slot can only be used on components or <template>.`,
slotBinding
)
}
if (el.slotScope || el.slotTarget) {
warn(
`Unexpected mixed usage of different slot syntaxes.`,
el
)
}
if (el.scopedSlots) {
warn(
`To avoid scope ambiguity, the default slot should also use ` +
`<template> syntax when there are other named slots.`,
slotBinding
)
}
}
// add the component's children to its default slot
const slots = el.scopedSlots || (el.scopedSlots = {})
const { name, dynamic } = getSlotName(slotBinding)
const slotContainer = slots[name] = createASTElement('template', [], el)
slotContainer.slotTarget = name
slotContainer.slotTargetDynamic = dynamic
slotContainer.children = el.children.filter((c: any) => {
if (!c.slotScope) {
c.parent = slotContainer
return true
}
})
slotContainer.slotScope = slotBinding.value || emptySlotScopeToken
// remove children as they are returned from scopedSlots now
el.children = []
// mark el non-plain so data gets generated
el.plain = false
}
}
}
}
function getSlotName (binding) {
let name = binding.name.replace(slotRE, '')
if (!name) {
if (binding.name[0] !== '#') {
name = 'default'
} else if (process.env.NODE_ENV !== 'production') {
warn(
`v-slot shorthand syntax requires a slot name.`,
binding
)
}
}
return dynamicArgRE.test(name)
// dynamic [name]
? { name: name.slice(1, -1), dynamic: true }
// static name
: { name: `"${name}"`, dynamic: false }
}
// handle <slot/> outlets
function processSlotOutlet (el) {
if (el.tag === 'slot') {
el.slotName = getBindingAttr(el, 'name')
if (process.env.NODE_ENV !== 'production' && el.key) {
warn(
`\`key\` does not work on <slot> because slots are abstract outlets ` +
`and can possibly expand into multiple elements. ` +
`Use the key on a wrapping element instead.`
`Use the key on a wrapping element instead.`,
getRawBindingAttr(el, 'key')
)
}
} else {
let slotScope
if (el.tag === 'template') {
slotScope = getAndRemoveAttr(el, 'scope')
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && slotScope) {
warn(
`the "scope" attribute for scoped slots have been deprecated and ` +
`replaced by "slot-scope" since 2.5. The new "slot-scope" attribute ` +
`can also be used on plain elements in addition to <template> to ` +
`denote scoped slots.`,
true
)
}
el.slotScope = slotScope || getAndRemoveAttr(el, 'slot-scope')
} else if ((slotScope = getAndRemoveAttr(el, 'slot-scope'))) {
/* istanbul ignore if */
if (process.env.NODE_ENV !== 'production' && el.attrsMap['v-for']) {
warn(
`Ambiguous combined usage of slot-scope and v-for on <${el.tag}> ` +
`(v-for takes higher priority). Use a wrapper <template> for the ` +
`scoped slot to make it clearer.`,
true
)
}
el.slotScope = slotScope
}
const slotTarget = getBindingAttr(el, 'slot')
if (slotTarget) {
el.slotTarget = slotTarget === '""' ? '"default"' : slotTarget
// preserve slot as an attribute for native shadow DOM compat
// only for non-scoped slots.
if (el.tag !== 'template' && !el.slotScope) {
addAttr(el, 'slot', slotTarget)
}
}
}
}
@@ -512,7 +757,7 @@ function processComponent (el) {
function processAttrs (el) {
const list = el.attrsList
let i, l, name, rawName, value, modifiers, isProp
let i, l, name, rawName, value, modifiers, syncGen, isDynamic
for (i = 0, l = list.length; i < l; i++) {
name = rawName = list[i].name
value = list[i].value
@@ -520,50 +765,103 @@ function processAttrs (el) {
// mark element as dynamic
el.hasBindings = true
// modifiers
modifiers = parseModifiers(name)
if (modifiers) {
modifiers = parseModifiers(name.replace(dirRE, ''))
// support .foo shorthand syntax for the .prop modifier
if (process.env.VBIND_PROP_SHORTHAND && propBindRE.test(name)) {
(modifiers || (modifiers = {})).prop = true
name = `.` + name.slice(1).replace(modifierRE, '')
} else if (modifiers) {
name = name.replace(modifierRE, '')
}
if (bindRE.test(name)) { // v-bind
name = name.replace(bindRE, '')
value = parseFilters(value)
isProp = false
isDynamic = dynamicArgRE.test(name)
if (isDynamic) {
name = name.slice(1, -1)
}
if (
process.env.NODE_ENV !== 'production' &&
value.trim().length === 0
) {
warn(
`The value for a v-bind expression cannot be empty. Found in "v-bind:${name}"`
)
}
if (modifiers) {
if (modifiers.prop) {
isProp = true
if (modifiers.prop && !isDynamic) {
name = camelize(name)
if (name === 'innerHtml') name = 'innerHTML'
}
if (modifiers.camel) {
if (modifiers.camel && !isDynamic) {
name = camelize(name)
}
if (modifiers.sync) {
addHandler(
el,
`update:${camelize(name)}`,
genAssignmentCode(value, `$event`)
)
syncGen = genAssignmentCode(value, `$event`)
if (!isDynamic) {
addHandler(
el,
`update:${camelize(name)}`,
syncGen,
null,
false,
warn,
list[i]
)
if (hyphenate(name) !== camelize(name)) {
addHandler(
el,
`update:${hyphenate(name)}`,
syncGen,
null,
false,
warn,
list[i]
)
}
} else {
// handler w/ dynamic event name
addHandler(
el,
`"update:"+(${name})`,
syncGen,
null,
false,
warn,
list[i],
true // dynamic
)
}
}
}
if (isProp || (
if ((modifiers && modifiers.prop) || (
!el.component && platformMustUseProp(el.tag, el.attrsMap.type, name)
)) {
addProp(el, name, value)
addProp(el, name, value, list[i], isDynamic)
} else {
addAttr(el, name, value)
addAttr(el, name, value, list[i], isDynamic)
}
} else if (onRE.test(name)) { // v-on
name = name.replace(onRE, '')
addHandler(el, name, value, modifiers, false, warn)
isDynamic = dynamicArgRE.test(name)
if (isDynamic) {
name = name.slice(1, -1)
}
addHandler(el, name, value, modifiers, false, warn, list[i], isDynamic)
} else { // normal directives
name = name.replace(dirRE, '')
// parse arg
const argMatch = name.match(argRE)
const arg = argMatch && argMatch[1]
let arg = argMatch && argMatch[1]
isDynamic = false
if (arg) {
name = name.slice(0, -(arg.length + 1))
if (dynamicArgRE.test(arg)) {
arg = arg.slice(1, -1)
isDynamic = true
}
}
addDirective(el, name, rawName, value, arg, modifiers)
addDirective(el, name, rawName, value, arg, isDynamic, modifiers, list[i])
if (process.env.NODE_ENV !== 'production' && name === 'model') {
checkForAliasModel(el, value)
}
@@ -577,17 +875,18 @@ function processAttrs (el) {
`${name}="${value}": ` +
'Interpolation inside attributes has been removed. ' +
'Use v-bind or the colon shorthand instead. For example, ' +
'instead of <div id="{{ val }}">, use <div :id="val">.'
'instead of <div id="{{ val }}">, use <div :id="val">.',
list[i]
)
}
}
addAttr(el, name, JSON.stringify(value))
addAttr(el, name, JSON.stringify(value), list[i])
// #6887 firefox doesn't update muted state if set via attribute
// even immediately after element creation
if (!el.component &&
name === 'muted' &&
platformMustUseProp(el.tag, el.attrsMap.type, name)) {
addProp(el, name, 'true')
addProp(el, name, 'true', list[i])
}
}
}
@@ -620,7 +919,7 @@ function makeAttrsMap (attrs: Array<Object>): Object {
process.env.NODE_ENV !== 'production' &&
map[attrs[i].name] && !isIE && !isEdge
) {
warn('duplicate attribute: ' + attrs[i].name)
warn('duplicate attribute: ' + attrs[i].name, attrs[i])
}
map[attrs[i].name] = attrs[i].value
}
@@ -667,7 +966,8 @@ function checkForAliasModel (el, value) {
`You are binding v-model directly to a v-for iteration alias. ` +
`This will not be able to modify the v-for source array because ` +
`writing to the alias is like modifying a function local variable. ` +
`Consider using an array of objects and use v-model on an object property instead.`
`Consider using an array of objects and use v-model on an object property instead.`,
el.rawAttrsMap['v-model']
)
}
_el = _el.parent

View File

@@ -3,7 +3,7 @@
import { cached } from 'shared/util'
import { parseFilters } from './filter-parser'
const defaultTagRE = /\{\{((?:.|\n)+?)\}\}/g
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g
const regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g
const buildRegex = cached(delimiters => {

View File

@@ -2,6 +2,7 @@
import { noop, extend } from 'shared/util'
import { warn as baseWarn, tip } from 'core/util/debug'
import { generateCodeFrame } from './codeframe'
type CompiledFunctionResult = {
render: Function;
@@ -61,14 +62,28 @@ export function createCompileToFunctionFn (compile: Function): Function {
// 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 (options.outputSourceRange) {
compiled.errors.forEach(e => {
warn(
`Error compiling template:\n\n${e.msg}\n\n` +
generateCodeFrame(template, e.start, e.end),
vm
)
})
} else {
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))
if (options.outputSourceRange) {
compiled.tips.forEach(e => tip(e.msg, vm))
} else {
compiled.tips.forEach(msg => tip(msg, vm))
}
}
}

View File

@@ -28,6 +28,9 @@ export type Config = {
getTagNamespace: (x?: string) => string | void;
mustUseProp: (tag: string, type: ?string, name: string) => boolean;
// private
async: boolean;
// legacy
_lifecycleHooks: Array<string>;
};
@@ -114,6 +117,12 @@ export default ({
*/
mustUseProp: no,
/**
* Perform updates asynchronously. Intended to be used by Vue Test Utils
* This will significantly reduce performance if set to false.
*/
async: true,
/**
* Exposed for legacy reasons
*/

View File

@@ -8,6 +8,7 @@ import { initAssetRegisters } from './assets'
import { set, del } from '../observer/index'
import { ASSET_TYPES } from 'shared/constants'
import builtInComponents from '../components/index'
import { observe } from 'core/observer/index'
import {
warn,
@@ -44,6 +45,12 @@ export function initGlobalAPI (Vue: GlobalAPI) {
Vue.delete = del
Vue.nextTick = nextTick
// 2.6 explicit observable API
Vue.observable = <T>(obj: T): T => {
observe(obj)
return obj
}
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)

View File

@@ -4,8 +4,8 @@ import {
tip,
toArray,
hyphenate,
handleError,
formatComponentName
formatComponentName,
invokeWithErrorHandling
} from '../util/index'
import { updateListeners } from '../vdom/helpers/index'
@@ -21,25 +21,31 @@ export function initEvents (vm: Component) {
let target: any
function add (event, fn, once) {
if (once) {
target.$once(event, fn)
} else {
target.$on(event, fn)
}
function add (event, fn) {
target.$on(event, fn)
}
function remove (event, fn) {
target.$off(event, fn)
}
function createOnceHandler (event, fn) {
const _target = target
return function onceHandler () {
const res = fn.apply(null, arguments)
if (res !== null) {
_target.$off(event, onceHandler)
}
}
}
export function updateComponentListeners (
vm: Component,
listeners: Object,
oldListeners: ?Object
) {
target = vm
updateListeners(listeners, oldListeners || {}, add, remove, vm)
updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
target = undefined
}
@@ -49,7 +55,7 @@ export function eventsMixin (Vue: Class<Component>) {
const vm: Component = this
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
this.$on(event[i], fn)
vm.$on(event[i], fn)
}
} else {
(vm._events[event] || (vm._events[event] = [])).push(fn)
@@ -83,7 +89,7 @@ export function eventsMixin (Vue: Class<Component>) {
// array of events
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
this.$off(event[i], fn)
vm.$off(event[i], fn)
}
return vm
}
@@ -96,16 +102,14 @@ export function eventsMixin (Vue: Class<Component>) {
vm._events[event] = null
return vm
}
if (fn) {
// specific handler
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
// 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
@@ -129,12 +133,9 @@ export function eventsMixin (Vue: Class<Component>) {
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
const args = toArray(arguments, 1)
const info = `event handler for "${event}"`
for (let i = 0, l = cbs.length; i < l; i++) {
try {
cbs[i].apply(vm, args)
} catch (e) {
handleError(e, vm, `event handler for "${event}"`)
}
invokeWithErrorHandling(cbs[i], vm, args, vm, info)
}
}
return vm

View File

@@ -77,8 +77,6 @@ export function initInternalComponent (vm: Component, options: InternalComponent
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
opts._parentElm = options._parentElm
opts._refElm = options._refElm
const vnodeComponentOptions = parentVnode.componentOptions
opts.propsData = vnodeComponentOptions.propsData
@@ -119,32 +117,12 @@ export function resolveConstructorOptions (Ctor: Class<Component>) {
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
let modified
const latest = Ctor.options
const extended = Ctor.extendOptions
const sealed = Ctor.sealedOptions
for (const key in latest) {
if (latest[key] !== sealed[key]) {
if (!modified) modified = {}
modified[key] = dedupe(latest[key], extended[key], sealed[key])
modified[key] = latest[key]
}
}
return modified
}
function dedupe (latest, extended, sealed) {
// compare latest and sealed to ensure lifecycle hooks won't be duplicated
// between merges
if (Array.isArray(latest)) {
const res = []
sealed = Array.isArray(sealed) ? sealed : [sealed]
extended = Array.isArray(extended) ? extended : [extended]
for (let i = 0; i < latest.length; i++) {
// push original options and not sealed options to exclude duplicated options
if (extended.indexOf(latest[i]) >= 0 || sealed.indexOf(latest[i]) < 0) {
res.push(latest[i])
}
}
return res
} else {
return latest
}
}

View File

@@ -41,14 +41,13 @@ export function resolveInject (inject: any, vm: Component): ?Object {
// inject is :any because flow is not smart enough to figure out cached
const result = Object.create(null)
const keys = hasSymbol
? Reflect.ownKeys(inject).filter(key => {
/* istanbul ignore next */
return Object.getOwnPropertyDescriptor(inject, key).enumerable
})
? Reflect.ownKeys(inject)
: Object.keys(inject)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
// #6574 in case the inject object is observed...
if (key === '__ob__') continue
const provideKey = inject[key].from
let source = vm
while (source) {

View File

@@ -13,14 +13,22 @@ import {
warn,
noop,
remove,
handleError,
emptyObject,
validateProp
validateProp,
invokeWithErrorHandling
} from '../util/index'
export let activeInstance: any = null
export let isUpdatingChildComponent: boolean = false
export function setActiveInstance(vm: Component) {
const prevActiveInstance = activeInstance
activeInstance = vm
return () => {
activeInstance = prevActiveInstance
}
}
export function initLifecycle (vm: Component) {
const options = vm.$options
@@ -50,31 +58,20 @@ export function initLifecycle (vm: Component) {
export function lifecycleMixin (Vue: Class<Component>) {
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
if (vm._isMounted) {
callHook(vm, 'beforeUpdate')
}
const prevEl = vm.$el
const prevVnode = vm._vnode
const prevActiveInstance = activeInstance
activeInstance = vm
const restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(
vm.$el, vnode, hydrating, false /* removeOnly */,
vm.$options._parentElm,
vm.$options._refElm
)
// no need for the ref nodes after initial patch
// this prevents keeping a detached DOM tree in memory (#5851)
vm.$options._parentElm = vm.$options._refElm = null
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
activeInstance = prevActiveInstance
restoreActiveInstance()
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
@@ -197,7 +194,13 @@ export function mountComponent (
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)
new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)
hydrating = false
// manually mounted instance, call mounted on self
@@ -221,12 +224,26 @@ export function updateChildComponent (
}
// determine whether component has slot children
// we need to do this before overwriting $options._renderChildren
const hasChildren = !!(
// we need to do this before overwriting $options._renderChildren.
// check if there are dynamic scopedSlots (hand-written or compiled but with
// dynamic slot names). Static scoped slots compiled from template has the
// "$stable" marker.
const newScopedSlots = parentVnode.data.scopedSlots
const oldScopedSlots = vm.$scopedSlots
const hasDynamicScopedSlot = !!(
(newScopedSlots && !newScopedSlots.$stable) ||
(oldScopedSlots !== emptyObject && !oldScopedSlots.$stable) ||
(newScopedSlots && vm.$scopedSlots.$key !== newScopedSlots.$key)
)
// Any static slot children from the parent may have changed during parent's
// update. Dynamic scoped slots may also have changed. In such cases, a forced
// update is necessary to ensure correctness.
const needsForceUpdate = !!(
renderChildren || // has new static slots
vm.$options._renderChildren || // has old static slots
parentVnode.data.scopedSlots || // has new scoped slots
vm.$scopedSlots !== emptyObject // has old scoped slots
hasDynamicScopedSlot
)
vm.$options._parentVnode = parentVnode
@@ -265,7 +282,7 @@ export function updateChildComponent (
updateComponentListeners(vm, listeners, oldListeners)
// resolve slots + force update if has children
if (hasChildren) {
if (needsForceUpdate) {
vm.$slots = resolveSlots(renderChildren, parentVnode.context)
vm.$forceUpdate()
}
@@ -320,13 +337,10 @@ export function callHook (vm: Component, hook: string) {
// #7573 disable dep collection when invoking lifecycle hooks
pushTarget()
const handlers = vm.$options[hook]
const info = `${hook} hook`
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
try {
handlers[i].call(vm)
} catch (e) {
handleError(e, vm, `${hook} hook`)
}
invokeWithErrorHandling(handlers[i], vm, null, vm, info)
}
}
if (vm._hasHookEvent) {

View File

@@ -24,6 +24,16 @@ if (process.env.NODE_ENV !== 'production') {
)
}
const warnReservedPrefix = (target, key) => {
warn(
`Property "${key}" must be accessed with "$data.${key}" because ` +
'properties starting with "$" or "_" are not proxied in the Vue instance to ' +
'prevent conflicts with Vue internals' +
'See: https://vuejs.org/v2/api/#data',
target
)
}
const hasProxy =
typeof Proxy !== 'undefined' && isNative(Proxy)
@@ -45,9 +55,11 @@ if (process.env.NODE_ENV !== 'production') {
const hasHandler = {
has (target, key) {
const has = key in target
const isAllowed = allowedGlobals(key) || key.charAt(0) === '_'
const isAllowed = allowedGlobals(key) ||
(typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data))
if (!has && !isAllowed) {
warnNonPresent(target, key)
if (key in target.$data) warnReservedPrefix(target, key)
else warnNonPresent(target, key)
}
return has || !isAllowed
}
@@ -56,7 +68,8 @@ if (process.env.NODE_ENV !== 'production') {
const getHandler = {
get (target, key) {
if (typeof key === 'string' && !(key in target)) {
warnNonPresent(target, key)
if (key in target.$data) warnReservedPrefix(target, key)
else warnNonPresent(target, key)
}
return target[key]
}

View File

@@ -6,7 +6,8 @@ import {
warn,
isObject,
toObject,
isReservedAttribute
isReservedAttribute,
camelize
} from 'core/util/index'
/**
@@ -43,12 +44,13 @@ export function bindObjectProps (
? data.domProps || (data.domProps = {})
: data.attrs || (data.attrs = {})
}
if (!(key in hash)) {
const camelizedKey = camelize(key)
if (!(key in hash) && !(camelizedKey in hash)) {
hash[key] = value[key]
if (isSync) {
const on = data.on || (data.on = {})
on[`update:${key}`] = function ($event) {
on[`update:${camelizedKey}`] = function ($event) {
value[key] = $event
}
}

View File

@@ -9,7 +9,8 @@ import { checkKeyCodes } from './check-keycodes'
import { bindObjectProps } from './bind-object-props'
import { renderStatic, markOnce } from './render-static'
import { bindObjectListeners } from './bind-object-listeners'
import { resolveScopedSlots } from './resolve-slots'
import { resolveScopedSlots } from './resolve-scoped-slots'
import { bindDynamicKeys, prependModifier } from './bind-dynamic-keys'
export function installRenderHelpers (target: any) {
target._o = markOnce
@@ -27,4 +28,6 @@ export function installRenderHelpers (target: any) {
target._e = createEmptyVNode
target._u = resolveScopedSlots
target._g = bindObjectListeners
target._d = bindDynamicKeys
target._p = prependModifier
}

View File

@@ -1,6 +1,6 @@
/* @flow */
import { isObject, isDef } from 'core/util/index'
import { isObject, isDef, hasSymbol } from 'core/util/index'
/**
* Runtime helper for rendering v-for lists.
@@ -25,15 +25,26 @@ export function renderList (
ret[i] = render(i + 1, i)
}
} else if (isObject(val)) {
keys = Object.keys(val)
ret = new Array(keys.length)
for (i = 0, l = keys.length; i < l; i++) {
key = keys[i]
ret[i] = render(val[key], key, i)
if (hasSymbol && val[Symbol.iterator]) {
ret = []
const iterator: Iterator<any> = val[Symbol.iterator]()
let result = iterator.next()
while (!result.done) {
ret.push(render(result.value, ret.length))
result = iterator.next()
}
} else {
keys = Object.keys(val)
ret = new Array(keys.length)
for (i = 0, l = keys.length; i < l; i++) {
key = keys[i]
ret[i] = render(val[key], key, i)
}
}
}
if (isDef(ret)) {
(ret: any)._isVList = true
if (!isDef(ret)) {
ret = []
}
(ret: any)._isVList = true
return ret
}

View File

@@ -26,19 +26,7 @@ export function renderSlot (
}
nodes = scopedSlotFn(props) || fallback
} else {
const slotNodes = this.$slots[name]
// warn duplicate slot usage
if (slotNodes) {
if (process.env.NODE_ENV !== 'production' && slotNodes._rendered) {
warn(
`Duplicate presence of slot "${name}" found in the same render tree ` +
`- this will likely cause render errors.`,
this
)
}
slotNodes._rendered = true
}
nodes = slotNodes || fallback
nodes = this.$slots[name] || fallback
}
const target = props && props.slot

View File

@@ -9,10 +9,10 @@ export function resolveSlots (
children: ?Array<VNode>,
context: ?Component
): { [key: string]: Array<VNode> } {
const slots = {}
if (!children) {
return slots
if (!children || !children.length) {
return {}
}
const slots = {}
for (let i = 0, l = children.length; i < l; i++) {
const child = children[i]
const data = child.data
@@ -48,18 +48,3 @@ export function resolveSlots (
function isWhitespace (node: VNode): boolean {
return (node.isComment && !node.asyncFactory) || node.text === ' '
}
export function resolveScopedSlots (
fns: ScopedSlotsData, // see flow/vnode
res?: Object
): { [key: string]: Function } {
res = res || {}
for (let i = 0; i < fns.length; i++) {
if (Array.isArray(fns[i])) {
resolveScopedSlots(fns[i], res)
} else {
res[fns[i].key] = fns[i].fn
}
}
return res
}

View File

@@ -11,6 +11,7 @@ import {
import { createElement } from '../vdom/create-element'
import { installRenderHelpers } from './render-helpers/index'
import { resolveSlots } from './render-helpers/resolve-slots'
import { normalizeScopedSlots } from '../vdom/helpers/normalize-scoped-slots'
import VNode, { createEmptyVNode } from '../vdom/vnode'
import { isUpdatingChildComponent } from './lifecycle'
@@ -50,6 +51,13 @@ export function initRender (vm: Component) {
}
}
export let currentRenderingInstance: Component | null = null
// for testing only
export function setCurrentRenderingInstance (vm: Component) {
currentRenderingInstance = vm
}
export function renderMixin (Vue: Class<Component>) {
// install runtime convenience helpers
installRenderHelpers(Vue.prototype)
@@ -62,16 +70,12 @@ export function renderMixin (Vue: Class<Component>) {
const vm: Component = this
const { render, _parentVnode } = vm.$options
// reset _rendered flag on slots for duplicate slot check
if (process.env.NODE_ENV !== 'production') {
for (const key in vm.$slots) {
// $flow-disable-line
vm.$slots[key]._rendered = false
}
}
if (_parentVnode) {
vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject
vm.$scopedSlots = normalizeScopedSlots(
_parentVnode.data.scopedSlots,
vm.$slots,
vm.$scopedSlots
)
}
// set parent vnode. this allows render functions to have access
@@ -80,26 +84,32 @@ export function renderMixin (Vue: Class<Component>) {
// render self
let vnode
try {
// There's no need to maintain a stack becaues all render fns are called
// separately from one another. Nested component's render fns are called
// when parent component is patched.
currentRenderingInstance = vm
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {
handleError(e, vm, `render`)
// return error render result,
// or previous vnode to prevent render error causing blank component
/* istanbul ignore else */
if (process.env.NODE_ENV !== 'production') {
if (vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
} catch (e) {
handleError(e, vm, `renderError`)
vnode = vm._vnode
}
} else {
if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
try {
vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
} catch (e) {
handleError(e, vm, `renderError`)
vnode = vm._vnode
}
} else {
vnode = vm._vnode
}
} finally {
currentRenderingInstance = null
}
// if the returned array contains only a single node, allow it
if (Array.isArray(vnode) && vnode.length === 1) {
vnode = vnode[0]
}
// return empty vnode in case the render function errored out
if (!(vnode instanceof VNode)) {

View File

@@ -86,7 +86,7 @@ function initProps (vm: Component, propsOptions: Object) {
)
}
defineReactive(props, key, value, () => {
if (vm.$parent && !isUpdatingChildComponent) {
if (!isRoot && !isUpdatingChildComponent) {
warn(
`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
@@ -216,17 +216,15 @@ export function defineComputed (
if (typeof userDef === 'function') {
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: userDef
: createGetterInvoker(userDef)
sharedPropertyDefinition.set = noop
} else {
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: userDef.get
: noop
sharedPropertyDefinition.set = userDef.set
? userDef.set
: createGetterInvoker(userDef.get)
: noop
sharedPropertyDefinition.set = userDef.set || noop
}
if (process.env.NODE_ENV !== 'production' &&
sharedPropertyDefinition.set === noop) {
@@ -255,13 +253,19 @@ function createComputedGetter (key) {
}
}
function createGetterInvoker(fn) {
return function computedGetter () {
return fn.call(this, this)
}
}
function initMethods (vm: Component, methods: Object) {
const props = vm.$options.props
for (const key in methods) {
if (process.env.NODE_ENV !== 'production') {
if (methods[key] == null) {
if (typeof methods[key] !== 'function') {
warn(
`Method "${key}" has an undefined value in the component definition. ` +
`Method "${key}" has type "${typeof methods[key]}" in the component definition. ` +
`Did you reference the function correctly?`,
vm
)
@@ -279,7 +283,7 @@ function initMethods (vm: Component, methods: Object) {
)
}
}
vm[key] = methods[key] == null ? noop : bind(methods[key], vm)
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
}
}
@@ -321,7 +325,7 @@ export function stateMixin (Vue: Class<Component>) {
const propsDef = {}
propsDef.get = function () { return this._props }
if (process.env.NODE_ENV !== 'production') {
dataDef.set = function (newData: Object) {
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
@@ -351,7 +355,11 @@ export function stateMixin (Vue: Class<Component>) {
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
cb.call(vm, watcher.value)
try {
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
}
}
return function unwatchFn () {
watcher.teardown()

View File

@@ -2,6 +2,7 @@
import type Watcher from './watcher'
import { remove } from '../util/index'
import config from '../config'
let uid = 0
@@ -36,23 +37,30 @@ export default class Dep {
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
// the current target watcher being evaluated.
// this is globally unique because there could be only one
// watcher being evaluated at any time.
// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
Dep.target = null
const targetStack = []
export function pushTarget (_target: ?Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
export function popTarget () {
Dep.target = targetStack.pop()
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}

View File

@@ -37,7 +37,7 @@ export function toggleObserving (value: boolean) {
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
@@ -45,10 +45,11 @@ export class Observer {
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
@@ -56,7 +57,7 @@ export class Observer {
}
/**
* Walk through each property and convert them into
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
@@ -80,17 +81,17 @@ export class Observer {
// helpers
/**
* Augment an target Object or Array by intercepting
* Augment a target Object or Array by intercepting
* the prototype chain using __proto__
*/
function protoAugment (target, src: Object, keys: any) {
function protoAugment (target, src: Object) {
/* eslint-disable no-proto */
target.__proto__ = src
/* eslint-enable no-proto */
}
/**
* Augment an target Object or Array by defining
* Augment a target Object or Array by defining
* hidden properties.
*/
/* istanbul ignore next */
@@ -147,10 +148,10 @@ export function defineReactive (
// cater for pre-defined getter/setters
const getter = property && property.get
if (!getter && arguments.length === 2) {
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
const setter = property && property.set
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
@@ -179,6 +180,8 @@ export function defineReactive (
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {

View File

@@ -7,7 +7,8 @@ import { callHook, activateChildComponent } from '../instance/lifecycle'
import {
warn,
nextTick,
devtools
devtools,
inBrowser
} from '../util/index'
export const MAX_UPDATE_COUNT = 100
@@ -32,10 +33,32 @@ function resetSchedulerState () {
waiting = flushing = false
}
// Async edge case #6566 requires saving the timestamp when event listeners are
// attached. However, calling performance.now() has a perf overhead especially
// if the page has thousands of event listeners. Instead, we take a timestamp
// every time the scheduler flushes and use that for all event listeners
// attached during that flush.
export let currentFlushTimestamp = 0
// Async edge case fix requires storing an event listener's attach timestamp.
let getNow: () => number = Date.now
// Determine what event timestamp the browser is using. Annoyingly, the
// timestamp can either be hi-res (relative to page load) or low-res
// (relative to UNIX epoch), so in order to compare time we have to use the
// same timestamp type when saving the flush timestamp.
if (inBrowser && getNow() > document.createEvent('Event').timeStamp) {
// if the low-res timestamp which is bigger than the event timestamp
// (which is evaluated AFTER) it means the event is using a hi-res timestamp,
// and we need to use the hi-res version for event listeners as well.
getNow = () => performance.now()
}
/**
* Flush both queues and run the watchers.
*/
function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
flushing = true
let watcher, id
@@ -53,6 +76,9 @@ function flushSchedulerQueue () {
// as we run existing watchers
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
watcher.before()
}
id = watcher.id
has[id] = null
watcher.run()
@@ -95,7 +121,7 @@ function callUpdatedHooks (queue) {
while (i--) {
const watcher = queue[i]
const vm = watcher.vm
if (vm._watcher === watcher && vm._isMounted) {
if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'updated')
}
}
@@ -142,6 +168,11 @@ export function queueWatcher (watcher: Watcher) {
// queue the flush
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue)
}
}

View File

@@ -6,7 +6,8 @@ import {
isObject,
parsePath,
_Set as Set,
handleError
handleError,
noop
} from '../util/index'
import { traverse } from './traverse'
@@ -37,6 +38,7 @@ export default class Watcher {
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
before: ?Function;
getter: Function;
value: any;
@@ -58,6 +60,7 @@ export default class Watcher {
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
@@ -78,7 +81,7 @@ export default class Watcher {
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
this.getter = noop
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +

View File

@@ -41,7 +41,7 @@ if (process.env.NODE_ENV !== 'production') {
? vm.options
: vm._isVue
? vm.$options || vm.constructor.options
: vm || {}
: vm
let name = options.name || options._componentTag
const file = options.__file
if (!name && file) {

View File

@@ -14,6 +14,8 @@ export const isEdge = UA && UA.indexOf('edge/') > 0
export const isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android')
export const isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios')
export const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge
export const isPhantomJS = UA && /phantomjs/.test(UA)
export const isFF = UA && UA.match(/firefox\/(\d+)/)
// Firefox has a "watch" function on Object.prototype...
export const nativeWatch = ({}).watch
@@ -41,7 +43,7 @@ export const isServerRendering = () => {
if (!inBrowser && !inWeex && typeof global !== 'undefined') {
// detect presence of vue-server-renderer and avoid
// Webpack shimming the process
_isServer = global['process'].env.VUE_ENV === 'server'
_isServer = global['process'] && global['process'].env.VUE_ENV === 'server'
} else {
_isServer = false
}

View File

@@ -3,25 +3,55 @@
import config from '../config'
import { warn } from './debug'
import { inBrowser, inWeex } from './env'
import { isPromise } from 'shared/util'
import { pushTarget, popTarget } from '../observer/dep'
export function handleError (err: Error, vm: any, info: string) {
if (vm) {
let cur = vm
while ((cur = cur.$parent)) {
const hooks = cur.$options.errorCaptured
if (hooks) {
for (let i = 0; i < hooks.length; i++) {
try {
const capture = hooks[i].call(cur, err, vm, info) === false
if (capture) return
} catch (e) {
globalHandleError(e, cur, 'errorCaptured hook')
// Deactivate deps tracking while processing error handler to avoid possible infinite rendering.
// See: https://github.com/vuejs/vuex/issues/1505
pushTarget()
try {
if (vm) {
let cur = vm
while ((cur = cur.$parent)) {
const hooks = cur.$options.errorCaptured
if (hooks) {
for (let i = 0; i < hooks.length; i++) {
try {
const capture = hooks[i].call(cur, err, vm, info) === false
if (capture) return
} catch (e) {
globalHandleError(e, cur, 'errorCaptured hook')
}
}
}
}
}
globalHandleError(err, vm, info)
} finally {
popTarget()
}
globalHandleError(err, vm, info)
}
export function invokeWithErrorHandling (
handler: Function,
context: any,
args: null | any[],
vm: any,
info: string
) {
let res
try {
res = args ? handler.apply(context, args) : handler.call(context)
if (res && !res._isVue && isPromise(res)) {
// issue #9511
// reassign to res to avoid catch triggering multiple times when nested calls
res = res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
}
} catch (e) {
handleError(e, vm, info)
}
return res
}
function globalHandleError (err, vm, info) {
@@ -29,7 +59,11 @@ function globalHandleError (err, vm, info) {
try {
return config.errorHandler.call(null, err, vm, info)
} catch (e) {
logError(e, null, 'config.errorHandler')
// if the user intentionally throws the original error in the handler,
// do not log it twice
if (e !== err) {
logError(e, null, 'config.errorHandler')
}
}
}
logError(err, vm, info)

View File

@@ -1,5 +1,12 @@
/* @flow */
/**
* unicode letters used for parsing html tags, component names and property paths.
* using https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname
* skipping \u10000-\uEFFFF due to it freezing up PhantomJS
*/
export const unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/
/**
* Check if a string starts with $ or _
*/
@@ -23,7 +30,7 @@ export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
/**
* Parse simple path.
*/
const bailRE = /[^\w.$]/
const bailRE = new RegExp(`[^${unicodeRegExp.source}.$_\\d]`)
export function parsePath (path: string): any {
if (bailRE.test(path)) {
return

View File

@@ -1,9 +1,11 @@
/* @flow */
/* globals MessageChannel */
/* globals MutationObserver */
import { noop } from 'shared/util'
import { handleError } from './error'
import { isIOS, isNative } from './env'
import { isIE, isIOS, isNative } from './env'
export let isUsingMicroTask = false
const callbacks = []
let pending = false
@@ -17,74 +19,69 @@ function flushCallbacks () {
}
}
// Here we have async deferring wrappers using both microtasks and (macro) tasks.
// In < 2.4 we used microtasks everywhere, but there are some scenarios where
// microtasks have too high a priority and fire in between supposedly
// sequential events (e.g. #4521, #6690) or even between bubbling of the same
// event (#6566). However, using (macro) tasks everywhere also has subtle problems
// when state is changed right before repaint (e.g. #6813, out-in transitions).
// Here we use microtask by default, but expose a way to force (macro) task when
// needed (e.g. in event handlers attached by v-on).
let microTimerFunc
let macroTimerFunc
let useMacroTask = false
// Here we have async deferring wrappers using microtasks.
// In 2.5 we used (macro) tasks (in combination with microtasks).
// However, it has subtle problems when state is changed right before repaint
// (e.g. #6813, out-in transitions).
// Also, using (macro) tasks in event handler would cause some weird behaviors
// that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109).
// So we now use microtasks everywhere, again.
// A major drawback of this tradeoff is that there are some scenarios
// where microtasks have too high a priority and fire in between supposedly
// sequential events (e.g. #4521, #6690, which have workarounds)
// or even between bubbling of the same event (#6566).
let timerFunc
// Determine (macro) task defer implementation.
// Technically setImmediate should be the ideal choice, but it's only available
// in IE. The only polyfill that consistently queues the callback after all DOM
// events triggered in the same loop is by using MessageChannel.
/* istanbul ignore if */
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
macroTimerFunc = () => {
setImmediate(flushCallbacks)
}
} else if (typeof MessageChannel !== 'undefined' && (
isNative(MessageChannel) ||
// PhantomJS
MessageChannel.toString() === '[object MessageChannelConstructor]'
)) {
const channel = new MessageChannel()
const port = channel.port2
channel.port1.onmessage = flushCallbacks
macroTimerFunc = () => {
port.postMessage(1)
}
} else {
/* istanbul ignore next */
macroTimerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
// Determine microtask defer implementation.
// The nextTick behavior leverages the microtask queue, which can be accessed
// via either native Promise.then or MutationObserver.
// MutationObserver has wider support, however it is seriously bugged in
// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It
// completely stops working after triggering a few times... so, if native
// Promise is available, we will use it:
/* istanbul ignore next, $flow-disable-line */
if (typeof Promise !== 'undefined' && isNative(Promise)) {
const p = Promise.resolve()
microTimerFunc = () => {
timerFunc = () => {
p.then(flushCallbacks)
// in problematic UIWebViews, Promise.then doesn't completely break, but
// In problematic UIWebViews, Promise.then doesn't completely break, but
// it can get stuck in a weird state where callbacks are pushed into the
// microtask queue but the queue isn't being flushed, until the browser
// needs to do some other work, e.g. handle a timer. Therefore we can
// "force" the microtask queue to be flushed by adding an empty timer.
if (isIOS) setTimeout(noop)
}
} else {
// fallback to macro
microTimerFunc = macroTimerFunc
}
/**
* Wrap a function so that if any code inside triggers state change,
* the changes are queued using a (macro) task instead of a microtask.
*/
export function withMacroTask (fn: Function): Function {
return fn._withTask || (fn._withTask = function () {
useMacroTask = true
const res = fn.apply(null, arguments)
useMacroTask = false
return res
isUsingMicroTask = true
} 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, iOS7, Android 4.4
// (#6466 MutationObserver is unreliable in IE11)
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
// Fallback to setImmediate.
// Techinically it leverages the (macro) task queue,
// but it is still a better choice than setTimeout.
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
// Fallback to setTimeout.
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
export function nextTick (cb?: Function, ctx?: Object) {
@@ -102,11 +99,7 @@ export function nextTick (cb?: Function, ctx?: Object) {
})
if (!pending) {
pending = true
if (useMacroTask) {
macroTimerFunc()
} else {
microTimerFunc()
}
timerFunc()
}
// $flow-disable-line
if (!cb && typeof Promise !== 'undefined') {

View File

@@ -2,8 +2,9 @@
import config from '../config'
import { warn } from './debug'
import { nativeWatch } from './env'
import { set } from '../observer/index'
import { unicodeRegExp } from './lang'
import { nativeWatch, hasSymbol } from './env'
import {
ASSET_TYPES,
@@ -48,14 +49,24 @@ if (process.env.NODE_ENV !== 'production') {
function mergeData (to: Object, from: ?Object): Object {
if (!from) return to
let key, toVal, fromVal
const keys = Object.keys(from)
const keys = hasSymbol
? Reflect.ownKeys(from)
: Object.keys(from)
for (let i = 0; i < keys.length; i++) {
key = keys[i]
// in case the object is already observed...
if (key === '__ob__') continue
toVal = to[key]
fromVal = from[key]
if (!hasOwn(to, key)) {
set(to, key, fromVal)
} else if (isPlainObject(toVal) && isPlainObject(fromVal)) {
} else if (
toVal !== fromVal &&
isPlainObject(toVal) &&
isPlainObject(fromVal)
) {
mergeData(toVal, fromVal)
}
}
@@ -136,13 +147,26 @@ function mergeHook (
parentVal: ?Array<Function>,
childVal: ?Function | ?Array<Function>
): ?Array<Function> {
return childVal
const res = childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal
return res
? dedupeHooks(res)
: res
}
function dedupeHooks (hooks) {
const res = []
for (let i = 0; i < hooks.length; i++) {
if (res.indexOf(hooks[i]) === -1) {
res.push(hooks[i])
}
}
return res
}
LIFECYCLE_HOOKS.forEach(hook => {
@@ -253,11 +277,10 @@ function checkComponents (options: Object) {
}
export function validateComponentName (name: string) {
if (!/^[a-zA-Z][\w-]*$/.test(name)) {
if (!new RegExp(`^[a-zA-Z][\\-\\.0-9_${unicodeRegExp.source}]*$`).test(name)) {
warn(
'Invalid component name: "' + name + '". Component names ' +
'can only contain alphanumeric characters and the hyphen, ' +
'and must start with a letter.'
'should conform to valid custom element name in html5 specification.'
)
}
if (isBuiltInTag(name) || config.isReservedTag(name)) {
@@ -378,15 +401,22 @@ export function mergeOptions (
normalizeProps(child, vm)
normalizeInject(child, vm)
normalizeDirectives(child)
const extendsFrom = child.extends
if (extendsFrom) {
parent = mergeOptions(parent, extendsFrom, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
// Apply extends and mixins on the child options,
// but only if it is a raw options object that isn't
// the result of another mergeOptions call.
// Only merged options has the _base property.
if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
const options = {}
let key
for (key in parent) {

View File

@@ -18,7 +18,7 @@ if (process.env.NODE_ENV !== 'production') {
perf.measure(name, startTag, endTag)
perf.clearMarks(startTag)
perf.clearMarks(endTag)
perf.clearMeasures(name)
// perf.clearMeasures(name)
}
}
}

View File

@@ -127,11 +127,10 @@ function assertProp (
valid = assertedType.valid
}
}
if (!valid) {
warn(
`Invalid prop: type check failed for prop "${name}".` +
` Expected ${expectedTypes.map(capitalize).join(', ')}` +
`, got ${toRawType(value)}.`,
getInvalidTypeMessage(name, value, expectedTypes),
vm
)
return
@@ -200,3 +199,43 @@ function getTypeIndex (type, expectedTypes): number {
}
return -1
}
function getInvalidTypeMessage (name, value, expectedTypes) {
let message = `Invalid prop: type check failed for prop "${name}".` +
` Expected ${expectedTypes.map(capitalize).join(', ')}`
const expectedType = expectedTypes[0]
const receivedType = toRawType(value)
const expectedValue = styleValue(value, expectedType)
const receivedValue = styleValue(value, receivedType)
// check if we need to specify expected value
if (expectedTypes.length === 1 &&
isExplicable(expectedType) &&
!isBoolean(expectedType, receivedType)) {
message += ` with value ${expectedValue}`
}
message += `, got ${receivedType} `
// check if we need to specify received value
if (isExplicable(receivedType)) {
message += `with value ${receivedValue}.`
}
return message
}
function styleValue (value, type) {
if (type === 'String') {
return `"${value}"`
} else if (type === 'Number') {
return `${Number(value)}`
} else {
return `${value}`
}
}
function isExplicable (value) {
const explicitTypes = ['string', 'number', 'boolean']
return explicitTypes.some(elem => value.toLowerCase() === elem)
}
function isBoolean (...args) {
return args.some(elem => elem.toLowerCase() === 'boolean')
}

View File

@@ -34,12 +34,7 @@ import {
// inline hooks to be invoked on component VNodes during patch
const componentVNodeHooks = {
init (
vnode: VNodeWithData,
hydrating: boolean,
parentElm: ?Node,
refElm: ?Node
): ?boolean {
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
@@ -51,9 +46,7 @@ const componentVNodeHooks = {
} else {
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance,
parentElm,
refElm
activeInstance
)
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
@@ -136,7 +129,7 @@ export function createComponent (
let asyncFactory
if (isUndef(Ctor.cid)) {
asyncFactory = Ctor
Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
if (Ctor === undefined) {
// return a placeholder node for async component, which is rendered
// as a comment node but preserves all the raw information for the node.
@@ -215,15 +208,11 @@ export function createComponent (
export function createComponentInstanceForVnode (
vnode: any, // we know it's MountedComponentVNode but flow doesn't
parent: any, // activeInstance in lifecycle state
parentElm?: ?Node,
refElm?: ?Node
): Component {
const options: InternalComponentOptions = {
_isComponent: true,
parent,
_parentVnode: vnode,
_parentElm: parentElm || null,
_refElm: refElm || null
parent
}
// check inline-template render functions
const inlineTemplate = vnode.data.inlineTemplate
@@ -238,20 +227,42 @@ function installComponentHooks (data: VNodeData) {
const hooks = data.hook || (data.hook = {})
for (let i = 0; i < hooksToMerge.length; i++) {
const key = hooksToMerge[i]
hooks[key] = componentVNodeHooks[key]
const existing = hooks[key]
const toMerge = componentVNodeHooks[key]
if (existing !== toMerge && !(existing && existing._merged)) {
hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
}
}
}
function mergeHook (f1: any, f2: any): Function {
const merged = (a, b) => {
// flow complains about extra args which is why we use any
f1(a, b)
f2(a, b)
}
merged._merged = true
return merged
}
// transform component v-model info (value and callback) into
// prop and event handler respectively.
function transformModel (options, data: any) {
const prop = (options.model && options.model.prop) || 'value'
const event = (options.model && options.model.event) || 'input'
;(data.props || (data.props = {}))[prop] = data.model.value
;(data.attrs || (data.attrs = {}))[prop] = data.model.value
const on = data.on || (data.on = {})
if (isDef(on[event])) {
on[event] = [data.model.callback].concat(on[event])
const existing = on[event]
const callback = data.model.callback
if (isDef(existing)) {
if (
Array.isArray(existing)
? existing.indexOf(callback) === -1
: existing !== callback
) {
on[event] = [callback].concat(existing)
}
} else {
on[event] = data.model.callback
on[event] = callback
}
}

View File

@@ -102,7 +102,7 @@ export function _createElement (
config.parsePlatformTagName(tag), data, children,
undefined, undefined, context
)
} else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
// component
vnode = createComponent(Ctor, data, context, children, tag)
} else {

View File

@@ -5,6 +5,7 @@ import { createElement } from './create-element'
import { resolveInject } from '../instance/inject'
import { normalizeChildren } from '../vdom/helpers/normalize-children'
import { resolveSlots } from '../instance/render-helpers/resolve-slots'
import { normalizeScopedSlots } from '../vdom/helpers/normalize-scoped-slots'
import { installRenderHelpers } from '../instance/render-helpers/index'
import {
@@ -48,7 +49,22 @@ export function FunctionalRenderContext (
this.parent = parent
this.listeners = data.on || emptyObject
this.injections = resolveInject(options.inject, parent)
this.slots = () => resolveSlots(children, parent)
this.slots = () => {
if (!this.$slots) {
normalizeScopedSlots(
data.scopedSlots,
this.$slots = resolveSlots(children, parent)
)
}
return this.$slots
}
Object.defineProperty(this, 'scopedSlots', ({
enumerable: true,
get () {
return normalizeScopedSlots(data.scopedSlots, this.slots())
}
}: any))
// support for compiled functional template
if (isCompiled) {
@@ -56,7 +72,7 @@ export function FunctionalRenderContext (
this.$options = options
// pre-resolve slots for renderSlot()
this.$slots = this.slots()
this.$scopedSlots = data.scopedSlots || emptyObject
this.$scopedSlots = normalizeScopedSlots(data.scopedSlots, this.$slots)
}
if (options._scopeId) {
@@ -105,24 +121,27 @@ export function createFunctionalComponent (
const vnode = options.render.call(null, renderContext._c, renderContext)
if (vnode instanceof VNode) {
return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options)
return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options, renderContext)
} else if (Array.isArray(vnode)) {
const vnodes = normalizeChildren(vnode) || []
const res = new Array(vnodes.length)
for (let i = 0; i < vnodes.length; i++) {
res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options)
res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options, renderContext)
}
return res
}
}
function cloneAndMarkFunctionalResult (vnode, data, contextVm, options) {
function cloneAndMarkFunctionalResult (vnode, data, contextVm, options, renderContext) {
// #7817 clone node before setting fnContext, otherwise if the node is reused
// (e.g. it was from a cached normal slot) the fnContext causes named slots
// that should not be matched to match.
const clone = cloneVNode(vnode)
clone.fnContext = contextVm
clone.fnOptions = options
if (process.env.NODE_ENV !== 'production') {
(clone.devtoolsMeta = clone.devtoolsMeta || {}).renderContext = renderContext
}
if (data.slot) {
(clone.data || (clone.data = {})).slot = data.slot
}

View File

@@ -7,10 +7,13 @@ import {
isUndef,
isTrue,
isObject,
hasSymbol
hasSymbol,
isPromise,
remove
} from 'core/util/index'
import { createEmptyVNode } from 'core/vdom/vnode'
import { currentRenderingInstance } from 'core/instance/render'
function ensureCtor (comp: any, base) {
if (
@@ -39,8 +42,7 @@ export function createAsyncPlaceholder (
export function resolveAsyncComponent (
factory: Function,
baseCtor: Class<Component>,
context: Component
baseCtor: Class<Component>
): Class<Component> | void {
if (isTrue(factory.error) && isDef(factory.errorComp)) {
return factory.errorComp
@@ -50,20 +52,29 @@ export function resolveAsyncComponent (
return factory.resolved
}
const owner = currentRenderingInstance
if (isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
// already pending
factory.owners.push(owner)
}
if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
return factory.loadingComp
}
if (isDef(factory.contexts)) {
// already pending
factory.contexts.push(context)
} else {
const contexts = factory.contexts = [context]
if (!isDef(factory.owners)) {
const owners = factory.owners = [owner]
let sync = true
const forceRender = () => {
for (let i = 0, l = contexts.length; i < l; i++) {
contexts[i].$forceUpdate()
;(owner: any).$on('hook:destroyed', () => remove(owners, owner))
const forceRender = (renderCompleted: boolean) => {
for (let i = 0, l = owners.length; i < l; i++) {
(owners[i]: any).$forceUpdate()
}
if (renderCompleted) {
owners.length = 0
}
}
@@ -73,7 +84,9 @@ export function resolveAsyncComponent (
// invoke callbacks only if this is not a synchronous resolve
// (async resolves are shimmed as synchronous during SSR)
if (!sync) {
forceRender()
forceRender(true)
} else {
owners.length = 0
}
})
@@ -84,19 +97,19 @@ export function resolveAsyncComponent (
)
if (isDef(factory.errorComp)) {
factory.error = true
forceRender()
forceRender(true)
}
})
const res = factory(resolve, reject)
if (isObject(res)) {
if (typeof res.then === 'function') {
if (isPromise(res)) {
// () => Promise
if (isUndef(factory.resolved)) {
res.then(resolve, reject)
}
} else if (isDef(res.component) && typeof res.component.then === 'function') {
} else if (isPromise(res.component)) {
res.component.then(resolve, reject)
if (isDef(res.error)) {
@@ -111,7 +124,7 @@ export function resolveAsyncComponent (
setTimeout(() => {
if (isUndef(factory.resolved) && isUndef(factory.error)) {
factory.loading = true
forceRender()
forceRender(false)
}
}, res.delay || 200)
}

View File

@@ -1,7 +1,15 @@
/* @flow */
import { warn } from 'core/util/index'
import { cached, isUndef, isPlainObject } from 'shared/util'
import {
warn,
invokeWithErrorHandling
} from 'core/util/index'
import {
cached,
isUndef,
isTrue,
isPlainObject
} from 'shared/util'
const normalizeEvent = cached((name: string): {
name: string,
@@ -25,17 +33,17 @@ const normalizeEvent = cached((name: string): {
}
})
export function createFnInvoker (fns: Function | Array<Function>): Function {
export function createFnInvoker (fns: Function | Array<Function>, vm: ?Component): Function {
function invoker () {
const fns = invoker.fns
if (Array.isArray(fns)) {
const cloned = fns.slice()
for (let i = 0; i < cloned.length; i++) {
cloned[i].apply(null, arguments)
invokeWithErrorHandling(cloned[i], null, arguments, vm, `v-on handler`)
}
} else {
// return handler return value for single handlers
return fns.apply(null, arguments)
return invokeWithErrorHandling(fns, null, arguments, vm, `v-on handler`)
}
}
invoker.fns = fns
@@ -47,6 +55,7 @@ export function updateListeners (
oldOn: Object,
add: Function,
remove: Function,
createOnceHandler: Function,
vm: Component
) {
let name, def, cur, old, event
@@ -66,9 +75,12 @@ export function updateListeners (
)
} else if (isUndef(old)) {
if (isUndef(cur.fns)) {
cur = on[name] = createFnInvoker(cur)
cur = on[name] = createFnInvoker(cur, vm)
}
add(event.name, cur, event.once, event.capture, event.passive, event.params)
if (isTrue(event.once)) {
cur = on[name] = createOnceHandler(event.name, cur, event.capture)
}
add(event.name, cur, event.capture, event.passive, event.params)
} else if (cur !== old) {
old.fns = cur
on[name] = old

View File

@@ -40,6 +40,7 @@ function _update (oldVnode, vnode) {
} else {
// existing directive, update
dir.oldValue = oldDir.value
dir.oldArg = oldDir.arg
callHook(dir, 'update', vnode, oldVnode)
if (dir.def && dir.def.componentUpdated) {
dirsWithPostpatch.push(dir)

View File

@@ -212,7 +212,7 @@ export function createPatchFunction (backend) {
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */, parentElm, refElm)
i(vnode, false /* hydrating */)
}
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
@@ -220,6 +220,7 @@ export function createPatchFunction (backend) {
// in that case we can just return the element and be done.
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
@@ -271,7 +272,7 @@ export function createPatchFunction (backend) {
function insert (parent, elm, ref) {
if (isDef(parent)) {
if (isDef(ref)) {
if (ref.parentNode === parent) {
if (nodeOps.parentNode(ref) === parent) {
nodeOps.insertBefore(parent, elm, ref)
}
} else {
@@ -426,20 +427,20 @@ export function createPatchFunction (backend) {
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
@@ -453,7 +454,7 @@ export function createPatchFunction (backend) {
} else {
vnodeToMove = oldCh[idxInOld]
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
@@ -497,11 +498,23 @@ export function createPatchFunction (backend) {
}
}
function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
function patchVnode (
oldVnode,
vnode,
insertedVnodeQueue,
ownerArray,
index,
removeOnly
) {
if (oldVnode === vnode) {
return
}
if (isDef(vnode.elm) && isDef(ownerArray)) {
// clone reused vnode
vnode = ownerArray[index] = cloneVNode(vnode)
}
const elm = vnode.elm = oldVnode.elm
if (isTrue(oldVnode.isAsyncPlaceholder)) {
@@ -542,6 +555,9 @@ export function createPatchFunction (backend) {
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
} else if (isDef(ch)) {
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(ch)
}
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) {
@@ -681,7 +697,7 @@ export function createPatchFunction (backend) {
}
}
return function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
return function patch (oldVnode, vnode, hydrating, removeOnly) {
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
@@ -693,12 +709,12 @@ export function createPatchFunction (backend) {
if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
isInitialPatch = true
createElm(vnode, insertedVnodeQueue, parentElm, refElm)
createElm(vnode, insertedVnodeQueue)
} else {
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly)
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
} else {
if (isRealElement) {
// mounting to a real element

View File

@@ -26,6 +26,7 @@ export default class VNode {
ssrContext: Object | void;
fnContext: Component | void; // real context vm for functional nodes
fnOptions: ?ComponentOptions; // for SSR caching
devtoolsMeta: ?Object; // used to store functional render context for devtools
fnScopeId: ?string; // functional scope id support
constructor (
@@ -89,7 +90,10 @@ export function cloneVNode (vnode: VNode): VNode {
const cloned = new VNode(
vnode.tag,
vnode.data,
vnode.children,
// #7975
// clone children array to avoid mutating original in case of cloning
// a child.
vnode.children && vnode.children.slice(),
vnode.text,
vnode.elm,
vnode.context,
@@ -103,6 +107,7 @@ export function cloneVNode (vnode: VNode): VNode {
cloned.fnContext = vnode.fnContext
cloned.fnOptions = vnode.fnOptions
cloned.fnScopeId = vnode.fnScopeId
cloned.asyncMeta = vnode.asyncMeta
cloned.isCloned = true
return cloned
}

View File

@@ -4,6 +4,6 @@ import { addProp } from 'compiler/helpers'
export default function html (el: ASTElement, dir: ASTDirective) {
if (dir.value) {
addProp(el, 'innerHTML', `_s(${dir.value})`)
addProp(el, 'innerHTML', `_s(${dir.value})`, dir)
}
}

View File

@@ -28,7 +28,8 @@ export default function model (
if (tag === 'input' && type === 'file') {
warn(
`<${el.tag} v-model="${value}" type="file">:\n` +
`File inputs are read only. Use a v-on:change listener instead.`
`File inputs are read only. Use a v-on:change listener instead.`,
el.rawAttrsMap['v-model']
)
}
}
@@ -54,7 +55,8 @@ export default function model (
`<${el.tag} v-model="${value}">: ` +
`v-model is not supported on this element type. ` +
'If you are working with contenteditable, it\'s recommended to ' +
'wrap a library dedicated for that purpose inside a custom component.'
'wrap a library dedicated for that purpose inside a custom component.',
el.rawAttrsMap['v-model']
)
}
@@ -138,7 +140,8 @@ function genDefaultModel (
const binding = el.attrsMap['v-bind:value'] ? 'v-bind:value' : ':value'
warn(
`${binding}="${value}" conflicts with v-model on the same element ` +
'because the latter already expands to a value binding internally'
'because the latter already expands to a value binding internally',
el.rawAttrsMap[binding]
)
}
}

View File

@@ -4,6 +4,6 @@ import { addProp } from 'compiler/helpers'
export default function text (el: ASTElement, dir: ASTDirective) {
if (dir.value) {
addProp(el, 'textContent', `_s(${dir.value})`)
addProp(el, 'textContent', `_s(${dir.value})`, dir)
}
}

View File

@@ -17,7 +17,8 @@ function transformNode (el: ASTElement, options: CompilerOptions) {
`class="${staticClass}": ` +
'Interpolation inside attributes has been removed. ' +
'Use v-bind or the colon shorthand instead. For example, ' +
'instead of <div class="{{ val }}">, use <div :class="val">.'
'instead of <div class="{{ val }}">, use <div :class="val">.',
el.rawAttrsMap['class']
)
}
}

View File

@@ -20,7 +20,8 @@ function transformNode (el: ASTElement, options: CompilerOptions) {
`style="${staticStyle}": ` +
'Interpolation inside attributes has been removed. ' +
'Use v-bind or the colon shorthand instead. For example, ' +
'instead of <div style="{{ val }}">, use <div :style="val">.'
'instead of <div style="{{ val }}">, use <div :style="val">.',
el.rawAttrsMap['style']
)
}
}

View File

@@ -3,3 +3,4 @@
export { parseComponent } from 'sfc/parser'
export { compile, compileToFunctions } from './compiler/index'
export { ssrCompile, ssrCompileToFunctions } from './server/compiler'
export { generateCodeFrame } from 'compiler/codeframe'

View File

@@ -63,6 +63,7 @@ Vue.prototype.$mount = function (
}
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV !== 'production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
delimiters: options.delimiters,

View File

@@ -1,5 +1,7 @@
/* @flow */
const whitespaceRE = /\s+/
/**
* Add class with compatibility for SVG since classList is not supported on
* SVG elements in IE
@@ -13,7 +15,7 @@ export function addClass (el: HTMLElement, cls: ?string) {
/* istanbul ignore else */
if (el.classList) {
if (cls.indexOf(' ') > -1) {
cls.split(/\s+/).forEach(c => el.classList.add(c))
cls.split(whitespaceRE).forEach(c => el.classList.add(c))
} else {
el.classList.add(cls)
}
@@ -38,7 +40,7 @@ export function removeClass (el: HTMLElement, cls: ?string) {
/* istanbul ignore else */
if (el.classList) {
if (cls.indexOf(' ') > -1) {
cls.split(/\s+/).forEach(c => el.classList.remove(c))
cls.split(whitespaceRE).forEach(c => el.classList.remove(c))
} else {
el.classList.remove(cls)
}

View File

@@ -14,6 +14,7 @@
import { warn, extend } from 'core/util/index'
import { addClass, removeClass } from '../class-util'
import { transitionProps, extractTransitionData } from './transition'
import { setActiveInstance } from 'core/instance/lifecycle'
import {
hasTransition,
@@ -33,6 +34,23 @@ delete props.mode
export default {
props,
beforeMount () {
const update = this._update
this._update = (vnode, hydrating) => {
const restoreActiveInstance = setActiveInstance(this)
// force removing pass
this.__patch__(
this._vnode,
this.kept,
false, // hydrating
true // removeOnly (!important, avoids unnecessary moves)
)
this._vnode = this.kept
restoreActiveInstance()
update.call(this, vnode, hydrating)
}
},
render (h: Function) {
const tag: string = this.tag || this.$vnode.data.tag || 'span'
const map: Object = Object.create(null)
@@ -76,17 +94,6 @@ export default {
return h(tag, null, children)
},
beforeUpdate () {
// force removing pass
this.__patch__(
this._vnode,
this.kept,
false, // hydrating
true // removeOnly (!important, avoids unnecessary moves)
)
this._vnode = this.kept
},
updated () {
const children: Array<VNode> = this.prevChildren
const moveClass: string = this.moveClass || ((this.name || 'v') + '-move')
@@ -107,11 +114,14 @@ export default {
children.forEach((c: VNode) => {
if (c.data.moved) {
var el: any = c.elm
var s: any = el.style
const el: any = c.elm
const s: any = el.style
addTransitionClass(el, moveClass)
s.transform = s.WebkitTransform = s.transitionDuration = ''
el.addEventListener(transitionEndEvent, el._moveCb = function cb (e) {
if (e && e.target !== el) {
return
}
if (!e || /transform$/.test(e.propertyName)) {
el.removeEventListener(transitionEndEvent, cb)
el._moveCb = null

View File

@@ -76,6 +76,10 @@ function isSameChild (child: VNode, oldChild: VNode): boolean {
return oldChild.key === child.key && oldChild.tag === child.tag
}
const isNotTextNode = (c: VNode) => c.tag || isAsyncPlaceholder(c)
const isVShowDirective = d => d.name === 'show'
export default {
name: 'transition',
props: transitionProps,
@@ -88,7 +92,7 @@ export default {
}
// filter out text nodes (possible whitespaces)
children = children.filter((c: VNode) => c.tag || isAsyncPlaceholder(c))
children = children.filter(isNotTextNode)
/* istanbul ignore if */
if (!children.length) {
return
@@ -153,7 +157,7 @@ export default {
// mark v-show
// so that the transition module can hand over the control to the directive
if (child.data.directives && child.data.directives.some(d => d.name === 'show')) {
if (child.data.directives && child.data.directives.some(isVShowDirective)) {
child.data.show = true
}

View File

@@ -4,7 +4,7 @@ import Vue from 'core/index'
import config from 'core/config'
import { extend, noop } from 'shared/util'
import { mountComponent } from 'core/instance/lifecycle'
import { devtools, inBrowser, isChrome } from 'core/util/index'
import { devtools, inBrowser } from 'core/util/index'
import {
query,
@@ -51,8 +51,7 @@ if (inBrowser) {
devtools.emit('init', Vue)
} else if (
process.env.NODE_ENV !== 'production' &&
process.env.NODE_ENV !== 'test' &&
isChrome
process.env.NODE_ENV !== 'test'
) {
console[console.info ? 'info' : 'log'](
'Download the Vue Devtools extension for a better development experience:\n' +

View File

@@ -14,7 +14,8 @@ import {
getXlinkProp,
isBooleanAttr,
isEnumeratedAttr,
isFalsyAttrValue
isFalsyAttrValue,
convertEnumeratedValue
} from 'web/util/index'
function updateAttrs (oldVnode: VNodeWithData, vnode: VNodeWithData) {
@@ -75,7 +76,7 @@ function setAttr (el: Element, key: string, value: any) {
el.setAttribute(key, value)
}
} else if (isEnumeratedAttr(key)) {
el.setAttribute(key, isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true')
el.setAttribute(key, convertEnumeratedValue(key, value))
} else if (isXlink(key)) {
if (isFalsyAttrValue(value)) {
el.removeAttributeNS(xlinkNS, getXlinkProp(key))
@@ -98,7 +99,7 @@ function baseSetAttr (el, key, value) {
if (
isIE && !isIE9 &&
el.tagName === 'TEXTAREA' &&
key === 'placeholder' && !el.__ieph
key === 'placeholder' && value !== '' && !el.__ieph
) {
const blocker = e => {
e.stopImmediatePropagation()

View File

@@ -1,6 +1,9 @@
/* @flow */
import { isDef, isUndef, extend, toNumber } from 'shared/util'
import { isSVG } from 'web/util/index'
let svgContainer
function updateDOMProps (oldVnode: VNodeWithData, vnode: VNodeWithData) {
if (isUndef(oldVnode.data.domProps) && isUndef(vnode.data.domProps)) {
@@ -35,7 +38,7 @@ function updateDOMProps (oldVnode: VNodeWithData, vnode: VNodeWithData) {
}
}
if (key === 'value') {
if (key === 'value' && elm.tagName !== 'PROGRESS') {
// store value as _value as well since
// non-string values will be stringified
elm._value = cur
@@ -44,8 +47,29 @@ function updateDOMProps (oldVnode: VNodeWithData, vnode: VNodeWithData) {
if (shouldUpdateValue(elm, strCur)) {
elm.value = strCur
}
} else {
elm[key] = cur
} else if (key === 'innerHTML' && isSVG(elm.tagName) && isUndef(elm.innerHTML)) {
// IE doesn't support innerHTML for SVG elements
svgContainer = svgContainer || document.createElement('div')
svgContainer.innerHTML = `<svg>${cur}</svg>`
const svg = svgContainer.firstChild
while (elm.firstChild) {
elm.removeChild(elm.firstChild)
}
while (svg.firstChild) {
elm.appendChild(svg.firstChild)
}
} else if (
// skip the update if old and new VDOM state is the same.
// `value` is handled separately because the DOM value may be temporarily
// out of sync with VDOM state due to focus, composition and modifiers.
// This #4521 by skipping the unnecesarry `checked` update.
cur !== oldProps[key]
) {
// some property updates can throw
// e.g. `value` on <progress> w/ non-finite value
try {
elm[key] = cur
} catch (e) {}
}
}
}
@@ -75,10 +99,6 @@ function isDirtyWithModifiers (elm: any, newVal: string): boolean {
const value = elm.value
const modifiers = elm._vModifiers // injected by v-model runtime
if (isDef(modifiers)) {
if (modifiers.lazy) {
// inputs with lazy should only be updated when not in focus
return false
}
if (modifiers.number) {
return toNumber(value) !== toNumber(newVal)
}

View File

@@ -2,8 +2,9 @@
import { isDef, isUndef } from 'shared/util'
import { updateListeners } from 'core/vdom/helpers/index'
import { withMacroTask, isIE, supportsPassive } from 'core/util/index'
import { isIE, isFF, supportsPassive, isUsingMicroTask } from 'core/util/index'
import { RANGE_TOKEN, CHECKBOX_RADIO_TOKEN } from 'web/compiler/directives/model'
import { currentFlushTimestamp } from 'core/observer/scheduler'
// normalize v-model event tokens that can only be determined at runtime.
// it's important to place the event as the first in the array because
@@ -28,7 +29,7 @@ function normalizeEvents (on) {
let target: any
function createOnceHandler (handler, event, capture) {
function createOnceHandler (event, handler, capture) {
const _target = target // save current target element in closure
return function onceHandler () {
const res = handler.apply(null, arguments)
@@ -38,17 +39,47 @@ function createOnceHandler (handler, event, capture) {
}
}
// #9446: Firefox <= 53 (in particular, ESR 52) has incorrect Event.timeStamp
// implementation and does not fire microtasks in between event propagation, so
// safe to exclude.
const useMicrotaskFix = isUsingMicroTask && !(isFF && Number(isFF[1]) <= 53)
function add (
event: string,
name: string,
handler: Function,
once: boolean,
capture: boolean,
passive: boolean
) {
handler = withMacroTask(handler)
if (once) handler = createOnceHandler(handler, event, capture)
// async edge case #6566: inner click event triggers patch, event handler
// attached to outer element during patch, and triggered again. This
// happens because browsers fire microtask ticks between event propagation.
// the solution is simple: we save the timestamp when a handler is attached,
// and the handler would only fire if the event passed to it was fired
// AFTER it was attached.
if (useMicrotaskFix) {
const attachedTimestamp = currentFlushTimestamp
const original = handler
handler = original._wrapper = function (e) {
if (
// no bubbling, should always fire.
// this is just a safety net in case event.timeStamp is unreliable in
// certain weird environments...
e.target === e.currentTarget ||
// event is fired after handler attachment
e.timeStamp >= attachedTimestamp ||
// #9462 bail for iOS 9 bug: event.timeStamp is 0 after history.pushState
e.timeStamp === 0 ||
// #9448 bail if event is fired in another document in a multi-page
// electron/nw.js app, since event.timeStamp will be using a different
// starting reference
e.target.ownerDocument !== document
) {
return original.apply(this, arguments)
}
}
}
target.addEventListener(
event,
name,
handler,
supportsPassive
? { capture, passive }
@@ -57,14 +88,14 @@ function add (
}
function remove (
event: string,
name: string,
handler: Function,
capture: boolean,
_target?: HTMLElement
) {
(_target || target).removeEventListener(
event,
handler._withTask || handler,
name,
handler._wrapper || handler,
capture
)
}
@@ -77,7 +108,7 @@ function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {
const oldOn = oldVnode.data.on || {}
target = vnode.elm
normalizeEvents(on)
updateListeners(on, oldOn, add, remove, vnode.context)
updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context)
target = undefined
}

View File

@@ -1,7 +1,7 @@
/* @flow */
import { getStyle, normalizeStyleBinding } from 'web/util/style'
import { cached, camelize, extend, isDef, isUndef } from 'shared/util'
import { cached, camelize, extend, isDef, isUndef, hyphenate } from 'shared/util'
const cssVarRE = /^--/
const importantRE = /\s*!important$/
@@ -10,7 +10,7 @@ const setProp = (el, name, val) => {
if (cssVarRE.test(name)) {
el.style.setProperty(name, val)
} else if (importantRE.test(val)) {
el.style.setProperty(name, val.replace(importantRE, ''), 'important')
el.style.setProperty(hyphenate(name), val.replace(importantRE, ''), 'important')
} else {
const normalizedName = normalize(name)
if (Array.isArray(val)) {

View File

@@ -251,7 +251,7 @@ export function leave (vnode: VNodeWithData, rm: Function) {
return
}
// record leaving element
if (!vnode.data.show) {
if (!vnode.data.show && el.parentNode) {
(el.parentNode._pending || (el.parentNode._pending = {}))[(vnode.key: any)] = vnode
}
beforeLeave && beforeLeave(el)

View File

@@ -122,11 +122,12 @@ export function getTransitionInfo (el: Element, expectedType?: ?string): {
hasTransform: boolean;
} {
const styles: any = window.getComputedStyle(el)
const transitionDelays: Array<string> = styles[transitionProp + 'Delay'].split(', ')
const transitionDurations: Array<string> = styles[transitionProp + 'Duration'].split(', ')
// JSDOM may return undefined for transition properties
const transitionDelays: Array<string> = (styles[transitionProp + 'Delay'] || '').split(', ')
const transitionDurations: Array<string> = (styles[transitionProp + 'Duration'] || '').split(', ')
const transitionTimeout: number = getTimeout(transitionDelays, transitionDurations)
const animationDelays: Array<string> = styles[animationProp + 'Delay'].split(', ')
const animationDurations: Array<string> = styles[animationProp + 'Duration'].split(', ')
const animationDelays: Array<string> = (styles[animationProp + 'Delay'] || '').split(', ')
const animationDurations: Array<string> = (styles[animationProp + 'Duration'] || '').split(', ')
const animationTimeout: number = getTimeout(animationDelays, animationDurations)
let type: ?string
@@ -180,6 +181,10 @@ function getTimeout (delays: Array<string>, durations: Array<string>): number {
}))
}
// Old versions of Chromium (below 61.0.3163.100) formats floating pointer numbers
// in a locale-dependent way, using a comma instead of a dot.
// If comma is not replaced with a dot, the input will be rounded down (i.e. acting
// as a floor function) causing unexpected behaviors
function toMs (s: string): number {
return Number(s.slice(0, -1)) * 1000
return Number(s.slice(0, -1).replace(',', '.')) * 1000
}

View File

@@ -11,7 +11,8 @@ import {
import {
isBooleanAttr,
isEnumeratedAttr,
isFalsyAttrValue
isFalsyAttrValue,
convertEnumeratedValue
} from 'web/util/attrs'
import { isSSRUnsafeAttr } from 'web/server/util'
@@ -54,7 +55,7 @@ export function renderAttr (key: string, value: string): string {
return ` ${key}="${key}"`
}
} else if (isEnumeratedAttr(key)) {
return ` ${key}="${isFalsyAttrValue(value) || value === 'false' ? 'false' : 'true'}"`
return ` ${key}="${escape(convertEnumeratedValue(key, value))}"`
} else if (!isFalsyAttrValue(value)) {
return ` ${key}="${escape(String(value))}"`
}

View File

@@ -1,6 +1,6 @@
/* @flow */
import { escape } from '../util'
import { escape, noUnitNumericStyleProps } from '../util'
import { hyphenate } from 'shared/util'
import { getStyle } from 'web/util/style'
@@ -11,15 +11,28 @@ export function genStyle (style: Object): string {
const hyphenatedKey = hyphenate(key)
if (Array.isArray(value)) {
for (let i = 0, len = value.length; i < len; i++) {
styleText += `${hyphenatedKey}:${value[i]};`
styleText += normalizeValue(hyphenatedKey, value[i])
}
} else {
styleText += `${hyphenatedKey}:${value};`
styleText += normalizeValue(hyphenatedKey, value)
}
}
return styleText
}
function normalizeValue(key: string, value: any): string {
if (
typeof value === 'string' ||
(typeof value === 'number' && noUnitNumericStyleProps[key]) ||
value === 0
) {
return `${key}:${value};`
} else {
// invalid values
return ``
}
}
export default function renderStyle (vnode: VNodeWithData): ?string {
const styleText = genStyle(getStyle(vnode, false))
if (styleText !== '') {

View File

@@ -18,7 +18,7 @@ const isAttr = makeMap(
'target,title,type,usemap,value,width,wrap'
)
const unsafeAttrCharRE = /[>/="'\u0009\u000a\u000c\u0020]/
const unsafeAttrCharRE = /[>/="'\u0009\u000a\u000c\u0020]/ // eslint-disable-line no-control-regex
export const isSSRUnsafeAttr = (name: string): boolean => {
return unsafeAttrCharRE.test(name)
}
@@ -54,3 +54,48 @@ export function escape (s: string) {
function escapeChar (a) {
return ESC[a] || a
}
export const noUnitNumericStyleProps = {
"animation-iteration-count": true,
"border-image-outset": true,
"border-image-slice": true,
"border-image-width": true,
"box-flex": true,
"box-flex-group": true,
"box-ordinal-group": true,
"column-count": true,
"columns": true,
"flex": true,
"flex-grow": true,
"flex-positive": true,
"flex-shrink": true,
"flex-negative": true,
"flex-order": true,
"grid-row": true,
"grid-row-end": true,
"grid-row-span": true,
"grid-row-start": true,
"grid-column": true,
"grid-column-end": true,
"grid-column-span": true,
"grid-column-start": true,
"font-weight": true,
"line-clamp": true,
"line-height": true,
"opacity": true,
"order": true,
"orphans": true,
"tab-size": true,
"widows": true,
"z-index": true,
"zoom": true,
// SVG
"fill-opacity": true,
"flood-opacity": true,
"stop-opacity": true,
"stroke-dasharray": true,
"stroke-dashoffset": true,
"stroke-miterlimit": true,
"stroke-opacity": true,
"stroke-width": true
}

View File

@@ -19,6 +19,17 @@ export const mustUseProp = (tag: string, type: ?string, attr: string): boolean =
export const isEnumeratedAttr = makeMap('contenteditable,draggable,spellcheck')
const isValidContentEditableValue = makeMap('events,caret,typing,plaintext-only')
export const convertEnumeratedValue = (key: string, value: any) => {
return isFalsyAttrValue(value) || value === 'false'
? 'false'
// allow arbitrary string value for contenteditable
: key === 'contenteditable' && isValidContentEditableValue(value)
? value
: 'true'
}
export const isBooleanAttr = makeMap(
'allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,' +
'default,defaultchecked,defaultmuted,defaultselected,defer,disabled,' +

View File

@@ -8,7 +8,7 @@ export const parseStyleText = cached(function (cssText) {
const propertyDelimiter = /:(.+)/
cssText.split(listDelimiter).forEach(function (item) {
if (item) {
var tmp = item.split(propertyDelimiter)
const tmp = item.split(propertyDelimiter)
tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim())
}
})
@@ -69,4 +69,3 @@ export function getStyle (vnode: VNodeWithData, checkChild: boolean): Object {
}
return res
}

View File

@@ -5,8 +5,7 @@ import { genComponentModel, genAssignmentCode } from 'compiler/directives/model'
export default function model (
el: ASTElement,
dir: ASTDirective,
_warn: Function
dir: ASTDirective
): ?boolean {
if (el.tag === 'input' || el.tag === 'textarea') {
genDefaultModel(el, dir.value, dir.modifiers)

View File

@@ -6,7 +6,7 @@ import { makeMap } from 'shared/util'
// must be sent to the native together.
const isUnitaryTag = makeMap('cell,header,cell-slot,recycle-list', true)
function preTransformNode (el: ASTElement, options: CompilerOptions) {
function preTransformNode (el: ASTElement) {
if (isUnitaryTag(el.tag) && !el.attrsList.some(item => item.name === 'append')) {
el.attrsMap.append = 'tree'
el.attrsList.push({ name: 'append', value: 'tree' })

View File

@@ -20,7 +20,8 @@ function transformNode (el: ASTElement, options: CompilerOptions) {
warn(
`class="${staticClass}": ` +
'Interpolation inside attributes has been deprecated. ' +
'Use v-bind or the colon shorthand instead.'
'Use v-bind or the colon shorthand instead.',
el.rawAttrsMap['class']
)
}
if (!dynamic && classResult) {

View File

@@ -13,7 +13,7 @@ function normalizeKeyName (str: string): string {
return normalize(str)
}
function transformNode (el: ASTElement, options: CompilerOptions) {
function transformNode (el: ASTElement) {
if (Array.isArray(el.attrsList)) {
el.attrsList.forEach(attr => {
if (attr.name && attr.name.match(/\-/)) {
@@ -22,6 +22,11 @@ function transformNode (el: ASTElement, options: CompilerOptions) {
el.attrsMap[realName] = el.attrsMap[attr.name]
delete el.attrsMap[attr.name]
}
if (el.rawAttrsMap && el.rawAttrsMap[attr.name]) {
el.rawAttrsMap[realName] = el.rawAttrsMap[attr.name]
// $flow-disable-line
delete el.rawAttrsMap[attr.name]
}
attr.name = realName
}
})

View File

@@ -3,10 +3,7 @@
import { addAttr } from 'compiler/helpers'
// mark component root nodes as
export function postTransformComponentRoot (
el: ASTElement,
options: WeexCompilerOptions
) {
export function postTransformComponentRoot (el: ASTElement) {
if (!el.parent) {
// component root
addAttr(el, '@isComponentRoot', 'true')

View File

@@ -23,10 +23,10 @@ function preTransformNode (el: ASTElement, options: WeexCompilerOptions) {
currentRecycleList = el
}
if (shouldCompile(el, options)) {
preTransformVBind(el, options)
preTransformVBind(el)
preTransformVIf(el, options) // also v-else-if and v-else
preTransformVFor(el, options)
preTransformVOnce(el, options)
preTransformVOnce(el)
}
}
@@ -41,12 +41,12 @@ function postTransformNode (el: ASTElement, options: WeexCompilerOptions) {
// mark child component in parent template
postTransformComponent(el, options)
// mark root in child component template
postTransformComponentRoot(el, options)
postTransformComponentRoot(el)
// <text>: transform children text into value attr
if (el.tag === 'text') {
postTransformText(el, options)
postTransformText(el)
}
postTransformVOn(el, options)
postTransformVOn(el)
}
if (el === currentRecycleList) {
currentRecycleList = null

View File

@@ -13,7 +13,7 @@ function genText (node: ASTNode) {
return JSON.stringify(value)
}
export function postTransformText (el: ASTElement, options: WeexCompilerOptions) {
export function postTransformText (el: ASTElement) {
// weex <text> can only contain text, so the parser
// always generates a single child.
if (el.children.length) {

View File

@@ -9,7 +9,7 @@ function parseAttrName (name: string): string {
return camelize(name.replace(bindRE, ''))
}
export function preTransformVBind (el: ASTElement, options: WeexCompilerOptions) {
export function preTransformVBind (el: ASTElement) {
for (const attr in el.attrsMap) {
if (bindRE.test(attr)) {
const name: string = parseAttrName(attr)

View File

@@ -9,7 +9,7 @@ function parseHandlerParams (handler: ASTElementHandler) {
}
}
export function postTransformVOn (el: ASTElement, options: WeexCompilerOptions) {
export function postTransformVOn (el: ASTElement) {
const events: ASTElementHandlers | void = el.events
if (!events) {
return

View File

@@ -11,7 +11,7 @@ function containVOnce (el: ASTElement): boolean {
return false
}
export function preTransformVOnce (el: ASTElement, options: WeexCompilerOptions) {
export function preTransformVOnce (el: ASTElement) {
if (containVOnce(el)) {
getAndRemoveAttr(el, 'v-once', true)
addRawAttr(el, '[[once]]', true)

View File

@@ -23,7 +23,8 @@ function transformNode (el: ASTElement, options: CompilerOptions) {
warn(
`style="${String(staticStyle)}": ` +
'Interpolation inside attributes has been deprecated. ' +
'Use v-bind or the colon shorthand instead.'
'Use v-bind or the colon shorthand instead.',
el.rawAttrsMap['style']
)
}
if (!dynamic && styleResult) {

View File

@@ -1 +1,2 @@
export { compile } from 'weex/compiler/index'
export { generateCodeFrame } from 'compiler/codeframe'

View File

@@ -1,6 +1,6 @@
/* @flow */
import { extend } from 'shared/util'
import { extend, isObject } from 'shared/util'
function updateClass (oldVnode: VNodeWithData, vnode: VNodeWithData) {
const el = vnode.elm
@@ -15,25 +15,8 @@ function updateClass (oldVnode: VNodeWithData, vnode: VNodeWithData) {
return
}
const oldClassList = []
// unlike web, weex vnode staticClass is an Array
const oldStaticClass: any = oldData.staticClass
if (oldStaticClass) {
oldClassList.push.apply(oldClassList, oldStaticClass)
}
if (oldData.class) {
oldClassList.push.apply(oldClassList, oldData.class)
}
const classList = []
// unlike web, weex vnode staticClass is an Array
const staticClass: any = data.staticClass
if (staticClass) {
classList.push.apply(classList, staticClass)
}
if (data.class) {
classList.push.apply(classList, data.class)
}
const oldClassList = makeClassList(oldData)
const classList = makeClassList(data)
if (typeof el.setClassList === 'function') {
el.setClassList(classList)
@@ -49,6 +32,24 @@ function updateClass (oldVnode: VNodeWithData, vnode: VNodeWithData) {
}
}
function makeClassList (data: VNodeData): Array<string> {
const classList = []
// unlike web, weex vnode staticClass is an Array
const staticClass: any = data.staticClass
const dataClass = data.class
if (staticClass) {
classList.push.apply(classList, staticClass)
}
if (Array.isArray(dataClass)) {
classList.push.apply(classList, dataClass)
} else if (isObject(dataClass)) {
classList.push.apply(classList, Object.keys(dataClass).filter(className => dataClass[className]))
} else if (typeof dataClass === 'string') {
classList.push.apply(classList, dataClass.trim().split(/\s+/))
}
return classList
}
function getStyle (oldClassList: Array<string>, classList: Array<string>, ctx: Component): Object {
// style is a weex-only injected object
// compiled from <style> tags in weex files

View File

@@ -4,10 +4,19 @@ import { updateListeners } from 'core/vdom/helpers/update-listeners'
let target: any
function createOnceHandler (event, handler, capture) {
const _target = target // save current target element in closure
return function onceHandler () {
const res = handler.apply(null, arguments)
if (res !== null) {
remove(event, onceHandler, capture, _target)
}
}
}
function add (
event: string,
handler: Function,
once: boolean,
capture: boolean,
passive?: boolean,
params?: Array<any>
@@ -16,18 +25,6 @@ function add (
console.log('Weex do not support event in bubble phase.')
return
}
if (once) {
const oldHandler = handler
const _target = target // save current target element in closure
handler = function (ev) {
const res = arguments.length === 1
? oldHandler(ev)
: oldHandler.apply(null, arguments)
if (res !== null) {
remove(event, null, null, _target)
}
}
}
target.addEvent(event, handler, params)
}
@@ -47,7 +44,7 @@ function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {
const on = vnode.data.on || {}
const oldOn = oldVnode.data.on || {}
target = vnode.elm
updateListeners(on, oldOn, add, remove, vnode.context)
updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context)
target = undefined
}

View File

@@ -70,7 +70,7 @@ function updateStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) {
function toObject (arr) {
const res = {}
for (var i = 0; i < arr.length; i++) {
for (let i = 0; i < arr.length; i++) {
if (arr[i]) {
extend(res, arr[i])
}

View File

@@ -75,7 +75,7 @@ function enter (_, vnode) {
const stylesheet = vnode.context.$options.style || {}
const startState = stylesheet[startClass]
const transitionProperties = (stylesheet['@TRANSITION'] && stylesheet['@TRANSITION'][activeClass]) || {}
const endState = getEnterTargetState(el, stylesheet, startClass, toClass, activeClass, vnode.context)
const endState = getEnterTargetState(el, stylesheet, startClass, toClass, activeClass)
const needAnimation = Object.keys(endState).length > 0
const cb = el._enterCb = once(() => {
@@ -229,7 +229,7 @@ function leave (vnode, rm) {
}
// determine the target animation style for an entering transition.
function getEnterTargetState (el, stylesheet, startClass, endClass, activeClass, vm) {
function getEnterTargetState (el, stylesheet, startClass, endClass, activeClass) {
const targetState = {}
const startState = stylesheet[startClass]
const endState = stylesheet[endClass]

View File

@@ -4,7 +4,7 @@
// makeMap() due to potential side effects, so these variables end up
// bloating the web builds.
import { makeMap } from 'shared/util'
import { makeMap, noop } from 'shared/util'
export const isReservedTag = makeMap(
'template,script,style,element,content,slot,link,meta,svg,view,' +
@@ -33,20 +33,20 @@ export const isUnaryTag = makeMap(
true
)
export function mustUseProp (tag: string, type: ?string, name: string): boolean {
export function mustUseProp (): boolean {
return false
}
export function getTagNamespace (tag?: string): string | void { }
export function getTagNamespace (): void { }
export function isUnknownElement (tag?: string): boolean {
export function isUnknownElement (): boolean {
return false
}
export function query (el: string | Element, document: Object) {
// document is injected by weex factory wrapper
const placeholder = document.createComment('root')
placeholder.hasAttribute = placeholder.removeAttribute = function () {} // hack for patch
placeholder.hasAttribute = placeholder.removeAttribute = noop // hack for patch
document.documentElement.appendChild(placeholder)
return placeholder
}

View File

@@ -23,12 +23,13 @@ export type RenderOptions = {
directives?: Object;
isUnaryTag?: Function;
cache?: RenderCache;
template?: string;
template?: string | (content: string, context: any) => string;
inject?: boolean;
basedir?: string;
shouldPreload?: Function;
shouldPrefetch?: Function;
clientManifest?: ClientManifest;
serializer?: Function;
runInNewContext?: boolean | 'once';
};
@@ -41,7 +42,8 @@ export function createRenderer ({
cache,
shouldPreload,
shouldPrefetch,
clientManifest
clientManifest,
serializer
}: RenderOptions = {}): Renderer {
const render = createRenderFunction(modules, directives, isUnaryTag, cache)
const templateRenderer = new TemplateRenderer({
@@ -49,7 +51,8 @@ export function createRenderer ({
inject,
shouldPreload,
shouldPrefetch,
clientManifest
clientManifest,
serializer
})
return {
@@ -79,11 +82,26 @@ export function createRenderer ({
}, cb)
try {
render(component, write, context, err => {
if (template) {
result = templateRenderer.renderSync(result, context)
}
if (err) {
cb(err)
return cb(err)
}
if (context && context.rendered) {
context.rendered(context)
}
if (template) {
try {
const res = templateRenderer.render(result, context)
if (typeof res !== 'string') {
// function template returning promise
res
.then(html => cb(null, html))
.catch(cb)
} else {
cb(null, res)
}
} catch (e) {
cb(e)
}
} else {
cb(null, result)
}
@@ -106,13 +124,27 @@ export function createRenderer ({
render(component, write, context, done)
})
if (!template) {
if (context && context.rendered) {
const rendered = context.rendered
renderStream.once('beforeEnd', () => {
rendered(context)
})
}
return renderStream
} else if (typeof template === 'function') {
throw new Error(`function template is only supported in renderToString.`)
} else {
const templateStream = templateRenderer.createStream(context)
renderStream.on('error', err => {
templateStream.emit('error', err)
})
renderStream.pipe(templateStream)
if (context && context.rendered) {
const rendered = context.rendered
renderStream.once('beforeEnd', () => {
rendered(context)
})
}
return templateStream
}
}

View File

@@ -225,7 +225,11 @@ function nodesToSegments (
} else if (c.type === 2) {
segments.push({ type: INTERPOLATION, value: c.expression })
} else if (c.type === 3) {
segments.push({ type: RAW, value: escape(c.text) })
let text = escape(c.text)
if (c.isComment) {
text = '<!--' + text + '-->'
}
segments.push({ type: RAW, value: text })
}
}
return segments

View File

@@ -19,8 +19,6 @@ import {
import type { StringSegment } from './codegen'
import type { CodegenState } from 'compiler/codegen/index'
type Attr = { name: string; value: string };
const plainStringRE = /^"(?:[^"\\]|\\.)*"$|^'(?:[^'\\]|\\.)*'$/
// let the model AST transform translate v-model into appropriate
@@ -42,14 +40,14 @@ export function applyModelTransform (el: ASTElement, state: CodegenState) {
}
export function genAttrSegments (
attrs: Array<Attr>
attrs: Array<ASTAttr>
): Array<StringSegment> {
return attrs.map(({ name, value }) => genAttrSegment(name, value))
}
export function genDOMPropSegments (
props: Array<Attr>,
attrs: ?Array<Attr>
props: Array<ASTAttr>,
attrs: ?Array<ASTAttr>
): Array<StringSegment> {
const segments = []
props.forEach(({ name, value }) => {
@@ -92,7 +90,7 @@ export function genClassSegments (
classBinding: ?string
): Array<StringSegment> {
if (staticClass && !classBinding) {
return [{ type: RAW, value: ` class=${staticClass}` }]
return [{ type: RAW, value: ` class="${JSON.parse(staticClass)}"` }]
} else {
return [{
type: EXPRESSION,

View File

@@ -84,6 +84,7 @@ function optimizeSiblings (el) {
tag: 'template',
attrsList: [],
attrsMap: {},
rawAttrsMap: {},
children: currentOptimizableGroup,
ssrOptimizability: optimizability.FULL
})

Some files were not shown because too many files have changed in this diff Show More