从0开始搭建 Egg + Vue + Webpack 服务端渲染项目
1. 初始化环境
安装 Node LST (8.x.x) 环境: https://nodejs.org/zh-cn
2. 初始化 egg 项目
https://github.com/eggjs/egg-init/blob/master/README.zh-CN.md
npm i egg-init -g
egg-init
- 选择
Simple egg app boilerplate
project 初始化 egg 项目 - 新建
${app_root}/app/view
目录(egg view规范目录),并添加.gitkeep
文件,保证该空目录被 git 提交到仓库 - 新建
${app_root}/app/view/layout.html
文件,用于服务端渲染失败后,采用客户端渲染
<!DOCTYPE html>
<html lang="en">
<head>
<title>Egg + Vue + Webpack</title>
<meta name="keywords">
<meta name="description">
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
</head>
<body>
<div id="app"></div>
</body>
</html>
3. 安装依赖
- 服务端渲染依赖
vue
和 vue-server-renderer
没有内置在 egg-view-vue-ssr 里面, 项目需要显示安装依赖。
npm i vue vue-server-renderer vuex axios egg-view-vue-ssr --save
- 构建开发依赖
npm i cross-env easywebpack-cli easywebpack-vue egg-webpack egg-webpack-vue --save-dev
npm i vue-template-compiler uglifyjs-webpack-plugin webpack-manifest-resource-plugin --save-dev
- 安装全部依赖
npm install
4. 添加配置
- 添加
${app_root}/config/plugin.local.js
配置
exports.webpack = {
enable: true,
package: 'egg-webpack'
};
exports.webpackvue = {
enable: true,
package: 'egg-webpack-vue'
};
- 添加
${app_root}/config/plugin.js
配置
exports.vuessr = {
enable: true,
package: 'egg-view-vue-ssr'
};
- 添加
${app_root}/config/config.default.js
配置
'use strict';
const path = require('path');
module.exports = appInfo => {
const config = exports = {};
// 保证构建的静态资源文件能够被访问到
config.static = {
prefix: '/public/',
dir: path.join(appInfo.baseDir, 'public')
};
return config;
}
- 添加
${app_root}/config/config.local.js
配置
'use strict';
module.exports = () => {
const config = exports = {};
config.vuessr = {
// 本地开发 css 采用 inline 方式, 无需注入 css 链接。
injectCss: false,
};
return config;
};
- 添加
easywebpack-cli
配置文件${app_root}/webpack.config.js
module.exports = {
egg: true,
framework: 'vue', // 使用 easywebpack-vue 构建解决方案
entry: {
include: ['app/web/page'], // 自动遍历 app/web/page 目录下的 js 文件入口
exclude: ['app/web/page/[a-z]+/component'],
},
alias: {
~: __dirname,
asset: 'app/web/asset',
component: 'app/web/component',
framework: 'app/web/framework',
store: 'app/web/store'
},
dll: ['vue/dist/vue.common.js', 'axios'], // webpack dll 构建
install:{
npm: 'npm', // 默认是 npm, 可以是 cnpm
check: true // 默认为禁用,自动安装缺少的 loader 和 plugin,建议首次 运行成功后,改成 false,加快构建速度
},
loaders: {
eslint: false
},
plugins: {},
done() { // 编译完成回调
}
};
- 添加
${app_root}/.babelrc
文件
{
"presets": [["env",{ "modules": false }]],
"plugins": [
"transform-object-rest-spread",
"syntax-dynamic-import",
"transform-object-assign"
],
"comments": false
}
安装 babel 相关依赖
npm i babel-core babel-loader --save-dev
npm i babel-preset-env babel-plugin-syntax-dynamic-import babel-plugin-transform-object-assign babel-plugin-transform-object-rest-spread --save-dev
- 添加
${app_root}/postcss.config.js
文件
module.exports = {
plugins: [
require('autoprefixer')
]
};
安装 autoprefixer 依赖
npm i autoprefixer --save-dev
- 添加
${app_root}/.gitignore
配置
.DS_Store
.happypack/
node_modules/
npm-debug.log
.idea/
dist
static
public
private
run
*.iml
*tmp
_site
logs
.vscode
config/manifest.json
app/view/*
!app/view/layout.html
!app/view/.gitkeep
package-lock.json
5. 写代码
编写前端 vue 代码
- 编写 vue 服务端公共入口
${app_root}/app/web/framework/vue/entry/server.js
import Vue from 'vue';
export default function render(options) {
if (options.store && options.router) {
return context => {
options.router.push(context.state.url);
const matchedComponents = options.router.getMatchedComponents();
if (!matchedComponents) {
return Promise.reject({ code: '404' });
}
return Promise.all(
matchedComponents.map(component => {
if (component.preFetch) {
return component.preFetch(options.store);
}
return null;
})
).then(() => {
context.state = options.store.state;
return new Vue(options);
});
};
}
return context => {
const VueApp = Vue.extend(options);
const app = new VueApp({ data: context.state });
return new Promise(resolve => {
resolve(app);
});
};
}
- 编写 vue 客户端公共入口
${app_root}/app/web/framework/vue/entry/client.js
import Vue from 'vue';
export default function(options) {
Vue.prototype.$http = require('axios');
if (options.store) {
options.store.replaceState(window.__INITIAL_STATE__ || {});
} else if (window.__INITIAL_STATE__) {
options.data = Object.assign(window.__INITIAL_STATE__, options.data && options.data());
}
const app = new Vue(options);
app.$mount('#app');
}
- 新建
${app_root}/app/web/page/home/home.js
页面文件
import Home from './home.vue';
import serverRender from '~/app/web/framework/vue/entry/server.js';
import clientRender from '~/app/web/framework/vue/entry/client.js';
export default EASY_ENV_IS_NODE ? serverRender({ ...Home }) : clientRender({ ...Home });
- 新建
${app_root}/app/web/page/home/home.vue
文件
下面 layout
根元素为自定义组件, 全局注册, 统一的html, meta, header, body, layout component 实现见 egg-vue-webpack-boilerplate项目layout实现 主要通过 slot 解决服务端和前端渲染模板和title, meta问题
<template>
<layout>
<div v-html="message"> </div>
</layout>
</template>
<style>
</style>
<script type="text/babel">
export default {
components: {},
computed: {},
methods: {},
mounted() {
}
}
</script>
编写 Node 端代码
通过 egg-view-vue-ssr
插件 render
方法实现
- 创建 controller 文件
${app_root}/app/controller/home.js
module.exports = app => {
return class HomeController extends app.Controller {
async server() {
const { ctx } = this;
// render 实现是服务端渲染 vue 组件
await ctx.render('home/home.js', { message: 'egg vue server side render' });
}
async client() {
const { ctx } = this;
// renderClient 前端渲染,Node层只做 layout.html和资源依赖组装,渲染交给前端渲染。与服务端渲染的差别你可以通过查看运行后页面源代码即可明白两者之间的差异
await ctx.renderClient('home/home.js', { message: 'egg vue server side render' });
}
};
};
- 添加路由配置
app.get('/', app.controller.home.server);
app.get('/client', app.controller.home.client);
6. 本地运行
npm run dev
npm run dev 做了如下三件事情
- 首先启动 egg 应用
- 启动 webpack(egg-webpack) 构建, 文件不落地磁盘,构建的文件都在内存里面(只在本地启动, 发布模式是提前构建好文件到磁盘)
- 构建会同时启动两个 Webpack 构建服务, 客户端js构建端口9000, 服务端端口9001
- 构建完成,Egg应用正式可用,自动打开浏览器
7. 发布模式
${app_root}/package.json
添加命令
{
"scripts": {
"build": "cross-env easywebpack build prod",
"build:dev": "cross-env easywebpack build dev",
"build:test": "cross-env easywebpack build test",
"build:prod": "cross-env easywebpack build prod",
"start:test": "cross-env EGG_SERVER_ENV=test node index.js",
"start:prod": "cross-env EGG_SERVER_ENV=prod NODE_ENV=production npm start",
}
}
- 命令行运行 webpack 编译
npm run build 或 easywebpack build prod
- 启动 Webpack 构建,文件落地磁盘
- 服务端构建的文件放到
app/view
目录 - 客户端构建的文件放到
public
目录 - 生成的
manifest.json
放到config
目录 - 构建的文件都是gitignore的,部署时请注意把这些文件打包进去
- 部署
启动应用前, 请设置 EGG_SERVER_ENV
环境变量,本地local, 测试环境设置 test
, 正式环境设置 prod
npm start
8. 项目和插件
- egg-vue-webpack-boilerplate基于easywebpack-vue和egg-view-vue(ssr)插件的工程骨架项目
- easywebpack Webpack 构建工程化.
- easywebpack-cli Webpack 构建工程化脚手架.
- egg-view-vue-ssr vue ssr 解决方案.
- egg-webpack 本地开发热更新使用.
- egg-webpack-vue 本地开发渲染内存读取辅助插件
9. 建议
- 建议使用 easywebpack-cli 初始化项目 或者 clone 代码初始化项目egg-vue-webpack-boilerplate。
- 阅读 快速开始