在 Vite + Vue 项目上使用 vite-ssg 和 unplugin-vue-router
· 技术最近在用 Vite + Vue 3 模板做一个小项目,正好需要配置 SSG 和路由,故写下本文、给其他有需要的站长做一个参考。
创建一个 Vite + Vue 3 项目
文章的开始可不能缺一个 Vite + Vue 3 项目。如果你已经有了,可以继续往下看;如果你还没有,那还不火速创建一个?
pnpm create vite --template vue
vite-ssg
为什么要使用它?
默认创建下来的 Vite + Vue 3 项目是一个单页面应用。单页面应用即 Single-Page Application,也就是大家所熟知的 SPA。SPA 并不意味着整个应用只有一个页面可供访问,而是构建出来的产物只有 index.html
这么一个 HTML 文件,页面的切换则是通过 JavaScript 来完成。
虽然 SPA 在后期不需要完全刷新便可切换整个页面,但代价也是有的:
- SPA 需要执行完 JavaScript 才会渲染出内容,因此一些爬虫无法得知你的页面有什么内容,这一点你可以在网站 URL 前加一个
view-source:
来体会:<body />
只有两行用于 JavaScript 挂载的 HTML 代码,其余什么都没有; - 同理,用户需要等待 JavaScript 执行完才能看到网页实际内容,在此期间他们什么也看不见;
- 由于整个 SPA 只有
index.html
这么一个 HTML 文件,你需要将所有请求 rewrite 到这个 HTML 文件上,否则会喜提 HTTP 404 状态码。
若我们只追求静态的构建产物(比如你要部署网站在 GitHub Pages)的话,不如来看看近年非常流行的渲染模式:静态站点生成。静态站点生成即 Static-Site Generation,也就是大家所熟知的 SSG,又称为 JAMStack。与 Hexo 这类拼接 HTML 模板的静态站点生成器相比,SSG 有以下的不同点:
- SSG 相当于提前完成了一次服务端渲染(Server-Side Render,即 SSR),提前将页面上不会变动的内容渲染到对应的 HTML 文件;
- SSG 在初始页面加载后可将其「激活」为 SPA,你在享受无刷新切换页面的同时也拥有了良好的 SEO 和性能,也不需要将所有请求 rewrite 到
index.html
。
不过,作为一个打包器,Vite 并没有提供 SSG 渲染方式,看起来只有 Nuxt 和 VitePress 才会有了......吗?
不要质疑 Vite 的丰富生态。Anthony Fu 做了一个用于 Vite + Vue 3 的静态站点生成器:vite-ssg,我们将用它来在 Vite + Vue 3 项目上实现 SSG。
安装和配置
首先来将它安装到项目中:
pnpm add -D vite-ssg vue-router
接下来,打开项目中的 package.json
,修改一下 Vite 的构建指令:
{
"scripts": {
"dev": "vite",
"build": "vite build" // 将 vite build 改为 vite-ssg build
}
}
最后,打开入口文件 main.ts
修改成如下的配置:
import { ViteSSG } from "vite-ssg" // 用此行替换 import { createApp } from 'vue'
import App from "./App.vue"
// 用下述内容替换 createApp(App).mount(#app)
export const createApp = ViteSSG(
App,
{ routes },
({ app, router, routes, isClient, initialState }) => {
// 在这里使用例如 app.use(pinia) 或者 router.use()
}
)
注意到了 { routes }
是必须填入的吗?如果你的项目使用了 Vue Router 并有专门的配置文件,那么你现在可以留下路由配置 routes
并扬掉整个配置文件了。因为你只需要告诉 vite-ssg 你的路由配置,其余的它会帮你搞定。
如果你的 Vite + Vue 3 项目真的只有一个页面,那么就修改一下 ViteSSG
的导入即可:
import { ViteSSG } from "vite-ssg/single-page" // vite-ssg => vite-ssg/single-page
unplugin-vue-router
为什么我又需要这个捏?
Nuxt 可以根据 pages/
下的目录和 Vue 组件生成路由;VitePress 可以根据指定目录下的子目录和 Markdown 文件生成路由;而看看你的 Vite + Vue 3 项目,路由还得自己手动修改 routes
配置,生不生气?
别急,在 Vite + Vue 3 项目上也可以实现这一点。Vue Router 作者 Eduardo 做了一个小插件:unplugin-vue-router,能够自动为你生成路由配置。
安装
还是惯例,先将它安装到项目中:
pnpm add -D unplugin-vue-router
接下来,打开 vite.config.ts
,将它添加进 plugins
里:
import { defineConfig } from "vite"
import vue from "@vitejs/plugin-vue"
import VueRouter from "unplugin-vue-router/vite"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [VueRouter(), vue()]
})
注意,一定要将 unplugin-vue-router 放在 Vue plugin 前面。
unplugin-vue-router 也支持 Rollup, Webpack 和 ESBuild 打包器。具体配置详见 插件仓库 README。
配置
安装好 unplugin-vue-router 后,请直接运行一次 pnpm dev
而不是先把原本需要手动配置的 routes
扬了,unplugin-vue-router 需要先生成一个类型文件。
生成的类型文件名为 typed-router.d.ts
、需要添加到 tsconfig.json
里。Vite + Vue 3 模板项目的 tsconfig.json
分出了两个配置文件,一个 tsconfig.app.json
一个 tsconfig.node.json
,我们只需要将它添加到 tsconfig.app.json
里即可。
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "preserve",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"./typed-router.d.ts" // 添加这一行
]
}
Vite + Vue 3 项目的 src
目录下有一个 vite-env.d.ts
(希望你没把它删掉.jpg),在里面多加一行 unplugin-vue-router 的 reference:
/// <reference types="vite/client" />
// 添加下面这一行
/// <reference types="unplugin-vue-router/client" />
如果你的项目实在没有 vite-env.d.ts
,那也可以添加到 tsconfig.app.json
里:
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"types": ["unplugin-vue-router/client"], // 添加这一行
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "preserve",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"./typed-router.d.ts"
]
}
最后,回到入口文件 main.ts
,将原本的 routes 删掉并从 vue-router/auto-routes
导入:
import { ViteSSG } from "vite-ssg"
import App from "./App.vue"
import { routes } from "vue-router/auto-routes" // 将原本的 routes 更换成这个
export const createApp = ViteSSG(
App,
{ routes },
({ app, router, routes, isClient, initialState }) => {}
)
上述的类型配置如果有一步没设定好,那么
vue-router/auto-routes
就会提示不存在。
现在你就可以享受自动路由配置带来的快乐啦。