簡(jiǎn)介
極速傳送門: 基于vue-cli4.0+Typescript+SSR的小Demo
最終效果:
訪問秕噪,返回的是html片段,并且從chunk中分離了vue依賴
Vue SSR Demo
使用Vue-cli4.0+Typescript+SSR 的小目Demo
怎么說呢,從vue3開始,之前的build目錄舊沒有了,想要對(duì)webpack配置進(jìn)行修改的話嘁酿,只能在根目錄下新建 vue.config.js文件,具體修改可參照vue-cli官網(wǎng)
之前用的是vue-cli2.+,此次直接升級(jí)到vue-cli4.+并且直接上了TS男应,踩了不少坑闹司,不過好在是熬過來了,分享一下我的項(xiàng)目Demo
已實(shí)現(xiàn):
- 項(xiàng)目搭建
- 本地ssr開發(fā)環(huán)境搭建(koa)
- 使用Vuex實(shí)現(xiàn)數(shù)據(jù)預(yù)取(demo刪掉了該部分)
- 打包c(diǎn)hunk分離優(yōu)化
- Jenkins + Docker 持續(xù)集成沐飘、部署
- 集成到后端項(xiàng)目(egg.js)
主要命令
$ npm install # 安裝依賴
$ npm run dev # 本地開發(fā)
├── 8080 本地非ssr環(huán)境
└── 3000 本地ssr環(huán)境
$ npm run build # window下打包命令
$ npm run build:linux # linux下打包命令
項(xiàng)目目錄
┌── .vscode IDE工作區(qū)配置
│ └── settings.json
├── dist # 靜態(tài)資源(自動(dòng)生成)
│ └── index.temp.html # ssr模板文件
├── public # 靜態(tài)目錄游桩,打包的時(shí)候該目錄會(huì)原封不動(dòng)的復(fù)制到dist目錄下
├── server # koa服務(wù)
│ ├── index.js # koa入口
│ └── server.js # 服務(wù)邏輯
├── src # 主要源碼
│ ├── api # 請(qǐng)求管理
│ │ └── api.config.ts 接口管理
│ │ └── axios.config.ts 攔截器管理
│ ├── assets # 資源
│ │ └── ......
│ ├── plugins # 插件
│ │ └── ......
│ ├── utils # 全局方法等
│ │ └── ......
│ ├── views # 頁面/視圖
│ │ └── ......
│ ├── App.vue # 入口Vue
│ ├── entry-client.ts # 客戶端入口
│ ├── entry-server.ts # 服務(wù)端入口
│ ├── main # 公共入口
│ ├── router.index.ts # 路由配置
│ ├── store.index.ts # Vuex配置
│ ├── vue-shim.d.ts # ts模塊聲明
└── .browserslistrc # 瀏覽器設(shè)置
└── .eslintrc.js # eslintrc設(shè)置
└── .gitignore # git忽略
└── babel.config.js # babel配置
└── Jenkinsfile # jenkins配置
└── package-lock.json # 版本鎖定
└── package.json # 項(xiàng)目依賴
└── tsconfig.json # ts配置文件
└── vue.config.js # webpack配置文件
webpack配置文件 解析
- 關(guān)閉css分離
extract: false, // 目前還沒分離出css,正在努力嘗試
- 本地ssr搭建
本地搭建ssr環(huán)境,webpack-dev-server跑本地服務(wù)時(shí)耐朴,會(huì)打一次包并進(jìn)行熱更新借卧,所以思路是再起一個(gè)node服務(wù),讀取監(jiān)聽打包的chunk文件進(jìn)行ssr的操作筛峭,這里有兩個(gè)點(diǎn)
- 設(shè)置 webpack-dev-serve 允許跨域
headers: { 'Access-Control-Allow-Origin': '*' },
- 設(shè)置publicPath
// 加上端口前綴才能訪問到靜態(tài)資源铐刘,生產(chǎn)環(huán)境因?yàn)槭羌傻胶蠖隧?xiàng)目設(shè)置跟路徑就OK了
process.env.NODE_ENV !== 'production' ? 'http://localhost:8080' : '/',
- 分離 chunk 減少打包體積
// 可以分離大部分依賴,使打包出來的chunk體積變小
// 客戶端生產(chǎn)環(huán)境 才分離
externals:
// 可以分離大部分依賴影晓,使打包出來的chunk體積變小
process.env.NODE_ENV === 'production' && !TARGET_NODE
? {
vue: 'Vue',
}
: undefined,
- 注入版本信息
new webpack.DefinePlugin({
'process.env.NODE_ENV': `"${process.env.NODE_ENV || 'development'}"`, // 注意需要用雙引號(hào)包住
}),
- 關(guān)閉splitChunks
chainWebpack: (config) => {
config.optimization.splitChunks(undefined);
},
ssr 基本搭建
- 公共入口 main.ts
// 防止單例污染镰吵,需要導(dǎo)出一個(gè)工廠函數(shù)
export function createApp(): any {
const router = createRouter();
const store = createStore();
const app = new Vue({
router,
store,
render: (h) => h(App),
});
return { app, router, store };
}
- 客戶端入口 entry-client.ts
// 掛載到app上
const { app, router } = createApp();
router.onReady(() => {
app.$mount('#app');
});
- 服務(wù)端入口 entry-server.ts
// 匹配路由,需要返回一個(gè)promise挂签,因?yàn)橛锌赡苁钱惒浇M件疤祭,若無匹配,則返回404
import { createApp } from './main';
export default (context: any) =>
new Promise((resolve, reject) => {
const { app, router } = createApp();
router.push(context.url);
router.onReady(() => {
const matchedComponents = router.getMatchedComponents();
if (!matchedComponents.length) {
return reject({
code: 404,
});
}
resolve(app);
}, reject);
});
4 路由 router.index.ts
// 注意要模式改為 history饵婆,讓路由請(qǐng)求能到后端
export function createRouter() {
return new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'Home',
component: () => import(/* webpackChunkName: "Home" */ './views/Home.vue'),
},
{
path: '/about',
name: 'About',
component: () => import(/* webpackChunkName: "about" */ './views/About.vue'),
},
],
});
}
5 注意不要在組件的 mounted 鉤子之前進(jìn)行 dom 相關(guān)的操作勺馆,因?yàn)榉?wù)端會(huì)執(zhí)行到這些鉤子
6 實(shí)現(xiàn)本地 ssr
- 讀取web-dev-server打包出來的chunk并實(shí)時(shí)監(jiān)聽
- 當(dāng)chunk變化時(shí),使用'vue-server-renderer'的renderToString,渲染成html字符串
7 本地 ssr 環(huán)境下谓传,接口轉(zhuǎn)發(fā)問題
// 雖然可以配置 devServer proxy,但是ssr環(huán)境在另一個(gè)node端口下芹关,需要對(duì)接口進(jìn)行轉(zhuǎn)發(fā)
- 對(duì)接口進(jìn)行規(guī)范续挟,接口類的請(qǐng)求都加上'/api'前綴
- koa server.js 下,對(duì)匹配的接口類請(qǐng)求侥衬,使用'http-proxy-middleware'和'koa2-connect'進(jìn)行接口轉(zhuǎn)發(fā)