diff --git a/.eslintignore b/.eslintignore index 1521c8b76..2b88bf081 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ dist +*.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b407187e..520f3ff13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,64 @@ +## [3.6.5](https://github.com/vuejs/vue-router/compare/v3.6.4...v3.6.5) (2022-09-06) + +### Bug Fixes + +- **types:** Component with 4 generics for Vue 2.6 ([d6064df](https://github.com/vuejs/vue-router/commit/d6064df1112497dac98e4302d81607efdb1a58c6)), closes [#3786](https://github.com/vuejs/vue-router/issues/3786) + +## [3.6.4](https://github.com/vuejs/vue-router/compare/v3.6.3...v3.6.4) (2022-08-25) + +This release fixes some compatibility issues of the new `vue-router/composables` with webpack 4. + +### Features + +- **types:** add composables.d.ts in root ([#3784](https://github.com/vuejs/vue-router/issues/3784)) ([0cf54de](https://github.com/vuejs/vue-router/commit/0cf54de782a0b05692bbe78a7181495b6a35b8d9)) + +## [3.6.3](https://github.com/vuejs/vue-router/compare/v3.6.2...v3.6.3) (2022-08-23) + +### Bug Fixes + +- **build:** export all named exports esm build ([a6647c8](https://github.com/vuejs/vue-router/commit/a6647c8c3d7022f1b702935461c7d234b052ca06)) +- **types:** allow jsx components ([0cb86b3](https://github.com/vuejs/vue-router/commit/0cb86b3865b713201f9db49c7a8d23e9a2876f29)), closes [#3776](https://github.com/vuejs/vue-router/issues/3776) +- **types:** missing NavigationFailureType and isNavigationFailure ([#3777](https://github.com/vuejs/vue-router/issues/3777)) ([9d001dd](https://github.com/vuejs/vue-router/commit/9d001dd0bebdea1e1a8ec2f0c77113b6a2e2b6a3)) + +## [3.6.2](https://github.com/vuejs/vue-router/compare/v3.6.1...v3.6.2) (2022-08-23) + +### Bug Fixes + +- **build:** add mjs build ([b4c3940](https://github.com/vuejs/vue-router/commit/b4c39404eff7ae2f657c405d7b0f939ce20cfdec)) +- **types:** missing start location ([1356acb](https://github.com/vuejs/vue-router/commit/1356acb983c5eccb00c5c0ec3f406218ae49a8c1)) + +## [3.6.1](https://github.com/vuejs/vue-router/compare/v3.6.0...v3.6.1) (2022-08-23) + +### Bug Fixes + +- **build:** ensure install fn before Vue.use ([0126bcb](https://github.com/vuejs/vue-router/commit/0126bcbfb0e3cb824bfce05090ca018faf02ce5e)), closes [#3772](https://github.com/vuejs/vue-router/issues/3772) + +# [3.6.0](https://github.com/vuejs/vue-router/compare/v3.5.4...v3.6.0) (2022-08-22) + +This release of Vue Router introduces composables from Vue Router 4: + +```js +import { useRoute, useRouter, useLink, onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router/composables' +``` + +Since these composables **require Vue 2.7**, they are only exposed under `vue-router/composables` submodule, so it shouldn't affect you if you stay on Vue 2.6 (Note there are no new features or fixes besides the composables in this release). Refer to [the Vue Router 4 API documentation](https://router.vuejs.org/api/#onbeforerouteleave) for details on the composables. + +### Features + +- **types:** useLink() ([77bd0e3](https://github.com/vuejs/vue-router/commit/77bd0e317dd5a9aebfca515f0f28f3284c7d8260)) +- useLink() ([50332e5](https://github.com/vuejs/vue-router/commit/50332e5e93e6aa1194a9e68a60937a6f9e8bcecd)) +- **types:** expose RouterLink and RouterView in d.ts ([cad978a](https://github.com/vuejs/vue-router/commit/cad978a832174aac59cad86fe780f8a64a9754d7)) +- add RouterLink and RouterView to esm ([4511f39](https://github.com/vuejs/vue-router/commit/4511f393334247c9702ed378220bf925cdc09add)) +- add vue 2.7 types ([cba9650](https://github.com/vuejs/vue-router/commit/cba9650e5cbf958c1db9cd259a2e7bfbc28bddbe)) +- onBeforeRouteUpdate onBeforeRouteLeave ([9861c55](https://github.com/vuejs/vue-router/commit/9861c553627f5f34a07ad3ac28e2ed02aab99d47)) +- useRoute and useRouter ([ea35594](https://github.com/vuejs/vue-router/commit/ea355943e097914ae55fa54ccb7df929c901e80d)) + +## [3.5.4](https://github.com/vuejs/vue-router/compare/v3.5.3...v3.5.4) (2022-05-16) + +### Bug Fixes + +- remove whitespace between mulitple slashes ([86d7f1f](https://github.com/vuejs/vue-router/commit/86d7f1fdaa36432f6564309925690ec20bb2981e)), closes [#3743](https://github.com/vuejs/vue-router/issues/3743) + ## [3.5.3](https://github.com/vuejs/vue-router/compare/v3.5.2...v3.5.3) (2021-10-26) ### Bug Fixes diff --git a/README.md b/README.md index 83dfce166..40cba38ce 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # 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). +> 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). +> - For Vue Router 4 (for Vue 3) see [vuejs/router](https://github.com/vuejs/router).

