{{ post.title }}
+{{ post.body }}
+diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 66c268145..e10c85f4f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -9,7 +9,7 @@ body: id: reproduction attributes: label: Reproduction - description: "If possible, provide a boiled down editable reproduction using a service like [JSFiddle](https://jsfiddle.net/posva/3yq6ojLv), Codepen, [CodeSandbox](https://codesandbox.io/s/vue-router-4-reproduction-s1sqc), or a GitHub repository. A failing unit test is even better! Otherwise provide as much information as possible to reproduce the problem. If we can't reproduce the problem, we won't be able to give it a look **and the issue will be converted into a question and moved to discussions**." + description: "If possible, provide a boiled down editable reproduction with the [Vue.js Playground](https://play.vuejs.org/#eNqlVG1P2zAQ/iu3bFKLRuNSEB+yUMEQGpv2gti0L8s+pInbGhLbsp1SVPW/72znrVD4MlVp7Lvn7p67x/EmKFPGwzsdRAErpVAGNpApmhp6ISVsYa5ECYNVRQcJbwFKVIaq1hsSb+ggNrbxpVKGPj7hbeYhPgcJBwgrTYc+3O9LUXEzHLzFsMFBcBjU4cgvNrSUBcZPLTBeHk2vaVEIW+xNTHDrzNK9cKGNEnwxvayUotx4ziBTs4xiUvtgs4F3zhHOq6K4QSdsty4N8Xlinq6ahLeO5VfG78GIsyQgSTD9JHAN16KkMen8L0akM7S0YRd29ywuJk3N2Gqzm+s3ow9Aalztjkk7GJyXn2Vo9ij6jZZCPV4zbfB1WBt93r7So1bNVk/boCvdiNoYamUb2W1DO7jW0h4BunbQTHBdi6LgbIfLcGPbW3qa0T7uw4NDC3HROoI/fkQbry4MyACbE0iJo/BRR37rovpAp8cuuuvBw//i/xaPJo6237Q9jzpTTBrQ1FQSipQvUGKjUV6riXfiEjdPDu5k2mTCYzt5puDOzP6vTptqbyGv2qhMJV4AgmMpN/mkdmCFCJzF2rqDYc1JsDRG6oiQisv7RYgDJB3i/CQ8DsckR7V61pDqcjRT4kHj+g6z13IkwTmCSE5XRohCj1LJXirxDHh+Gp6GR6RgM4LZCeM5XbvcNjV+yVts02g8bHO2eNKk1ZwVVP2QhuFh3Gk2xXvl4YuzGVXRlmi2pNn9HvudXnvKN4pidyvaa86kakHxo7fuq5/f6RrXrbMUeVUg+hXnLdWiqCxHD/tY8Rxp93CO7WenGeOLX/pqbSjXTVOWqJuGwzshL19pvaN7HJ70pqjNY0F1mGl7seANdQj2+vFxM6FyqiKYyDUgWZbD2/F4/MG6SkzH+GgmjBFlBEdjuXZ2meY5km0tWCXhmBamkMJ7fFziOrqgc/wy+8jlpF+5S98RyLKsRyCCMf4mdYZg+w+goli8), or an **editable** and **up to date** [CodeSandbox](https://codesandbox.io/s/vue-router-4-reproduction-s1sqc), Stackblitz, or a GitHub repository. A failing unit test is even better! Otherwise provide as much information as possible to reproduce the problem. If we can't reproduce the problem, we won't be able to give it a look **and the issue will be converted into a question and moved to discussions**." placeholder: Reproduction validations: required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 36c8163d4..53668b5df 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,9 +1,15 @@ blank_issues_enabled: false contact_links: - - name: Help and Question + - name: 👨💻 Support + url: https://cal.com/posva/consultancy + about: Get direct help from the author of Vue Router with your project + - name: ❓ Help & Questions url: https://github.com/vuejs/router/discussions/new?category=help-and-questions about: Ask a question or discuss about Vue Router - - name: GitHub Sponsors + - name: 💡 Ideas + url: https://github.com/vuejs/router/discussions/new?category=ideas + about: Start a discussion to improve Vue Router + - name: 🌟 GitHub Sponsors url: https://github.com/sponsors/posva about: Like this project? Please consider supporting the author. - name: Open Collective diff --git a/.github/contributing.md b/.github/contributing.md index 087e09b95..1bdb74077 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -34,9 +34,9 @@ Hi! I'm really excited that you are interested in contributing to Vue Router. Be - Make sure tests pass! -- Commit messages must follow the [commit message convention](./commit-convention.md) so that the changelog can be automatically generated. Commit messages are automatically validated before commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [yorkie](https://github.com/yyx990803/yorkie)). +- Commit messages must follow the [commit message convention](./commit-convention.md) so that the changelog can be automatically generated. Commit messages are automatically validated before commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [simple-git-hooks](https://github.com/toplenboren/simple-git-hooks)). -- No need to worry about code style as long as you have installed the dev dependencies - modified files are automatically formatted with Prettier on commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [yorkie](https://github.com/yyx990803/yorkie)). +- No need to worry about code style as long as you have installed the dev dependencies - modified files are automatically formatted with Prettier on commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [simple-git-hooks](https://github.com/toplenboren/simple-git-hooks)). ## Development Setup @@ -52,7 +52,7 @@ A high-level overview of tools used: - [TypeScript](https://www.typescriptlang.org/) as the development language - [Rollup](https://rollupjs.org) for bundling -- [Jest](https://jestjs.io/) for unit testing +- [Vitest](https://vitest.dev/) for unit testing - [Prettier](https://prettier.io/) for code formatting ## Scripts @@ -83,7 +83,7 @@ The `pnpm test` script runs all checks: $ pnpm test # run unit tests in watch mode -$ pnpm jest --watch +$ pnpm test:unit --watch ``` ## Project Structure @@ -102,7 +102,7 @@ Vue Router source code can be found in the `src` directory: ## Contributing Tests -Unit tests are located inside `__tests__`. Consult the [Jest docs](https://jestjs.io/docs/en/using-matchers) and existing test cases for how to write new test specs. Here are some additional guidelines: +Unit tests are located inside `__tests__`. Consult the [Vitest docs](https://vitest.dev/guide/) and existing test cases for how to write new test specs. Here are some additional guidelines: - Use the minimal API needed for a test case. For example, if a test can be written without involving the reactivity system or a component, it should be written so. This limits the test's exposure to changes in unrelated parts and makes it more stable. - Use the minimal API needed for a test case. For example, if a test concerns the `router-link` component, don't create a router instance, mock the required properties instead. diff --git a/.github/workflows/pkg.pr.new.yml b/.github/workflows/pkg.pr.new.yml new file mode 100644 index 000000000..47abd6026 --- /dev/null +++ b/.github/workflows/pkg.pr.new.yml @@ -0,0 +1,45 @@ +name: Publish Any Commit + +on: + pull_request: + branches: main + paths-ignore: + - 'packages/docs/**' + - 'packages/playground/**' + + push: + branches: + - '**' + tags: + - '!**' + paths-ignore: + - 'packages/docs/**' + - 'packages/playground/**' + +jobs: + build: + if: github.repository == 'vuejs/router' + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + cache: pnpm + + - name: Install + run: pnpm install --frozen-lockfile + + - name: Build + run: pnpm -C packages/router build + + - name: Build DTS + run: pnpm -C packages/router build:dts + + - name: Release + run: pnpm dlx pkg-pr-new publish --compact --pnpm './packages/*' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 97255b0bf..5bbdc52fc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,10 +2,14 @@ name: test on: push: + branches: + - main paths-ignore: - 'packages/docs/**' - 'packages/playground/**' pull_request: + branches: + - main paths-ignore: - 'packages/docs/**' - 'packages/playground/**' @@ -15,34 +19,33 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: pnpm/action-setup@v2 + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 with: - version: 8.5.0 - - uses: actions/setup-node@v3 - with: - node-version: '18' - cache: 'pnpm' + node-version: 'lts/*' + cache: pnpm - name: 'BrowserStack Env Setup' uses: 'browserstack/github-actions/setup-env@master' # forks do not have access to secrets so just skip this - if: ${{ !github.event.pull_request.head.repo.fork }} + if: ${{ github.repository == 'vuejs/router' && !github.event.pull_request.head.repo.fork }} with: username: ${{ secrets.BROWSERSTACK_USERNAME }} access-key: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} - run: pnpm install - run: pnpm run lint - - run: pnpm run -r test:types - - run: pnpm run -r test:unit - run: pnpm run -r build - run: pnpm run -r build:dts - - run: pnpm run -r test:dts + - run: pnpm run -r test:types + - run: pnpm run -r test:unit # e2e tests that that run locally - run: pnpm run -r test:e2e:ci - - uses: codecov/codecov-action@v2 + - uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} # - name: 'Start BrowserStackLocal Tunnel' # uses: 'browserstack/github-actions/setup-local@master' diff --git a/.prettierignore b/.prettierignore index fbe4183e8..dd4104bc7 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,3 +2,4 @@ __build__ dist coverage tests_output +packages/docs/.vitepress/cache diff --git a/README.md b/README.md index 578d76d97..25eef7b6c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# vue-router [](https://www.npmjs.com/package/vue-router) [](https://github.com/vuejs/router/actions/workflows/test.yml) +# vue-router [](https://www.npmjs.com/package/vue-router) [](https://github.com/vuejs/router/actions/workflows/test.yml) [](https://codecov.io/gh/vuejs/router) > - This is the repository for Vue Router 4 (for Vue 3) > - For Vue Router 3 (for Vue 2) see [vuejs/vue-router](https://github.com/vuejs/vue-router). @@ -12,6 +12,16 @@ Vue Router is part of the Vue Ecosystem and is an MIT-licensed open source proje +
+
+
+
@@ -26,32 +36,38 @@ Vue Router is part of the Vue Ecosystem and is an MIT-licensed open source proje
-
+
+
The official
+Vue.js Certification
+Get certified!
+ diff --git a/packages/docs/.vitepress/theme/components/HomeSponsors.vue b/packages/docs/.vitepress/theme/components/HomeSponsors.vue index 458b02c7d..8bd5f1748 100644 --- a/packages/docs/.vitepress/theme/components/HomeSponsors.vue +++ b/packages/docs/.vitepress/theme/components/HomeSponsors.vue @@ -1,3 +1,16 @@ + +{{ post.body }}
+
-
-
-
-
Current route path: {{ $route.fullPath }}
+ +{{ $route.fullPath }}
。你可以在组件模板中使用 `$route` 来访问当前的路由对象。
-### `router-view`
+${name || 'No Name'}
`, diff --git a/packages/router/__tests__/hash-manual-navigation.spec.ts b/packages/router/__tests__/hash-manual-navigation.spec.ts index a465bb309..9a0887c43 100644 --- a/packages/router/__tests__/hash-manual-navigation.spec.ts +++ b/packages/router/__tests__/hash-manual-navigation.spec.ts @@ -1,5 +1,6 @@ import { createMemoryHistory, createRouter, RouterHistory } from '../src' import { tick } from './utils' +import { describe, expect, it } from 'vitest' const component = {} @@ -34,7 +35,7 @@ describe('hash history edge cases', () => { return }) - // const spy = jest.spyOn(history, 'go') + // const spy = vi.spyOn(history, 'go') history.changeURL('/') await tick() @@ -71,7 +72,7 @@ describe('hash history edge cases', () => { return }) - // const spy = jest.spyOn(history, 'go') + // const spy = vi.spyOn(history, 'go') history.changeURL('/') await tick() diff --git a/packages/router/__tests__/history/hash.spec.ts b/packages/router/__tests__/history/hash.spec.ts index 43a299c15..65cc6dfdd 100644 --- a/packages/router/__tests__/history/hash.spec.ts +++ b/packages/router/__tests__/history/hash.spec.ts @@ -2,12 +2,23 @@ import { JSDOM } from 'jsdom' import { createWebHashHistory } from '../../src/history/hash' import { createWebHistory } from '../../src/history/html5' import { createDom } from '../utils' -import { mockWarn } from 'jest-mock-warn' - -jest.mock('../../src/history/html5') +import { mockWarn } from '../vitest-mock-warn' +import { + vi, + describe, + expect, + it, + beforeAll, + beforeEach, + Mock, + afterAll, + afterEach, +} from 'vitest' + +vi.mock('../../src/history/html5') // override the value of isBrowser because the variable is created before JSDOM // is created -jest.mock('../../src/utils/env', () => ({ +vi.mock('../../src/utils/env', () => ({ isBrowser: true, })) @@ -19,7 +30,7 @@ describe('History Hash', () => { mockWarn() beforeEach(() => { - ;(createWebHistory as jest.Mock).mockClear() + ;(createWebHistory as Mock).mockClear() }) afterAll(() => { diff --git a/packages/router/__tests__/history/html5.spec.ts b/packages/router/__tests__/history/html5.spec.ts index 4eb446a36..88bc12f65 100644 --- a/packages/router/__tests__/history/html5.spec.ts +++ b/packages/router/__tests__/history/html5.spec.ts @@ -1,10 +1,20 @@ import { JSDOM } from 'jsdom' import { createWebHistory } from '../../src/history/html5' import { createDom } from '../utils' +import { + vi, + describe, + expect, + it, + beforeAll, + beforeEach, + afterAll, + afterEach, +} from 'vitest' // override the value of isBrowser because the variable is created before JSDOM // is created -jest.mock('../../src/utils/env', () => ({ +vi.mock('../../src/utils/env', () => ({ isBrowser: true, })) @@ -89,7 +99,7 @@ describe('History HTMl5', () => { it('prepends the host to support // urls', () => { let history = createWebHistory() - let spy = jest.spyOn(window.history, 'pushState') + let spy = vi.spyOn(window.history, 'pushState') history.push('/foo') expect(spy).toHaveBeenCalledWith( expect.anything(), @@ -108,7 +118,7 @@ describe('History HTMl5', () => { describe('specific to base containing a hash', () => { it('calls push with hash part of the url with a base', () => { dom.reconfigure({ url: 'file:///usr/etc/index.html' }) - let initialSpy = jest.spyOn(window.history, 'replaceState') + let initialSpy = vi.spyOn(window.history, 'replaceState') let history = createWebHistory('#') // initial navigation expect(initialSpy).toHaveBeenCalledWith( @@ -116,7 +126,7 @@ describe('History HTMl5', () => { expect.any(String), '#/' ) - let spy = jest.spyOn(window.history, 'pushState') + let spy = vi.spyOn(window.history, 'pushState') history.push('/foo') expect(spy).toHaveBeenCalledWith( expect.anything(), @@ -129,7 +139,7 @@ describe('History HTMl5', () => { it('works with something after the hash in the base', () => { dom.reconfigure({ url: 'file:///usr/etc/index.html' }) - let initialSpy = jest.spyOn(window.history, 'replaceState') + let initialSpy = vi.spyOn(window.history, 'replaceState') let history = createWebHistory('#something') // initial navigation expect(initialSpy).toHaveBeenCalledWith( @@ -137,7 +147,7 @@ describe('History HTMl5', () => { expect.any(String), '#something/' ) - let spy = jest.spyOn(window.history, 'pushState') + let spy = vi.spyOn(window.history, 'pushState') history.push('/foo') expect(spy).toHaveBeenCalledWith( expect.anything(), @@ -150,7 +160,7 @@ describe('History HTMl5', () => { it('works with #! and on a file with initial location', () => { dom.reconfigure({ url: 'file:///usr/etc/index.html#!/foo' }) - let spy = jest.spyOn(window.history, 'replaceState') + let spy = vi.spyOn(window.history, 'replaceState') createWebHistory('#!') expect(spy).toHaveBeenCalledWith( expect.anything(), @@ -162,7 +172,7 @@ describe('History HTMl5', () => { it('works with #other', () => { dom.reconfigure({ url: 'file:///usr/etc/index.html' }) - let spy = jest.spyOn(window.history, 'replaceState') + let spy = vi.spyOn(window.history, 'replaceState') createWebHistory('#other') expect(spy).toHaveBeenCalledWith( expect.anything(), @@ -174,7 +184,7 @@ describe('History HTMl5', () => { it('works with custom#other in domain', () => { dom.reconfigure({ url: 'https://esm.dev/custom' }) - let spy = jest.spyOn(window.history, 'replaceState') + let spy = vi.spyOn(window.history, 'replaceState') createWebHistory('custom#other') expect(spy).toHaveBeenCalledWith( expect.anything(), @@ -186,7 +196,7 @@ describe('History HTMl5', () => { it('works with #! and a host with initial location', () => { dom.reconfigure({ url: 'https://esm.dev/#!/foo' }) - let spy = jest.spyOn(window.history, 'replaceState') + let spy = vi.spyOn(window.history, 'replaceState') createWebHistory('/#!') expect(spy).toHaveBeenCalledWith( expect.anything(), diff --git a/packages/router/__tests__/history/memory.spec.ts b/packages/router/__tests__/history/memory.spec.ts index a1fab2351..b2de91e6d 100644 --- a/packages/router/__tests__/history/memory.spec.ts +++ b/packages/router/__tests__/history/memory.spec.ts @@ -1,5 +1,6 @@ import { createMemoryHistory } from '../../src/history/memory' import { START, HistoryLocation } from '../../src/history/common' +import { vi, describe, expect, it } from 'vitest' const loc: HistoryLocation = '/foo' @@ -26,7 +27,7 @@ describe('Memory history', () => { it('does not trigger listeners with push', () => { const history = createMemoryHistory() - const spy = jest.fn() + const spy = vi.fn() history.listen(spy) history.push(loc) expect(spy).not.toHaveBeenCalled() @@ -34,7 +35,7 @@ describe('Memory history', () => { it('does not trigger listeners with replace', () => { const history = createMemoryHistory() - const spy = jest.fn() + const spy = vi.fn() history.listen(spy) history.replace(loc) expect(spy).not.toHaveBeenCalled() @@ -50,6 +51,16 @@ describe('Memory history', () => { expect(history.location).toEqual(START) }) + it('stores a state', () => { + const history = createMemoryHistory() + history.push(loc, { foo: 'bar' }) + expect(history.state).toEqual({ foo: 'bar' }) + history.push(loc, { foo: 'baz' }) + expect(history.state).toEqual({ foo: 'baz' }) + history.go(-1) + expect(history.state).toEqual({ foo: 'bar' }) + }) + it('does nothing with back if queue contains only one element', () => { const history = createMemoryHistory() history.go(-1) @@ -91,7 +102,7 @@ describe('Memory history', () => { it('can listen to navigations', () => { const history = createMemoryHistory() - const spy = jest.fn() + const spy = vi.fn() history.listen(spy) history.push(loc) history.go(-1) @@ -112,8 +123,8 @@ describe('Memory history', () => { it('can stop listening to navigation', () => { const history = createMemoryHistory() - const spy = jest.fn() - const spy2 = jest.fn() + const spy = vi.fn() + const spy2 = vi.fn() // remove right away history.listen(spy)() const remove = history.listen(spy2) @@ -129,8 +140,8 @@ describe('Memory history', () => { it('removing the same listener is a noop', () => { const history = createMemoryHistory() - const spy = jest.fn() - const spy2 = jest.fn() + const spy = vi.fn() + const spy2 = vi.fn() const rem = history.listen(spy) const rem2 = history.listen(spy2) rem() @@ -149,7 +160,7 @@ describe('Memory history', () => { it('removes all listeners with destroy', () => { const history = createMemoryHistory() history.push('/other') - const spy = jest.fn() + const spy = vi.fn() history.listen(spy) history.destroy() history.push('/2') @@ -177,7 +188,7 @@ describe('Memory history', () => { it('can avoid listeners with back and forward', () => { const history = createMemoryHistory() - const spy = jest.fn() + const spy = vi.fn() history.listen(spy) history.push(loc) history.go(-1, false) diff --git a/packages/router/__tests__/initialNavigation.spec.ts b/packages/router/__tests__/initialNavigation.spec.ts index 04ee5cbaa..c30d7fd34 100644 --- a/packages/router/__tests__/initialNavigation.spec.ts +++ b/packages/router/__tests__/initialNavigation.spec.ts @@ -2,10 +2,11 @@ import { JSDOM } from 'jsdom' import { createRouter, createWebHistory } from '../src' import { createDom, components, nextNavigation } from './utils' import { RouteRecordRaw } from '../src/types' +import { describe, expect, it, beforeAll, vi, afterAll } from 'vitest' // override the value of isBrowser because the variable is created before JSDOM // is created -jest.mock('../src/utils/env', () => ({ +vi.mock('../src/utils/env', () => ({ isBrowser: true, })) diff --git a/packages/router/__tests__/isReady.spec.ts b/packages/router/__tests__/isReady.spec.ts index cad258a00..fb559e7e5 100644 --- a/packages/router/__tests__/isReady.spec.ts +++ b/packages/router/__tests__/isReady.spec.ts @@ -1,6 +1,7 @@ import { createMemoryHistory, createRouter } from '../src' import { components } from './utils' import { RouteRecordRaw } from '../src/types' +import { vi, describe, expect, it } from 'vitest' // generic component because we are not displaying anything so it doesn't matter const component = components.Home @@ -49,7 +50,7 @@ describe('isReady', () => { it('rejects when an error is thrown in a navigation guard', async () => { const router = newRouter() - const errorSpy = jest.fn() + const errorSpy = vi.fn() const error = new Error('failed') router.onError(errorSpy) const remove = router.beforeEach(async () => { @@ -75,7 +76,7 @@ describe('isReady', () => { it('rejects a cancelled navigation', async () => { const router = newRouter() - const errorSpy = jest.fn() + const errorSpy = vi.fn() router.onError(errorSpy) const remove = router.beforeEach(() => false) router.push('/foo').catch(() => {}) @@ -102,7 +103,7 @@ describe('isReady', () => { it('rejects failed lazy loading', async () => { const router = newRouter() - const errorSpy = jest.fn() + const errorSpy = vi.fn() router.onError(errorSpy) router.push('/fail-lazy').catch(() => {}) await expect(router.isReady()).rejects.toEqual(expect.any(Error)) diff --git a/packages/router/__tests__/lazyLoading.spec.ts b/packages/router/__tests__/lazyLoading.spec.ts index 09e97b178..1a8210937 100644 --- a/packages/router/__tests__/lazyLoading.spec.ts +++ b/packages/router/__tests__/lazyLoading.spec.ts @@ -4,7 +4,16 @@ import { RouterOptions } from '../src/router' import { RouteComponent } from '../src/types' import { ticks } from './utils' import { FunctionalComponent, h } from 'vue' -import { mockWarn } from 'jest-mock-warn' +import { mockWarn } from './vitest-mock-warn' +import { + vi, + describe, + expect, + it, + beforeEach, + MockInstance, + afterEach, +} from 'vitest' function newRouter(options: Partial