從零開始搭建React同構應用(三):配置SSR

從零開始搭建React同構應用(三):配置SSR

這篇文章來說解來配置server side render,咱們先從最簡單的方法開始,用cli的方式模擬實現SSR。css

demo在這裏html

主要內容:node

  1. 添加webpack的server render配置react

  2. 使用CLI的方式測試SSR輸出webpack

添加webpack的server render配置

以前我是考慮在node端直接require源碼,例如:git

//hook require
require("babel-register")({
    babelrc: "false",
    presets: ['react'],
    plugins: [
        "transform-decorators-legacy",
        "transform-es2015-modules-commonjs"
    ]
});

//直接引入源碼
const IndexBundle = require("./src/index/Index.jsx");


//do server side render...

這樣少編譯一套代碼,以爲這樣維護起來更方便,可是後來實踐發現有幾個問題:github

  1. import "xxx.styl",引入樣式文件會報錯。web

  2. 這種模式下須要使用babel-registerbabel編譯速度較慢,開發模式下每次修改文件再重啓服務器耗時太長。ajax

  3. 影響生產環境下執行效率。npm

最後權衡下,仍是決定使用如今多一套ssr編譯配置的方案。

webpack.config.js添加如下代碼

let serverConfig = {};

Object.assign(serverConfig, browserConfig, {
    output: {
        path: path.join(__dirname, 'build_server'),
        filename: "[name].bundle.js",
        libraryTarget: 'commonjs2' //設置導出類型,web端默認是var,node須要module.exports = xxx的形式
    },
    module: {
        loaders: [
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                loader: "babel-loader",
                query: {                //node端的babel編譯配置能夠簡化不少
                    babelrc: "false",
                    presets: ['react'],
                    plugins: [
                        "transform-decorators-legacy",
                        "transform-es2015-modules-commonjs" //若是不轉換成require,import 'xxx.styl'會報錯
                    ]
                }
            },
            {
                test: /\.(styl|css)$/,          //node端不能 require('xx.css'),會報錯
                loader: 'null'
            },
        ]
    },
    plugins: [
        new webpack.ProvidePlugin({
            React: 'react',
            ReactDOM: 'react-dom',
            fetch: 'isomorphic-fetch',
            promise: 'promise'
        }),
        new webpack.DefinePlugin({
            'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) || JSON.stringify('development')
        }),
    ],
    target: 'node',
    externals: [nodeExternals()], //不把node_modules中的文件打包
});

由於serverConfig的配置和browserConfig類似,我就使用Object.assign來複制一份,同時作下修改。

nodejs啓用 --harmony參數就能夠支持絕大部分的ES6,ES7語法,如async等,所以只須要編譯JSX語法和import語法。babel的編譯速度也所以能夠提升不少。babelrc: "false"是爲了屏蔽項目目錄下的babel.rc文件,那是給瀏覽器端編譯使用的。

同時,在node環境不支持直接引入CSS文件的,如require('xx.css'),所以在打包的時候要忽略樣式文件和資源文件,不然會報錯。

這裏我使用了webpack-node-externals插件,這個插件的原理是利用了webapck中的externals配置項,來剔除node_modules文件的,由於默認webapck會把全部用到的js文件通通打包,而咱們因爲是在node端,所以不須要把用到的庫也打包了。

執行試試

npm run watch

clipboard.png

若是不用webpack-node-externals,打包出的文件體積會大不少

clipboard.png

測試SSR輸出

其實使用React的ssr很簡單,熟悉下面兩個API便可:

  1. React.createElement

  2. ReactDOMServer.renderToString

React.createElement

clipboard.png

這裏簡單解釋下,React.createElementReact類進行實例化,實例化後的組件就能夠進行mount操做了,在瀏覽器環境咱們是使用ReactDOM.render()來進行掛載操做的。

ReactDOMServer.renderToString

clipboard.png

ReactDOMServer.renderToString則是把React實例渲染成HTML標籤。

測試

這裏咱們先不搭建HTTP server,暫時用cli的方式模擬一下,方便你們理解。

新建cli.js,寫入如下內容(以Index.jsx爲例),注意:.defalut不能少。

/**
 * Created by chenchen on 2017/2/4.
 *
 * React server render 命令行測試
 */

//以Index.jsx爲例
const IndexBundle = require("../build_server/index.bundle.js");
const React = require("react");
const ReactDOMServer = require("react-dom/server");
let {renderToString} = ReactDOMServer;
let initialData = {todoList: ['11', '22', '33']};
let instance = React.createElement(IndexBundle.default, initialData); //.defalut不能少


let str = renderToString(instance);

console.log(str);

咱們添加一條npm script

"test-ssr": "node --harmony test/cli.js"

執行後效果如圖

clipboard.png

能夠看到咱們已經成功輸出了組件渲染後的HTML文本了。

下一篇文章我將講解如何搭建一個簡單的Koa server,並結合這邊文章內容,實現真正意義上的server side render ^_^。

要注意的地方

React生命週期

React組件的聲明週期只會到componentWillMount,所以你不能在componentWillMount及其以前的生命週期鉤子中寫瀏覽器環境下的代碼,如$.ajax(...),會報錯。

先後端數據同步

要注意瀏覽器端和服務器端的數據要一致,不然會出現HTML重用失敗的錯誤:
clipboard.png

server side render 沒有用到redux

可能有人會疑惑,在瀏覽器編譯的代碼是:

//初始數據,用於和server render數據同步
let initialData = window._SERVER_DATA || {};

let store = createStore(reducers, initialData, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());

let App = connect(_ => _)(Layout);//用connect包裝一下,這裏只用到mapStateToProps,並且不對state加以過濾

ReactDOM.render(
    <Provider store={store}>
        <App/>
    </Provider>,
    document.getElementById('wrap'));

而server端的編譯沒有和Redux沾邊,由於Providerconnect(...)(Layout)functional component,自己不會多渲染出來HTML,所以能夠不用Redux參與渲染。

相關文章
相關標籤/搜索