-e2e/__build__
+__build__
dist
+coverage
"docs:build": "pnpm run -r docs:build --filter ./packages/docs",
"play": "pnpm run -r play",
"build:size": "pnpm run -r build:size",
- "lint": "prettier -c --parser typescript \"packages/*/{src,__tests__,e2e}/**/*.[jt]s?(x)\"",
- "lint:fix": "pnpm run lint --write",
+ "lint": "pnpm run lint:script && pnpm run lint:html",
+ "lint:script": "prettier -c --parser typescript \"packages/*/{src,__tests__,e2e}/**/*.[jt]s?(x)\"",
+ "lint:html": "prettier -c --parser html \"packages/**/*.html\"",
+ "lint:fix": "pnpm run lint:script --write && pnpm run lint:html --write",
"test": "pnpm run -r test"
},
"devDependencies": {
+ "@vue/compiler-sfc": "^3.2.31",
+ "@vue/server-renderer": "^3.2.37",
+ "@vue/test-utils": "^2.0.0-rc.3",
"brotli": "^1.3.3",
"chalk": "^4.1.2",
"enquirer": "^2.3.6",
"lint-staged": "^13.0.0",
"minimist": "^1.2.6",
"p-series": "^3.0.0",
+ "prettier": "^2.4.1",
"semver": "^7.3.7",
+ "typescript": "~4.7.2",
+ "vue": "^3.2.37",
+ "vue-tsc": "^0.37.2",
"yorkie": "^2.0.0"
},
"gitHooks": {
<script>
import { defineComponent, inject, computed, ref } from 'vue'
import { scrollWaiter } from './scrollWaiter'
-import { useLink, useRoute } from '../src'
+import { useLink, useRoute } from 'vue-router'
import AppLink from './AppLink.vue'
export default defineComponent({
+/// <reference types="vite/client" />
+/// <reference path="vue-router/global.d.ts"/>
+
declare module '*.vue' {
import { Component } from 'vue'
var component: Component
}
</style>
</head>
-
<body>
<div id="app"></div>
- <script type="module" src="/main.ts"></script>
+ <script type="module" src="/src/main.ts"></script>
</body>
</html>
--- /dev/null
+{
+ "name": "@vue/router-playground",
+ "private": true,
+ "version": "0.0.0",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview --port 4173"
+ },
+ "dependencies": {
+ "vue": "^3.2.36"
+ },
+ "devDependencies": {
+ "@types/node": "^16.11.36",
+ "@vitejs/plugin-vue": "^2.3.3",
+ "@vue/tsconfig": "^0.1.3",
+ "typescript": "~4.7.2",
+ "vite": "^2.9.9",
+ "vue-router": "workspace:*",
+ "vue-tsc": "^0.37.2"
+ }
+}
--- /dev/null
+<template>
+ <div>
+ <pre>{{ currentLocation }}</pre>
+ <section class="info">
+ Name:
+ <pre id="name">{{ currentLocation.name }}</pre>
+ </section>
+
+ <section class="info">
+ Params:
+ <pre id="params">{{ currentLocation.params }}</pre>
+ </section>
+
+ <section class="info">
+ Query:
+ <pre id="query">{{ currentLocation.query }}</pre>
+ </section>
+
+ <section class="info">
+ Hash:
+ <pre id="hash">{{ currentLocation.hash }}</pre>
+ </section>
+
+ <section class="info">
+ FullPath:
+ <pre id="fullPath">{{ currentLocation.fullPath }}</pre>
+ </section>
+
+ <section class="info">
+ path:
+ <pre id="path">{{ currentLocation.path }}</pre>
+ </section>
+
+ <hr />
+
+ <label>
+ <input type="checkbox" v-model="state.cancelNextNavigation" /> Cancel Next
+ Navigation
+ </label>
+ <ul>
+ <li>
+ <router-link to="/n/%E2%82%AC">/n/%E2%82%AC</router-link>
+ </li>
+ <li>
+ <router-link :to="{ name: 'docs', params: { id: '€uro' } }"
+ >/docs/€uro (object)</router-link
+ >
+ </li>
+ <li>
+ <router-link :to="{ path: '/', query: { currency: '€uro', é: 'e' } }"
+ >/currency=€uro&é=e (object)</router-link
+ >
+ </li>
+ <li>
+ <router-link to="/documents/€">/n/€</router-link>
+ </li>
+ <li>
+ <a href="/documents/%E2%82%AC">/documents/%E2%82%AC (force reload)</a>
+ </li>
+ <li>
+ <a href="/documents/€">/documents/€ (force reload): not valid tho</a>
+ </li>
+ <li>
+ <router-link to="/home">Home (redirects)</router-link>
+ </li>
+ <li>
+ <router-link to="/">Home</router-link>
+ </li>
+ <li>
+ <AppLink to="/">AppLink Home</AppLink>
+ </li>
+ <li>
+ <router-link to="/always-redirect">/always-redirect</router-link>
+ </li>
+ <li>
+ <router-link to="/children">/children</router-link>
+ </li>
+ <li>
+ <router-link to="/children/alias">/children/alias</router-link>
+ </li>
+ <li>
+ <router-link :to="{ name: 'default-child' }"
+ >/children (child named)</router-link
+ >
+ </li>
+ <li>
+ <router-link :to="{ name: 'WithChildren' }"
+ >/children (parent named)</router-link
+ >
+ </li>
+ <li>
+ <router-link to="/children/a">/children/a</router-link>
+ </li>
+ <li>
+ <router-link to="/children/b">/children/b</router-link>
+ </li>
+ <li>
+ <router-link to="/children/b/a2">/children/b/a2</router-link>
+ </li>
+ <li>
+ <router-link to="/children/b/b2">/children/b/b2</router-link>
+ </li>
+ <li>
+ <router-link to="/nested">/nested</router-link>
+ </li>
+ <li>
+ <router-link to="/anidado">/anidado</router-link>
+ </li>
+ <li>
+ <router-link to="/long-0">/long-0</router-link>
+ </li>
+ <li>
+ <router-link to="/users/5">/users/5</router-link>
+ </li>
+ <li>
+ <router-link
+ :to="{
+ name: 'user',
+ params: { id: '' + (Number(currentLocation.params.id || 0) + 1) },
+ }"
+ >/users/{{ Number(currentLocation.params.id || 0) + 1 }}</router-link
+ >
+ </li>
+ <li>
+ <router-link to="/with-data">/with-data</router-link>
+ </li>
+ <li>
+ <router-link to="/cant-leave">/cant-leave</router-link>
+ </li>
+ <li>
+ <router-link :to="{ name: 'docs', params: { id: 'é' } }"
+ >/docs/é</router-link
+ >
+ </li>
+ <li>
+ <router-link to="/rep">/rep</router-link>
+ </li>
+ <li>
+ <router-link to="/rep/a">/rep/a</router-link>
+ </li>
+ <li>
+ <router-link to="/rep/a/b">/rep/a/b</router-link>
+ </li>
+ <li>
+ <router-link to="/parent/1">/parent/1</router-link>
+ </li>
+ <li>
+ <router-link to="/p/1">/p/1</router-link>
+ </li>
+ <li>
+ <router-link to="/parent/1/as-absolute-a"
+ >/parent/1/as-absolute-a</router-link
+ >
+ </li>
+ <li>
+ <router-link to="/p/1/as-absolute-a">/p/1/as-absolute-a</router-link>
+ </li>
+ <li>
+ <router-link to="/p_1/absolute-a">/p_1/absolute-a</router-link>
+ </li>
+ </ul>
+ <button @click="toggleViewName">Toggle view</button>
+ <Suspense>
+ <template #default>
+ <router-view :name="viewName" v-slot="{ Component, route }">
+ <transition
+ :name="route.meta.transition || 'fade'"
+ mode="out-in"
+ @before-enter="flushWaiter"
+ @before-leave="setupWaiter"
+ >
+ <keep-alive>
+ <component
+ :is="Component"
+ :key="route.name === 'repeat' ? route.path : undefined"
+ />
+ </keep-alive>
+ </transition>
+ </router-view>
+ </template>
+ <template #fallback> Loading... </template>
+ </Suspense>
+ </div>
+</template>
+
+<script>
+import { defineComponent, inject, computed, ref } from 'vue'
+import { scrollWaiter } from './scrollWaiter'
+import { useLink, useRoute } from 'vue-router'
+import AppLink from './AppLink.vue'
+
+export default defineComponent({
+ name: 'App',
+ components: { AppLink },
+ setup() {
+ const route = useRoute()
+ const state = inject('state')
+ const viewName = ref('default')
+
+ useLink({ to: '/' })
+ useLink({ to: '/documents/hello' })
+ useLink({ to: '/children' })
+
+ const currentLocation = computed(() => {
+ const { matched, ...rest } = route
+ return rest
+ })
+
+ function flushWaiter() {
+ scrollWaiter.flush()
+ }
+ function setupWaiter() {
+ scrollWaiter.add()
+ }
+
+ const nextUserLink = computed(
+ () => '/users/' + String((Number(route.value.params.id) || 0) + 1)
+ )
+
+ return {
+ currentLocation,
+ nextUserLink,
+ state,
+ flushWaiter,
+ setupWaiter,
+ viewName,
+ toggleViewName() {
+ viewName.value = viewName.value === 'default' ? 'other' : 'default'
+ },
+ }
+ },
+})
+</script>
</template>
<script>
-import { RouterLinkImpl } from '../src/RouterLink'
+import { RouterLink, START_LOCATION, useLink, useRoute } from 'vue-router'
import { computed, defineComponent, toRefs } from 'vue'
-import { START_LOCATION, useLink, useRoute } from '../src'
export default defineComponent({
props: {
- ...RouterLinkImpl.props,
+ ...RouterLink.props,
disabled: Boolean,
},
--- /dev/null
+export let delay = (t = 100) => new Promise(resolve => setTimeout(resolve, t))
+export async function getData() {
+ await delay(500)
+ return {
+ message: 'Hello',
+ time: Date.now(),
+ }
+}
--- /dev/null
+// necessary for webpack
+import { createApp } from 'vue'
+import { router, routerHistory } from './router'
+import { globalState } from './store'
+import App from './App.vue'
+// for testing purposes
+window.h = routerHistory
+window.r = router
+const app = createApp(App)
+app.mixin({
+ beforeRouteEnter() {
+ console.log('mixin enter')
+ },
+})
+app.provide('state', globalState)
+app.use(router)
+window.vm = app.mount('#app')
// necessary for webpack
-///<reference path="../src/global.d.ts"/>
-import { createApp, ComponentPublicInstance } from 'vue'
+import { createApp } from 'vue'
+import type { ComponentPublicInstance } from 'vue'
import { router, routerHistory } from './router'
import { globalState } from './store'
import App from './App.vue'
--- /dev/null
+import { createRouter, createWebHistory, RouterView } from 'vue-router'
+import Home from './views/Home.vue'
+import Nested from './views/Nested.vue'
+import NestedWithId from './views/NestedWithId.vue'
+import Dynamic from './views/Dynamic.vue'
+import User from './views/User.vue'
+import NotFound from './views/NotFound.vue'
+const component = () => {
+ console.log('fetching component')
+ return import('./views/Generic.vue')
+}
+import LongView from './views/LongView.vue'
+import GuardedWithLeave from './views/GuardedWithLeave.vue'
+import ComponentWithData from './views/ComponentWithData.vue'
+import { globalState } from './store'
+import { scrollWaiter } from './scrollWaiter'
+import RepeatedParams from './views/RepeatedParams.vue'
+import { h } from 'vue'
+let removeRoute
+const TransparentWrapper = () => h(RouterView)
+TransparentWrapper.displayName = 'NestedView'
+export const routerHistory = createWebHistory()
+export const router = createRouter({
+ history: routerHistory,
+ strict: true,
+ routes: [
+ { path: '/home', redirect: '/' },
+ {
+ path: '/',
+ components: { default: Home, other: component },
+ props: { default: to => ({ waited: to.meta.waitedFor }) },
+ },
+ {
+ path: '/always-redirect',
+ redirect: () => ({
+ name: 'user',
+ params: { id: String(Math.round(Math.random() * 100)) },
+ }),
+ },
+ { path: '/users/:id', name: 'user', component: User, props: true },
+ { path: '/documents/:id', name: 'docs', component: User, props: true },
+ { path: '/optional/:id?', name: 'optional', component: User, props: true },
+ { path: encodeURI('/n/€'), name: 'euro', component },
+ { path: '/n/:n', name: 'increment', component },
+ { path: '/multiple/:a/:b', name: 'multiple', component },
+ { path: '/long-:n', name: 'long', component: LongView },
+ {
+ path: '/lazy',
+ meta: { transition: 'slide-left' },
+ component: async () => {
+ await delay(500)
+ return component()
+ },
+ },
+ {
+ path: '/with-guard/:n',
+ name: 'guarded',
+ component,
+ beforeEnter(to, from, next) {
+ if (to.params.n !== 'valid') next(false)
+ next()
+ },
+ },
+ { path: '/cant-leave', component: GuardedWithLeave },
+ {
+ path: '/children',
+ name: 'WithChildren',
+ component: Nested,
+ children: [
+ { path: '', alias: 'alias', name: 'default-child', component: Nested },
+ { path: 'a', name: 'a-child', component: Nested },
+ {
+ path: 'b',
+ name: 'b-child',
+ component: Nested,
+ children: [
+ { path: '', component: Nested },
+ { path: 'a2', component: Nested },
+ { path: 'b2', component: Nested },
+ ],
+ },
+ ],
+ },
+ { path: '/with-data', component: ComponentWithData, name: 'WithData' },
+ { path: '/rep/:a*', component: RepeatedParams, name: 'repeat' },
+ { path: '/:data(.*)', component: NotFound, name: 'NotFound' },
+ {
+ path: '/nested',
+ alias: '/anidado',
+ component: Nested,
+ name: 'Nested',
+ children: [
+ {
+ path: 'nested',
+ alias: 'a',
+ name: 'NestedNested',
+ component: Nested,
+ children: [
+ {
+ name: 'NestedNestedNested',
+ path: 'nested',
+ component: Nested,
+ },
+ ],
+ },
+ {
+ path: 'other',
+ alias: 'otherAlias',
+ component: Nested,
+ name: 'NestedOther',
+ },
+ {
+ path: 'also-as-absolute',
+ alias: '/absolute',
+ name: 'absolute-child',
+ component: Nested,
+ },
+ ],
+ },
+ {
+ path: '/parent/:id',
+ name: 'parent',
+ component: NestedWithId,
+ props: true,
+ alias: '/p/:id',
+ children: [
+ // empty child
+ { path: '', component },
+ // child with absolute path. we need to add an `id` because the parent needs it
+ { path: '/p_:id/absolute-a', alias: 'as-absolute-a', component },
+ // same as above but the alias is absolute
+ { path: 'as-absolute-b', alias: '/p_:id/absolute-b', component },
+ ],
+ },
+ {
+ path: '/dynamic',
+ name: 'dynamic',
+ component: Nested,
+ end: false,
+ strict: true,
+ beforeEnter(to, from, next) {
+ if (!removeRoute) {
+ removeRoute = router.addRoute('dynamic', {
+ path: 'child',
+ component: Dynamic,
+ })
+ next(to.fullPath)
+ } else next()
+ },
+ },
+ {
+ path: '/admin',
+ component: TransparentWrapper,
+ children: [
+ { path: '', component },
+ { path: 'dashboard', component },
+ { path: 'settings', component },
+ ],
+ },
+ ],
+ async scrollBehavior(to, from, savedPosition) {
+ await scrollWaiter.wait()
+ if (savedPosition) {
+ return savedPosition
+ } else {
+ if (to.matched.every((record, i) => from.matched[i] !== record))
+ return { left: 0, top: 0 }
+ }
+ // leave scroll as it is by not returning anything
+ // https://github.com/Microsoft/TypeScript/issues/18319
+ return false
+ },
+})
+const myRouter = createRouter({
+ history: routerHistory,
+ strict: true,
+ routes: [
+ { path: '/home', redirect: '/' },
+ {
+ path: '/',
+ components: { default: Home, other: component },
+ props: { default: to => ({ waited: to.meta.waitedFor }) },
+ },
+ {
+ path: '/always-redirect',
+ redirect: () => ({
+ name: 'user',
+ params: { id: String(Math.round(Math.random() * 100)) },
+ }),
+ },
+ { path: '/users/:id', name: 'user', component: User, props: true },
+ { path: '/documents/:id', name: 'docs', component: User, props: true },
+ { path: '/optional/:id?', name: 'optional', component: User, props: true },
+ { path: encodeURI('/n/€'), name: 'euro', component },
+ { path: '/n/:n', name: 'increment', component },
+ { path: '/multiple/:a/:b', name: 'multiple', component },
+ { path: '/long-:n', name: 'long', component: LongView },
+ {
+ path: '/lazy',
+ meta: { transition: 'slide-left' },
+ component: async () => {
+ await delay(500)
+ return component()
+ },
+ },
+ {
+ path: '/with-guard/:n',
+ name: 'guarded',
+ component,
+ beforeEnter(to, from, next) {
+ if (to.params.n !== 'valid') next(false)
+ next()
+ },
+ },
+ { path: '/cant-leave', component: GuardedWithLeave },
+ {
+ path: '/children',
+ name: 'WithChildren',
+ component: Nested,
+ children: [
+ { path: '', alias: 'alias', name: 'default-child', component: Nested },
+ { path: 'a', name: 'a-child', component: Nested },
+ {
+ path: 'b',
+ name: 'b-child',
+ component: Nested,
+ children: [
+ { path: '', component: Nested },
+ { path: 'a2', component: Nested },
+ { path: 'b2', component: Nested },
+ ],
+ },
+ ],
+ },
+ { path: '/with-data', component: ComponentWithData, name: 'WithData' },
+ { path: '/rep/:a*', component: RepeatedParams, name: 'repeat' },
+ // { path: '/:data(.*)', component: NotFound, name: 'NotFound' },
+ {
+ path: '/nested',
+ alias: '/anidado',
+ component: Nested,
+ name: 'Nested',
+ children: [
+ {
+ path: 'nested',
+ alias: 'a',
+ name: 'NestedNested',
+ component: Nested,
+ children: [
+ {
+ name: 'NestedNestedNested',
+ path: 'nested',
+ component: Nested,
+ },
+ ],
+ },
+ {
+ path: 'other',
+ alias: 'otherAlias',
+ component: Nested,
+ name: 'NestedOther',
+ },
+ {
+ path: 'also-as-absolute',
+ alias: '/absolute',
+ name: 'absolute-child',
+ component: Nested,
+ },
+ ],
+ },
+ {
+ path: '/parent/:id',
+ name: 'parent',
+ component: NestedWithId,
+ props: true,
+ alias: '/p/:id',
+ children: [
+ // empty child
+ { path: '', component },
+ // child with absolute path. we need to add an `id` because the parent needs it
+ { path: '/p_:id/absolute-a', alias: 'as-absolute-a', component },
+ // same as above but the alias is absolute
+ { path: 'as-absolute-b', alias: '/p_:id/absolute-b', component },
+ ],
+ },
+ {
+ path: '/dynamic',
+ name: 'dynamic',
+ component: Nested,
+ end: false,
+ strict: true,
+ beforeEnter(to, from, next) {
+ if (!removeRoute) {
+ removeRoute = router.addRoute('dynamic', {
+ path: 'child',
+ component: Dynamic,
+ })
+ next(to.fullPath)
+ } else next()
+ },
+ },
+ {
+ path: '/admin',
+ component: TransparentWrapper,
+ children: [
+ { path: '', component },
+ { path: 'dashboard', component },
+ { path: 'settings', component },
+ ],
+ },
+ ],
+ async scrollBehavior(to, from, savedPosition) {
+ await scrollWaiter.wait()
+ if (savedPosition) {
+ return savedPosition
+ } else {
+ if (to.matched.every((record, i) => from.matched[i] !== record))
+ return { left: 0, top: 0 }
+ }
+ // leave scroll as it is by not returning anything
+ // https://github.com/Microsoft/TypeScript/issues/18319
+ return false
+ },
+})
+const delay = t => new Promise(resolve => setTimeout(resolve, t))
+// remove trailing slashes
+router.beforeEach(to => {
+ if (/.\/$/.test(to.path)) {
+ to.meta.redirectCode = 301
+ return to.path.replace(/\/$/, '')
+ }
+})
+router.beforeEach(async to => {
+ // console.log(`Guard from ${from.fullPath} to ${to.fullPath}`)
+ if (to.params.id === 'no-name') return false
+ const time = Number(to.query.delay)
+ if (time > 0) {
+ console.log('⏳ waiting ' + time + 'ms')
+ to.meta.waitedFor = time
+ await delay(time)
+ }
+})
+router.beforeEach(() => {
+ if (globalState.cancelNextNavigation) return false
+})
+router.afterEach((to, from) => {
+ if (to.name === from.name && to.name === 'repeat') {
+ const toDepth = to.path.split('/').length
+ const fromDepth = from.path.split('/').length
+ to.meta.transition = toDepth < fromDepth ? 'slide-right' : 'slide-left'
+ }
+})
+router.afterEach((to, from) => {
+ // console.log(
+ // `After guard: from ${from.fullPath} to ${
+ // to.fullPath
+ // } | location = ${location.href.replace(location.origin, '')}`
+ // )
+})
+export function go(delta) {
+ return new Promise((resolve, reject) => {
+ function popStateListener() {
+ clearTimeout(timeout)
+ }
+ window.addEventListener('popstate', popStateListener)
+ function clearHooks() {
+ removeAfterEach()
+ removeOnError()
+ window.removeEventListener('popstate', popStateListener)
+ }
+ // if the popstate event is not called, consider this a failure
+ const timeout = setTimeout(() => {
+ clearHooks()
+ reject(new Error('Failed to use router.go()'))
+ // using 0 leads to false positives
+ }, 1)
+ const removeAfterEach = router.afterEach((_to, _from, failure) => {
+ clearHooks()
+ resolve(failure)
+ })
+ const removeOnError = router.onError(err => {
+ clearHooks()
+ reject(err)
+ })
+ router.go(delta)
+ })
+}
+// @ts-expect-error
+window._go = go
+router.beforeEach(to => {
+ // console.log('second guard')
+ if (typeof to.query.to === 'string' && to.query.to) return to.query.to
+})
+const dirLog = {
+ '': '?',
+ back: '⏪',
+ forward: '⏩',
+}
+routerHistory.listen((to, from, info) => {
+ console.log(`${dirLog[info.direction]} as a ${info.type}`)
+})
-import { createRouter, createWebHistory, RouterView } from '../src'
+import { createRouter, createWebHistory, RouterView } from 'vue-router'
import Home from './views/Home.vue'
import Nested from './views/Nested.vue'
import NestedWithId from './views/NestedWithId.vue'
import { globalState } from './store'
import { scrollWaiter } from './scrollWaiter'
import RepeatedParams from './views/RepeatedParams.vue'
-import { FunctionalComponent, h } from 'vue'
+import { h } from 'vue'
+import type { FunctionalComponent } from 'vue'
let removeRoute: (() => void) | undefined
const TransparentWrapper: FunctionalComponent = () => h(RouterView)
},
})
-// TODO: move to pnpm, workspaces, and use an alias 'vue-router' to be closer to a real project
+// router.push('/admin/dashboard')
-declare module '../src' {
+declare module 'vue-router' {
export interface Config {
Router: typeof router
}
const delay = (t: number) => new Promise(resolve => setTimeout(resolve, t))
// remove trailing slashes
-router.beforeEach((to, from, next) => {
+router.beforeEach(to => {
if (/.\/$/.test(to.path)) {
to.meta.redirectCode = 301
- next(to.path.replace(/\/$/, ''))
- } else next()
- // next()
+ return to.path.replace(/\/$/, '')
+ }
})
-router.beforeEach(async (to, from, next) => {
+router.beforeEach(async to => {
// console.log(`Guard from ${from.fullPath} to ${to.fullPath}`)
- if (to.params.id === 'no-name') return next(false)
+ if (to.params.id === 'no-name') return false
const time = Number(to.query.delay)
if (time > 0) {
to.meta.waitedFor = time
await delay(time)
}
- next()
})
-router.beforeEach((to, from, next) => {
- if (globalState.cancelNextNavigation) return next(false)
- next()
+router.beforeEach(() => {
+ if (globalState.cancelNextNavigation) return false
})
router.afterEach((to, from) => {
// using 0 leads to false positives
}, 1)
- setImmediate
-
const removeAfterEach = router.afterEach((_to, _from, failure) => {
clearHooks()
resolve(failure)
// @ts-expect-error
window._go = go
-router.beforeEach((to, from, next) => {
+router.beforeEach(to => {
// console.log('second guard')
- if (to.query.to) next(to.query.to as string)
- else next()
+ if (typeof to.query.to === 'string' && to.query.to) return to.query.to
})
const dirLog = {
--- /dev/null
+class ScrollQueue {
+ resolve = null
+ promise = null
+ add() {
+ this.promise = new Promise(resolve => {
+ this.resolve = resolve
+ })
+ }
+ flush() {
+ this.resolve && this.resolve()
+ this.resolve = null
+ this.promise = null
+ }
+ async wait() {
+ await this.promise
+ }
+}
+export const scrollWaiter = new ScrollQueue()
--- /dev/null
+import { reactive } from 'vue'
+export const globalState = reactive({
+ cancelNextNavigation: false,
+})
<script>
import { defineComponent, toRefs, reactive } from 'vue'
import { getData, delay } from '../api'
-import { onBeforeRouteUpdate } from '../../src'
+import { onBeforeRouteUpdate } from 'vue-router'
export default defineComponent({
name: 'ComponentWithData',
<script>
// @ts-check
import { defineComponent } from 'vue'
-import { onBeforeRouteLeave } from '../../src'
+import { onBeforeRouteLeave } from 'vue-router'
export default defineComponent({
name: 'GuardedWithLeave',
<script>
import { defineComponent } from 'vue'
-import { useRoute } from '../../src'
+import { useRoute } from 'vue-router'
export default defineComponent({
name: 'LongView',
<script>
import { defineComponent } from 'vue'
-import { useRoute } from '../../src'
+import { useRoute } from 'vue-router'
export default defineComponent({
name: 'NotFound',
<script>
import { defineComponent, computed } from 'vue'
-import { useRoute } from '../../src'
+import { useRoute } from 'vue-router'
export default defineComponent({
name: 'RepeatedParams',
--- /dev/null
+{
+ "extends": "@vue/tsconfig/tsconfig.node.json",
+ "include": ["vite.config.*", "vitest.config.*", "cypress.config.*"],
+ "compilerOptions": {
+ "composite": true,
+ "types": ["node"]
+ }
+}
--- /dev/null
+{
+ "extends": "@vue/tsconfig/tsconfig.web.json",
+ "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
+ "exclude": ["**/node_modules", "**/.*/"],
+ "compilerOptions": {
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ // "vue-router": ["../router/src"]
+ }
+ },
+
+ "references": [
+ {
+ "path": "./tsconfig.config.json"
+ }
+ ]
+}
+import { fileURLToPath, URL } from 'url'
+
import { defineConfig } from 'vite'
-import vue from '@vitejs/plugin-vue'
-import analyze from 'rollup-plugin-analyzer'
-import { resolve } from 'path'
+import Vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
- root: resolve(process.cwd(), 'playground'),
- build: {
- outDir: '../playground_dist',
- rollupOptions: {
- plugins: [analyze()],
+ plugins: [Vue()],
+ resolve: {
+ alias: {
+ '@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
- plugins: [vue()],
define: {
__DEV__: JSON.stringify(!process.env.prod),
__BROWSER__: 'true',
"build:e2e": "vue-tsc --noEmit && vite build --config e2e/vite.config.js",
"build:size": "pnpm run build && rollup -c size-checks/rollup.config.js",
"dev:e2e": "vite --config e2e/vite.config.js",
- "docs": "vitepress dev docs",
- "docs:build": "vitepress build docs",
- "lint": "pnpm run lint:script && pnpm run lint:html",
- "lint:script": "prettier -c --parser typescript \"{src,__tests__,e2e,playground}/**/*.[jt]s?(x)\"",
- "lint:html": "prettier -c --parser html \"{playground,e2e}/**/*.html\"",
- "lint:fix": "pnpm run lint:script --write && pnpm run lint:html --write",
"test:types": "tsc --build tsconfig.json",
"test:dts": "tsc -p ./test-dts/tsconfig.json",
"test:unit": "jest --coverage",
"@types/jest": "^27.4.1",
"@types/jsdom": "^16.2.13",
"@types/nightwatch": "^2.0.8",
- "@vitejs/plugin-vue": "^2.2.2",
- "@vue/compiler-sfc": "^3.2.31",
- "@vue/server-renderer": "^3.2.31",
- "@vue/test-utils": "^2.0.0-rc.3",
- "axios": "^0.27.2",
"browserstack-local": "^1.4.5",
"chromedriver": "^102.0.0",
"connect-history-api-fallback": "^1.6.0",
"jest-mock-warn": "^1.1.0",
"nightwatch": "^2.0.0",
"nightwatch-helpers": "^1.2.0",
- "prettier": "^2.4.1",
"rimraf": "^3.0.2",
"rollup": "^2.68.0",
"rollup-plugin-analyzer": "^4.0.0",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.32.1",
- "typescript": "~4.7.3",
- "vite": "~2.9.10",
- "vue": "^3.2.31",
- "vue-tsc": "^0.37.2"
+ "typescript": "~4.7.2"
}
}
import { RouteRecord } from './matcher/types'
import { NavigationFailure } from './errors'
import { isArray, isBrowser, noop } from './utils'
-import { RouterTyped } from './typedRouter'
export interface RouterLinkOptions {
/**
* Route Location the link should navigate to when clicked on.
*/
- to: Parameters<RouterTyped['push']>[0]
+ to: RouteLocationRaw
/**
* Calls `router.replace` instead of `router.push`.
*/
+++ /dev/null
-{
- "include": ["./**/*.ts", "api", "../src/global.d.ts", "shim.d.ts"],
- "compilerOptions": {
- "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
- "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
- // "lib": ["es2017.object"] /* Specify library files to be included in the compilation. */,
- // "declaration": true /* Generates corresponding '.d.ts' file. */,
- // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
- "sourceMap": true /* Generates corresponding '.map' file. */,
- // "outFile": "./", /* Concatenate and emit output to single file. */
- "outDir": "./dist" /* Redirect output structure to the directory. */,
-
- "strict": true /* Enable all strict type-checking options. */,
- "noUnusedLocals": true /* Report errors on unused locals. */,
- "noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
-
- "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
- "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
- }
-}
.:
specifiers:
+ '@vue/compiler-sfc': ^3.2.31
+ '@vue/server-renderer': ^3.2.37
+ '@vue/test-utils': ^2.0.0-rc.3
brotli: ^1.3.3
chalk: ^4.1.2
enquirer: ^2.3.6
lint-staged: ^13.0.0
minimist: ^1.2.6
p-series: ^3.0.0
+ prettier: ^2.4.1
semver: ^7.3.7
+ typescript: ~4.7.2
+ vue: ^3.2.37
+ vue-tsc: ^0.37.2
yorkie: ^2.0.0
devDependencies:
+ '@vue/compiler-sfc': 3.2.37
+ '@vue/server-renderer': 3.2.37_vue@3.2.37
+ '@vue/test-utils': 2.0.0_vue@3.2.37
brotli: 1.3.3
chalk: 4.1.2
enquirer: 2.3.6
lint-staged: 13.0.0_enquirer@2.3.6
minimist: 1.2.6
p-series: 3.0.0
+ prettier: 2.6.2
semver: 7.3.7
+ typescript: 4.7.3
+ vue: 3.2.37
+ vue-tsc: 0.37.3_typescript@4.7.3
yorkie: 2.0.0
packages/docs:
dependencies:
vitepress: 0.20.10
+ packages/playground:
+ specifiers:
+ '@types/node': ^16.11.36
+ '@vitejs/plugin-vue': ^2.3.3
+ '@vue/tsconfig': ^0.1.3
+ typescript: ~4.7.2
+ vite: ^2.9.9
+ vue: ^3.2.36
+ vue-router: workspace:*
+ vue-tsc: ^0.37.2
+ dependencies:
+ vue: 3.2.37
+ devDependencies:
+ '@types/node': 16.11.39
+ '@vitejs/plugin-vue': 2.3.3_vite@2.9.10+vue@3.2.37
+ '@vue/tsconfig': 0.1.3_@types+node@16.11.39
+ typescript: 4.7.3
+ vite: 2.9.10
+ vue-router: link:../router
+ vue-tsc: 0.37.3_typescript@4.7.3
+
packages/router:
specifiers:
'@microsoft/api-extractor': ^7.18.11
'@types/jest': ^27.4.1
'@types/jsdom': ^16.2.13
'@types/nightwatch': ^2.0.8
- '@vitejs/plugin-vue': ^2.2.2
- '@vue/compiler-sfc': ^3.2.31
'@vue/devtools-api': ^6.1.4
- '@vue/server-renderer': ^3.2.31
- '@vue/test-utils': ^2.0.0-rc.3
- axios: ^0.27.2
browserstack-local: ^1.4.5
chromedriver: ^102.0.0
connect-history-api-fallback: ^1.6.0
jest-mock-warn: ^1.1.0
nightwatch: ^2.0.0
nightwatch-helpers: ^1.2.0
- prettier: ^2.4.1
rimraf: ^3.0.2
rollup: ^2.68.0
rollup-plugin-analyzer: ^4.0.0
rollup-plugin-terser: ^7.0.2
rollup-plugin-typescript2: ^0.32.1
- typescript: ~4.7.3
- vite: ~2.9.10
- vue: ^3.2.31
- vue-tsc: ^0.37.2
+ typescript: ~4.7.2
dependencies:
'@vue/devtools-api': 6.1.4
devDependencies:
'@types/jest': 27.5.2
'@types/jsdom': 16.2.14
'@types/nightwatch': 2.0.8
- '@vitejs/plugin-vue': 2.3.3_vite@2.9.10+vue@3.2.37
- '@vue/compiler-sfc': 3.2.37
- '@vue/server-renderer': 3.2.37_vue@3.2.37
- '@vue/test-utils': 2.0.0_vue@3.2.37
- axios: 0.27.2
browserstack-local: 1.5.1
chromedriver: 102.0.0
connect-history-api-fallback: 1.6.0
jest-mock-warn: 1.1.0
nightwatch: 2.1.8_3ocnie545c4b2yffuhqjjjsvb4
nightwatch-helpers: 1.2.0
- prettier: 2.6.2
rimraf: 3.0.2
rollup: 2.75.6
rollup-plugin-analyzer: 4.0.0
rollup-plugin-terser: 7.0.2_rollup@2.75.6
rollup-plugin-typescript2: 0.32.1_fgms252lqu3rk7srzpqqayl4ya
typescript: 4.7.3
- vite: 2.9.10
- vue: 3.2.37
- vue-tsc: 0.37.3_typescript@4.7.3
packages:
resolution: {integrity: sha512-yxDeaQIAJlMav7fH5AQqPH1u8YIuhYJXYBzxaQ4PifsU0GDO38MSdmEDeRlIxrKbC6NbEaaEHDanWb+y30U8SQ==}
dev: true
+ /@types/node/16.11.39:
+ resolution: {integrity: sha512-K0MsdV42vPwm9L6UwhIxMAOmcvH/1OoVkZyCgEtVu4Wx7sElGloy/W7kMBNe/oJ7V/jW9BVt1F6RahH6e7tPXw==}
+ dev: true
+
/@types/node/17.0.41:
resolution: {integrity: sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw==}
dev: true
vue: 3.2.37
dev: true
+ /@vue/tsconfig/0.1.3_@types+node@16.11.39:
+ resolution: {integrity: sha512-kQVsh8yyWPvHpb8gIc9l/HIDiiVUy1amynLNpCy8p+FoCiZXCo6fQos5/097MmnNZc9AtseDsCrfkhqCrJ8Olg==}
+ peerDependencies:
+ '@types/node': '*'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ dependencies:
+ '@types/node': 16.11.39
+ dev: true
+
/JSONStream/1.3.5:
resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==}
hasBin: true
dev: true
/to-fast-properties/2.0.0:
- resolution: {integrity: sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=}
+ resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
engines: {node: '>=4'}
/to-regex-range/5.0.1: