从零开始搭建 Egg + React + Webpack 服务端渲染项目
1. 初始化环境
安装 Node LST (>=8) 环境: 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
文件,该文件用于服务端渲染失败后,采用客户端渲染的 layout 模板配置文件。
<!DOCTYPE html>
<html lang="en">
<head>
<title>Egg + React + 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. 安装依赖
- 服务端渲染依赖
react 没有内置在 egg-view-react-ssr 里面, 项目需要显示安装依赖。
npm i react react-dom axios egg-view-react-ssr egg-scripts --save
- 构建开发依赖
npm i egg-bin cross-env @easy-team/easywebpack-cli @easy-team/easywebpack-react egg-webpack egg-webpack-react --save-dev
- 安装全部依赖
npm install
4. 添加配置
- 添加
${app_root}/config/plugin.local.js
配置
exports.webpack = {
enable: true,
package: 'egg-webpack'
};
exports.webpackreact = {
enable: true,
package: 'egg-webpack-react'
};
- 添加
${app_root}/config/plugin.js
配置
exports.reactssr = {
enable: true,
package: 'egg-view-react-ssr'
};
- 添加
${app_root}/config/config.default.js
配置
'use strict';
const path = require('path');
module.exports = app => {
const config = exports = {};
config.keys = '123456';
// 保证构建的静态资源文件能够被访问到
config.static = {
prefix: '/public/',
dir: path.join(app.baseDir, 'public')
};
config.reactssr = {
layout: path.join(app.baseDir, 'app/view/layout.html')
};
return config;
}
- 添加
easywebpack-cli
配置文件${app_root}/webpack.config.js
关于 entry 配置,请务必先看这篇文档:https://www.yuque.com/easy-team/egg-react/config
module.exports = {
entry: {
'home/index': 'app/web/page/home/index.jsx'
}
};
- 添加
${app_root}/babel.config.js
文件
module.exports = {
"env": {
"node": {
"presets": [
"@babel/preset-react",
[
"@babel/preset-env",
{
"modules": false,
"targets": {
"node": "current"
}
}
]
],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose": true }],
"@babel/plugin-syntax-dynamic-import"
]
},
"web": {
"presets": [
"@babel/preset-react",
[
"@babel/preset-env",
{
"modules": false,
"targets": {
"browsers": [
"last 2 versions",
"safari >= 7"
]
}
}
]
],
"plugins": [
'react-hot-loader/babel',
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose": true }],
"@babel/plugin-transform-runtime",
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-proposal-object-rest-spread"
]
}
}
}
- 添加
${app_root}/postcss.config.js
文件(非必须)
module.exports = {
plugins: [
require('autoprefixer')
]
};
- 添加
${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. 写代码
编写前端 react 代码
- 新建 React layout.jsx 文件
${app_root}/app/web/component/layout.jsx
import React, { Component } from 'react';
export default class Layout extends Component {
render() {
if(EASY_ENV_IS_NODE) {
return <html>
<head>
<title>{this.props.title}</title>
<meta charSet="utf-8"></meta>
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui"></meta>
<meta name="keywords" content={this.props.keywords}></meta>
<meta name="description" content={this.props.description}></meta>
</head>
<body><div id="app">{this.props.children}</div></body>
</html>;
}
return this.props.children;
}
}
- 新建
${app_root}/app/web/page/home/index.jsx
页面文件
import React, { Component } from 'react';
// ${root}/app/web/component/layout.jsx 通过 webpack alias 配置 component
import Layout from 'component/layout.jsx';
// 页面列表 List 组件
import List from './componets/list';
import './index.css';
export default class HomeIndex extends Component {
componentDidMount() {
console.log('----componentDidMount-----');
}
render() {
return <Layout>
<div className="main">
<div className="page-container page-component">
<List list={this.props.message}></List>
</div>
</div>
</Layout>;
}
}
编写 Node 端代码
通过 egg-view-react-ssr
插件 render
方法实现, 请看服务端渲染和前端渲染模式
- 创建 controller 文件
${app_root}/app/controller/home.js
module.exports = app => {
return class HomeController extends app.Controller {
async server() {
const { ctx } = this;
// home/index.js 对应 app/web/page/home/index.jsx webpack 构建后 JSBundle 文件
await ctx.render('home/index.js', { message: 'egg react server side render' });
}
async client() {
const { ctx } = this;
// renderClient 前端渲染,Node层只做 layout.html 和资源依赖组装,渲染交给前端渲染。
// 与服务端渲染的差别你可以通过查看运行后页面源代码即可明白两者之间的差异
await ctx.renderClient('home/index.js', { message: 'egg react client render' });
}
};
};
- 添加路由配置
app.get('/', app.controller.home.server);
app.get('/client', app.controller.home.client);
webpack 构建配置
'use strict';
// https://yuque.com/easy-team/egg-react
module.exports = {
entry: {
'home/index': 'app/web/page/home/index.js',
}
};
6. 本地运行
npm run dev
npm run dev 做了如下三件事情
首先启动 egg 应用
启动 webpack(egg-webpack) 构建, 文件不落地磁盘,构建的文件都在内存里面(只在本地启动, 发布模式是提前构建好文件到磁盘)
构建会同时启动两个 Webpack 构建服务, 客户端js构建端口9000, 服务端端口9001
构建完成,Egg 应用正式可用,自动打开浏览器
7. 发布模式
${app_root}/package.json
添加命令
{
"scripts": {
"dev": "egg-bin dev",
"start": "egg-scripts start",
"debug": "egg-bin debug",
"clean": "easy clean all",
"build": "easy build",
},
}
- 命令行运行 webpack 编译
npm run build 或 easy build prod
启动 Webpack 构建,文件落地磁盘
服务端构建的文件放到
app/view
目录客户端构建的文件放到
public
目录生成的
manifest.json
放到config
目录构建的文件都是gitignore的,部署时请注意把这些文件打包进去
- 部署
启动应用前, 如果是非 egg-scripts
方式启动, 请设置 EGG_SERVER_ENV
环境变量,本地local, 测试环境设置 test
, 正式环境设置 prod
npm start
8. 项目和插件
egg-react-webpack-boilerplate 基于easywebpack-react和 egg-view-react-ssr插件的工程骨架项目
easywebpack-react Webpack React 构建工程化基础
easywebpack-cli Webpack 构建工程化脚手架.
egg-view-react-ssr egg react ssr 插件.
egg-webpack 本地开发热更新使用.
egg-webpack-react 本地开发渲染内存读取辅助 egg-webpack-react插件
9. 建议
以上详细步骤只是告诉大家 Egg + React + easywebpack 搭建项目整个流程,帮助搭建理清流程和细节。实际使用使用时建议使用 easywebpack-cli 初始化项目或者 clone egg-react-webpack-boilerplate 代码初始化项目。