## 1. 项目初始化
1.1 通过 easywebpack-cli 脚手架初始化
- 安装脚手架
npm install easywebpack-cli -g
命令行,然后就可以使用easywebpack
或easy
命令 - 命令行运行
easywebpack init
- 选择 egg + react server side render boilerplate 初始化骨架项目
- 安装依赖
npm install
1.2 通过骨架项目初始化
- git clone https://github.com/hubcarl/egg-react-webpack-boilerplate.git
- npm install
初始化的项目提供多页面和SPA(react-router/react-redux)服务端渲染实例,可以直接运行。
2. 项目运行
2.1 本地运行
- npm start
npm start 做了如下三件事情
- 启动 egg 应用
- 启动 Webpack 构建,文件不落地磁盘,构建的文件都在内存里面(只在本地启动,发布模式是提前构建好文件到磁盘)
- 构建会同时启动两个 Webpack 构建服务,客户端js构建端口9000,服务端端口9001
- 构建完成,Egg应用正式可用,自动打开浏览器
2.2 发布模式
- 构建文件落地磁盘
- npm run build 或 easywebpack build prod
- 启动 Webpack 构建,文件落地磁盘
- 服务端构建的文件放到
app/view
目录 - 客户端构建的文件放到
public
目录 - 生成的
buildConfig.json
和manifest.json
放到config
目录 - 构建的文件都是
gitignore
的,部署时请注意把这些文件打包进去
- 运行
启动应用前, 请设置 EGG_SERVER_ENV
环境变量,测试环境设置 test
, 正式环境设置 prod
- npm start
3. 项目构建
- 通过
easywebpack-cli
统一构建,支持 dev,test,prod 模式构建 -
easywebpack-cli
通过项目根目录下的webpack.config.js
配置文件构造出 Webpack 实际的配置文件,配置项请见 webpack.config.js - 获取 Webpack 实际的配置文件,egg-webpack 会使用到该功能。构建会根据
webpackConfigList.length
启动对应个数的 Webpack 编译实例,这里会同时启动两个 Webpack 构建服务,客户端jsbundle构建,端口9000,服务端jsbundle构建端口9001。默认端口为9000,端口依次递增。
- // config/config.local.js 本地 npm start 使用
- const EasyWebpack = require('easywebpack-react');
- exports.webpack = {
- webpackConfigList:EasyWebpack.getWebpackConfig()
- };
- 该项目中,
app/web/page
目录中所有 .jsx 文件当作 Webpack 构建入口是采用 app/web/framework/entry/loader.js 模板实现的,这个需要结合webpack.config.js
下的 entry.loader 使用。
- entry: {
- include: ['app/web/page',{ layout: 'app/web/view/layout.jsx?loader=false' },{ 'spa/redux': 'app/web/page/spa/redux.jsx?loader=false' },{ 'spa/client': 'app/web/page/spa/client.jsx?loader=false' },{ 'spa/ssr': 'app/web/page/spa/ssr.jsx?loader=false' }
- ],exclude: ['app/web/page/test'],loader: {
- client: 'app/web/framework/entry/loader.js'
- }
- }
上面 { 'app/app': 'app/web/page/app/app.js?loader=false' }
这个 loader=false
的含义表示 app/web/page
目录下的 app/app.js
不使用 entry.loader 模板。因为这个app/app.js是一个SPA服务端渲染Example,实现逻辑与其他普通的页面不一样,不能用 entry.loader 模板, 这个功能在自定义entry文件构建规范时使用。
4. 项目规范
- 遵循 egg 开发规范
- React 项目代码放到 app/web 目录,页面入口目录为 page,该目录的 所有 .jsx 文件默认会作为 Webpack 的 entry 构建入口。建议每个页面目录的只保留一个.jsx 文件,jsx关联的组件可以放到widget 或者 component 目录。如果非要放到当前目前,请配置
webpack.config.js
entry.exclude 排除 .jsx 文件。
5. 项目开发
5.1 多页面服务端渲染实现
5.1.1 多页面前端页面实现
在app/web/page 目录下面创建home目录,home.jsx 文件,Webpack自动根据.jsx 文件创建entry入口,具体实现请见webpack.config.js
- home.jsx 以组件的方式实现页面逻辑
- import React,{ Component } from 'react';
- import Header from 'component/layout/standard/header/header.jsx';
- import List from 'component/home/list.jsx';
- import './home.css';
- export default class Home extends Component {
- componentDidMount() {
- console.log('----componentDidMount-----');
- }
- render() {
- return <div>
- <Header></Header>
- <div className="main">
- <div className="page-container page-component">
- <List list={this.props.list}></List>
- </div>
- </div>
- </div>;
- }
5.1.2 多页面后端渲染实现,通过 egg-view-react-ssr
插件 render
方法实现
- 创建controller文件home.js
- exports.index = function* (ctx) {
- yield ctx.render('home/home.js',Model.getPage(1,10));
- };
- 添加路由配置
- app.get('/home',app.controller.home.home.index);
5.1.3 多页面走前端渲染(后端路由)实现,通过 egg-view-react-ssr
插件 renderClient
方法实现
- 创建controller文件home.js
- exports.client = function* (ctx) {
- yield ctx.renderClient('home/home.js',10));
- };
- 添加路由配置
- app.get('/client',app.controller.home.home.client);
5.2 HTML静态页面前端渲染
5.3 单页面服务器渲染同构实现
5.3.1 单页面前端实现
在app/web/page 目录下面创建app目录,spa/ssr.jsx 文件.
- import React,{ Component } from 'react'
- import ReactDOM from 'react-dom'
- import { Provider } from 'react-redux'
- import {match,RouterContext} from 'react-router'
- import { BrowserRouter,StaticRouter } from 'react-router-dom';
- import { matchRoutes,renderRoutes } from 'react-router-config'
- import Header from 'component/layout/standard/header/header';
- import SSR from 'component/spa/ssr/ssr';
- import { create } from 'component/spa/ssr/store';
- import routes from 'component/spa/ssr/routes'
- import './spa.css';
- if (typeof window === 'object') { // 前端渲染构建
- const store = create(window.__INITIAL_STATE__);
- const url = store.getState().url;
- ReactDOM.render(
- <div>
- <Header></Header>
- <Provider store={ store }>
- <BrowserRouter>
- <SSR url={ url }/>
- </BrowserRouter>
- </Provider>
- </div>,document.getElementById('app')
- );
- } else { // 服务端渲染构建和render入口,这里 export 函数,服务端会负责处理
- module.exports = (context,options) => {
- const url = context.state.url;
- const branch = matchRoutes(routes,url);
- // 获取组件数据
- const promises = branch.map(({route}) => {
- const fetch = route.component.fetch;
- return fetch instanceof Function ? fetch() : Promise.resolve(null)
- });
- return Promise.all(promises).then(data => {
- // 初始化store数据
- const initState = context.state;
- data.forEach(item => {
- Object.assign(initState,item);
- });
- context.state = Object.assign({},context.state,initState);
- const store = create(initState);
- return () =>(
- <div>
- <Header></Header>
- <Provider store={store}>
- <StaticRouter location={url} context={{}}>
- <SSR url={url}/>
- </StaticRouter>
- </Provider>
- </div>
- )
- });
- };
- }
5.3.2 单页面后端实现
- 创建controller文件app.js
- exports.ssr = function* (ctx) {
- yield ctx.render('spa/ssr.js',{ url: this.url });
- };
- 添加路由配置
- app.get('/spa/ssr',app.controller.spa.ssr);
- 构建配置
spa 单页面实现复杂,不能使用 entry.loader,所以需要在 webpack.config.js
配置
- {
- entry: {
- include: ['app/web/page',loader: {
- client: 'app/web/framework/entry/loader.js'
- }
- },}
6. 项目部署
- 正式环境部署,请设置
EGG_SERVER_ENV=prod
环境变量,更多请见运行环境 - 构建的
app/view
目录,public
目录以及buildConfig.json
和manifest.json
等文件,都是gitignore
的,部署时请注意把这些文件打包进去。
7. 项目和插件
- egg-react-webpack-boilerplate基于easywebpack-react和egg-view-react(ssr)插件的工程骨架项目
- easywebpack Webpack 构建工程化.
- easywebpack-cli Webpack 构建工程化脚手架.
- egg-view-react react ssr engine.
- egg-view-react-ssr react ssr 解决方案.
- egg-webpack 本地开发热更新使用.
- egg-webpack-react 本地开发渲染内存读取辅助插件