This commit is contained in:
darenhsu
2022-07-17 13:16:16 +08:00
parent 84759556ff
commit befd344ab0
28070 changed files with 4008428 additions and 1 deletions
+250
View File
@@ -0,0 +1,250 @@
## [3.3.4](https://github.com/vuejs/vue-router/compare/v3.3.3...v3.3.4) (2020-06-13)
### Bug Fixes
* **matcher:** navigate to same as current location ([62598b9](https://github.com/vuejs/vue-router/commit/62598b9)), closes [#3216](https://github.com/vuejs/vue-router/issues/3216)
* **types:** missing children ([c1df447](https://github.com/vuejs/vue-router/commit/c1df447)), closes [#3230](https://github.com/vuejs/vue-router/issues/3230)
## [3.3.3](https://github.com/vuejs/vue-router/compare/v3.3.2...v3.3.3) (2020-06-12)
### Bug Fixes
* **history:** initial redirect call onReady's onSuccess ([4d484bf](https://github.com/vuejs/vue-router/commit/4d484bf)), closes [#3225](https://github.com/vuejs/vue-router/issues/3225)
* update ja docs ([#3214](https://github.com/vuejs/vue-router/issues/3214)) ([c05f741](https://github.com/vuejs/vue-router/commit/c05f741))
### Features
* better wording for navigation redirected failure ([1f3aea6](https://github.com/vuejs/vue-router/commit/1f3aea6))
* **types:** RouterConfig for multiple components ([#3217](https://github.com/vuejs/vue-router/issues/3217)) ([#3218](https://github.com/vuejs/vue-router/issues/3218)) ([dab86c5](https://github.com/vuejs/vue-router/commit/dab86c5))
## [3.3.2](https://github.com/vuejs/vue-router/compare/v3.3.1...v3.3.2) (2020-05-29)
### Bug Fixes
* **errors:** NavigationCanceled with async components ([#3211](https://github.com/vuejs/vue-router/issues/3211)) ([be39ca3](https://github.com/vuejs/vue-router/commit/be39ca3))
* remove error.stack modification ([#3212](https://github.com/vuejs/vue-router/issues/3212)) ([a0075ed](https://github.com/vuejs/vue-router/commit/a0075ed))
## [3.3.1](https://github.com/vuejs/vue-router/compare/v3.3.0...v3.3.1) (2020-05-27)
### Bug Fixes
* **errors:** avoid unnecessary log of errors ([2c77247](https://github.com/vuejs/vue-router/commit/2c77247))
# [3.3.0](https://github.com/vuejs/vue-router/compare/v3.2.0...v3.3.0) (2020-05-27)
### Features
* **errors:** create router errors ([#3047](https://github.com/vuejs/vue-router/issues/3047)) ([4c727f9](https://github.com/vuejs/vue-router/commit/4c727f9))
* **history:** Remove event listeners when all apps are destroyed. ([#3172](https://github.com/vuejs/vue-router/issues/3172)) ([4c81be8](https://github.com/vuejs/vue-router/commit/4c81be8)), closes [#3152](https://github.com/vuejs/vue-router/issues/3152) [#2341](https://github.com/vuejs/vue-router/issues/2341)
* **url:** call afterEach hooks after url is ensured ([#2292](https://github.com/vuejs/vue-router/issues/2292)) ([1575a18](https://github.com/vuejs/vue-router/commit/1575a18)), closes [#2079](https://github.com/vuejs/vue-router/issues/2079)
# [3.2.0](https://github.com/vuejs/vue-router/compare/v3.1.6...v3.2.0) (2020-05-19)
### Bug Fixes
* **html5:** make base case insensitive ([04a2143](https://github.com/vuejs/vue-router/commit/04a2143)), closes [#2154](https://github.com/vuejs/vue-router/issues/2154)
* check for pushState being a function ([bc41f67](https://github.com/vuejs/vue-router/commit/bc41f67)), closes [#3154](https://github.com/vuejs/vue-router/issues/3154)
### Features
* **link:** add aria-current to active links (close [#2116](https://github.com/vuejs/vue-router/issues/2116)) ([#3073](https://github.com/vuejs/vue-router/issues/3073)) ([6ec0ee5](https://github.com/vuejs/vue-router/commit/6ec0ee5))
* **scroll:** use manual scrollRestoration with scrollBehavior ([#1814](https://github.com/vuejs/vue-router/issues/1814)) ([1261363](https://github.com/vuejs/vue-router/commit/1261363))
* **types:** NavigationGuardNext ([#2497](https://github.com/vuejs/vue-router/issues/2497)) ([d18c497](https://github.com/vuejs/vue-router/commit/d18c497))
## [3.1.6](https://github.com/vuejs/vue-router/compare/v3.1.5...v3.1.6) (2020-02-26)
### Bug Fixes
* preserve history state when reloading ([a4ec3e2](https://github.com/vuejs/vue-router/commit/a4ec3e2))
* **ts:** add null to Route.name ([#3117](https://github.com/vuejs/vue-router/issues/3117)) ([8f831f2](https://github.com/vuejs/vue-router/commit/8f831f2))
* correctly calculate `path` when `pathMatch` is empty string ([#3111](https://github.com/vuejs/vue-router/issues/3111)) ([38e6ccd](https://github.com/vuejs/vue-router/commit/38e6ccd)), closes [#3106](https://github.com/vuejs/vue-router/issues/3106)
## [3.1.5](https://github.com/vuejs/vue-router/compare/v3.1.4...v3.1.5) (2020-01-15)
### Bug Fixes
* **view:** add passing props to inactive component ([#2773](https://github.com/vuejs/vue-router/issues/2773)) ([0fb1343](https://github.com/vuejs/vue-router/commit/0fb1343)), closes [#2301](https://github.com/vuejs/vue-router/issues/2301)
* **view:** fix deeply nested keep-alive router-views displaying ([#2930](https://github.com/vuejs/vue-router/issues/2930)) ([0c2b1aa](https://github.com/vuejs/vue-router/commit/0c2b1aa)), closes [#2923](https://github.com/vuejs/vue-router/issues/2923)
## [3.1.4](https://github.com/vuejs/vue-router/compare/v3.1.3...v3.1.4) (2020-01-14)
### Bug Fixes
* suppress warning if `pathMatch` is empty ([#3081](https://github.com/vuejs/vue-router/issues/3081)) ([ddc6bc7](https://github.com/vuejs/vue-router/commit/ddc6bc7)), closes [#3072](https://github.com/vuejs/vue-router/issues/3072)
* **link:** correctly warn wrong v-slot usage ([a150291](https://github.com/vuejs/vue-router/commit/a150291)), closes [#3091](https://github.com/vuejs/vue-router/issues/3091)
* **location:** add a copy for params with named locations ([#2802](https://github.com/vuejs/vue-router/issues/2802)) ([2b39f5a](https://github.com/vuejs/vue-router/commit/2b39f5a)), closes [#2800](https://github.com/vuejs/vue-router/issues/2800) [#2938](https://github.com/vuejs/vue-router/issues/2938) [#2938](https://github.com/vuejs/vue-router/issues/2938)
### Features
* **history:** preserve existing history.state ([c0d3376](https://github.com/vuejs/vue-router/commit/c0d3376)), closes [#3006](https://github.com/vuejs/vue-router/issues/3006)
## [3.1.3](https://github.com/vuejs/vue-router/compare/v3.1.2...v3.1.3) (2019-08-30)
### Bug Fixes
- **link:** merge event listeners when provided in an anchor ([e0d4dc4](https://github.com/vuejs/vue-router/commit/e0d4dc4)), closes [#2890](https://github.com/vuejs/vue-router/issues/2890)
### Features
- **errors:** add stack trace to NavigationDuplicated ([5ef5d73](https://github.com/vuejs/vue-router/commit/5ef5d73)), closes [#2881](https://github.com/vuejs/vue-router/issues/2881)
- warn about root paths without a leading slash ([#2591](https://github.com/vuejs/vue-router/issues/2591)) ([7d7e048](https://github.com/vuejs/vue-router/commit/7d7e048)), closes [#2550](https://github.com/vuejs/vue-router/issues/2550) [#2550](https://github.com/vuejs/vue-router/issues/2550)
## [3.1.2](https://github.com/vuejs/vue-router/compare/v3.1.1...v3.1.2) (2019-08-08)
### Bug Fixes
- **types:** prioritize promise based push/replace ([1243e8b](https://github.com/vuejs/vue-router/commit/1243e8b))
### Reverts
- "fix(hash): correctly place query if placed before hash ([#2851](https://github.com/vuejs/vue-router/issues/2851))" ([9b30e4c](https://github.com/vuejs/vue-router/commit/9b30e4c)), closes [#2876](https://github.com/vuejs/vue-router/issues/2876). See more information at https://github.com/vuejs/vue-router/issues/2125#issuecomment-519521424
## [3.1.1](https://github.com/vuejs/vue-router/compare/v3.1.0...v3.1.1) (2019-08-06)
### Bug Fixes
- **link:** silence back navigations errors ([59b6da3](https://github.com/vuejs/vue-router/commit/59b6da3))
# [3.1.0](https://github.com/vuejs/vue-router/compare/v3.0.7...v3.1.0) (2019-08-06)
### Bug Fixes
- **abstract history:** allow router.back in abstract mode when 2 consecutive same routes appear in history stack ([#2771](https://github.com/vuejs/vue-router/issues/2771)) ([8910979](https://github.com/vuejs/vue-router/commit/8910979)), closes [#2607](https://github.com/vuejs/vue-router/issues/2607)
- **hash:** correctly place query if placed before hash ([#2851](https://github.com/vuejs/vue-router/issues/2851)) ([b7715dc](https://github.com/vuejs/vue-router/commit/b7715dc)), closes [#2125](https://github.com/vuejs/vue-router/issues/2125) [#2262](https://github.com/vuejs/vue-router/issues/2262)
- **link:** Fix active links when parent link redirects to child ([#2772](https://github.com/vuejs/vue-router/issues/2772)) ([64785a9](https://github.com/vuejs/vue-router/commit/64785a9)), closes [#2724](https://github.com/vuejs/vue-router/issues/2724)
- adapt error to work on IE9 ([527d6d5](https://github.com/vuejs/vue-router/commit/527d6d5))
### Features
- **alias:** warn against redundant aliases ([04a02c0](https://github.com/vuejs/vue-router/commit/04a02c0)), closes [#2461](https://github.com/vuejs/vue-router/issues/2461) [#2462](https://github.com/vuejs/vue-router/issues/2462)
- **scroll:** handle id selectors starting with a number ([799ceca](https://github.com/vuejs/vue-router/commit/799ceca)), closes [#2163](https://github.com/vuejs/vue-router/issues/2163)
- return a promise with push and replace ([#2862](https://github.com/vuejs/vue-router/issues/2862)) ([d907a13](https://github.com/vuejs/vue-router/commit/d907a13)), closes [#1769](https://github.com/vuejs/vue-router/issues/1769) [#1769](https://github.com/vuejs/vue-router/issues/1769)
- scoped slot for link ([e289dde](https://github.com/vuejs/vue-router/commit/e289dde))
- warn the user for invalid uses of v-slot with Link ([44c63a9](https://github.com/vuejs/vue-router/commit/44c63a9))
## [3.0.7](https://github.com/vuejs/vue-router/compare/v3.0.6...v3.0.7) (2019-07-03)
### Bug Fixes
- apps loaded from Windows file shares not mapped to network drives ([#2774](https://github.com/vuejs/vue-router/issues/2774)) ([c2c78a3](https://github.com/vuejs/vue-router/commit/c2c78a3))
- make callback of next in beforeRouterEnter more consistent ([#2738](https://github.com/vuejs/vue-router/issues/2738)) ([8ac478f](https://github.com/vuejs/vue-router/commit/8ac478f)), closes [#2761](https://github.com/vuejs/vue-router/issues/2761) [#2728](https://github.com/vuejs/vue-router/issues/2728)
## [3.0.6](https://github.com/vuejs/vue-router/compare/v3.0.5...v3.0.6) (2019-04-17)
### Bug Fixes
- revert [#2713](https://github.com/vuejs/vue-router/issues/2713) ([#2723](https://github.com/vuejs/vue-router/issues/2723)) ([ec6eab7](https://github.com/vuejs/vue-router/commit/ec6eab7)), closes [#2719](https://github.com/vuejs/vue-router/issues/2719)
## [3.0.5](https://github.com/vuejs/vue-router/compare/v3.0.4...v3.0.5) (2019-04-15)
### Bug Fixes
- push before creating Vue instance ([#2713](https://github.com/vuejs/vue-router/issues/2713)) ([6974a6f](https://github.com/vuejs/vue-router/commit/6974a6f)), closes [#2712](https://github.com/vuejs/vue-router/issues/2712)
- **router-view:** add condition to see whether the tree is inactive (fix [#2552](https://github.com/vuejs/vue-router/issues/2552)) ([#2592](https://github.com/vuejs/vue-router/issues/2592)) ([e6d8fd2](https://github.com/vuejs/vue-router/commit/e6d8fd2))
- **router-view:** register instance in init hook ([c3abdf6](https://github.com/vuejs/vue-router/commit/c3abdf6)), closes [#2561](https://github.com/vuejs/vue-router/issues/2561) [#2689](https://github.com/vuejs/vue-router/issues/2689) [#2561](https://github.com/vuejs/vue-router/issues/2561) [#2561](https://github.com/vuejs/vue-router/issues/2561)
## [3.0.4](https://github.com/vuejs/vue-router/compare/v3.0.3...v3.0.4) (2019-04-12)
### Bug Fixes
- prevent memory leaks by removing app references ([#2706](https://github.com/vuejs/vue-router/issues/2706)) ([8056105](https://github.com/vuejs/vue-router/commit/8056105)), closes [#2639](https://github.com/vuejs/vue-router/issues/2639)
- **hash:** prevent double decoding ([#2711](https://github.com/vuejs/vue-router/issues/2711)) ([a775fb1](https://github.com/vuejs/vue-router/commit/a775fb1)), closes [#2708](https://github.com/vuejs/vue-router/issues/2708)
### Features
- **esm build:** build ES modules for browser ([#2705](https://github.com/vuejs/vue-router/issues/2705)) ([627027f](https://github.com/vuejs/vue-router/commit/627027f))
## [3.0.3](https://github.com/vuejs/vue-router/compare/v3.0.2...v3.0.3) (2019-04-08)
### Bug Fixes
- removes warning resolving asterisk routes ([e224637](https://github.com/vuejs/vue-router/commit/e224637)), closes [#2505](https://github.com/vuejs/vue-router/issues/2505) [#2505](https://github.com/vuejs/vue-router/issues/2505)
- **normalizeLocation:** create a copy with named locations ([#2286](https://github.com/vuejs/vue-router/issues/2286)) ([53cce99](https://github.com/vuejs/vue-router/commit/53cce99)), closes [#2121](https://github.com/vuejs/vue-router/issues/2121)
- **resolve:** use current location if not provided ([#2390](https://github.com/vuejs/vue-router/issues/2390)) ([7ff4de4](https://github.com/vuejs/vue-router/commit/7ff4de4)), closes [#2385](https://github.com/vuejs/vue-router/issues/2385)
- **types:** allow null/undefined in query params ([ca30a75](https://github.com/vuejs/vue-router/commit/ca30a75)), closes [#2605](https://github.com/vuejs/vue-router/issues/2605)
## [3.0.2](https://github.com/vuejs/vue-router/compare/v3.0.1...v3.0.2) (2018-11-23)
### Bug Fixes
- **errors:** throws with invalid route objects ([#1893](https://github.com/vuejs/vue-router/issues/1893)) ([c837666](https://github.com/vuejs/vue-router/commit/c837666))
- fix the test in async.spec.js ([#1953](https://github.com/vuejs/vue-router/issues/1953)) ([4e9e66b](https://github.com/vuejs/vue-router/commit/4e9e66b))
- initial url path for non ascii urls ([#2375](https://github.com/vuejs/vue-router/issues/2375)) ([c3b0a33](https://github.com/vuejs/vue-router/commit/c3b0a33))
- only setupScroll when support pushState due to possible fallback: false ([#1835](https://github.com/vuejs/vue-router/issues/1835)) ([fac60f6](https://github.com/vuejs/vue-router/commit/fac60f6)), closes [#1834](https://github.com/vuejs/vue-router/issues/1834)
- workaround replaceState bug in Safari ([#2295](https://github.com/vuejs/vue-router/issues/2295)) ([3c7d8ab](https://github.com/vuejs/vue-router/commit/3c7d8ab)), closes [#2195](https://github.com/vuejs/vue-router/issues/2195)
- **hash:** support unicode in initial route ([8369c6b](https://github.com/vuejs/vue-router/commit/8369c6b))
- **history-mode:** correcting indentation in web.config example ([#1948](https://github.com/vuejs/vue-router/issues/1948)) ([4b071f9](https://github.com/vuejs/vue-router/commit/4b071f9))
- **match:** use pathMatch for the param of \* routes ([#1995](https://github.com/vuejs/vue-router/issues/1995)) ([ca1fccd](https://github.com/vuejs/vue-router/commit/ca1fccd)), closes [#1994](https://github.com/vuejs/vue-router/issues/1994)
### Features
- call scrollBehavior with app context ([#1804](https://github.com/vuejs/vue-router/issues/1804)) ([c93a734](https://github.com/vuejs/vue-router/commit/c93a734))
## [3.0.1](https://github.com/vuejs/vue-router/compare/v3.0.0...v3.0.1) (2017-10-13)
### Bug Fixes
- fix props-passing regression ([02ff792](https://github.com/vuejs/vue-router/commit/02ff792)), closes [#1800](https://github.com/vuejs/vue-router/issues/1800)
## [3.0.0](https://github.com/vuejs/vue-router/compare/v2.8.0...v3.0.0) (2017-10-11)
### Features
- **typings:** adapt to the new Vue typings ([#1685](https://github.com/vuejs/vue-router/issues/1685)) ([8855e36](https://github.com/vuejs/vue-router/commit/8855e36))
### BREAKING CHANGES
- **typings:** It is no longer compatible with the old Vue typings
## [2.8.0](https://github.com/vuejs/vue-router/compare/v2.7.0...v2.8.0) (2017-10-11)
### Bug Fixes
- allow insllation on extended Vue copies ([f62c5d6](https://github.com/vuejs/vue-router/commit/f62c5d6))
- avoid first popstate event with async guard together (fix [#1508](https://github.com/vuejs/vue-router/issues/1508)) ([#1661](https://github.com/vuejs/vue-router/issues/1661)) ([3cbc0f3](https://github.com/vuejs/vue-router/commit/3cbc0f3))
- deep clone query when creating routes ([effb114](https://github.com/vuejs/vue-router/commit/effb114)), closes [#1690](https://github.com/vuejs/vue-router/issues/1690)
- fix scroll when going back to initial route ([#1586](https://github.com/vuejs/vue-router/issues/1586)) ([c166822](https://github.com/vuejs/vue-router/commit/c166822))
- handle null values when comparing objects ([#1568](https://github.com/vuejs/vue-router/issues/1568)) ([4e95bd8](https://github.com/vuejs/vue-router/commit/4e95bd8)), closes [#1566](https://github.com/vuejs/vue-router/issues/1566)
- resolve native ES modules ([8a28426](https://github.com/vuejs/vue-router/commit/8a28426))
- send props not defined on the route component in \$attrs. Fixes [#1695](https://github.com/vuejs/vue-router/issues/1695). ([#1702](https://github.com/vuejs/vue-router/issues/1702)) ([a722b6a](https://github.com/vuejs/vue-router/commit/a722b6a))
### Features
- enhance hashHistory to support scrollBehavior ([#1662](https://github.com/vuejs/vue-router/issues/1662)) ([1422eb5](https://github.com/vuejs/vue-router/commit/1422eb5))
- scrollBehavior accept returning a promise ([#1758](https://github.com/vuejs/vue-router/issues/1758)) ([ce13b55](https://github.com/vuejs/vue-router/commit/ce13b55))
# [2.7.0](https://github.com/vuejs/vue-router/compare/v2.6.0...v2.7.0) (2017-06-29)
### Features
- auto resolve ES module default when resolving async components ([d539788](https://github.com/vuejs/vue-router/commit/d539788))
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2013-present Evan You
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+82
View File
@@ -0,0 +1,82 @@
# vue-router [![Build Status](https://img.shields.io/circleci/project/github/vuejs/vue-router/dev.svg)](https://circleci.com/gh/vuejs/vue-router)
> This is vue-router 3.0 which works only with Vue 2.0. For the 1.x router see the [1.0 branch](https://github.com/vuejs/vue-router/tree/1.0).
### Introduction
`vue-router` is the official router for [Vue.js](http://vuejs.org). It deeply integrates with Vue.js core to make building Single Page Applications with Vue.js a breeze. Features include:
- Nested route/view mapping
- Modular, component-based router configuration
- Route params, query, wildcards
- View transition effects powered by Vue.js' transition system
- Fine-grained navigation control
- Links with automatic active CSS classes
- HTML5 history mode or hash mode, with auto-fallback in IE9
- Customizable Scroll Behavior
Get started with the [documentation](http://router.vuejs.org), or play with the [examples](https://github.com/vuejs/vue-router/tree/dev/examples) (see how to run them below).
### Development Setup
``` bash
# install deps
npm install
# build dist files
npm run build
# serve examples at localhost:8080
npm run dev
# lint & run all tests
npm test
# serve docs at localhost:8080
npm run docs
```
## Releasing
- `yarn run release`
- Ensure tests are passing `yarn run test`
- Build dist files `VERSION=<the_version> yarn run build`
- Build changelog `yarn run changelog`
- Commit dist files `git add dist CHANGELOG.md && git commit -m "[build $VERSION]"`
- Publish a new version `npm version $VERSION --message "[release] $VERSION"
- Push tags `git push origin refs/tags/v$VERSION && git push`
- Publish to npm `npm publish`
## Questions
For questions and support please use the [Discord chat server](https://chat.vuejs.org) or [the official forum](http://forum.vuejs.org). The issue list of this repo is **exclusively** for bug reports and feature requests.
## Issues
Please make sure to read the [Issue Reporting Checklist](https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#issue-reporting-guidelines) before opening an issue. Issues not conforming to the guidelines may be closed immediately.
## Contribution
Please make sure to read the [Contributing Guide](https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md) before making a pull request.
## Changelog
Details changes for each release are documented in the [release notes](https://github.com/vuejs/vue-router/releases).
## Stay In Touch
- For latest releases and announcements, follow on Twitter: [@vuejs](https://twitter.com/vuejs)
## License
[MIT](http://opensource.org/licenses/MIT)
Copyright (c) 2013-present Evan You
## Special Thanks
<a href="https://www.browserstack.com">
<img src="/assets/browserstack-logo-600x315.png" height="80" title="BrowserStack Logo" alt="BrowserStack Logo" />
</a>
Special thanks to [BrowserStack](https://www.browserstack.com) for letting the maintainers use their service to debug browser specific issues.
+3018
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+3016
View File
File diff suppressed because it is too large Load Diff
+3024
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+137
View File
@@ -0,0 +1,137 @@
{
"_args": [
[
"vue-router@3.3.4",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_from": "vue-router@3.3.4",
"_id": "vue-router@3.3.4",
"_inBundle": false,
"_integrity": "sha512-SdKRBeoXUjaZ9R/8AyxsdTqkOfMcI5tWxPZOUX5Ie1BTL5rPSZ0O++pbiZCeYeythiZIdLEfkDiQPKIaWk5hDg==",
"_location": "/vue-router",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "vue-router@3.3.4",
"name": "vue-router",
"escapedName": "vue-router",
"rawSpec": "3.3.4",
"saveSpec": null,
"fetchSpec": "3.3.4"
},
"_requiredBy": [
"/@nuxt/vue-app"
],
"_resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.3.4.tgz",
"_spec": "3.3.4",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Evan You"
},
"bugs": {
"url": "https://github.com/vuejs/vue-router/issues"
},
"description": "Official router for Vue.js 2",
"devDependencies": {
"axios": "^0.19.0",
"babel-core": "^6.24.1",
"babel-eslint": "^10.0.2",
"babel-loader": "^7.1.3",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-preset-env": "^1.6.1",
"babel-preset-flow-vue": "^1.0.0",
"browserstack-local": "^1.4.0",
"buble": "^0.19.8",
"chromedriver": "^83.0.0",
"conventional-changelog-cli": "^2.0.11",
"cross-spawn": "^6.0.5",
"css-loader": "^2.1.1",
"dotenv": "^8.0.0",
"es6-promise": "^4.2.8",
"eslint": "^4.19.1",
"eslint-plugin-flowtype": "^2.46.1",
"eslint-plugin-jasmine": "^2.10.1",
"eslint-plugin-vue-libs": "^2.1.0",
"express": "^4.17.1",
"express-urlrewrite": "^1.2.0",
"flow-bin": "^0.66.0",
"geckodriver": "^1.19.1",
"jasmine": "2.8.0",
"lint-staged": "^8.2.0",
"nightwatch": "^1.3.6",
"nightwatch-helpers": "^1.0.0",
"path-to-regexp": "^1.8.0",
"rollup": "^1.20.1",
"rollup-plugin-buble": "^0.19.8",
"rollup-plugin-commonjs": "^10.0.2",
"rollup-plugin-flow-no-whitespace": "^1.0.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-replace": "^2.0.0",
"rollup-watch": "^4.0.0",
"selenium-server": "^3.141.59",
"terser": "^4.2.0",
"typescript": "^3.5.2",
"vue": "^2.5.16",
"vue-loader": "^15.2.1",
"vue-template-compiler": "^2.5.16",
"vuepress": "^0.14.11",
"vuepress-theme-vue": "^1.1.0",
"webpack": "^4.35.2",
"webpack-dev-middleware": "^3.7.0",
"yorkie": "^2.0.0"
},
"files": [
"src",
"dist/*.js",
"types/*.d.ts"
],
"gitHooks": {
"pre-commit": "lint-staged",
"commit-msg": "node scripts/verifyCommitMsg.js"
},
"homepage": "https://github.com/vuejs/vue-router#readme",
"jsdelivr": "dist/vue-router.js",
"keywords": [
"vue",
"router",
"routing"
],
"license": "MIT",
"lint-staged": {
"*.{js,vue}": [
"eslint --fix",
"git add"
]
},
"main": "dist/vue-router.common.js",
"module": "dist/vue-router.esm.js",
"name": "vue-router",
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/vue-router.git"
},
"scripts": {
"build": "node build/build.js",
"changelog": "conventional-changelog -p angular -r 2 -i CHANGELOG.md -s",
"dev": "node examples/server.js",
"dev:dist": "rollup -wm -c build/rollup.dev.config.js",
"docs": "vuepress dev docs",
"docs:build": "vuepress build docs",
"flow": "flow check",
"lint": "eslint src examples test",
"release": "bash scripts/release.sh",
"test": "npm run lint && npm run flow && npm run test:unit && npm run test:e2e && npm run test:types",
"test:e2e": "node test/e2e/runner.js",
"test:e2e:ci": "node test/e2e/runner.js --local -e ie,android44 -c test/e2e/nightwatch.browserstack.js test/e2e/specs/active-links.js",
"test:e2e:ff": "node test/e2e/runner.js -e firefox -c test/e2e/nightwatch.config.js",
"test:e2e:ie9": "node test/e2e/runner.js --local -e ie9 -c test/e2e/nightwatch.browserstack.js --skiptags history,ie9-fail",
"test:types": "tsc -p types/test",
"test:unit": "jasmine JASMINE_CONFIG_PATH=test/unit/jasmine.json"
},
"sideEffects": false,
"typings": "types/index.d.ts",
"unpkg": "dist/vue-router.js",
"version": "3.3.4"
}
+197
View File
@@ -0,0 +1,197 @@
/* @flow */
import { createRoute, isSameRoute, isIncludedRoute } from '../util/route'
import { extend } from '../util/misc'
import { normalizeLocation } from '../util/location'
import { warn } from '../util/warn'
// work around weird flow bug
const toTypes: Array<Function> = [String, Object]
const eventTypes: Array<Function> = [String, Array]
const noop = () => {}
export default {
name: 'RouterLink',
props: {
to: {
type: toTypes,
required: true
},
tag: {
type: String,
default: 'a'
},
exact: Boolean,
append: Boolean,
replace: Boolean,
activeClass: String,
exactActiveClass: String,
ariaCurrentValue: {
type: String,
default: 'page'
},
event: {
type: eventTypes,
default: 'click'
}
},
render (h: Function) {
const router = this.$router
const current = this.$route
const { location, route, href } = router.resolve(
this.to,
current,
this.append
)
const classes = {}
const globalActiveClass = router.options.linkActiveClass
const globalExactActiveClass = router.options.linkExactActiveClass
// Support global empty active class
const activeClassFallback =
globalActiveClass == null ? 'router-link-active' : globalActiveClass
const exactActiveClassFallback =
globalExactActiveClass == null
? 'router-link-exact-active'
: globalExactActiveClass
const activeClass =
this.activeClass == null ? activeClassFallback : this.activeClass
const exactActiveClass =
this.exactActiveClass == null
? exactActiveClassFallback
: this.exactActiveClass
const compareTarget = route.redirectedFrom
? createRoute(null, normalizeLocation(route.redirectedFrom), null, router)
: route
classes[exactActiveClass] = isSameRoute(current, compareTarget)
classes[activeClass] = this.exact
? classes[exactActiveClass]
: isIncludedRoute(current, compareTarget)
const ariaCurrentValue = classes[exactActiveClass] ? this.ariaCurrentValue : null
const handler = e => {
if (guardEvent(e)) {
if (this.replace) {
router.replace(location, noop)
} else {
router.push(location, noop)
}
}
}
const on = { click: guardEvent }
if (Array.isArray(this.event)) {
this.event.forEach(e => {
on[e] = handler
})
} else {
on[this.event] = handler
}
const data: any = { class: classes }
const scopedSlot =
!this.$scopedSlots.$hasNormal &&
this.$scopedSlots.default &&
this.$scopedSlots.default({
href,
route,
navigate: handler,
isActive: classes[activeClass],
isExactActive: classes[exactActiveClass]
})
if (scopedSlot) {
if (scopedSlot.length === 1) {
return scopedSlot[0]
} else if (scopedSlot.length > 1 || !scopedSlot.length) {
if (process.env.NODE_ENV !== 'production') {
warn(
false,
`RouterLink with to="${
this.to
}" is trying to use a scoped slot but it didn't provide exactly one child. Wrapping the content with a span element.`
)
}
return scopedSlot.length === 0 ? h() : h('span', {}, scopedSlot)
}
}
if (this.tag === 'a') {
data.on = on
data.attrs = { href, 'aria-current': ariaCurrentValue }
} else {
// find the first <a> child and apply listener and href
const a = findAnchor(this.$slots.default)
if (a) {
// in case the <a> is a static node
a.isStatic = false
const aData = (a.data = extend({}, a.data))
aData.on = aData.on || {}
// transform existing events in both objects into arrays so we can push later
for (const event in aData.on) {
const handler = aData.on[event]
if (event in on) {
aData.on[event] = Array.isArray(handler) ? handler : [handler]
}
}
// append new listeners for router-link
for (const event in on) {
if (event in aData.on) {
// on[event] is always a function
aData.on[event].push(on[event])
} else {
aData.on[event] = handler
}
}
const aAttrs = (a.data.attrs = extend({}, a.data.attrs))
aAttrs.href = href
aAttrs['aria-current'] = ariaCurrentValue
} else {
// doesn't have <a> child, apply listener to self
data.on = on
}
}
return h(this.tag, data, this.$slots.default)
}
}
function guardEvent (e) {
// don't redirect with control keys
if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return
// don't redirect when preventDefault called
if (e.defaultPrevented) return
// don't redirect on right click
if (e.button !== undefined && e.button !== 0) return
// don't redirect if `target="_blank"`
if (e.currentTarget && e.currentTarget.getAttribute) {
const target = e.currentTarget.getAttribute('target')
if (/\b_blank\b/i.test(target)) return
}
// this may be a Weex event which doesn't have this method
if (e.preventDefault) {
e.preventDefault()
}
return true
}
function findAnchor (children) {
if (children) {
let child
for (let i = 0; i < children.length; i++) {
child = children[i]
if (child.tag === 'a') {
return child
}
if (child.children && (child = findAnchor(child.children))) {
return child
}
}
}
}
+149
View File
@@ -0,0 +1,149 @@
import { warn } from '../util/warn'
import { extend } from '../util/misc'
export default {
name: 'RouterView',
functional: true,
props: {
name: {
type: String,
default: 'default'
}
},
render (_, { props, children, parent, data }) {
// used by devtools to display a router-view badge
data.routerView = true
// directly use parent context's createElement() function
// so that components rendered by router-view can resolve named slots
const h = parent.$createElement
const name = props.name
const route = parent.$route
const cache = parent._routerViewCache || (parent._routerViewCache = {})
// determine current view depth, also check to see if the tree
// has been toggled inactive but kept-alive.
let depth = 0
let inactive = false
while (parent && parent._routerRoot !== parent) {
const vnodeData = parent.$vnode ? parent.$vnode.data : {}
if (vnodeData.routerView) {
depth++
}
if (vnodeData.keepAlive && parent._directInactive && parent._inactive) {
inactive = true
}
parent = parent.$parent
}
data.routerViewDepth = depth
// render previous view if the tree is inactive and kept-alive
if (inactive) {
const cachedData = cache[name]
const cachedComponent = cachedData && cachedData.component
if (cachedComponent) {
// #2301
// pass props
if (cachedData.configProps) {
fillPropsinData(cachedComponent, data, cachedData.route, cachedData.configProps)
}
return h(cachedComponent, data, children)
} else {
// render previous empty view
return h()
}
}
const matched = route.matched[depth]
const component = matched && matched.components[name]
// render empty node if no matched route or no config component
if (!matched || !component) {
cache[name] = null
return h()
}
// cache component
cache[name] = { component }
// attach instance registration hook
// this will be called in the instance's injected lifecycle hooks
data.registerRouteInstance = (vm, val) => {
// val could be undefined for unregistration
const current = matched.instances[name]
if (
(val && current !== vm) ||
(!val && current === vm)
) {
matched.instances[name] = val
}
}
// also register instance in prepatch hook
// in case the same component instance is reused across different routes
;(data.hook || (data.hook = {})).prepatch = (_, vnode) => {
matched.instances[name] = vnode.componentInstance
}
// register instance in init hook
// in case kept-alive component be actived when routes changed
data.hook.init = (vnode) => {
if (vnode.data.keepAlive &&
vnode.componentInstance &&
vnode.componentInstance !== matched.instances[name]
) {
matched.instances[name] = vnode.componentInstance
}
}
const configProps = matched.props && matched.props[name]
// save route and configProps in cachce
if (configProps) {
extend(cache[name], {
route,
configProps
})
fillPropsinData(component, data, route, configProps)
}
return h(component, data, children)
}
}
function fillPropsinData (component, data, route, configProps) {
// resolve props
let propsToPass = data.props = resolveProps(route, configProps)
if (propsToPass) {
// clone to prevent mutation
propsToPass = data.props = extend({}, propsToPass)
// pass non-declared props as attrs
const attrs = data.attrs = data.attrs || {}
for (const key in propsToPass) {
if (!component.props || !(key in component.props)) {
attrs[key] = propsToPass[key]
delete propsToPass[key]
}
}
}
}
function resolveProps (route, config) {
switch (typeof config) {
case 'undefined':
return
case 'object':
return config
case 'function':
return config(route)
case 'boolean':
return config ? route.params : undefined
default:
if (process.env.NODE_ENV !== 'production') {
warn(
false,
`props in "${route.path}" is a ${typeof config}, ` +
`expecting an object, function or boolean.`
)
}
}
}
+200
View File
@@ -0,0 +1,200 @@
/* @flow */
import type VueRouter from './index'
import { resolvePath } from './util/path'
import { assert, warn } from './util/warn'
import { createRoute } from './util/route'
import { fillParams } from './util/params'
import { createRouteMap } from './create-route-map'
import { normalizeLocation } from './util/location'
export type Matcher = {
match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route;
addRoutes: (routes: Array<RouteConfig>) => void;
};
export function createMatcher (
routes: Array<RouteConfig>,
router: VueRouter
): Matcher {
const { pathList, pathMap, nameMap } = createRouteMap(routes)
function addRoutes (routes) {
createRouteMap(routes, pathList, pathMap, nameMap)
}
function match (
raw: RawLocation,
currentRoute?: Route,
redirectedFrom?: Location
): Route {
const location = normalizeLocation(raw, currentRoute, false, router)
const { name } = location
if (name) {
const record = nameMap[name]
if (process.env.NODE_ENV !== 'production') {
warn(record, `Route with name '${name}' does not exist`)
}
if (!record) return _createRoute(null, location)
const paramNames = record.regex.keys
.filter(key => !key.optional)
.map(key => key.name)
if (typeof location.params !== 'object') {
location.params = {}
}
if (currentRoute && typeof currentRoute.params === 'object') {
for (const key in currentRoute.params) {
if (!(key in location.params) && paramNames.indexOf(key) > -1) {
location.params[key] = currentRoute.params[key]
}
}
}
location.path = fillParams(record.path, location.params, `named route "${name}"`)
return _createRoute(record, location, redirectedFrom)
} else if (location.path) {
location.params = {}
for (let i = 0; i < pathList.length; i++) {
const path = pathList[i]
const record = pathMap[path]
if (matchRoute(record.regex, location.path, location.params)) {
return _createRoute(record, location, redirectedFrom)
}
}
}
// no match
return _createRoute(null, location)
}
function redirect (
record: RouteRecord,
location: Location
): Route {
const originalRedirect = record.redirect
let redirect = typeof originalRedirect === 'function'
? originalRedirect(createRoute(record, location, null, router))
: originalRedirect
if (typeof redirect === 'string') {
redirect = { path: redirect }
}
if (!redirect || typeof redirect !== 'object') {
if (process.env.NODE_ENV !== 'production') {
warn(
false, `invalid redirect option: ${JSON.stringify(redirect)}`
)
}
return _createRoute(null, location)
}
const re: Object = redirect
const { name, path } = re
let { query, hash, params } = location
query = re.hasOwnProperty('query') ? re.query : query
hash = re.hasOwnProperty('hash') ? re.hash : hash
params = re.hasOwnProperty('params') ? re.params : params
if (name) {
// resolved named direct
const targetRecord = nameMap[name]
if (process.env.NODE_ENV !== 'production') {
assert(targetRecord, `redirect failed: named route "${name}" not found.`)
}
return match({
_normalized: true,
name,
query,
hash,
params
}, undefined, location)
} else if (path) {
// 1. resolve relative redirect
const rawPath = resolveRecordPath(path, record)
// 2. resolve params
const resolvedPath = fillParams(rawPath, params, `redirect route with path "${rawPath}"`)
// 3. rematch with existing query and hash
return match({
_normalized: true,
path: resolvedPath,
query,
hash
}, undefined, location)
} else {
if (process.env.NODE_ENV !== 'production') {
warn(false, `invalid redirect option: ${JSON.stringify(redirect)}`)
}
return _createRoute(null, location)
}
}
function alias (
record: RouteRecord,
location: Location,
matchAs: string
): Route {
const aliasedPath = fillParams(matchAs, location.params, `aliased route with path "${matchAs}"`)
const aliasedMatch = match({
_normalized: true,
path: aliasedPath
})
if (aliasedMatch) {
const matched = aliasedMatch.matched
const aliasedRecord = matched[matched.length - 1]
location.params = aliasedMatch.params
return _createRoute(aliasedRecord, location)
}
return _createRoute(null, location)
}
function _createRoute (
record: ?RouteRecord,
location: Location,
redirectedFrom?: Location
): Route {
if (record && record.redirect) {
return redirect(record, redirectedFrom || location)
}
if (record && record.matchAs) {
return alias(record, location, record.matchAs)
}
return createRoute(record, location, redirectedFrom, router)
}
return {
match,
addRoutes
}
}
function matchRoute (
regex: RouteRegExp,
path: string,
params: Object
): boolean {
const m = path.match(regex)
if (!m) {
return false
} else if (!params) {
return true
}
for (let i = 1, len = m.length; i < len; ++i) {
const key = regex.keys[i - 1]
const val = typeof m[i] === 'string' ? decodeURIComponent(m[i]) : m[i]
if (key) {
// Fix #1994: using * with props: true generates a param named 0
params[key.name || 'pathMatch'] = val
}
}
return true
}
function resolveRecordPath (path: string, record: RouteRecord): string {
return resolvePath(path, record.parent ? record.parent.path : '/', true)
}
+205
View File
@@ -0,0 +1,205 @@
/* @flow */
import Regexp from 'path-to-regexp'
import { cleanPath } from './util/path'
import { assert, warn } from './util/warn'
export function createRouteMap (
routes: Array<RouteConfig>,
oldPathList?: Array<string>,
oldPathMap?: Dictionary<RouteRecord>,
oldNameMap?: Dictionary<RouteRecord>
): {
pathList: Array<string>,
pathMap: Dictionary<RouteRecord>,
nameMap: Dictionary<RouteRecord>
} {
// the path list is used to control path matching priority
const pathList: Array<string> = oldPathList || []
// $flow-disable-line
const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)
// $flow-disable-line
const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)
routes.forEach(route => {
addRouteRecord(pathList, pathMap, nameMap, route)
})
// ensure wildcard routes are always at the end
for (let i = 0, l = pathList.length; i < l; i++) {
if (pathList[i] === '*') {
pathList.push(pathList.splice(i, 1)[0])
l--
i--
}
}
if (process.env.NODE_ENV === 'development') {
// warn if routes do not include leading slashes
const found = pathList
// check for missing leading slash
.filter(path => path && path.charAt(0) !== '*' && path.charAt(0) !== '/')
if (found.length > 0) {
const pathNames = found.map(path => `- ${path}`).join('\n')
warn(false, `Non-nested routes must include a leading slash character. Fix the following routes: \n${pathNames}`)
}
}
return {
pathList,
pathMap,
nameMap
}
}
function addRouteRecord (
pathList: Array<string>,
pathMap: Dictionary<RouteRecord>,
nameMap: Dictionary<RouteRecord>,
route: RouteConfig,
parent?: RouteRecord,
matchAs?: string
) {
const { path, name } = route
if (process.env.NODE_ENV !== 'production') {
assert(path != null, `"path" is required in a route configuration.`)
assert(
typeof route.component !== 'string',
`route config "component" for path: ${String(
path || name
)} cannot be a ` + `string id. Use an actual component instead.`
)
}
const pathToRegexpOptions: PathToRegexpOptions =
route.pathToRegexpOptions || {}
const normalizedPath = normalizePath(path, parent, pathToRegexpOptions.strict)
if (typeof route.caseSensitive === 'boolean') {
pathToRegexpOptions.sensitive = route.caseSensitive
}
const record: RouteRecord = {
path: normalizedPath,
regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
components: route.components || { default: route.component },
instances: {},
name,
parent,
matchAs,
redirect: route.redirect,
beforeEnter: route.beforeEnter,
meta: route.meta || {},
props:
route.props == null
? {}
: route.components
? route.props
: { default: route.props }
}
if (route.children) {
// Warn if route is named, does not redirect and has a default child route.
// If users navigate to this route by name, the default child will
// not be rendered (GH Issue #629)
if (process.env.NODE_ENV !== 'production') {
if (
route.name &&
!route.redirect &&
route.children.some(child => /^\/?$/.test(child.path))
) {
warn(
false,
`Named Route '${route.name}' has a default child route. ` +
`When navigating to this named route (:to="{name: '${
route.name
}'"), ` +
`the default child route will not be rendered. Remove the name from ` +
`this route and use the name of the default child route for named ` +
`links instead.`
)
}
}
route.children.forEach(child => {
const childMatchAs = matchAs
? cleanPath(`${matchAs}/${child.path}`)
: undefined
addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)
})
}
if (!pathMap[record.path]) {
pathList.push(record.path)
pathMap[record.path] = record
}
if (route.alias !== undefined) {
const aliases = Array.isArray(route.alias) ? route.alias : [route.alias]
for (let i = 0; i < aliases.length; ++i) {
const alias = aliases[i]
if (process.env.NODE_ENV !== 'production' && alias === path) {
warn(
false,
`Found an alias with the same value as the path: "${path}". You have to remove that alias. It will be ignored in development.`
)
// skip in dev to make it work
continue
}
const aliasRoute = {
path: alias,
children: route.children
}
addRouteRecord(
pathList,
pathMap,
nameMap,
aliasRoute,
parent,
record.path || '/' // matchAs
)
}
}
if (name) {
if (!nameMap[name]) {
nameMap[name] = record
} else if (process.env.NODE_ENV !== 'production' && !matchAs) {
warn(
false,
`Duplicate named routes definition: ` +
`{ name: "${name}", path: "${record.path}" }`
)
}
}
}
function compileRouteRegex (
path: string,
pathToRegexpOptions: PathToRegexpOptions
): RouteRegExp {
const regex = Regexp(path, [], pathToRegexpOptions)
if (process.env.NODE_ENV !== 'production') {
const keys: any = Object.create(null)
regex.keys.forEach(key => {
warn(
!keys[key.name],
`Duplicate param keys in route with path: "${path}"`
)
keys[key.name] = true
})
}
return regex
}
function normalizePath (
path: string,
parent?: RouteRecord,
strict?: boolean
): string {
if (!strict) path = path.replace(/\/$/, '')
if (path[0] === '/') return path
if (parent == null) return path
return cleanPath(`${parent.path}/${path}`)
}
+69
View File
@@ -0,0 +1,69 @@
/* @flow */
import type Router from '../index'
import { History } from './base'
import { isRouterError } from '../util/warn'
import { NavigationFailureType } from './errors'
export class AbstractHistory extends History {
index: number
stack: Array<Route>
constructor (router: Router, base: ?string) {
super(router, base)
this.stack = []
this.index = -1
}
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
this.transitionTo(
location,
route => {
this.stack = this.stack.slice(0, this.index + 1).concat(route)
this.index++
onComplete && onComplete(route)
},
onAbort
)
}
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
this.transitionTo(
location,
route => {
this.stack = this.stack.slice(0, this.index).concat(route)
onComplete && onComplete(route)
},
onAbort
)
}
go (n: number) {
const targetIndex = this.index + n
if (targetIndex < 0 || targetIndex >= this.stack.length) {
return
}
const route = this.stack[targetIndex]
this.confirmTransition(
route,
() => {
this.index = targetIndex
this.updateRoute(route)
},
err => {
if (isRouterError(err, NavigationFailureType.duplicated)) {
this.index = targetIndex
}
}
)
}
getCurrentLocation () {
const current = this.stack[this.stack.length - 1]
return current ? current.fullPath : '/'
}
ensureURL () {
// noop
}
}
+387
View File
@@ -0,0 +1,387 @@
/* @flow */
import { _Vue } from '../install'
import type Router from '../index'
import { inBrowser } from '../util/dom'
import { runQueue } from '../util/async'
import { warn, isError, isRouterError } from '../util/warn'
import { START, isSameRoute } from '../util/route'
import {
flatten,
flatMapComponents,
resolveAsyncComponents
} from '../util/resolve-components'
import {
createNavigationDuplicatedError,
createNavigationCancelledError,
createNavigationRedirectedError,
createNavigationAbortedError,
NavigationFailureType
} from './errors'
export class History {
router: Router
base: string
current: Route
pending: ?Route
cb: (r: Route) => void
ready: boolean
readyCbs: Array<Function>
readyErrorCbs: Array<Function>
errorCbs: Array<Function>
listeners: Array<Function>
cleanupListeners: Function
// implemented by sub-classes
+go: (n: number) => void
+push: (loc: RawLocation, onComplete?: Function, onAbort?: Function) => void
+replace: (loc: RawLocation, onComplete?: Function, onAbort?: Function) => void
+ensureURL: (push?: boolean) => void
+getCurrentLocation: () => string
+setupListeners: Function
constructor (router: Router, base: ?string) {
this.router = router
this.base = normalizeBase(base)
// start with a route object that stands for "nowhere"
this.current = START
this.pending = null
this.ready = false
this.readyCbs = []
this.readyErrorCbs = []
this.errorCbs = []
this.listeners = []
}
listen (cb: Function) {
this.cb = cb
}
onReady (cb: Function, errorCb: ?Function) {
if (this.ready) {
cb()
} else {
this.readyCbs.push(cb)
if (errorCb) {
this.readyErrorCbs.push(errorCb)
}
}
}
onError (errorCb: Function) {
this.errorCbs.push(errorCb)
}
transitionTo (
location: RawLocation,
onComplete?: Function,
onAbort?: Function
) {
const route = this.router.match(location, this.current)
this.confirmTransition(
route,
() => {
const prev = this.current
this.updateRoute(route)
onComplete && onComplete(route)
this.ensureURL()
this.router.afterHooks.forEach(hook => {
hook && hook(route, prev)
})
// fire ready cbs once
if (!this.ready) {
this.ready = true
this.readyCbs.forEach(cb => {
cb(route)
})
}
},
err => {
if (onAbort) {
onAbort(err)
}
if (err && !this.ready) {
this.ready = true
// Initial redirection should still trigger the onReady onSuccess
// https://github.com/vuejs/vue-router/issues/3225
if (!isRouterError(err, NavigationFailureType.redirected)) {
this.readyErrorCbs.forEach(cb => {
cb(err)
})
} else {
this.readyCbs.forEach(cb => {
cb(route)
})
}
}
}
)
}
confirmTransition (route: Route, onComplete: Function, onAbort?: Function) {
const current = this.current
const abort = err => {
// changed after adding errors with
// https://github.com/vuejs/vue-router/pull/3047 before that change,
// redirect and aborted navigation would produce an err == null
if (!isRouterError(err) && isError(err)) {
if (this.errorCbs.length) {
this.errorCbs.forEach(cb => {
cb(err)
})
} else {
warn(false, 'uncaught error during route navigation:')
console.error(err)
}
}
onAbort && onAbort(err)
}
const lastRouteIndex = route.matched.length - 1
const lastCurrentIndex = current.matched.length - 1
if (
isSameRoute(route, current) &&
// in the case the route map has been dynamically appended to
lastRouteIndex === lastCurrentIndex &&
route.matched[lastRouteIndex] === current.matched[lastCurrentIndex]
) {
this.ensureURL()
return abort(createNavigationDuplicatedError(current, route))
}
const { updated, deactivated, activated } = resolveQueue(
this.current.matched,
route.matched
)
const queue: Array<?NavigationGuard> = [].concat(
// in-component leave guards
extractLeaveGuards(deactivated),
// global before hooks
this.router.beforeHooks,
// in-component update hooks
extractUpdateHooks(updated),
// in-config enter guards
activated.map(m => m.beforeEnter),
// async components
resolveAsyncComponents(activated)
)
this.pending = route
const iterator = (hook: NavigationGuard, next) => {
if (this.pending !== route) {
return abort(createNavigationCancelledError(current, route))
}
try {
hook(route, current, (to: any) => {
if (to === false) {
// next(false) -> abort navigation, ensure current URL
this.ensureURL(true)
abort(createNavigationAbortedError(current, route))
} else if (isError(to)) {
this.ensureURL(true)
abort(to)
} else if (
typeof to === 'string' ||
(typeof to === 'object' &&
(typeof to.path === 'string' || typeof to.name === 'string'))
) {
// next('/') or next({ path: '/' }) -> redirect
abort(createNavigationRedirectedError(current, route))
if (typeof to === 'object' && to.replace) {
this.replace(to)
} else {
this.push(to)
}
} else {
// confirm transition and pass on the value
next(to)
}
})
} catch (e) {
abort(e)
}
}
runQueue(queue, iterator, () => {
const postEnterCbs = []
const isValid = () => this.current === route
// wait until async components are resolved before
// extracting in-component enter guards
const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)
const queue = enterGuards.concat(this.router.resolveHooks)
runQueue(queue, iterator, () => {
if (this.pending !== route) {
return abort(createNavigationCancelledError(current, route))
}
this.pending = null
onComplete(route)
if (this.router.app) {
this.router.app.$nextTick(() => {
postEnterCbs.forEach(cb => {
cb()
})
})
}
})
})
}
updateRoute (route: Route) {
this.current = route
this.cb && this.cb(route)
}
setupListeners () {
// Default implementation is empty
}
teardownListeners () {
this.listeners.forEach(cleanupListener => {
cleanupListener()
})
this.listeners = []
}
}
function normalizeBase (base: ?string): string {
if (!base) {
if (inBrowser) {
// respect <base> tag
const baseEl = document.querySelector('base')
base = (baseEl && baseEl.getAttribute('href')) || '/'
// strip full URL origin
base = base.replace(/^https?:\/\/[^\/]+/, '')
} else {
base = '/'
}
}
// make sure there's the starting slash
if (base.charAt(0) !== '/') {
base = '/' + base
}
// remove trailing slash
return base.replace(/\/$/, '')
}
function resolveQueue (
current: Array<RouteRecord>,
next: Array<RouteRecord>
): {
updated: Array<RouteRecord>,
activated: Array<RouteRecord>,
deactivated: Array<RouteRecord>
} {
let i
const max = Math.max(current.length, next.length)
for (i = 0; i < max; i++) {
if (current[i] !== next[i]) {
break
}
}
return {
updated: next.slice(0, i),
activated: next.slice(i),
deactivated: current.slice(i)
}
}
function extractGuards (
records: Array<RouteRecord>,
name: string,
bind: Function,
reverse?: boolean
): Array<?Function> {
const guards = flatMapComponents(records, (def, instance, match, key) => {
const guard = extractGuard(def, name)
if (guard) {
return Array.isArray(guard)
? guard.map(guard => bind(guard, instance, match, key))
: bind(guard, instance, match, key)
}
})
return flatten(reverse ? guards.reverse() : guards)
}
function extractGuard (
def: Object | Function,
key: string
): NavigationGuard | Array<NavigationGuard> {
if (typeof def !== 'function') {
// extend now so that global mixins are applied.
def = _Vue.extend(def)
}
return def.options[key]
}
function extractLeaveGuards (deactivated: Array<RouteRecord>): Array<?Function> {
return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)
}
function extractUpdateHooks (updated: Array<RouteRecord>): Array<?Function> {
return extractGuards(updated, 'beforeRouteUpdate', bindGuard)
}
function bindGuard (guard: NavigationGuard, instance: ?_Vue): ?NavigationGuard {
if (instance) {
return function boundRouteGuard () {
return guard.apply(instance, arguments)
}
}
}
function extractEnterGuards (
activated: Array<RouteRecord>,
cbs: Array<Function>,
isValid: () => boolean
): Array<?Function> {
return extractGuards(
activated,
'beforeRouteEnter',
(guard, _, match, key) => {
return bindEnterGuard(guard, match, key, cbs, isValid)
}
)
}
function bindEnterGuard (
guard: NavigationGuard,
match: RouteRecord,
key: string,
cbs: Array<Function>,
isValid: () => boolean
): NavigationGuard {
return function routeEnterGuard (to, from, next) {
return guard(to, from, cb => {
if (typeof cb === 'function') {
cbs.push(() => {
// #750
// if a router-view is wrapped with an out-in transition,
// the instance may not have been registered at this time.
// we will need to poll for registration until current route
// is no longer valid.
poll(cb, match.instances, key, isValid)
})
}
next(cb)
})
}
}
function poll (
cb: any, // somehow flow cannot infer this is a function
instances: Object,
key: string,
isValid: () => boolean
) {
if (
instances[key] &&
!instances[key]._isBeingDestroyed // do not reuse being destroyed instance
) {
cb(instances[key])
} else if (isValid()) {
setTimeout(() => {
poll(cb, instances, key, isValid)
}, 16)
}
}
+70
View File
@@ -0,0 +1,70 @@
export const NavigationFailureType = {
redirected: 1,
aborted: 2,
cancelled: 3,
duplicated: 4
}
export function createNavigationRedirectedError (from, to) {
return createRouterError(
from,
to,
NavigationFailureType.redirected,
`Redirected when going from "${from.fullPath}" to "${stringifyRoute(
to
)}" via a navigation guard.`
)
}
export function createNavigationDuplicatedError (from, to) {
return createRouterError(
from,
to,
NavigationFailureType.duplicated,
`Avoided redundant navigation to current location: "${from.fullPath}".`
)
}
export function createNavigationCancelledError (from, to) {
return createRouterError(
from,
to,
NavigationFailureType.cancelled,
`Navigation cancelled from "${from.fullPath}" to "${
to.fullPath
}" with a new navigation.`
)
}
export function createNavigationAbortedError (from, to) {
return createRouterError(
from,
to,
NavigationFailureType.aborted,
`Navigation aborted from "${from.fullPath}" to "${
to.fullPath
}" via a navigation guard.`
)
}
function createRouterError (from, to, type, message) {
const error = new Error(message)
error._isRouter = true
error.from = from
error.to = to
error.type = type
return error
}
const propertiesToLog = ['params', 'query', 'hash']
function stringifyRoute (to) {
if (typeof to === 'string') return to
if ('path' in to) return to.path
const location = {}
propertiesToLog.forEach(key => {
if (key in to) location[key] = to[key]
})
return JSON.stringify(location, null, 2)
}
+164
View File
@@ -0,0 +1,164 @@
/* @flow */
import type Router from '../index'
import { History } from './base'
import { cleanPath } from '../util/path'
import { getLocation } from './html5'
import { setupScroll, handleScroll } from '../util/scroll'
import { pushState, replaceState, supportsPushState } from '../util/push-state'
export class HashHistory extends History {
constructor (router: Router, base: ?string, fallback: boolean) {
super(router, base)
// check history fallback deeplinking
if (fallback && checkFallback(this.base)) {
return
}
ensureSlash()
}
// this is delayed until the app mounts
// to avoid the hashchange listener being fired too early
setupListeners () {
if (this.listeners.length > 0) {
return
}
const router = this.router
const expectScroll = router.options.scrollBehavior
const supportsScroll = supportsPushState && expectScroll
if (supportsScroll) {
this.listeners.push(setupScroll())
}
const handleRoutingEvent = () => {
const current = this.current
if (!ensureSlash()) {
return
}
this.transitionTo(getHash(), route => {
if (supportsScroll) {
handleScroll(this.router, route, current, true)
}
if (!supportsPushState) {
replaceHash(route.fullPath)
}
})
}
const eventType = supportsPushState ? 'popstate' : 'hashchange'
window.addEventListener(
eventType,
handleRoutingEvent
)
this.listeners.push(() => {
window.removeEventListener(eventType, handleRoutingEvent)
})
}
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(
location,
route => {
pushHash(route.fullPath)
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
},
onAbort
)
}
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(
location,
route => {
replaceHash(route.fullPath)
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
},
onAbort
)
}
go (n: number) {
window.history.go(n)
}
ensureURL (push?: boolean) {
const current = this.current.fullPath
if (getHash() !== current) {
push ? pushHash(current) : replaceHash(current)
}
}
getCurrentLocation () {
return getHash()
}
}
function checkFallback (base) {
const location = getLocation(base)
if (!/^\/#/.test(location)) {
window.location.replace(cleanPath(base + '/#' + location))
return true
}
}
function ensureSlash (): boolean {
const path = getHash()
if (path.charAt(0) === '/') {
return true
}
replaceHash('/' + path)
return false
}
export function getHash (): string {
// We can't use window.location.hash here because it's not
// consistent across browsers - Firefox will pre-decode it!
let href = window.location.href
const index = href.indexOf('#')
// empty path
if (index < 0) return ''
href = href.slice(index + 1)
// decode the hash but not the search or hash
// as search(query) is already decoded
// https://github.com/vuejs/vue-router/issues/2708
const searchIndex = href.indexOf('?')
if (searchIndex < 0) {
const hashIndex = href.indexOf('#')
if (hashIndex > -1) {
href = decodeURI(href.slice(0, hashIndex)) + href.slice(hashIndex)
} else href = decodeURI(href)
} else {
href = decodeURI(href.slice(0, searchIndex)) + href.slice(searchIndex)
}
return href
}
function getUrl (path) {
const href = window.location.href
const i = href.indexOf('#')
const base = i >= 0 ? href.slice(0, i) : href
return `${base}#${path}`
}
function pushHash (path) {
if (supportsPushState) {
pushState(getUrl(path))
} else {
window.location.hash = path
}
}
function replaceHash (path) {
if (supportsPushState) {
replaceState(getUrl(path))
} else {
window.location.replace(getUrl(path))
}
}
+94
View File
@@ -0,0 +1,94 @@
/* @flow */
import type Router from '../index'
import { History } from './base'
import { cleanPath } from '../util/path'
import { START } from '../util/route'
import { setupScroll, handleScroll } from '../util/scroll'
import { pushState, replaceState, supportsPushState } from '../util/push-state'
export class HTML5History extends History {
_startLocation: string
constructor (router: Router, base: ?string) {
super(router, base)
this._startLocation = getLocation(this.base)
}
setupListeners () {
if (this.listeners.length > 0) {
return
}
const router = this.router
const expectScroll = router.options.scrollBehavior
const supportsScroll = supportsPushState && expectScroll
if (supportsScroll) {
this.listeners.push(setupScroll())
}
const handleRoutingEvent = () => {
const current = this.current
// Avoiding first `popstate` event dispatched in some browsers but first
// history route not updated since async guard at the same time.
const location = getLocation(this.base)
if (this.current === START && location === this._startLocation) {
return
}
this.transitionTo(location, route => {
if (supportsScroll) {
handleScroll(router, route, current, true)
}
})
}
window.addEventListener('popstate', handleRoutingEvent)
this.listeners.push(() => {
window.removeEventListener('popstate', handleRoutingEvent)
})
}
go (n: number) {
window.history.go(n)
}
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
pushState(cleanPath(this.base + route.fullPath))
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
}
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
const { current: fromRoute } = this
this.transitionTo(location, route => {
replaceState(cleanPath(this.base + route.fullPath))
handleScroll(this.router, route, fromRoute, false)
onComplete && onComplete(route)
}, onAbort)
}
ensureURL (push?: boolean) {
if (getLocation(this.base) !== this.current.fullPath) {
const current = cleanPath(this.base + this.current.fullPath)
push ? pushState(current) : replaceState(current)
}
}
getCurrentLocation (): string {
return getLocation(this.base)
}
}
export function getLocation (base: string): string {
let path = decodeURI(window.location.pathname)
if (base && path.toLowerCase().indexOf(base.toLowerCase()) === 0) {
path = path.slice(base.length)
}
return (path || '/') + window.location.search + window.location.hash
}
+262
View File
@@ -0,0 +1,262 @@
/* @flow */
import { install } from './install'
import { START } from './util/route'
import { assert } from './util/warn'
import { inBrowser } from './util/dom'
import { cleanPath } from './util/path'
import { createMatcher } from './create-matcher'
import { normalizeLocation } from './util/location'
import { supportsPushState } from './util/push-state'
import { HashHistory } from './history/hash'
import { HTML5History } from './history/html5'
import { AbstractHistory } from './history/abstract'
import type { Matcher } from './create-matcher'
export default class VueRouter {
static install: () => void;
static version: string;
app: any;
apps: Array<any>;
ready: boolean;
readyCbs: Array<Function>;
options: RouterOptions;
mode: string;
history: HashHistory | HTML5History | AbstractHistory;
matcher: Matcher;
fallback: boolean;
beforeHooks: Array<?NavigationGuard>;
resolveHooks: Array<?NavigationGuard>;
afterHooks: Array<?AfterNavigationHook>;
constructor (options: RouterOptions = {}) {
this.app = null
this.apps = []
this.options = options
this.beforeHooks = []
this.resolveHooks = []
this.afterHooks = []
this.matcher = createMatcher(options.routes || [], this)
let mode = options.mode || 'hash'
this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false
if (this.fallback) {
mode = 'hash'
}
if (!inBrowser) {
mode = 'abstract'
}
this.mode = mode
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract':
this.history = new AbstractHistory(this, options.base)
break
default:
if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode: ${mode}`)
}
}
}
match (
raw: RawLocation,
current?: Route,
redirectedFrom?: Location
): Route {
return this.matcher.match(raw, current, redirectedFrom)
}
get currentRoute (): ?Route {
return this.history && this.history.current
}
init (app: any /* Vue component instance */) {
process.env.NODE_ENV !== 'production' && assert(
install.installed,
`not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
`before creating root instance.`
)
this.apps.push(app)
// set up app destroyed handler
// https://github.com/vuejs/vue-router/issues/2639
app.$once('hook:destroyed', () => {
// clean out app from this.apps array once destroyed
const index = this.apps.indexOf(app)
if (index > -1) this.apps.splice(index, 1)
// ensure we still have a main app or null if no apps
// we do not release the router so it can be reused
if (this.app === app) this.app = this.apps[0] || null
if (!this.app) {
// clean up event listeners
// https://github.com/vuejs/vue-router/issues/2341
this.history.teardownListeners()
}
})
// main app previously initialized
// return as we don't need to set up new history listener
if (this.app) {
return
}
this.app = app
const history = this.history
if (history instanceof HTML5History || history instanceof HashHistory) {
const setupListeners = () => {
history.setupListeners()
}
history.transitionTo(history.getCurrentLocation(), setupListeners, setupListeners)
}
history.listen(route => {
this.apps.forEach((app) => {
app._route = route
})
})
}
beforeEach (fn: Function): Function {
return registerHook(this.beforeHooks, fn)
}
beforeResolve (fn: Function): Function {
return registerHook(this.resolveHooks, fn)
}
afterEach (fn: Function): Function {
return registerHook(this.afterHooks, fn)
}
onReady (cb: Function, errorCb?: Function) {
this.history.onReady(cb, errorCb)
}
onError (errorCb: Function) {
this.history.onError(errorCb)
}
push (location: RawLocation, onComplete?: Function, onAbort?: Function) {
// $flow-disable-line
if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
return new Promise((resolve, reject) => {
this.history.push(location, resolve, reject)
})
} else {
this.history.push(location, onComplete, onAbort)
}
}
replace (location: RawLocation, onComplete?: Function, onAbort?: Function) {
// $flow-disable-line
if (!onComplete && !onAbort && typeof Promise !== 'undefined') {
return new Promise((resolve, reject) => {
this.history.replace(location, resolve, reject)
})
} else {
this.history.replace(location, onComplete, onAbort)
}
}
go (n: number) {
this.history.go(n)
}
back () {
this.go(-1)
}
forward () {
this.go(1)
}
getMatchedComponents (to?: RawLocation | Route): Array<any> {
const route: any = to
? to.matched
? to
: this.resolve(to).route
: this.currentRoute
if (!route) {
return []
}
return [].concat.apply([], route.matched.map(m => {
return Object.keys(m.components).map(key => {
return m.components[key]
})
}))
}
resolve (
to: RawLocation,
current?: Route,
append?: boolean
): {
location: Location,
route: Route,
href: string,
// for backwards compat
normalizedTo: Location,
resolved: Route
} {
current = current || this.history.current
const location = normalizeLocation(
to,
current,
append,
this
)
const route = this.match(location, current)
const fullPath = route.redirectedFrom || route.fullPath
const base = this.history.base
const href = createHref(base, fullPath, this.mode)
return {
location,
route,
href,
// for backwards compat
normalizedTo: location,
resolved: route
}
}
addRoutes (routes: Array<RouteConfig>) {
this.matcher.addRoutes(routes)
if (this.history.current !== START) {
this.history.transitionTo(this.history.getCurrentLocation())
}
}
}
function registerHook (list: Array<any>, fn: Function): Function {
list.push(fn)
return () => {
const i = list.indexOf(fn)
if (i > -1) list.splice(i, 1)
}
}
function createHref (base: string, fullPath: string, mode) {
var path = mode === 'hash' ? '#' + fullPath : fullPath
return base ? cleanPath(base + '/' + path) : path
}
VueRouter.install = install
VueRouter.version = '__VERSION__'
if (inBrowser && window.Vue) {
window.Vue.use(VueRouter)
}
+52
View File
@@ -0,0 +1,52 @@
import View from './components/view'
import Link from './components/link'
export let _Vue
export function install (Vue) {
if (install.installed && _Vue === Vue) return
install.installed = true
_Vue = Vue
const isDef = v => v !== undefined
const registerInstance = (vm, callVal) => {
let i = vm.$options._parentVnode
if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
i(vm, callVal)
}
}
Vue.mixin({
beforeCreate () {
if (isDef(this.$options.router)) {
this._routerRoot = this
this._router = this.$options.router
this._router.init(this)
Vue.util.defineReactive(this, '_route', this._router.history.current)
} else {
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
registerInstance(this, this)
},
destroyed () {
registerInstance(this)
}
})
Object.defineProperty(Vue.prototype, '$router', {
get () { return this._routerRoot._router }
})
Object.defineProperty(Vue.prototype, '$route', {
get () { return this._routerRoot._route }
})
Vue.component('RouterView', View)
Vue.component('RouterLink', Link)
const strats = Vue.config.optionMergeStrategies
// use the same hook merging strategy for route hooks
strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}
+18
View File
@@ -0,0 +1,18 @@
/* @flow */
export function runQueue (queue: Array<?NavigationGuard>, fn: Function, cb: Function) {
const step = index => {
if (index >= queue.length) {
cb()
} else {
if (queue[index]) {
fn(queue[index], () => {
step(index + 1)
})
} else {
step(index + 1)
}
}
}
step(0)
}
+3
View File
@@ -0,0 +1,3 @@
/* @flow */
export const inBrowser = typeof window !== 'undefined'
+69
View File
@@ -0,0 +1,69 @@
/* @flow */
import type VueRouter from '../index'
import { parsePath, resolvePath } from './path'
import { resolveQuery } from './query'
import { fillParams } from './params'
import { warn } from './warn'
import { extend } from './misc'
export function normalizeLocation (
raw: RawLocation,
current: ?Route,
append: ?boolean,
router: ?VueRouter
): Location {
let next: Location = typeof raw === 'string' ? { path: raw } : raw
// named target
if (next._normalized) {
return next
} else if (next.name) {
next = extend({}, raw)
const params = next.params
if (params && typeof params === 'object') {
next.params = extend({}, params)
}
return next
}
// relative params
if (!next.path && next.params && current) {
next = extend({}, next)
next._normalized = true
const params: any = extend(extend({}, current.params), next.params)
if (current.name) {
next.name = current.name
next.params = params
} else if (current.matched.length) {
const rawPath = current.matched[current.matched.length - 1].path
next.path = fillParams(rawPath, params, `path ${current.path}`)
} else if (process.env.NODE_ENV !== 'production') {
warn(false, `relative params navigation requires a current route.`)
}
return next
}
const parsedPath = parsePath(next.path || '')
const basePath = (current && current.path) || '/'
const path = parsedPath.path
? resolvePath(parsedPath.path, basePath, append || next.append)
: basePath
const query = resolveQuery(
parsedPath.query,
next.query,
router && router.options.parseQuery
)
let hash = next.hash || parsedPath.hash
if (hash && hash.charAt(0) !== '#') {
hash = `#${hash}`
}
return {
_normalized: true,
path,
query,
hash
}
}
+6
View File
@@ -0,0 +1,6 @@
export function extend (a, b) {
for (const key in b) {
a[key] = b[key]
}
return a
}
+37
View File
@@ -0,0 +1,37 @@
/* @flow */
import { warn } from './warn'
import Regexp from 'path-to-regexp'
// $flow-disable-line
const regexpCompileCache: {
[key: string]: Function
} = Object.create(null)
export function fillParams (
path: string,
params: ?Object,
routeMsg: string
): string {
params = params || {}
try {
const filler =
regexpCompileCache[path] ||
(regexpCompileCache[path] = Regexp.compile(path))
// Fix #2505 resolving asterisk routes { name: 'not-found', params: { pathMatch: '/not-found' }}
// and fix #3106 so that you can work with location descriptor object having params.pathMatch equal to empty string
if (typeof params.pathMatch === 'string') params[0] = params.pathMatch
return filler(params, { pretty: true })
} catch (e) {
if (process.env.NODE_ENV !== 'production') {
// Fix #3072 no warn if `pathMatch` is string
warn(typeof params.pathMatch === 'string', `missing param for ${routeMsg}: ${e.message}`)
}
return ''
} finally {
// delete the 0 if it was added
delete params[0]
}
}
+74
View File
@@ -0,0 +1,74 @@
/* @flow */
export function resolvePath (
relative: string,
base: string,
append?: boolean
): string {
const firstChar = relative.charAt(0)
if (firstChar === '/') {
return relative
}
if (firstChar === '?' || firstChar === '#') {
return base + relative
}
const stack = base.split('/')
// remove trailing segment if:
// - not appending
// - appending to trailing slash (last segment is empty)
if (!append || !stack[stack.length - 1]) {
stack.pop()
}
// resolve relative path
const segments = relative.replace(/^\//, '').split('/')
for (let i = 0; i < segments.length; i++) {
const segment = segments[i]
if (segment === '..') {
stack.pop()
} else if (segment !== '.') {
stack.push(segment)
}
}
// ensure leading slash
if (stack[0] !== '') {
stack.unshift('')
}
return stack.join('/')
}
export function parsePath (path: string): {
path: string;
query: string;
hash: string;
} {
let hash = ''
let query = ''
const hashIndex = path.indexOf('#')
if (hashIndex >= 0) {
hash = path.slice(hashIndex)
path = path.slice(0, hashIndex)
}
const queryIndex = path.indexOf('?')
if (queryIndex >= 0) {
query = path.slice(queryIndex + 1)
path = path.slice(0, queryIndex)
}
return {
path,
query,
hash
}
}
export function cleanPath (path: string): string {
return path.replace(/\/\//g, '/')
}
+46
View File
@@ -0,0 +1,46 @@
/* @flow */
import { inBrowser } from './dom'
import { saveScrollPosition } from './scroll'
import { genStateKey, setStateKey, getStateKey } from './state-key'
import { extend } from './misc'
export const supportsPushState =
inBrowser &&
(function () {
const ua = window.navigator.userAgent
if (
(ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) &&
ua.indexOf('Mobile Safari') !== -1 &&
ua.indexOf('Chrome') === -1 &&
ua.indexOf('Windows Phone') === -1
) {
return false
}
return window.history && typeof window.history.pushState === 'function'
})()
export function pushState (url?: string, replace?: boolean) {
saveScrollPosition()
// try...catch the pushState call to get around Safari
// DOM Exception 18 where it limits to 100 pushState calls
const history = window.history
try {
if (replace) {
// preserve existing history state as it could be overriden by the user
const stateCopy = extend({}, history.state)
stateCopy.key = getStateKey()
history.replaceState(stateCopy, '', url)
} else {
history.pushState({ key: setStateKey(genStateKey()) }, '', url)
}
} catch (e) {
window.location[replace ? 'replace' : 'assign'](url)
}
}
export function replaceState (url?: string) {
pushState(url, true)
}
+95
View File
@@ -0,0 +1,95 @@
/* @flow */
import { warn } from './warn'
const encodeReserveRE = /[!'()*]/g
const encodeReserveReplacer = c => '%' + c.charCodeAt(0).toString(16)
const commaRE = /%2C/g
// fixed encodeURIComponent which is more conformant to RFC3986:
// - escapes [!'()*]
// - preserve commas
const encode = str => encodeURIComponent(str)
.replace(encodeReserveRE, encodeReserveReplacer)
.replace(commaRE, ',')
const decode = decodeURIComponent
export function resolveQuery (
query: ?string,
extraQuery: Dictionary<string> = {},
_parseQuery: ?Function
): Dictionary<string> {
const parse = _parseQuery || parseQuery
let parsedQuery
try {
parsedQuery = parse(query || '')
} catch (e) {
process.env.NODE_ENV !== 'production' && warn(false, e.message)
parsedQuery = {}
}
for (const key in extraQuery) {
parsedQuery[key] = extraQuery[key]
}
return parsedQuery
}
function parseQuery (query: string): Dictionary<string> {
const res = {}
query = query.trim().replace(/^(\?|#|&)/, '')
if (!query) {
return res
}
query.split('&').forEach(param => {
const parts = param.replace(/\+/g, ' ').split('=')
const key = decode(parts.shift())
const val = parts.length > 0
? decode(parts.join('='))
: null
if (res[key] === undefined) {
res[key] = val
} else if (Array.isArray(res[key])) {
res[key].push(val)
} else {
res[key] = [res[key], val]
}
})
return res
}
export function stringifyQuery (obj: Dictionary<string>): string {
const res = obj ? Object.keys(obj).map(key => {
const val = obj[key]
if (val === undefined) {
return ''
}
if (val === null) {
return encode(key)
}
if (Array.isArray(val)) {
const result = []
val.forEach(val2 => {
if (val2 === undefined) {
return
}
if (val2 === null) {
result.push(encode(key))
} else {
result.push(encode(key) + '=' + encode(val2))
}
})
return result.join('&')
}
return encode(key) + '=' + encode(val)
}).filter(x => x.length > 0).join('&') : null
return res ? `?${res}` : ''
}
+108
View File
@@ -0,0 +1,108 @@
/* @flow */
import { _Vue } from '../install'
import { warn, isError } from './warn'
export function resolveAsyncComponents (matched: Array<RouteRecord>): Function {
return (to, from, next) => {
let hasAsync = false
let pending = 0
let error = null
flatMapComponents(matched, (def, _, match, key) => {
// if it's a function and doesn't have cid attached,
// assume it's an async component resolve function.
// we are not using Vue's default async resolving mechanism because
// we want to halt the navigation until the incoming component has been
// resolved.
if (typeof def === 'function' && def.cid === undefined) {
hasAsync = true
pending++
const resolve = once(resolvedDef => {
if (isESModule(resolvedDef)) {
resolvedDef = resolvedDef.default
}
// save resolved on async factory in case it's used elsewhere
def.resolved = typeof resolvedDef === 'function'
? resolvedDef
: _Vue.extend(resolvedDef)
match.components[key] = resolvedDef
pending--
if (pending <= 0) {
next()
}
})
const reject = once(reason => {
const msg = `Failed to resolve async component ${key}: ${reason}`
process.env.NODE_ENV !== 'production' && warn(false, msg)
if (!error) {
error = isError(reason)
? reason
: new Error(msg)
next(error)
}
})
let res
try {
res = def(resolve, reject)
} catch (e) {
reject(e)
}
if (res) {
if (typeof res.then === 'function') {
res.then(resolve, reject)
} else {
// new syntax in Vue 2.3
const comp = res.component
if (comp && typeof comp.then === 'function') {
comp.then(resolve, reject)
}
}
}
}
})
if (!hasAsync) next()
}
}
export function flatMapComponents (
matched: Array<RouteRecord>,
fn: Function
): Array<?Function> {
return flatten(matched.map(m => {
return Object.keys(m.components).map(key => fn(
m.components[key],
m.instances[key],
m, key
))
}))
}
export function flatten (arr: Array<any>): Array<any> {
return Array.prototype.concat.apply([], arr)
}
const hasSymbol =
typeof Symbol === 'function' &&
typeof Symbol.toStringTag === 'symbol'
function isESModule (obj) {
return obj.__esModule || (hasSymbol && obj[Symbol.toStringTag] === 'Module')
}
// in Webpack 2, require.ensure now also returns a Promise
// so the resolve/reject functions may get called an extra time
// if the user uses an arrow function shorthand that happens to
// return that Promise.
function once (fn) {
let called = false
return function (...args) {
if (called) return
called = true
return fn.apply(this, args)
}
}
+132
View File
@@ -0,0 +1,132 @@
/* @flow */
import type VueRouter from '../index'
import { stringifyQuery } from './query'
const trailingSlashRE = /\/?$/
export function createRoute (
record: ?RouteRecord,
location: Location,
redirectedFrom?: ?Location,
router?: VueRouter
): Route {
const stringifyQuery = router && router.options.stringifyQuery
let query: any = location.query || {}
try {
query = clone(query)
} catch (e) {}
const route: Route = {
name: location.name || (record && record.name),
meta: (record && record.meta) || {},
path: location.path || '/',
hash: location.hash || '',
query,
params: location.params || {},
fullPath: getFullPath(location, stringifyQuery),
matched: record ? formatMatch(record) : []
}
if (redirectedFrom) {
route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery)
}
return Object.freeze(route)
}
function clone (value) {
if (Array.isArray(value)) {
return value.map(clone)
} else if (value && typeof value === 'object') {
const res = {}
for (const key in value) {
res[key] = clone(value[key])
}
return res
} else {
return value
}
}
// the starting route that represents the initial state
export const START = createRoute(null, {
path: '/'
})
function formatMatch (record: ?RouteRecord): Array<RouteRecord> {
const res = []
while (record) {
res.unshift(record)
record = record.parent
}
return res
}
function getFullPath (
{ path, query = {}, hash = '' },
_stringifyQuery
): string {
const stringify = _stringifyQuery || stringifyQuery
return (path || '/') + stringify(query) + hash
}
export function isSameRoute (a: Route, b: ?Route): boolean {
if (b === START) {
return a === b
} else if (!b) {
return false
} else if (a.path && b.path) {
return (
a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') &&
a.hash === b.hash &&
isObjectEqual(a.query, b.query)
)
} else if (a.name && b.name) {
return (
a.name === b.name &&
a.hash === b.hash &&
isObjectEqual(a.query, b.query) &&
isObjectEqual(a.params, b.params)
)
} else {
return false
}
}
function isObjectEqual (a = {}, b = {}): boolean {
// handle null value #1566
if (!a || !b) return a === b
const aKeys = Object.keys(a)
const bKeys = Object.keys(b)
if (aKeys.length !== bKeys.length) {
return false
}
return aKeys.every(key => {
const aVal = a[key]
const bVal = b[key]
// check nested equality
if (typeof aVal === 'object' && typeof bVal === 'object') {
return isObjectEqual(aVal, bVal)
}
return String(aVal) === String(bVal)
})
}
export function isIncludedRoute (current: Route, target: Route): boolean {
return (
current.path.replace(trailingSlashRE, '/').indexOf(
target.path.replace(trailingSlashRE, '/')
) === 0 &&
(!target.hash || current.hash === target.hash) &&
queryIncludes(current.query, target.query)
)
}
function queryIncludes (current: Dictionary<string>, target: Dictionary<string>): boolean {
for (const key in target) {
if (!(key in current)) {
return false
}
}
return true
}
+165
View File
@@ -0,0 +1,165 @@
/* @flow */
import type Router from '../index'
import { assert } from './warn'
import { getStateKey, setStateKey } from './state-key'
import { extend } from './misc'
const positionStore = Object.create(null)
export function setupScroll () {
// Prevent browser scroll behavior on History popstate
if ('scrollRestoration' in window.history) {
window.history.scrollRestoration = 'manual'
}
// Fix for #1585 for Firefox
// Fix for #2195 Add optional third attribute to workaround a bug in safari https://bugs.webkit.org/show_bug.cgi?id=182678
// Fix for #2774 Support for apps loaded from Windows file shares not mapped to network drives: replaced location.origin with
// window.location.protocol + '//' + window.location.host
// location.host contains the port and location.hostname doesn't
const protocolAndPath = window.location.protocol + '//' + window.location.host
const absolutePath = window.location.href.replace(protocolAndPath, '')
// preserve existing history state as it could be overriden by the user
const stateCopy = extend({}, window.history.state)
stateCopy.key = getStateKey()
window.history.replaceState(stateCopy, '', absolutePath)
window.addEventListener('popstate', handlePopState)
return () => {
window.removeEventListener('popstate', handlePopState)
}
}
export function handleScroll (
router: Router,
to: Route,
from: Route,
isPop: boolean
) {
if (!router.app) {
return
}
const behavior = router.options.scrollBehavior
if (!behavior) {
return
}
if (process.env.NODE_ENV !== 'production') {
assert(typeof behavior === 'function', `scrollBehavior must be a function`)
}
// wait until re-render finishes before scrolling
router.app.$nextTick(() => {
const position = getScrollPosition()
const shouldScroll = behavior.call(
router,
to,
from,
isPop ? position : null
)
if (!shouldScroll) {
return
}
if (typeof shouldScroll.then === 'function') {
shouldScroll
.then(shouldScroll => {
scrollToPosition((shouldScroll: any), position)
})
.catch(err => {
if (process.env.NODE_ENV !== 'production') {
assert(false, err.toString())
}
})
} else {
scrollToPosition(shouldScroll, position)
}
})
}
export function saveScrollPosition () {
const key = getStateKey()
if (key) {
positionStore[key] = {
x: window.pageXOffset,
y: window.pageYOffset
}
}
}
function handlePopState (e) {
saveScrollPosition()
if (e.state && e.state.key) {
setStateKey(e.state.key)
}
}
function getScrollPosition (): ?Object {
const key = getStateKey()
if (key) {
return positionStore[key]
}
}
function getElementPosition (el: Element, offset: Object): Object {
const docEl: any = document.documentElement
const docRect = docEl.getBoundingClientRect()
const elRect = el.getBoundingClientRect()
return {
x: elRect.left - docRect.left - offset.x,
y: elRect.top - docRect.top - offset.y
}
}
function isValidPosition (obj: Object): boolean {
return isNumber(obj.x) || isNumber(obj.y)
}
function normalizePosition (obj: Object): Object {
return {
x: isNumber(obj.x) ? obj.x : window.pageXOffset,
y: isNumber(obj.y) ? obj.y : window.pageYOffset
}
}
function normalizeOffset (obj: Object): Object {
return {
x: isNumber(obj.x) ? obj.x : 0,
y: isNumber(obj.y) ? obj.y : 0
}
}
function isNumber (v: any): boolean {
return typeof v === 'number'
}
const hashStartsWithNumberRE = /^#\d/
function scrollToPosition (shouldScroll, position) {
const isObject = typeof shouldScroll === 'object'
if (isObject && typeof shouldScroll.selector === 'string') {
// getElementById would still fail if the selector contains a more complicated query like #main[data-attr]
// but at the same time, it doesn't make much sense to select an element with an id and an extra selector
const el = hashStartsWithNumberRE.test(shouldScroll.selector) // $flow-disable-line
? document.getElementById(shouldScroll.selector.slice(1)) // $flow-disable-line
: document.querySelector(shouldScroll.selector)
if (el) {
let offset =
shouldScroll.offset && typeof shouldScroll.offset === 'object'
? shouldScroll.offset
: {}
offset = normalizeOffset(offset)
position = getElementPosition(el, offset)
} else if (isValidPosition(shouldScroll)) {
position = normalizePosition(shouldScroll)
}
} else if (isObject && isValidPosition(shouldScroll)) {
position = normalizePosition(shouldScroll)
}
if (position) {
window.scrollTo(position.x, position.y)
}
}
+22
View File
@@ -0,0 +1,22 @@
/* @flow */
import { inBrowser } from './dom'
// use User Timing api (if present) for more accurate key precision
const Time =
inBrowser && window.performance && window.performance.now
? window.performance
: Date
export function genStateKey (): string {
return Time.now().toFixed(3)
}
let _key: string = genStateKey()
export function getStateKey () {
return _key
}
export function setStateKey (key: string) {
return (_key = key)
}
+21
View File
@@ -0,0 +1,21 @@
/* @flow */
export function assert (condition: any, message: string) {
if (!condition) {
throw new Error(`[vue-router] ${message}`)
}
}
export function warn (condition: any, message: string) {
if (process.env.NODE_ENV !== 'production' && !condition) {
typeof console !== 'undefined' && console.warn(`[vue-router] ${message}`)
}
}
export function isError (err: any): boolean {
return Object.prototype.toString.call(err).indexOf('Error') > -1
}
export function isRouterError (err: any, errorType: ?string): boolean {
return isError(err) && err._isRouter && (errorType == null || err.type === errorType)
}
+17
View File
@@ -0,0 +1,17 @@
import './vue'
import { VueRouter } from './router'
export default VueRouter
export {
RouterMode,
RawLocation,
RedirectOption,
RouterOptions,
RouteConfig,
RouteRecord,
Location,
Route,
NavigationGuard,
NavigationGuardNext
} from './router'
+158
View File
@@ -0,0 +1,158 @@
import Vue, { ComponentOptions, PluginFunction, AsyncComponent } from 'vue'
type Component = ComponentOptions<Vue> | typeof Vue | AsyncComponent
type Dictionary<T> = { [key: string]: T }
type ErrorHandler = (err: Error) => void
export type RouterMode = 'hash' | 'history' | 'abstract'
export type RawLocation = string | Location
export type RedirectOption = RawLocation | ((to: Route) => RawLocation)
export type NavigationGuardNext<V extends Vue = Vue> = (
to?: RawLocation | false | ((vm: V) => any) | void
) => void
export type NavigationGuard<V extends Vue = Vue> = (
to: Route,
from: Route,
next: NavigationGuardNext<V>
) => any
export declare class VueRouter {
constructor(options?: RouterOptions)
app: Vue
mode: RouterMode
currentRoute: Route
beforeEach(guard: NavigationGuard): Function
beforeResolve(guard: NavigationGuard): Function
afterEach(hook: (to: Route, from: Route) => any): Function
push(location: RawLocation): Promise<Route>
replace(location: RawLocation): Promise<Route>
push(
location: RawLocation,
onComplete?: Function,
onAbort?: ErrorHandler
): void
replace(
location: RawLocation,
onComplete?: Function,
onAbort?: ErrorHandler
): void
go(n: number): void
back(): void
forward(): void
getMatchedComponents(to?: RawLocation | Route): Component[]
onReady(cb: Function, errorCb?: ErrorHandler): void
onError(cb: ErrorHandler): void
addRoutes(routes: RouteConfig[]): void
resolve(
to: RawLocation,
current?: Route,
append?: boolean
): {
location: Location
route: Route
href: string
// backwards compat
normalizedTo: Location
resolved: Route
}
static install: PluginFunction<never>
}
type Position = { x: number; y: number }
type PositionResult = Position | { selector: string; offset?: Position } | void
export interface RouterOptions {
routes?: RouteConfig[]
mode?: RouterMode
fallback?: boolean
base?: string
linkActiveClass?: string
linkExactActiveClass?: string
parseQuery?: (query: string) => Object
stringifyQuery?: (query: Object) => string
scrollBehavior?: (
to: Route,
from: Route,
savedPosition: Position | void
) => PositionResult | Promise<PositionResult> | undefined | null
}
type RoutePropsFunction = (route: Route) => Object
export interface PathToRegexpOptions {
sensitive?: boolean
strict?: boolean
end?: boolean
}
interface _RouteConfigBase {
path: string
name?: string
children?: RouteConfig[]
redirect?: RedirectOption
alias?: string | string[]
meta?: any
beforeEnter?: NavigationGuard
caseSensitive?: boolean
pathToRegexpOptions?: PathToRegexpOptions
}
interface RouteConfigSingleView extends _RouteConfigBase {
component?: Component
props?: boolean | Object | RoutePropsFunction
}
interface RouteConfigMultipleViews extends _RouteConfigBase {
components?: Dictionary<Component>
props?: Dictionary<boolean | Object | RoutePropsFunction>
}
export type RouteConfig = RouteConfigSingleView | RouteConfigMultipleViews
export interface RouteRecord {
path: string
regex: RegExp
components: Dictionary<Component>
instances: Dictionary<Vue>
name?: string
parent?: RouteRecord
redirect?: RedirectOption
matchAs?: string
meta: any
beforeEnter?: (
route: Route,
redirect: (location: RawLocation) => void,
next: () => void
) => any
props:
| boolean
| Object
| RoutePropsFunction
| Dictionary<boolean | Object | RoutePropsFunction>
}
export interface Location {
name?: string
path?: string
hash?: string
query?: Dictionary<string | (string | null)[] | null | undefined>
params?: Dictionary<string>
append?: boolean
replace?: boolean
}
export interface Route {
path: string
name?: string | null
hash: string
query: Dictionary<string | (string | null)[]>
params: Dictionary<string>
fullPath: string
matched: RouteRecord[]
redirectedFrom?: string
meta?: any
}
+22
View File
@@ -0,0 +1,22 @@
/**
* Augment the typings of Vue.js
*/
import Vue from 'vue'
import VueRouter, { Route, RawLocation, NavigationGuard } from './index'
declare module 'vue/types/vue' {
interface Vue {
$router: VueRouter
$route: Route
}
}
declare module 'vue/types/options' {
interface ComponentOptions<V extends Vue> {
router?: VueRouter
beforeRouteEnter?: NavigationGuard<V>
beforeRouteLeave?: NavigationGuard<V>
beforeRouteUpdate?: NavigationGuard<V>
}
}