从零开始搭建 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 -gegg-init
选择
Simple egg app boilerplateproject 初始化 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/diststaticpublicprivaterun*.iml*tmp_sitelogs.vscodeconfig/manifest.jsonapp/view/*!app/view/layout.html!app/view/.gitkeeppackage-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 配置 componentimport 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-reactmodule.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 代码初始化项目。