Supporting Vue Router

@@ -10,15 +12,6 @@ Vue Router is part of the Vue Ecosystem and is an MIT-licensed open source proje - [One-time donation via PayPal](https://paypal.me/posva) -

Platinum Sponsors

-

- - - - Finogeeks - - -

Gold Sponsors

@@ -44,12 +37,6 @@ Vue Router is part of the Vue Ecosystem and is an MIT-licensed open source proje Prefect - - - - Bird Eats Bug - -

Bronze Sponsors

@@ -84,25 +71,25 @@ Vue Router is part of the Vue Ecosystem and is an MIT-licensed open source proje --- -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). +Get started with the [documentation](http://v3.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 +yarn # build dist files -npm run build +yarn build # serve examples at localhost:8080 -npm run dev +yarn dev # lint & run all tests -npm test +yarn test # serve docs at localhost:8080 -npm run docs +yarn docs ``` ## Releasing diff --git a/build/configs.js b/build/configs.js index 33117a641..4fbfa6d24 100644 --- a/build/configs.js +++ b/build/configs.js @@ -5,8 +5,7 @@ const cjs = require('@rollup/plugin-commonjs') const node = require('@rollup/plugin-node-resolve').nodeResolve const replace = require('rollup-plugin-replace') const version = process.env.VERSION || require('../package.json').version -const banner = -`/*! +const banner = `/*! * vue-router v${version} * (c) ${new Date().getFullYear()} Evan You * @license MIT @@ -31,27 +30,45 @@ module.exports = [ format: 'cjs' }, { + input: resolve('src/entries/esm.js'), file: resolve('dist/vue-router.esm.js'), format: 'es' }, { + input: resolve('src/entries/esm.js'), + file: resolve('dist/vue-router.mjs'), + format: 'es' + }, + { + input: resolve('src/entries/esm.js'), file: resolve('dist/vue-router.esm.browser.js'), format: 'es', env: 'development', transpile: false }, { + input: resolve('src/entries/esm.js'), file: resolve('dist/vue-router.esm.browser.min.js'), format: 'es', env: 'production', transpile: false + }, + { + input: resolve('src/composables/index.js'), + file: resolve('./composables.mjs'), + format: 'es' + }, + { + input: resolve('src/composables/index.js'), + file: resolve('./composables.js'), + format: 'cjs' } ].map(genConfig) function genConfig (opts) { const config = { input: { - input: resolve('src/index.js'), + input: opts.input || resolve('src/index.js'), plugins: [ flow(), node(), @@ -59,7 +76,8 @@ function genConfig (opts) { replace({ __VERSION__: version }) - ] + ], + external: ['vue'] }, output: { file: opts.file, @@ -70,9 +88,11 @@ function genConfig (opts) { } if (opts.env) { - config.input.plugins.unshift(replace({ - 'process.env.NODE_ENV': JSON.stringify(opts.env) - })) + config.input.plugins.unshift( + replace({ + 'process.env.NODE_ENV': JSON.stringify(opts.env) + }) + ) } if (opts.transpile !== false) { diff --git a/composables.d.ts b/composables.d.ts new file mode 100644 index 000000000..790fbfba8 --- /dev/null +++ b/composables.d.ts @@ -0,0 +1 @@ +export * from './types/composables' diff --git a/composables.js b/composables.js new file mode 100644 index 000000000..18d96d549 --- /dev/null +++ b/composables.js @@ -0,0 +1,257 @@ +/*! + * vue-router v3.6.5 + * (c) 2022 Evan You + * @license MIT + */ +'use strict' + +Object.defineProperty(exports, '__esModule', { value: true }) + +var vue = require('vue') + +// dev only warn if no current instance + +function throwNoCurrentInstance (method) { + if (!vue.getCurrentInstance()) { + throw new Error( + ('[vue-router]: Missing current instance. ' + method + '() must be called inside - + +

Bonjour l'application !

diff --git a/docs/fr/guide/essentials/history-mode.md b/docs/fr/guide/essentials/history-mode.md index 08df6f308..8337db43a 100644 --- a/docs/fr/guide/essentials/history-mode.md +++ b/docs/fr/guide/essentials/history-mode.md @@ -22,6 +22,9 @@ Ne vous inquiétez pas. Pour résoudre ce problème, il vous suffit d'ajouter un ### Apache ```apache + + Options -MultiViews + RewriteEngine On RewriteBase / diff --git a/docs/fr/installation.md b/docs/fr/installation.md index 63a4fdccb..0fc456b1f 100644 --- a/docs/fr/installation.md +++ b/docs/fr/installation.md @@ -2,10 +2,10 @@ ## Téléchargement direct / CDN -[https://unpkg.com/vue-router/dist/vue-router.js](https://unpkg.com/vue-router/dist/vue-router.js) +[https://unpkg.com/vue-router@3/dist/vue-router.js](https://unpkg.com/vue-router@3/dist/vue-router.js) -[Unpkg.com](https://unpkg.com) fournit des liens CDN basés sur npm. Le lien ci-dessus pointera toujours vers la dernière version sur npm. Vous pouvez aussi utiliser un tag ou une version spécifique via un URL comme `https://unpkg.com/vue-router@2.0.0/dist/vue-router.js`. +[Unpkg.com](https://unpkg.com) fournit des liens CDN basés sur npm. Le lien ci-dessus pointera toujours vers la dernière version sur npm. Vous pouvez aussi utiliser un tag ou une version spécifique via un URL comme `https://unpkg.com/vue-router@3.0.0/dist/vue-router.js`. Incluez `vue-router` après Vue et l'installation sera automatique : diff --git a/docs/guide/README.md b/docs/guide/README.md index 679a1cf40..017faa45f 100644 --- a/docs/guide/README.md +++ b/docs/guide/README.md @@ -15,8 +15,8 @@ Creating a Single-page Application with Vue + Vue Router feels natural: with Vue ## HTML ```html - - + +

Hello App!

diff --git a/docs/guide/advanced/transitions.md b/docs/guide/advanced/transitions.md index ce2d5c813..0b8a937df 100644 --- a/docs/guide/advanced/transitions.md +++ b/docs/guide/advanced/transitions.md @@ -10,7 +10,7 @@ Since the `` is essentially a dynamic component, we can apply trans ``` -[All transition APIs](https://vuejs.org/guide/transitions.html) work the same here. +[All transition APIs](https://v2.vuejs.org/v2/guide/transitions.html) work the same here. ## Per-Route Transition diff --git a/docs/guide/essentials/history-mode.md b/docs/guide/essentials/history-mode.md index 4e570374b..ed27c1baa 100644 --- a/docs/guide/essentials/history-mode.md +++ b/docs/guide/essentials/history-mode.md @@ -24,6 +24,9 @@ Not to worry: To fix the issue, all you need to do is add a simple catch-all fal #### Apache ```apache + + Options -MultiViews + RewriteEngine On RewriteBase / diff --git a/docs/installation.md b/docs/installation.md index 5308864d1..d3dd4f4c3 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -2,10 +2,10 @@ ## Direct Download / CDN -[https://unpkg.com/vue-router/dist/vue-router.js](https://unpkg.com/vue-router/dist/vue-router.js) +[https://unpkg.com/vue-router@3/dist/vue-router.js](https://unpkg.com/vue-router@3/dist/vue-router.js) -[Unpkg.com](https://unpkg.com) provides npm-based CDN links. The above link will always point to the latest release on npm. You can also use a specific version/tag via URLs like `https://unpkg.com/vue-router@2.0.0/dist/vue-router.js`. +[Unpkg.com](https://unpkg.com) provides npm-based CDN links. The above link will always point to the latest release on npm. You can also use a specific version/tag via URLs like `https://unpkg.com/vue-router@3.0.0/dist/vue-router.js`. Include `vue-router` after Vue and it will install itself automatically: diff --git a/docs/ja/guide/README.md b/docs/ja/guide/README.md index 8f1606cca..a01b4da18 100644 --- a/docs/ja/guide/README.md +++ b/docs/ja/guide/README.md @@ -13,8 +13,8 @@ Vue.js と vue-router を使ったシングルページアプリケーション ## HTML ```html - - + +

Hello App!

diff --git a/docs/ja/guide/essentials/history-mode.md b/docs/ja/guide/essentials/history-mode.md index 8352471e6..5bf831441 100644 --- a/docs/ja/guide/essentials/history-mode.md +++ b/docs/ja/guide/essentials/history-mode.md @@ -24,6 +24,9 @@ history モードを使用する時は、URL は "普通" に見えます e.g. ` #### Apache ```apache + + Options -MultiViews + RewriteEngine On RewriteBase / diff --git a/docs/ja/installation.md b/docs/ja/installation.md index a61b585bd..f25bc261c 100644 --- a/docs/ja/installation.md +++ b/docs/ja/installation.md @@ -2,10 +2,10 @@ ### 直接ダウンロード / CDN -[https://unpkg.com/vue-router/dist/vue-router.js](https://unpkg.com/vue-router/dist/vue-router.js) +[https://unpkg.com/vue-router@3/dist/vue-router.js](https://unpkg.com/vue-router@3/dist/vue-router.js) -[Unpkg.com](https://unpkg.com) は npm ベースの CDN リンクです。 上記のリンクは常に NPM 上の最新のリリースを指します。 `https://unpkg.com/vue-router@2.0.0/dist/vue-router.js` のような URL を利用することで特定のバージョンやタグを指定することもできます。 +[Unpkg.com](https://unpkg.com) は npm ベースの CDN リンクです。 上記のリンクは常に NPM 上の最新のリリースを指します。 `https://unpkg.com/vue-router@3.0.0/dist/vue-router.js` のような URL を利用することで特定のバージョンやタグを指定することもできます。 Vue の後に `vue-router` を含めると自動的にインストールされます。 diff --git a/docs/kr/guide/README.md b/docs/kr/guide/README.md index 21cdcd027..3a9cae8cc 100644 --- a/docs/kr/guide/README.md +++ b/docs/kr/guide/README.md @@ -13,8 +13,8 @@ Vue와 Vue 라우터를 이용해 싱글 페이지 앱을 만드는 것은 매 ## HTML ``` html - - + +

Hello App!

diff --git a/docs/kr/guide/essentials/getting-started.md b/docs/kr/guide/essentials/getting-started.md index 4e6ca1d61..91919aa1b 100644 --- a/docs/kr/guide/essentials/getting-started.md +++ b/docs/kr/guide/essentials/getting-started.md @@ -9,8 +9,8 @@ Vue.js와 vue-router로 단일 페이지 애플리케이션을 만드는 것은 ### HTML ``` html - - + +

Hello App!

diff --git a/docs/kr/guide/essentials/history-mode.md b/docs/kr/guide/essentials/history-mode.md index e5651ca2c..5b71b53d3 100644 --- a/docs/kr/guide/essentials/history-mode.md +++ b/docs/kr/guide/essentials/history-mode.md @@ -22,6 +22,9 @@ const router = new VueRouter({ #### Apache ```apache + + Options -MultiViews + RewriteEngine On RewriteBase / diff --git a/docs/kr/installation.md b/docs/kr/installation.md index 5187b078d..1def91f0c 100644 --- a/docs/kr/installation.md +++ b/docs/kr/installation.md @@ -2,10 +2,10 @@ ### 직접 다운로드 / CDN -[https://unpkg.com/vue-router/dist/vue-router.js](https://unpkg.com/vue-router/dist/vue-router.js) +[https://unpkg.com/vue-router@3/dist/vue-router.js](https://unpkg.com/vue-router@3/dist/vue-router.js) -[Unpkg.com](https://unpkg.com)은 NPM 기반 CDN 링크를 제공합니다. 위의 링크는 항상 NPM의 최신 릴리스를 가리킵니다. `https://unpkg.com/vue-router@2.0.0/dist/vue-router.js`와 같이 URL을 통해 특정 버전 / 태그를 사용할 수도 있습니다. +[Unpkg.com](https://unpkg.com)은 NPM 기반 CDN 링크를 제공합니다. 위의 링크는 항상 NPM의 최신 릴리스를 가리킵니다. `https://unpkg.com/vue-router@3.0.0/dist/vue-router.js`와 같이 URL을 통해 특정 버전 / 태그를 사용할 수도 있습니다. Vue 다음에 `vue-router`를 포함하면 자동으로 설치됩니다. diff --git a/docs/ru/guide/README.md b/docs/ru/guide/README.md index beea6bb0e..cf31e84d5 100644 --- a/docs/ru/guide/README.md +++ b/docs/ru/guide/README.md @@ -11,8 +11,8 @@ ## HTML ```html - - + +

Первое приложение!

diff --git a/docs/ru/guide/essentials/history-mode.md b/docs/ru/guide/essentials/history-mode.md index 25d6c1d2e..289f08b78 100644 --- a/docs/ru/guide/essentials/history-mode.md +++ b/docs/ru/guide/essentials/history-mode.md @@ -24,6 +24,9 @@ const router = new VueRouter({ #### Apache ```apache + + Options -MultiViews + RewriteEngine On RewriteBase / diff --git a/docs/ru/installation.md b/docs/ru/installation.md index 5e7ce264a..5ac66914f 100644 --- a/docs/ru/installation.md +++ b/docs/ru/installation.md @@ -2,10 +2,10 @@ ## Скачивание напрямую / CDN -[https://unpkg.com/vue-router/dist/vue-router.js](https://unpkg.com/vue-router/dist/vue-router.js) +[https://unpkg.com/vue-router@3/dist/vue-router.js](https://unpkg.com/vue-router@3/dist/vue-router.js) -[Unpkg.com](https://unpkg.com) предоставляет CDN-ссылки для NPM-пакетов. Ссылка выше всегда указывает на самую последнюю версию Vue-router на NPM. Вы можете также использовать конкретную версию, используя ссылки вида `https://unpkg.com/vue-router@2.0.0/dist/vue-router.js`. +[Unpkg.com](https://unpkg.com) предоставляет CDN-ссылки для NPM-пакетов. Ссылка выше всегда указывает на самую последнюю версию Vue-router на NPM. Вы можете также использовать конкретную версию, используя ссылки вида `https://unpkg.com/vue-router@3.0.0/dist/vue-router.js`. Подключите `vue-router` после Vue, и установка произойдёт автоматически: diff --git a/docs/zh/guide/README.md b/docs/zh/guide/README.md index 4c5348820..f03de3092 100644 --- a/docs/zh/guide/README.md +++ b/docs/zh/guide/README.md @@ -13,8 +13,8 @@ ## HTML ```html - - + +

Hello App!

diff --git a/docs/zh/guide/advanced/transitions.md b/docs/zh/guide/advanced/transitions.md index 877553486..2039bb6a9 100644 --- a/docs/zh/guide/advanced/transitions.md +++ b/docs/zh/guide/advanced/transitions.md @@ -10,7 +10,7 @@ ``` -[Transition 的所有功能](https://cn.vuejs.org/guide/transitions.html) 在这里同样适用。 +[Transition 的所有功能](https://v2.cn.vuejs.org/v2/guide/transitions.html) 在这里同样适用。 ## 单个路由的过渡 diff --git a/docs/zh/guide/essentials/history-mode.md b/docs/zh/guide/essentials/history-mode.md index 74351d9bd..0f6a5e6ec 100644 --- a/docs/zh/guide/essentials/history-mode.md +++ b/docs/zh/guide/essentials/history-mode.md @@ -24,6 +24,9 @@ const router = new VueRouter({ #### Apache ```apache + + Options -MultiViews + RewriteEngine On RewriteBase / diff --git a/docs/zh/installation.md b/docs/zh/installation.md index d496daa03..0ae1c03f3 100644 --- a/docs/zh/installation.md +++ b/docs/zh/installation.md @@ -2,10 +2,10 @@ ### 直接下载 / CDN -[https://unpkg.com/vue-router/dist/vue-router.js](https://unpkg.com/vue-router/dist/vue-router.js) +[https://unpkg.com/vue-router@3/dist/vue-router.js](https://unpkg.com/vue-router@3/dist/vue-router.js) -[Unpkg.com](https://unpkg.com) 提供了基于 NPM 的 CDN 链接。上面的链接会一直指向在 NPM 发布的最新版本。你也可以像 `https://unpkg.com/vue-router@2.0.0/dist/vue-router.js` 这样指定 版本号 或者 Tag。 +[Unpkg.com](https://unpkg.com) 提供了基于 NPM 的 CDN 链接。上面的链接会一直指向在 NPM 发布的最新版本。你也可以像 `https://unpkg.com/vue-router@3.0.0/dist/vue-router.js` 这样指定 版本号 或者 Tag。 在 Vue 后面加载 `vue-router`,它会自动安装的: diff --git a/examples/composables/app.js b/examples/composables/app.js new file mode 100644 index 000000000..1762aeea0 --- /dev/null +++ b/examples/composables/app.js @@ -0,0 +1,146 @@ +import Vue, { defineComponent, watch, ref } from 'vue' +import VueRouter from 'vue-router' +import { + useRoute, + useRouter, + onBeforeRouteLeave, + onBeforeRouteUpdate, + useLink +} from 'vue-router/composables' + +Vue.use(VueRouter) + +const Foo = defineComponent({ + setup () { + const route = useRoute() + onBeforeRouteUpdate((to, from, next) => { + console.log('Foo updating') + next() + }) + onBeforeRouteLeave((to, from, next) => { + console.log('Foo leaving') + next() + }) + + return { route } + }, + template: ` +
+

Foo

+ {{ route.fullPath }} +
+ ` +}) + +const Home = defineComponent({ + setup () { + const route = useRoute() + const router = useRouter() + + // should be / + const startRoute = route.fullPath + + onBeforeRouteUpdate((to, from, next) => { + console.log('Home updating') + next() + }) + + onBeforeRouteLeave((to, from, next) => { + console.log('Home leaving') + next() + }) + + const watchCount = ref(0) + + watch( + () => route.query.n, + () => { + watchCount.value++ + } + ) + + function navigate () { + router.push({ query: { n: 1 + (Number(route.query.n) || 0) }}) + } + return { route, navigate, watchCount, startRoute } + }, + template: ` +
+

Home

+

{{ startRoute }}

+

{{ watchCount }}

+

{{ route.fullPath }}

+ +
+ +
+ `, + components: { Foo } +}) + +const About = defineComponent({ + setup () { + const route = useRoute() + return { route } + }, + template: ` +
+

About

+

{{ route.fullPath }}

+
+ ` +}) + +const Nested = defineComponent({ + template: `` +}) + +const NestedEmpty = defineComponent({ + template: `
NestedEmpty
` +}) + +const NestedA = defineComponent({ + template: `
NestedA
` +}) + +const router = new VueRouter({ + mode: 'history', + base: __dirname, + routes: [ + { path: '/', component: Home }, + { + path: '/nested', + component: Nested, + children: [ + { path: '', component: NestedEmpty }, + { path: 'a', component: NestedA } + ] + }, + { path: '/about', component: About } + ] +}) + +new Vue({ + router, + template: ` +
+

Basic

+
    +
  • /
  • +
  • /about
  • +
  • /nested
  • +
  • /nested/a
  • +
+ + +
{{ href }}: {{ isActive }}, {{ isExactActive }}
+
+ `, + setup () { + const { href, isActive, isExactActive, navigate, route } = useLink({ + to: '/nested' + }) + + return { href, isActive, navigate, route, isExactActive } + } +}).$mount('#app') diff --git a/examples/composables/index.html b/examples/composables/index.html new file mode 100644 index 000000000..b6f6342a7 --- /dev/null +++ b/examples/composables/index.html @@ -0,0 +1,7 @@ + + +← Examples index +
+
+ + diff --git a/examples/index.html b/examples/index.html index 5f2cd4f32..2b41a845a 100644 --- a/examples/index.html +++ b/examples/index.html @@ -30,6 +30,7 @@

Vue Router Examples

  • Keepalive View
  • Multiple Apps
  • Restart App
  • +
  • Composables
  • diff --git a/examples/webpack.config.js b/examples/webpack.config.js index 85b68fcf4..a74f22fb4 100644 --- a/examples/webpack.config.js +++ b/examples/webpack.config.js @@ -49,7 +49,8 @@ module.exports = { resolve: { alias: { vue: 'vue/dist/vue.esm.js', - 'vue-router': path.join(__dirname, '..', 'src') + 'vue-router': path.join(__dirname, '..', 'src'), + 'vue-router/composables': path.join(__dirname, '..', 'src/composables') } }, diff --git a/package.json b/package.json index c44a25fd8..816a32865 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-router", - "version": "3.5.4", + "version": "3.6.5", "description": "Official router for Vue.js 2", "author": "Evan You", "license": "MIT", @@ -17,10 +17,32 @@ "files": [ "src", "dist/*.js", + "dist/*.mjs", "types/*.d.ts", + "composables.mjs", + "composables.js", + "composables.d.ts", "vetur/tags.json", "vetur/attributes.json" ], + "exports": { + ".": { + "import": { + "node": "./dist/vue-router.mjs", + "default": "./dist/vue-router.esm.js" + }, + "require": "./dist/vue-router.common.js", + "types": "./types/index.d.ts" + }, + "./composables": { + "import": "./composables.mjs", + "require": "./composables.js", + "types": "./composables.d.ts" + }, + "./dist/*": "./dist/*", + "./types/*": "./types/*", + "./package.json": "./package.json" + }, "vetur": { "tags": "vetur/tags.json", "attributes": "vetur/attributes.json" @@ -64,7 +86,7 @@ "@rollup/plugin-node-resolve": "^11.0.0", "@vuepress/plugin-pwa": "^1.5.3", "@vuepress/theme-vue": "^1.5.3", - "axios": "^0.21.1", + "axios": "^0.28.0", "babel-core": "^6.24.1", "babel-eslint": "^10.0.2", "babel-loader": "^7.1.3", @@ -73,7 +95,7 @@ "babel-preset-flow-vue": "^1.0.0", "browserstack-local": "^1.4.8", "buble": "^0.19.8", - "chromedriver": "^90.0.0", + "chromedriver": "^96.0.0", "conventional-changelog-cli": "^2.0.11", "cross-spawn": "^7.0.3", "css-loader": "^2.1.1", @@ -99,11 +121,11 @@ "rollup-watch": "^4.0.0", "selenium-server": "^3.141.59", "terser": "^4.2.0", - "typescript": "^3.5.2", - "vue": "^2.6.12", + "typescript": "^4.7.0", + "vue": "^2.7.0", "vue-loader": "^15.9.3", - "vue-server-renderer": "^2.6.12", - "vue-template-compiler": "^2.6.12", + "vue-server-renderer": "^2.7.0", + "vue-template-compiler": "^2.7.0", "vuepress": "^1.5.3", "vuepress-theme-vue": "^1.1.1", "webpack": "^4.35.2", diff --git a/scripts/release.sh b/scripts/release.sh index be596e04f..6b22ad516 100644 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -11,7 +11,7 @@ then # commit VERSION=$VERSION npm run build - git add dist + git add dist composables.* git commit -m "build: bundle $VERSION" npm version $VERSION --message "chore(release): %s" @@ -25,5 +25,5 @@ then # publish git push origin refs/tags/v$VERSION git push - npm publish + npm publish --tag legacy fi diff --git a/src/components/link.js b/src/components/link.js index 9c2a25784..821b69f02 100644 --- a/src/components/link.js +++ b/src/components/link.js @@ -189,7 +189,7 @@ export default { } } -function guardEvent (e) { +export function guardEvent (e: any) { // don't redirect with control keys if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return // don't redirect when preventDefault called diff --git a/src/composables/globals.js b/src/composables/globals.js new file mode 100644 index 000000000..8331f28ec --- /dev/null +++ b/src/composables/globals.js @@ -0,0 +1,34 @@ +import { + getCurrentInstance, + shallowReactive, + effectScope +} from 'vue' +import { throwNoCurrentInstance } from './utils' + +export function useRouter () { + if (process.env.NODE_ENV !== 'production') { + throwNoCurrentInstance('useRouter') + } + + return getCurrentInstance().proxy.$root.$router +} + +export function useRoute () { + if (process.env.NODE_ENV !== 'production') { + throwNoCurrentInstance('useRoute') + } + + const root = getCurrentInstance().proxy.$root + if (!root._$route) { + const route = effectScope(true).run(() => + shallowReactive(Object.assign({}, root.$router.currentRoute)) + ) + root._$route = route + + root.$router.afterEach(to => { + Object.assign(route, to) + }) + } + + return root._$route +} diff --git a/src/composables/guards.js b/src/composables/guards.js new file mode 100644 index 000000000..6312e9401 --- /dev/null +++ b/src/composables/guards.js @@ -0,0 +1,68 @@ +import { getCurrentInstance, onUnmounted } from 'vue' +import { throwNoCurrentInstance } from './utils' +import { useRouter } from './globals' + +export function onBeforeRouteUpdate (guard) { + if (process.env.NODE_ENV !== 'production') { + throwNoCurrentInstance('onBeforeRouteUpdate') + } + + return useFilteredGuard(guard, isUpdateNavigation) +} +function isUpdateNavigation (to, from, depth) { + const toMatched = to.matched + const fromMatched = from.matched + return ( + toMatched.length >= depth && + toMatched + .slice(0, depth + 1) + .every((record, i) => record === fromMatched[i]) + ) +} + +function isLeaveNavigation (to, from, depth) { + const toMatched = to.matched + const fromMatched = from.matched + return toMatched.length < depth || toMatched[depth] !== fromMatched[depth] +} + +export function onBeforeRouteLeave (guard) { + if (process.env.NODE_ENV !== 'production') { + throwNoCurrentInstance('onBeforeRouteLeave') + } + + return useFilteredGuard(guard, isLeaveNavigation) +} + +const noop = () => {} +function useFilteredGuard (guard, fn) { + const instance = getCurrentInstance() + const router = useRouter() + + let target = instance.proxy + // find the nearest RouterView to know the depth + while ( + target && + target.$vnode && + target.$vnode.data && + target.$vnode.data.routerViewDepth == null + ) { + target = target.$parent + } + + const depth = + target && target.$vnode && target.$vnode.data + ? target.$vnode.data.routerViewDepth + : null + + if (depth != null) { + const removeGuard = router.beforeEach((to, from, next) => { + return fn(to, from, depth) ? guard(to, from, next) : next() + }) + + onUnmounted(removeGuard) + return removeGuard + } + + return noop +} diff --git a/src/composables/index.js b/src/composables/index.js new file mode 100644 index 000000000..d1cefa8cf --- /dev/null +++ b/src/composables/index.js @@ -0,0 +1,3 @@ +export * from './guards' +export * from './globals' +export * from './useLink' diff --git a/src/composables/useLink.js b/src/composables/useLink.js new file mode 100644 index 000000000..237fd3355 --- /dev/null +++ b/src/composables/useLink.js @@ -0,0 +1,113 @@ +import { computed, unref } from 'vue' +import { guardEvent } from '../components/link' +import { throwNoCurrentInstance } from './utils' +import { useRouter, useRoute } from './globals' + +function includesParams (outer, inner) { + for (const key in inner) { + const innerValue = inner[key] + const outerValue = outer[key] + if (typeof innerValue === 'string') { + if (innerValue !== outerValue) return false + } else { + if ( + !Array.isArray(outerValue) || + outerValue.length !== innerValue.length || + innerValue.some((value, i) => value !== outerValue[i]) + ) { + return false + } + } + } + + return true +} + +// helpers from vue router 4 + +function isSameRouteLocationParamsValue (a, b) { + return Array.isArray(a) + ? isEquivalentArray(a, b) + : Array.isArray(b) + ? isEquivalentArray(b, a) + : a === b +} + +function isEquivalentArray (a, b) { + return Array.isArray(b) + ? a.length === b.length && a.every((value, i) => value === b[i]) + : a.length === 1 && a[0] === b +} + +export function isSameRouteLocationParams (a, b) { + if (Object.keys(a).length !== Object.keys(b).length) return false + + for (const key in a) { + if (!isSameRouteLocationParamsValue(a[key], b[key])) return false + } + + return true +} + +export function useLink (props) { + if (process.env.NODE_ENV !== 'production') { + throwNoCurrentInstance('useLink') + } + + const router = useRouter() + const currentRoute = useRoute() + + const resolvedRoute = computed(() => router.resolve(unref(props.to), currentRoute)) + + const activeRecordIndex = computed(() => { + const route = resolvedRoute.value.route + const { matched } = route + const { length } = matched + const routeMatched = matched[length - 1] + const currentMatched = currentRoute.matched + if (!routeMatched || !currentMatched.length) return -1 + const index = currentMatched.indexOf(routeMatched) + if (index > -1) return index + // possible parent record + const parentRecord = currentMatched[currentMatched.length - 2] + + return ( + // we are dealing with nested routes + length > 1 && + // if the parent and matched route have the same path, this link is + // referring to the empty child. Or we currently are on a different + // child of the same parent + parentRecord && parentRecord === routeMatched.parent + ) + }) + + const isActive = computed( + () => + activeRecordIndex.value > -1 && + includesParams(currentRoute.params, resolvedRoute.value.route.params) + ) + const isExactActive = computed( + () => + activeRecordIndex.value > -1 && + activeRecordIndex.value === currentRoute.matched.length - 1 && + isSameRouteLocationParams(currentRoute.params, resolvedRoute.value.route.params) + ) + + const navigate = e => { + const href = resolvedRoute.value.route + if (guardEvent(e)) { + return props.replace + ? router.replace(href) + : router.push(href) + } + return Promise.resolve() + } + + return { + href: computed(() => resolvedRoute.value.href), + route: computed(() => resolvedRoute.value.route), + isExactActive, + isActive, + navigate + } +} diff --git a/src/composables/utils.js b/src/composables/utils.js new file mode 100644 index 000000000..969d0f7c0 --- /dev/null +++ b/src/composables/utils.js @@ -0,0 +1,11 @@ +import { getCurrentInstance } from 'vue' + +// dev only warn if no current instance + +export function throwNoCurrentInstance (method) { + if (!getCurrentInstance()) { + throw new Error( + `[vue-router]: Missing current instance. ${method}() must be called inside