大体步骤
配置部分
- 引入路由
- vite 配置别名
- 配置cross 新建.env
样式部分
- 安装less
- 重置css
- 引入全局less 变量
- 引入element UI
axios部分
- 引入axios 封装拦截
- 封装api
其他
- 引入工具函数
npm 安装插件
- @types/node
- axios-mapper
- element-plus
- vue-router
- cross-env
- less
- less-loader
cross-env 配置
// package.json
{
"scripts": {
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview"
},
"scripts": {
"dev": "vite",
"test": "cross-env vite build --mode test",
"build": "cross-env vite build --mode production",
"preview": "vite preview"
},
}
在 src 目录下添加两个入口文件
项目目录下 修改 index.html文件 // entry-client.ts import { createSSRApp } from 'vue'; import App from './App.vue';
const app = createSSRApp(App); app.mount('#app', true);
// entry-server.js import { createSSRApp } from 'vue'; import App from './App.vue'; import { renderToString } from '@vue/server-renderer';
export async function render(url, manifest) { const app = createSSRApp(App); const context = {}; const appHtml = await renderToString(app, context); return { appHtml }; }
新建node端web服务器入口文件(开发环境): server-env.js ,官方推荐 express,安装node包: yarn add -D express const fs = require('fs'); const path = require('path'); const express = require('express'); const { createServer: createViteServer } = require('vite');
async function createServer() { const app = express();
const vite = await createViteServer({ server: { middlewareMode: true }, });
app.use(vite.middlewares);
app.use('*', async (req, res) => { // serve index.html - we will tackle this next const url = req.originalUrl;
try {
// 1. Read index.html
let template = fs.readFileSync(path.resolve(__dirname, 'index.html'), 'utf-8');
// 2. Apply vite HTML transforms.
template = await vite.transformIndexHtml(url, template);
// 3. Load the server entry. vite.ssrLoadModule
const { render } = await vite.ssrLoadModule('/src/entry-server.js');
// 4. render the app HTML.
const { appHtml } = await render(url);
// 5. Inject the app-rendered HTML into the template.
const html = template.replace(``, appHtml);
// 6. Send the rendered HTML back.
res.status(200).set({ 'Content-Type': 'text/html' }).end(html);
} catch (e) {
// If an error is caught,
vite.ssrFixStacktrace(e);
console.error(e);
res.status(500).end(e.message);
}
});
app.listen(3000, () => { console.log('http://localhost:3000'); }); }
createServer();
终端运行 yarn dev, 浏览器打开:http://localhost:3000/ 网页右键“显示页面源码”、
生产环境打包,package.json新增 build 相关命令
//package.json "scripts": { "dev": "node server-env.js", "build:client": "vite build --outDir dist/client --ssrManifest", "build:server": "vite build --outDir dist/server --ssr src/entry-server.js ", "build": "yarn build:client && yarn build:server", "preview": "yarn build && node server.js" }, 新建 node 端web服务器入口文件(生产环境): server.js ,个人选择 koa搭建生产环境服务器,安装 node 包:yarn add -D koa koa-static
// server.js const fs = require('fs'); const path = require('path'); const Koa = require('koa'); const staticPath = require('koa-static');
const app = new Koa(); const resolve = (p) => path.resolve(__dirname, p);
const template = fs.readFileSync(resolve('./dist/client/index.html'), 'utf-8'); const manifest = require('./dist/client/ssr-manifest.json'); const render = require('./dist/server/entry-server.js').render;
app.use(staticPath(resolve('./dist/client'), { index: false }));
app.use(async (ctx, next) => { const url = ctx.req.url; try { const { appHtml } = await render(url, manifest);
let html = template.replace(``, appHtml);
ctx.body = html;
} catch (error) { console.log(error); next(); } });
app.listen(3000, () => { console.log('http://localhost:3000'); });
终端运行: yarn preview,浏览器打开:http://localhost:3000。最简 ssr 改造完成。
安装生产上必备的 vue 全家桶: scss vuex vue-router 首先安装scss支持: yarn add -D sass.
安装vue-router 和 vuex : yarn add vuex@next vue-router@next vuex-router-sync@next
新建 src/store/index.ts 和 src/router/index.ts 两个文件
// src/router/index.ts
import { createRouter, createWebHistory, createMemoryHistory } from 'vue-router';
export default function () { const routerHistory = import.meta.env.SSR === false ? createWebHistory() : createMemoryHistory();
return createRouter({ history: routerHistory, routes: [ { path: '/', name: 'home', component: () => import('../views/Home.vue'), }, { path: '/about', name: 'about', component: () => import('../views/About.vue'), }, { path: '/:catchAll(.)', name: '404', component: () => import('../views/404.vue'), meta: { title: '404 Not Found', }, }, ], }); }
// src/store/index.ts
import { createStore as _createStore } from 'vuex';
export default function createStore() { return _createStore({ state: { message: 'Hello vite2 vue3 ssr', }, mutations: {}, actions: { fetchMessage: ({ state }) => { return new Promise((resolve) => { setTimeout(() => { state.message = 'Hello vite2 vue3 ssr typescript scss vuex vue-router'; resolve(0); }, 200); }); }, }, modules: {}, }); }
新建对应的 src/views/页面 Home.vue About.vue 404.vue, 略。
修改 entry-client.ts 和 entry-server.js文件,加入相应的 vuex 和 router
// entry-client.ts
import { createSSRApp } from 'vue'; import App from './App.vue'; import { sync } from 'vuex-router-sync';
import createStore from './store'; import createRouter from './router';
const router = createRouter(); const store = createStore(); sync(store, router);
const app = createSSRApp(App); app.use(router).use(store);
router.beforeResolve((to, from, next) => { next(); });
router.isReady().then(() => { app.mount('#app', true); });
// entry-server.js
import { createSSRApp } from 'vue'; import App from './App.vue'; import { renderToString } from '@vue/server-renderer';
import createStore from './store'; import createRouter from './router'; import { sync } from 'vuex-router-sync';
export async function render(url, manifest) { const router = createRouter(); const store = createStore(); sync(store, router);
const app = createSSRApp(App); app.use(router).use(store);
router.push(url);
await router.isReady();
const context = {}; const appHtml = await renderToString(app, context); return { appHtml }; }
终端运行: yarn dev 查看开发环境效果。终端运行: yarn preview 查看生产环境效果。
第一阶段源码: github.com/damowangzhu…
服务端预取数据 asyncData 服务端预取数据采用 vue2的 asyncData 方式。
新建 vue-extend.d.ts 文件
// vue-extend.d.ts
import { RouteRecordRaw } from 'vue-router';
export interface AsyncDataContextType { route: RouteRecordRaw; store: any; // 类型不决 用 any。 -.-! }
declare module '@vue/runtime-core' { interface ComponentCustomOptions { asyncData?(context: AsyncDataContextType): Promise; } }