Vue 3 集成

本指南涵盖了将 @esmx/router 与 Vue 3 集成所需的全部内容。阅读完毕后,你将拥有一个带有路由和服务端渲染(SSR)的完整 Vue 3 应用。

安装

安装核心路由包和 Vue 集成包:

npm install @esmx/router @esmx/router-vue

@esmx/router 是框架无关的路由核心。@esmx/router-vue 提供 Vue 专属绑定 —— 插件、组合式函数和组件。

核心概念

Vue 3 集成依赖三个部分:

  1. RouterPlugin — 将 RouterViewRouterLink 注册为全局组件
  2. useProvideRouter() — 通过 provide/inject 将路由实例提供给组件树
  3. useRouter() / useRoute() — 用于访问路由实例和当前路由的组合式 API

分步设置

1. 定义路由

创建路由定义的单一数据源:

src/routes.ts
import type { RouteConfig } from '@esmx/router';

export const routes: RouteConfig[] = [
  {
    path: '/',
    component: () => import('./layouts/MainLayout.vue'),
    children: [
      { path: '', component: () => import('./pages/Home.vue') },
      { path: 'about', component: () => import('./pages/About.vue') },
      {
        path: 'users/:id',
        component: () => import('./pages/UserProfile.vue'),
        meta: { requiresAuth: true }
      }
    ]
  }
];

2. 创建应用工厂

客户端和服务端入口共用的工厂函数:

src/create-app.ts
import { h, createApp, createSSRApp } from 'vue';
import { Router } from '@esmx/router';
import { RouterPlugin, useProvideRouter } from '@esmx/router-vue';
import App from './App.vue';

export function createVueApp(router: Router, ssr = false) {
  const create = ssr ? createSSRApp : createApp;

  const app = create({
    setup() {
      useProvideRouter(router);
      return () => h(App);
    }
  });

  app.use(RouterPlugin);

  return { app, router };
}

关键点:

  • useProvideRouter(router) 必须在根组件的 setup() 中调用。它通过 useRouter()useRoute() 将路由实例提供给所有后代组件。
  • RouterPluginRouterViewRouterLink 注册为全局组件,并在 globalProperties 上设置 $router$route
  • SSR 使用 createSSRApp,仅客户端使用 createApp

3. 客户端入口

客户端入口以 history 模式创建路由并挂载应用:

src/entry.client.ts
import { Router, RouterMode } from '@esmx/router';
import { createVueApp } from './create-app';
import { routes } from './routes';

const router = new Router({
  appId: 'app',
  mode: RouterMode.history,
  routes
});

const { app } = createVueApp(router);
app.mount('#app');

RouterMode.history 使用浏览器的 History API(pushStatepopstate)实现简洁的 URL。

4. 服务端入口(SSR)

服务端入口以 memory 模式创建路由并渲染 HTML:

src/entry.server.ts
import type { RenderContext } from '@esmx/core';
import { renderToString } from '@vue/server-renderer';
import { Router, RouterMode } from '@esmx/router';
import { createVueApp } from './create-app';
import { routes } from './routes';

export default async (rc: RenderContext) => {
  const router = new Router({
    mode: RouterMode.memory,
    base: new URL(rc.params.url, 'http://localhost'),
    routes
  });

  await router.replace(rc.params.url);

  const { app } = createVueApp(router, true);
  const html = await renderToString(app, {
    importMetaSet: rc.importMetaSet
  });

  rc.html = `<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    ${rc.preload()}
    ${rc.css()}
</head>
<body>
    <div id="app">${html}</div>
    ${rc.importmap()}
    ${rc.moduleEntry()}
    ${rc.modulePreload()}
</body>
</html>`;
};

RouterMode.memory 将路由状态保存在内存中 —— 不调用浏览器 API,非常适合 Node.js 环境。

5. Node 入口

Node 入口配置开发服务器和构建工具:

src/entry.node.ts
import http from 'node:http';
import type { EsmxOptions } from '@esmx/core';

