Skip to content

Routing

WinJS applications are single-page applications, where page navigation is completed on the browser side without requesting HTML from the server again. HTML is only loaded once during application initialization. All pages are composed of different components, and page switching is actually switching between different components. You just need to associate different route paths with corresponding components in the configuration.

Route Type Configuration

Please refer to history configuration.

Configuring Routes

Configure routes through routes in the configuration file, formatted as an array of route information.

For example:

ts
// .winrc.ts
export default {
  routes: [
    { path: '/', component: 'index' },
    { path: '/user', component: 'user' },
  ],
}

WinJS splits bundles by page by default for faster page loading speeds. Since the loading process is asynchronous, you often need to write or reference something like nprogress to add loading styles to your project and improve the experience.

Tip

You can set the network to slow speed in Chrome Devtools > Network tab, then switch routes to check if component loading is working.

path

  • Type: string

path only supports two types of placeholder configurations: the first is dynamic parameters in the form of :id, and the second is * wildcards, which can only appear at the end of the route string.

✅ The following are currently supported route path configuration forms:

txt
/groups
/groups/admin
/users/:id
/users/:id/messages
/files/*
/files/:id/*

❌ The following are currently not supported route path configuration forms:

txt
/users/:id?
/tweets/:id(\d+)
/files/*/cat.jpg
/files-*

name

Named routes

Besides path, you can also provide a name for any route:

ts
export default {
  routes: [
    {
      path: '/user/:username',
      name: 'user',
      component: 'index'
    }
  ]
}

To link to a named route, you can pass an object to the router-link component's to property:

html

<router-link :to="{ name: 'user', params: { username: 'erina' }}">
  User
</router-link>

This has the same effect as calling router.push imperatively:

ts
router.push({ name: 'user', params: { username: 'erina' } })

Both methods can navigate to the path /user/erina.

alias

A redirect means that when a user visits /home, the URL will be replaced by /, and then matched as /. So what is an alias?

Aliasing / as /home means that when a user visits /home, the URL remains /home, but it will be matched as if the user is visiting /.

The corresponding route configuration is:

ts
export default {
  routes: [
    {
      path: '/',
      component: 'index',
      alias: '/home'
    }
  ]
}

Through aliases, you can freely map UI structure to any URL without being constrained by the nested structure of the configuration. Make aliases start with / to make paths in nested routes absolute paths. You can even combine both and provide multiple aliases with an array:

ts
export default {
  routes: [
    {
      path: '/users',
      component: 'users',
      routes: [
        // Render UserList for these 3 URLs
        // - /users
        // - /users/list
        // - /people
        { path: '', component: '/users/UserList', alias: ['/people', 'list'] },
      ]
    }
  ]
}

meta

When defining routes, we can configure the meta field to extend route meta information and record some route-related information:

js
const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      routes: [
        {
          path: 'bar',
          component: Bar,
          // a meta field
          meta: {
            title: 'bar',
            requiresAuth: true
          },
        },
      ],
    },
  ],
});

component

  • Type: string

Configure the React component path used for rendering after location and path matching. It can be an absolute path or a relative path. If it's a relative path, it will start searching from src/pages.

If pointing to a file in the src directory, you can use @, for example component: '@/layouts/basic'. It's recommended to use @ to organize route file locations.

routes

Configure child routes, usually used when you need to add layout components for multiple paths.

For example:

js
export default {
  routes: [
    { path: '/login', component: 'login' },
    {
      path: '/',
      component: '@/layouts/index',
      routes: [
        { path: '/list', component: 'list' },
        { path: '/admin', component: 'admin' },
      ],
    },
  ],
}

In the global layout src/layouts/index, render child routes through <router-view />:

tsx
export default function Page() {
  return (
    <div style={{ padding: 20 }}>
      <router-view />
    </div>
  )
}

This way, accessing /list and /admin will include the src/layouts/index layout component.

redirect

  • Type: string

Configure route redirection.

For example:

js
export default {
  routes: [
    { path: '/', redirect: '/list' },
    { path: '/list', component: 'list' },
  ],
}

Accessing / will redirect to /list and be rendered by the src/pages/list file.

The redirect target can also be a named route. The following example redirects from /home to /:

ts
export default {
  routes: [
    {
      path: '/home',
      redirect: {
        name: 'homepage'
      }
    }
  ]
}

wrappers

  • Type: string[]

Configure wrapper components for route components. Wrapper components can combine more functionality into the current route component. For example, it can be used for route-level permission verification:

js
export default {
  routes: [
    {
      path: '/user', component: 'user',
      wrappers: [
        '@/wrappers/auth',
      ],
    },
    { path: '/login', component: 'login' },
  ]
}

Then in src/wrappers/auth,

jsx
export default (props) => {
  const { isLogin } = useAuth();
  if (isLogin) {
    return <router-view />;
  } else {
    return <router-link to="/login" />;
  }
}

This way, when accessing /user, permission verification is performed through the auth component. If passed, src/pages/user is rendered, otherwise it redirects to /login.

Note

