Egg React SSR 服務端渲染開發指南

## 1. 項目初始化javascript

1.1 經過 easywebpack-cli 腳手架初始化

  1. 安裝腳手架 npm install easywebpack-cli -g 命令行,而後就可使用 easywebpackeasy 命令
  2. 命令行運行 easywebpack init
  3. 選擇 egg + react server side render boilerplate 初始化骨架項目
  4. 安裝依賴 npm install

1.2 經過骨架項目初始化

git clone https://github.com/hubcarl/egg-react-webpack-boilerplate.git
npm install

初始化的項目提供多頁面和SPA(react-router/react-redux)服務端渲染實例,能夠直接運行。css

2. 項目運行

2.1 本地運行

npm start

npm start 作了以下三件事情html

  • 啓動 egg 應用
  • 啓動 Webpack 構建, 文件不落地磁盤,構建的文件都在內存裏面(只在本地啓動, 發佈模式是提早構建好文件到磁盤)
  • 構建會同時啓動兩個 Webpack 構建服務, 客戶端js構建端口9000, 服務端端口9001
  • 構建完成,Egg應用正式可用,自動打開瀏覽器

2.2 發佈模式

  • 構建文件落地磁盤
npm run build 或 easywebpack build prod
  1. 啓動 Webpack 構建,文件落地磁盤
  2. 服務端構建的文件放到 app/view 目錄
  3. 客戶端構建的文件放到 public 目錄
  4. 生成的 buildConfig.jsonmanifest.json 放到 config 目錄
  5. 構建的文件都是 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文件構建規範時使用。vue

4. 項目規範

  • 遵循 egg 開發規範
  • React 項目代碼放到 app/web 目錄,頁面入口目錄爲 page,該目錄的 全部 .jsx 文件默認會做爲 Webpack 的 entry 構建入口。建議每一個頁面目錄的只保留一個.jsx 文件,jsx關聯的組件能夠放到widget 或者 component 目錄。若是非要放到當前目前,請配置 webpack.config.js entry.exclude 排除 .jsx 文件。

圖片描述

5. 項目開發

支持多頁面/單頁面服務端渲染, 前端渲染, 靜態頁面三種方式.java

5.1 多頁面服務端渲染實現

5.1.1 多頁面前端頁面實現

在app/web/page 目錄下面建立home目錄, home.jsx 文件, Webpack自動根據.jsx 文件建立entry入口, 具體實現請見webpack.config.jsreact

  • 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', Model.getPage(1, 10));
};
  • 添加路由配置
app.get('/client', app.controller.home.home.client);

5.2 HTML靜態頁面前端渲染

  • 直接有easywebpack構建出靜態HTML文件, 請見 webpack.config.js 配置和 app/web/page/html代碼實現
  • 經過 egg-static 靜態文件訪問HTML文件

5.3 單頁面服務器渲染同構實現

5.3.1 單頁面前端實現

在app/web/page 目錄下面建立app目錄, spa/ssr.jsx 文件.webpack

  • 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 配置git

{
  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'
    }
  },
}

詳細代碼請參考骨架項目實現github

6. 項目部署

  • 正式環境部署,請設置 EGG_SERVER_ENV=prod 環境變量, 更多請見運行環境
  • 構建的 app/view 目錄, public 目錄以及 buildConfig.jsonmanifest.json等文件, 都是 gitignore 的,部署時請注意把這些文件打包進去。

7. 項目和插件

相關文章
相關標籤/搜索