export default {
  async devApp(esmx) {
    return import('@esmx/rspack-vue').then((m) =>
      m.createRspackVue3App(esmx)
    );
  },

  async server(esmx) {
    const server = http.createServer((req, res) => {
      esmx.middleware(req, res, async () => {
        const rc = await esmx.render({
          params: { url: req.url }
        });
        res.end(rc.html);
      });
    });

    server.listen(3000, () => {
      console.log('Server started: http://localhost:3000');
    });
  }
} satisfies EsmxOptions;

@esmx/rspack-vue 中的 createRspackVue3App 配置了 Rspack,支持 Vue 3 SFC、HMR 和 SSR 打包。

在组件中使用路由

RouterView 渲染当前路由匹配的组件。RouterLink 创建带有激活状态管理的导航链接:

src/App.vue
<template>
  <div>
    <nav>
      <RouterLink to="/">首页</RouterLink>
      <RouterLink to="/about">关于</RouterLink>
      <RouterLink to="/users/42">用户 42</RouterLink>
    </nav>

    <RouterView />
  </div>
</template>

当当前路由匹配链接的 to 路径时,RouterLink 会自动应用 router-link-active 类名。通过 activeClass 属性自定义:

<RouterLink to="/about" active-class="nav-active">关于</RouterLink>

useRouter 和 useRoute

<script setup> 中访问路由实例和当前路由:

src/pages/UserProfile.vue
<template>
  <div>
    <h1>用户 {{ route.params.id }}</h1>
    <p>当前路径:{{ route.path }}</p>
    <p>查询参数:{{ JSON.stringify(route.query) }}</p>
    <p>元信息:{{ JSON.stringify(route.meta) }}</p>

    <button @click="goHome">返回首页</button>
    <button @click="goToNextUser">下一位用户</button>
  </div>
</template>

<script setup lang="ts">
import { useRouter, useRoute } from '@esmx/router-vue';
import { watch } from 'vue';

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

function goHome() {
  router.push('/');
}

function goToNextUser() {
  const currentId = Number(route.params.id);
  router.push(`/users/${currentId + 1}`);
}

// 监听路由变化
watch(() => route.path, (newPath, oldPath) => {
  console.log(`路由从 ${oldPath} 变更为 ${newPath}`);
});
</script>

导航方法

路由提供多种导航方法:

<script setup lang="ts">
import { useRouter } from '@esmx/router-vue';

const router = useRouter();

// 向历史栈压入新记录
router.push('/about');

// 替换当前记录(不产生新的历史记录)
router.replace('/about');

// 后退
router.back();

// 前进
router.forward();

// 跳转到指定的历史偏移量
router.go(-2);
</script>

访问路由信息

route 对象提供当前路由的完整信息:

<script setup lang="ts">
import { useRoute } from '@esmx/router-vue';

const route = useRoute();

// 当前路径
route.path        // '/users/42'

// 路由参数
route.params       // { id: '42' }

// 查询字符串参数
route.query        // { tab: 'profile' }(对应 /users/42?tab=profile)

// 路由元信息
route.meta         // { requiresAuth: true }

// 完整的 URL 对象
route.url          // URL 实例

// 匹配的路由配置(从父到子)
route.matched      // RouteConfig[]
</script>

项目文件结构

典型的 Vue 3 + SSR 项目结构(使用 @esmx/router):

src/
├── entry.node.ts      # Node.js 服务器配置、开发/构建设置
├── entry.server.ts    # SSR 渲染逻辑
├── entry.client.ts    # 客户端挂载与应用激活
├── create-app.ts      # 共享的应用工厂(服务端和客户端共用)
├── routes.ts          # 路由定义
├── App.vue            # 根组件(RouterView + 导航)
├── layouts/
│   └── MainLayout.vue # 含嵌套 RouterView 的布局
└── pages/
    ├── Home.vue
    ├── About.vue
    └── UserProfile.vue

下一步