Each component in wrappers will add a nested route layer to the current route component. If you want the route structure to remain unchanged, it's recommended to use Higher-Order Components (HOC). First implement the wrapper logic in the HOC, then use the HOC to decorate the corresponding route component.

Example:

jsx
// src/hocs/withAuth.tsx

const withAuth = (Component) => () => {
  const { isLogin } = useAuth();
  if (isLogin) {
    return <router-view />;
  } else {
    return <router-link to="/login" />;
  }
}
jsx
// src/pages/user.tsx

const TheOldPage = () => {
  // ...
}

export default withAuth(TheOldPage)

Conventional Routing

When using conventional routing, all (j|t)sx? files under src/pages are treated as routes. If you need to modify the default rules, you can use the conventionRoutes configuration.

In addition to configured routing, WinJS also supports conventional routing. Conventional routing, also known as file-based routing, means you don't need to write configuration manually. The file system becomes the routes, and route configuration is analyzed from directory and file structure and naming, ultimately generating the corresponding route structure.

If there is no routes configuration, WinJS will enter conventional routing mode, then analyze the src/pages directory to get the route configuration.

For example, the following file structure:

bash
.
  └── pages
    ├── index.tsx
    └── users.tsx

Will generate the following route configuration:

js
[
  { path: '/', name: 'index', component: '@/pages/index' },
  { path: '/users', name: 'users', component: '@/pages/users' },
]

Configuring Extended Properties

We can set extended properties for pages in two ways, allowing conventional routing to read custom page properties such as meta.

  • Using the definePageMeta method (Recommended)
ts
definePageMeta(meta: PageMeta) => meta

interface PageMeta {
  name?: string
  alias?: string | string[]
  [key: string]: unknown
}
  • Using the routeProps property

Example:

vue

<script setup>
  import { ref } from 'vue';
  import { definePageMeta } from 'winjs';

  // You can get the currently set meta field in the route interceptor (router.beforeEach)
  
  // 1. Recommended: use definePageMeta()
  definePageMeta({
    meta: {
      title: 'pageMeta'
    }
  })

  // 2. export const routeProps
  export const routeProps = {
    meta: { requireAuth: true }
  }
  
  const title = ref('Welcome to Your Vue.js App.');
</script>

<template>
  <div class="page page-hello">
    <div class="page-content">
      <!-- Example of static asset path -->
      <img class="logo" src="@/assets/img/logo.png" alt="logo" />
    </div>
  </div>
</template>

Note

The above solution only works in conventional routing mode. That is, when it is detected that the WinJS configuration file .winrc does not contain the routes property, this feature will be automatically enabled.

Dynamic Routes

By convention, directories or files with the $ prefix are dynamic routes. If no parameter name is specified after $, it represents * wildcard. For example, the following directory structure:

For example:

  • src/pages/users/$id.tsx will become /users/:id
  • src/pages/users/$id/settings.tsx will become /users/:id/settings

A complete example, with the following file structure:

+ pages/
  + foo/
    - $slug.tsx
  + $bar/
    - $.tsx
  - index.tsx

Will generate the following route configuration:

javascript
[
  { path: '/', name: 'index', component: '@/pages/index.tsx' },
  { path: '/foo/:slug', name: 'fooSlug', component: '@/pages/foo/$slug.tsx' },
  { path: '/:bar/*', name: 'bar', component: '@/pages/$bar/$.tsx' }
];

Global Layout

By convention, src/layouts/index.tsx is the global layout. It returns a Vue component and renders nested routes through <router-view />.

With the following directory structure:

bash
.
└── src
    ├── layouts
   └── index.tsx
    └── pages
        ├── index.tsx
        └── users.tsx

Will generate the following routes:

js
[
  {
    path: '/',
    component: '@/layouts/index',
    routes: [
      { path: '', name: 'index', component: '@/pages/index' },
      { path: 'users', name: 'users', component: '@/pages/users' },
    ],
  },
]

You can use layout: false to fine-tune and disable the global layout display for a specific route. This option only takes effect at the first level:

ts
  routes: [
  {
    path: '/',
    name: 'index',
    component: './index',
    // 🟢 Works
    layout: false
  },
  {
    path: '/users',
    name: 'users',
    routes: [
      // 🔴 Does not work, at this point the layout of this route is not the global layout, but `/users`
      { layout: false }
    ]
  }
]

A custom global layout format looks like this:

tsx

export default function Layout() {
  return <router-view />
}

Different Global Layouts

You may need to output different global layouts for different routes. WinJS does not support such configuration, but you can still differentiate location.path in src/layouts/index.tsx and render different layouts.

For example, if you want to output a simple layout for /login:

jsx
export default defineComponent(() => {
  setup()
  {
    const route = useRoute();

    // Use `useAppData` to get more route information
    // const { clientRoutes } = useAppData()

    if (route.path === '/login') {
      return () => (
        <div>
          <SimpleLayout>
            <router-view />
          </SimpleLayout>
        </div>
      )
    }

    return () => (
      <div>
        <Header />
        <router-view />
        <Footer />
      </div>
    );
  }
});

