从零开始搭建 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

  1. npm i egg-init -g
  2. egg-init
  • 选择 Simple egg app boilerplate project 初始化 egg 项目

  • 新建 ${app_root}/app/view 目录(egg view规范目录),并添加 .gitkeep 文件,保证该空目录被 git 提交到仓库

  • 新建 ${app_root}/app/view/layout.html 文件,该文件用于服务端渲染失败后,采用客户端渲染的 layout 模板配置文件。

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <title>Egg + React + Webpack</title>
  5. <meta name="keywords">
  6. <meta name="description">
  7. <meta http-equiv="content-type" content="text/html;charset=utf-8">
  8. <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui">
  9. <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
  10. </head>
  11. <body>
  12. <div id="app"></div>
  13. </body>
  14. </html>

3. 安装依赖

  • 服务端渲染依赖

react 没有内置在 egg-view-react-ssr 里面, 项目需要显示安装依赖。

  1. npm i react react-dom axios egg-view-react-ssr egg-scripts --save
  • 构建开发依赖
  1. npm i egg-bin cross-env @easy-team/easywebpack-cli @easy-team/easywebpack-react egg-webpack egg-webpack-react --save-dev
  • 安装全部依赖
  1. npm install

4. 添加配置

  • 添加 ${app_root}/config/plugin.local.js 配置
  1. exports.webpack = {
  2. enable: true,
  3. package: 'egg-webpack'
  4. };
  5. exports.webpackreact = {
  6. enable: true,
  7. package: 'egg-webpack-react'
  8. };
  • 添加 ${app_root}/config/plugin.js 配置
  1. exports.reactssr = {
  2. enable: true,
  3. package: 'egg-view-react-ssr'
  4. };
  • 添加 ${app_root}/config/config.default.js 配置
  1. 'use strict';
  2. const path = require('path');
  3. module.exports = app => {
  4. const config = exports = {};
  5. config.keys = '123456';
  6. // 保证构建的静态资源文件能够被访问到
  7. config.static = {
  8. prefix: '/public/',
  9. dir: path.join(app.baseDir, 'public')
  10. };
  11. config.reactssr = {
  12. layout: path.join(app.baseDir, 'app/view/layout.html')
  13. };
  14. return config;
  15. }
  • 添加 easywebpack-cli 配置文件 ${app_root}/webpack.config.js

