Skip to content

feat: useRoute and useRouter #3769

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Aug 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
dist
*.ts
29 changes: 22 additions & 7 deletions build/configs.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -31,35 +30,49 @@ 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.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('./dist/composables.mjs'),
format: 'es'
},
{
input: resolve('src/composables/index.js'),
file: resolve('./dist/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(),
cjs(),
replace({
__VERSION__: version
})
]
],
external: ['vue']
},
output: {
file: opts.file,
Expand All @@ -70,9 +83,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) {
Expand Down
87 changes: 87 additions & 0 deletions dist/composables.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*!
* vue-router v3.5.4
* (c) 2022 Evan You
* @license MIT
*/
'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var vue = require('vue');

function useRouter () {
var i = vue.getCurrentInstance();
if (process.env.NODE_ENV !== 'production' && !i) {
throwNoCurrentInstance('useRouter');
}

return i.proxy.$root.$router
}

function useRoute () {
var i = vue.getCurrentInstance();
if (process.env.NODE_ENV !== 'production' && !i) {
throwNoCurrentInstance('useRoute');
}

var root = i.proxy.$root;
if (!root._$route) {
var route = vue.effectScope(true).run(
function () { return vue.shallowReactive(Object.assign({}, root.$router.currentRoute)); }
);
root._$route = route;

root.$router.afterEach(function (to) {
Object.assign(route, to);
});
}

return root._$route
}

// TODO:
// export function useLink () {}

function onBeforeRouteUpdate (guard) {
var i = vue.getCurrentInstance();
if (process.env.NODE_ENV !== 'production' && !i) {
throwNoCurrentInstance('onBeforeRouteUpdate');
}

var router = useRouter();

var target = i.proxy;
// find the nearest routerview to know the depth
while (target && target.$vnode && target.$vnode.data && target.$vnode.data.routerViewDepth == null) {
target = target.$parent;
}

var depth = target && target.$vnode && target.$vnode.data ? target.$vnode.data.routerViewDepth : null;

console.log('found depth', depth);

// TODO: allow multiple guards?
i.proxy.$options.beforeRouteUpdate = guard;

var removeGuard = router.beforeEach(function (to, from, next) {
// TODO: check it's an update
return guard(to, from, next)
});

vue.onUnmounted(removeGuard);

return removeGuard
}

// TODO:
// export function onBeforeRouteLeave () {}

function throwNoCurrentInstance (method) {
throw new Error(
("[vue-router]: Missing current instance. " + method + "() must be called inside <script setup> or setup().")
)
}

exports.onBeforeRouteUpdate = onBeforeRouteUpdate;
exports.useRoute = useRoute;
exports.useRouter = useRouter;
81 changes: 81 additions & 0 deletions dist/composables.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*!
* vue-router v3.5.4
* (c) 2022 Evan You
* @license MIT
*/
import { getCurrentInstance, effectScope, shallowReactive, onUnmounted } from 'vue';

function useRouter () {
var i = getCurrentInstance();
if (process.env.NODE_ENV !== 'production' && !i) {
throwNoCurrentInstance('useRouter');
}

return i.proxy.$root.$router
}

function useRoute () {
var i = getCurrentInstance();
if (process.env.NODE_ENV !== 'production' && !i) {
throwNoCurrentInstance('useRoute');
}

var root = i.proxy.$root;
if (!root._$route) {
var route = effectScope(true).run(
function () { return shallowReactive(Object.assign({}, root.$router.currentRoute)); }
);
root._$route = route;

root.$router.afterEach(function (to) {
Object.assign(route, to);
});
}

return root._$route
}

// TODO:
// export function useLink () {}

function onBeforeRouteUpdate (guard) {
var i = getCurrentInstance();
if (process.env.NODE_ENV !== 'production' && !i) {
throwNoCurrentInstance('onBeforeRouteUpdate');
}

var router = useRouter();

var target = i.proxy;
// find the nearest routerview to know the depth
while (target && target.$vnode && target.$vnode.data && target.$vnode.data.routerViewDepth == null) {
target = target.$parent;
}

var depth = target && target.$vnode && target.$vnode.data ? target.$vnode.data.routerViewDepth : null;

console.log('found depth', depth);

// TODO: allow multiple guards?
i.proxy.$options.beforeRouteUpdate = guard;

var removeGuard = router.beforeEach(function (to, from, next) {
// TODO: check it's an update
return guard(to, from, next)
});

onUnmounted(removeGuard);

return removeGuard
}

// TODO:
// export function onBeforeRouteLeave () {}

function throwNoCurrentInstance (method) {
throw new Error(
("[vue-router]: Missing current instance. " + method + "() must be called inside <script setup> or setup().")
)
}

export { onBeforeRouteUpdate, useRoute, useRouter };
146 changes: 146 additions & 0 deletions examples/composables/app.js
Original file line number Diff line number Diff line change
@@ -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: `
<div>
<h3>Foo</h3>
{{ route.fullPath }}
</div>
`
})

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: `
<div>
<h2>Home</h2>
<p id="start-route">{{ startRoute }}</p>
<p id='watch-count'>{{ watchCount }}</p>
<p id="fullpath">{{ route.fullPath }}</p>
<button id="nav" @click="navigate">Navigate</button>
<hr>
<Foo />
</div>
`,
components: { Foo }
})

const About = defineComponent({
setup () {
const route = useRoute()
return { route }
},
template: `
<div>
<h2>About</h2>
<p id="fullpath">{{ route.fullPath }}</p>
</div>
`
})

const Nested = defineComponent({
template: `<RouterView />`
})

const NestedEmpty = defineComponent({
template: `<div>NestedEmpty</div>`
})

const NestedA = defineComponent({
template: `<div>NestedA</div>`
})

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: `
<div id="app">
<h1>Basic</h1>
<ul>
<li><router-link to="/">/</router-link></li>
<li><router-link to="/about">/about</router-link></li>
<li><router-link to="/nested">/nested</router-link></li>
<li><router-link to="/nested/a">/nested/a</router-link></li>
</ul>
<router-view class="view"></router-view>

<pre id="nested-active" @click="navigate">{{ href }}: {{ isActive }}, {{ isExactActive }}</pre>
</div>
`,
setup () {
const { href, isActive, isExactActive, navigate, route } = useLink({
to: '/nested'
})

return { href, isActive, navigate, route, isExactActive }
}
}).$mount('#app')
Loading