Nested Routes

Although in WinJS, conventional routing does not support configuring different global layouts, we can create nested routes through the "directory convention" below, where both the current directory and subdirectories are child routes.

Assuming the directory structure of src/pages is as follows:

bash
.
├── docs.vue
├── hello
   ├── child.vue
   └── style.less
├── hello.vue
└── index.vue

Note

The folder and current-level route should have the same name.

bash
./src/pages
├── ${name}
   └── a.vue[tsx]
└── ${name}.vue[tsx]

Among them, the example code in hello.vue is:

vue
<template>
  <div class="page page-hello">
    <h3>Hello parent</h3>
    <router-view />
  </div>
</template>

Then, the route configuration automatically generated by WinJS is as follows:

json
{
  "hello": {
    "path": "hello",
    "id": "hello",
    "name": "hello",
    "parentId": "@@/global-layout"
  },
  "hello/child": {
    "path": "child",
    "id": "hello/child",
    "name": "helloChild",
    "parentId": "hello"
  },
  "index": {
    "path": "/",
    "id": "index",
    "name": "index",
    "parentId": "@@/global-layout"
  },
  "docs": {
    "path": "docs",
    "id": "docs",
    "name": "docs",
    "parentId": "@@/global-layout"
  },
  "@@/global-layout": {
    "id": "@@/global-layout",
    "path": "/",
    "isLayout": true
  }
};

404 Route

By convention, src/pages/404.tsx is the 404 page and must return a Vue component. The default value of name is NotFound.

For example, the following directory structure:

bash
.
└── pages
    ├── 404.tsx
    ├── index.tsx
    └── users.tsx

Will generate routes:

js
[
  { path: '/', name: 'index', component: '@/pages/index' },
  { path: '/users', name: 'users', component: '@/pages/users' },
  { path: '/:pathMatch(.*)*', name: 'NotFound', component: '@/pages/404' },
]

In this way, if you access /foo, neither / nor /users can match, it will fallback to the 404 route and be rendered through src/pages/404.tsx.

404 only takes effect automatically with conventional routing. If using configured routing, you need to configure the 404 wildcard route yourself.

Tip

Different versions of vue-router, 4.x and 3.x will have differences in the generated 404-related code. For 4.x, refer to 404 Route. For 3.x, refer to 404 Route.

4.x corresponds to Vue3, and the generated code is as follows:

js
[
  { path: '/', name: 'index', component: '@/pages/index' },
  { path: '/users', name: 'users', component: '@/pages/users' },
  { path: '/:pathMatch(.*)*', name: 'NotFound', component: '@/pages/404' },
]

2.x corresponds to Vue2, and the generated code is as follows:

js
[
  { path: '/', name: 'index', component: '@/pages/index' },
  { path: '/users', name: 'users', component: '@/pages/users' },
  // Must be placed at the last position in the route table, otherwise any route access will enter 404
  { path: '/*', name: 'NotFound', component: '@/pages/404' },
]

For programmatic navigation or within components, please use the useRouter API

html

<script lang="ts" setup>
  import { useRouter, useRoute } from 'winjs';

  const router = useRouter()
  const route = useRoute()

  const onHello = () => {
    router.push({
      name: 'search',
      query: {
        ...route.query,
      },
    })
  }

  // These three forms are equivalent
  router.push('/users/posva#bio');
  router.push({ path: '/users/posva', hash: '#bio' });
  router.push({ name: 'users', params: { username: 'posva' }, hash: '#bio' });
  // Only change hash
  router.push({ hash: '#bio' });
  // Only change query
  router.push({ query: { page: '2' } });
  // Only change param
  router.push({ params: { username: 'jolyne' } });

  // Navigate to the previous route
  router.back();

  // Navigate to the forward history record
  router.go(1);

  // Replace the record in the history stack
  router.replace('/new');
</script>

For more details, see here

See details

router-view

See details

See details

For example:

jsx
export default function Page () {
  return (
    <div>
      <router-link to="/users">Users Page</router-link>
    </div>
  )
}

Then clicking Users Page will navigate to the /users address.

Note:

  • RouterLink is only used for internal navigation within a single-page application. For external URL navigation, please use the a tag

Runtime Configuration

You can control vue and vue-router related configuration through export in the conventional src/app.tsx

router

Configure router configuration

ts
// src/app.tsx
export const router: RouterConfig = {
  // @ts-ignore
  scrollBehavior(to, from) {
    console.log('scrollBehavior', to, from);
  },
};

onMounted({app, router})

Vue app mount success callback. Here you can get the app instance and router instance, and can register global components, route interceptors, etc.

ts
export function onMounted({ app, router }: any) {
  console.log('onMounted', app, router);
  app.provide('win-hello', {
    h: 'hello',
    w: 'word',
  });
}

rootContainer(container)

Modify the root component when handing over to vue-router for rendering.

For example, to wrap an outer parent component

ts
import { h } from 'vue'

export function rootContainer(container) {
  return h(ThemeProvider, null, container);
}

Released under the MIT License.