关于 entry 配置,请务必先看这篇文档:https://www.yuque.com/easy-team/egg-react/config

  1. module.exports = {
  2. entry: {
  3. 'home/index': 'app/web/page/home/index.jsx'
  4. }
  5. };
  • 添加 ${app_root}/babel.config.js 文件
  1. module.exports = {
  2. "env": {
  3. "node": {
  4. "presets": [
  5. "@babel/preset-react",
  6. [
  7. "@babel/preset-env",
  8. {
  9. "modules": false,
  10. "targets": {
  11. "node": "current"
  12. }
  13. }
  14. ]
  15. ],
  16. "plugins": [
  17. ["@babel/plugin-proposal-decorators", { "legacy": true }],
  18. ["@babel/plugin-proposal-class-properties", { "loose": true }],
  19. "@babel/plugin-syntax-dynamic-import"
  20. ]
  21. },
  22. "web": {
  23. "presets": [
  24. "@babel/preset-react",
  25. [
  26. "@babel/preset-env",
  27. {
  28. "modules": false,
  29. "targets": {
  30. "browsers": [
  31. "last 2 versions",
  32. "safari >= 7"
  33. ]
  34. }
  35. }
  36. ]
  37. ],
  38. "plugins": [
  39. 'react-hot-loader/babel',
  40. ["@babel/plugin-proposal-decorators", { "legacy": true }],
  41. ["@babel/plugin-proposal-class-properties", { "loose": true }],
  42. "@babel/plugin-transform-runtime",
  43. "@babel/plugin-syntax-dynamic-import",
  44. "@babel/plugin-proposal-object-rest-spread"
  45. ]
  46. }
  47. }
  48. }
  • 添加 ${app_root}/postcss.config.js 文件(非必须)
  1. module.exports = {
  2. plugins: [
  3. require('autoprefixer')
  4. ]
  5. };
  • 添加 ${app_root}/.gitignore 配置
  1. .DS_Store
  2. .happypack/
  3. node_modules/
  4. npm-debug.log
  5. .idea/
  6. dist
  7. static
  8. public
  9. private
  10. run
  11. *.iml
  12. *tmp
  13. _site
  14. logs
  15. .vscode
  16. config/manifest.json
  17. app/view/*
  18. !app/view/layout.html
  19. !app/view/.gitkeep
  20. package-lock.json

5. 写代码

编写前端 react 代码

  • 新建 React layout.jsx 文件 ${app_root}/app/web/component/layout.jsx
  1. import React, { Component } from 'react';
  2. export default class Layout extends Component {
  3. render() {
  4. if(EASY_ENV_IS_NODE) {
  5. return <html>
  6. <head>
  7. <title>{this.props.title}</title>
  8. <meta charSet="utf-8"></meta>
  9. <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, minimal-ui"></meta>
  10. <meta name="keywords" content={this.props.keywords}></meta>
  11. <meta name="description" content={this.props.description}></meta>
  12. </head>
  13. <body><div id="app">{this.props.children}</div></body>
  14. </html>;
  15. }
  16. return this.props.children;
  17. }
  18. }
  • 新建 ${app_root}/app/web/page/home/index.jsx 页面文件
  1. import React, { Component } from 'react';
  2. // ${root}/app/web/component/layout.jsx 通过 webpack alias 配置 component
  3. import Layout from 'component/layout.jsx';
  4. // 页面列表 List 组件
  5. import List from './componets/list';
  6. import './index.css';
  7. export default class HomeIndex extends Component {
  8. componentDidMount() {
  9. console.log('----componentDidMount-----');
  10. }
  11. render() {
  12. return <Layout>
  13. <div className="main">
  14. <div className="page-container page-component">
  15. <List list={this.props.message}></List>
  16. </div>
  17. </div>
  18. </Layout>;
  19. }
  20. }

编写 Node 端代码

通过 egg-view-react-ssr 插件 render 方法实现, 请看服务端渲染前端渲染模式

  • 创建 controller 文件 ${app_root}/app/controller/home.js
  1. module.exports = app => {
  2. return class HomeController extends app.Controller {
  3. async server() {
  4. const { ctx } = this;
  5. // home/index.js 对应 app/web/page/home/index.jsx webpack 构建后 JSBundle 文件
  6. await ctx.render('home/index.js', { message: 'egg react server side render' });
  7. }
  8. async client() {
  9. const { ctx } = this;
  10. // renderClient 前端渲染,Node层只做 layout.html 和资源依赖组装,渲染交给前端渲染。
  11. // 与服务端渲染的差别你可以通过查看运行后页面源代码即可明白两者之间的差异
  12. await ctx.renderClient('home/index.js', { message: 'egg react client render' });
  13. }
  14. };
  15. };
  • 添加路由配置
  1. app.get('/', app.controller.home.server);
  2. app.get('/client', app.controller.home.client);

webpack 构建配置

  1. 'use strict';
  2. // https://yuque.com/easy-team/egg-react
  3. module.exports = {
  4. entry: {
  5. 'home/index': 'app/web/page/home/index.js',
  6. }
  7. };

6. 本地运行

  1. npm run dev

npm run dev 做了如下三件事情

  • 首先启动 egg 应用

  • 启动 webpack(egg-webpack) 构建, 文件不落地磁盘,构建的文件都在内存里面(只在本地启动, 发布模式是提前构建好文件到磁盘)

  • 构建会同时启动两个 Webpack 构建服务, 客户端js构建端口9000, 服务端端口9001

  • 构建完成,Egg 应用正式可用,自动打开浏览器

7. 发布模式

  • ${app_root}/package.json 添加命令
  1. {
  2. "scripts": {
  3. "dev": "egg-bin dev",
  4. "start": "egg-scripts start",
  5. "debug": "egg-bin debug",
  6. "clean": "easy clean all",
  7. "build": "easy build",
  8. },
  9. }
  • 命令行运行 webpack 编译
  1. npm run build easy build prod
  1. 启动 Webpack 构建,文件落地磁盘

  2. 服务端构建的文件放到 app/view 目录

  3. 客户端构建的文件放到 public 目录

  4. 生成的 manifest.json 放到 config 目录

  5. 构建的文件都是gitignore的,部署时请注意把这些文件打包进去

  • 部署

启动应用前, 如果是非 egg-scripts 方式启动, 请设置 EGG_SERVER_ENV 环境变量,本地local, 测试环境设置 test, 正式环境设置 prod

  1. npm start

8. 项目和插件

9. 建议

以上详细步骤只是告诉大家 Egg + React + easywebpack 搭建项目整个流程,帮助搭建理清流程和细节。实际使用使用时建议使用 easywebpack-cli 初始化项目或者 clone egg-react-webpack-boilerplate 代码初始化项目。