背景:
原项目已经是一个完整的项目了,不过由于seo的需求,需要寻找ssg或者ssr方案,根据vue官网的推荐,最终我决定基于vite使用vike(原vite-plugin-ssr)这个方案。原项目是我个人编写的(对项目结构等熟悉),并是普通网站类那种仅需要预渲染少量的页面,不需要预渲染几百个几千个页面的项目(适合ssg)。
一、安装并配置vike
npm i vike
vite.config.ts文件里面写上( 如果不需要ssg预渲染,仅需要ssr,写vike()即可,其他项目内修改的逻辑是一样的 ):
import vike from 'vike/plugin'
export default defineConfig({
plugins: [
vue(),
vike({ prerender: true })
]
})
二、创建renderer文件夹
我把renderer文件夹放在和src同级的地方,基础的renderer内容,直接复制官网例子vike-with-pinia/renderer at main · brillout/vike-with-pinia · GitHub这个项目里面的即可(因为这里面的文件内容行数比较少,适合新手),由于我是ts项目,我还结合了vike/boilerplates/boilerplate-vue-ts/renderer at main · vikejs/vike · GitHub,其中我只保留了:+config.ts、+onBeforeRender.ts、+onRenderClient.ts、+onRenderHtml.ts、app.ts、types.ts、usePageContext.ts这几个文件
1. 关于+onRenderClient.ts
该文件定义页面的浏览器端代码(vite-plugin-ssr里面是.page.client.js),其中我把“判断当前是什么浏览器”的逻辑写到了这里。
2.关于+onRenderHtml.ts
该文件定义页面服务端代码(vite-plugin-ssr里面是.page.server.js
),这里会return documentHtml,其中预渲染的html模板就是依据这个documentHtml,所以我们可以在这里判断不同路由预渲染不同的title或者description等(要写上<meta charset="UTF-8" />,不然渲染的html文件中文会乱码)
// pageContext.urlPathname:获取当前路由
switch (pageContext.urlPathname) {
case '/about':
title = '关于'
break
default:
title = '默认标题'
}
const documentHtml = escapeInject`<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>${title}</title>
</head>
<body>
<div id="app">${stream}</div>
</body>
</html>`
return {
documentHtml,
pageContext: {
enableEagerStreaming: true
}
}
你可能注意到了pageContext.urlPathname可以获取当前页面的路由,在.vue文件里面获取可以参考下面代码:
// 可以在vite.config.ts配置某个符合直接定位到renderer,但是我没有配置
import { usePageContext } from '../../../renderer/usePageContext'
const pageContext = usePageContext()
const currentPath = pageContext.urlPathname
3.关于app.ts
该文件类似于原来项目里面的main.ts,ssr/ssg模式下,main.ts文件无效,所以pinia等要在这里导入。另外还要注意,预渲染后会生成不同的.html文件,单单的pinia无法满足项目的需求,需要安装pinia-plugin-persistedstate处理持久化。
注意:ssr/ssg模式下,main.ts、App.vue、index.html文件无效
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import { createSSRApp, h, reactive, markRaw } from 'vue'
import { setPageContext } from './usePageContext'
import '@/assets/main.scss' // 全局scss我也是这里导入
export { createApp }
function createApp(pageContext) {
let rootComponent
const app = createSSRApp({
data: () => ({
Page: markRaw(pageContext.Page),
pageProps: markRaw(pageContext.pageProps || {})
}),
render() {
return h(this.Page, this.pageProps)
},
created() {
rootComponent = this
}
})
// 这一步就是安装pinia
const store = createPinia()
store.use(piniaPluginPersistedstate)
app.use(store)
Object.assign(app, {
changePage: (pageContext) => {
Object.assign(pageContextReactive, pageContext)
rootComponent.Page = markRaw(pageContext.Page)
rootComponent.pageProps = markRaw(pageContext.pageProps || {})
}
})
const pageContextReactive = reactive(pageContext)
setPageContext(app, pageContextReactive)
return { app, store }
}
三、给原来的项目文件夹、文件改名
1. src->views,其中views文件夹需改名为pages文件夹,路由会自动从这个文件夹开始寻找(同时也意味着vue-router没有用了,并且使用router方法跳转的地方,都应该更改为window.location.href = ‘/xx’)。
原文件夹和视图命名为about->aboutView.vue都改为about->+Page.vue(即文件夹名字不变,视图文件改为+Page.vue),这样当路由是/about,时,就会定位到about->+Page.vue这个文件。
如果你原路由是about/test,则你的文件夹和文件路径应该是:about->test->+Page.vue(意思是about里面是test文件夹,再里面是+Page.vue文件)
目录参考(_error->+Page.vue,这样命名文件夹和文件,找不到路由都会被定位到该页面):
四、一些组件可能仅浏览器渲染,需要ClientOnly组件
比如一些用了window.xx等方法的,这些方法只有浏览器端才有,所以需要仅浏览器渲染
ClientOnly组件参考:https://github.com/vikejs/vike-vue/blob/main/packages/vike-vue/src/components/ClientOnly.vue
五、可能会报依赖包损坏的错误
需要在vite.config.ts配置noExternal(下面代码的作用是告诉构建系统在进行服务器端渲染构建时,不要把element-plus当作外部依赖排除出去)
export default defineConfig({
plugins: [
vue(),
vike({ prerender: true })
],
ssr: {
noExternal: ['element-plus']
}
})
六、打包
项目的报错都解决后,使用npm run build打包,生成的预渲染html文件位于dist\client里面,都是静态文件,像以前那样放到服务器上即可,不需要服务器额外处理(除非你使用的是ssr)
七、总结
在我写下这篇文章时,使用的vike在网上完全搜不到别人使用的经验(如果不是其他人写的seo太差,这应该是第一篇vike的文章了),它原身vite-plugin-ssr也很少,使用vike问ai,ai也不知道这个是什么,所以前期啃着文档过来的确实感觉有点复杂,幸好vike的文档和官网给的github例子都很详细,然后就这样慢慢一步一步弄出来了,真的vike的文档、vue的生态真的太好了,非常感谢!
这里还想表扬一下自己的文件、文件夹命名、代码都很清晰,组件和视图也分明,所以给views里面的文件改名的时候,很快就改完且没有冲突,给组件套上ClientOnly也很容易定位到什么组件。
另外,推荐vue使用3.4.27版本(修复了样式属性水合的bug,我是使用了postcss-px-to-viewport里面的selectorBlackList(需要忽略的CSS选择器),才发现原来的vue有这个bug的,没想到刚好最新版本的vue修复了)
参考:
1. https://github.com/vikejs/vike-vue/blob/main/packages/vike-vue/src/components/ClientOnly.vue
2. vike-with-pinia/renderer at main · brillout/vike-with-pinia · GitHub
3. vike/boilerplates/boilerplate-vue-ts/renderer at main · vikejs/vike · GitHub