React伺服器端渲染——webpack-isomorphic

React伺服器端渲染的問題

這幾天在學習react + redux + react-router + webpack伺服器端渲染是如何實現時遇到以下幾個問題:

  1. 如何根據url生成store對應的初始state?
  2. 如何使得獲取後台數據的api既能夠在瀏覽器上運行,也能在node下運行?
  3. 如何在使用css-loader和file-loader的情況下,使得伺服器端生成正確的html?

對於第一點,react、redux和react-router本身已經提供了方案來解決這個問題,具體實現請參考以下內同:

ReactDOMServer

Redux伺服器端渲染

react-router伺服器端渲染

對於第二點我使用了isomorphic-fetch解決了:

isomorphic-fetch

對於第三點我引入了webpack-isomorphic庫來解決:

因為使用了css-loader的css modules和file-loader,所以在寫的代碼時候一般會import了.css、.png、.jpg等後綴的文件,對於這些文件在node環境下import是會報錯的,為了解決這個問題,我引入了webpack-isomorphic這個庫,這個庫之所以能夠解決這個問題主要是因為它擴展了node本身require的功能,使得require能夠處理.css、.png、jpg等後綴的文件。那麼在擴展了require功能之後又是如何生成正確的html的呢?這個問題就是這篇文章重點解釋的地方。webpack-isomorphic的原理涉及到一些webpack和node模塊載入原理的知識,對於這方面知識不太了解的同學,不妨先看看下面的材料:

webpack中文文檔

如何編寫一個webpack插件

node源碼筆記——模塊載入

webpack-isomorphic

webpack-isomorphic的demo

對於webpack-isomorphic如何實現正確渲染html,我們先從官網給出的例子看起(網址為webpack-isomorphic-example):

運行前的目錄結構如下:

根據官網給出的步驟運行結果如下:

通過結果可以在運行後多了個dist目錄,dist目錄下有一個webpack.assets.json文件,這個文件對於伺服器端能夠正確的解析html有著重要的作用。

webpack-isomorphic解析

根據webpack-isomorphic的文檔介紹,這裡之所以能夠順利的正確的解析html,主要是通過兩個步驟實現的,第一在webpack下配置webpack-isomorphic/plugin,具體實現如下:

var IsomorphicPlugin = require(webpack-isomorphic/plugin);nnvar isomorphicPlugin = new IsomorphicPlugin({ntextensions: [jpg, png, gif, css]n});nnmodule.exports = {ntcontext: __dirname + /src,nt...ntplugins: [ntt//...nttisomorphicPluginnt]n};n

然後在進行伺服器端渲染之前需要對webpack-isomorphic進行配置一下:

var webpackIsomorphic = require(webpack-isomorphic);nnwebpackIsomorphic.install(__dirname + /dist, {ntcache: process.env[NODE_ENV] !== developmentn});n

這裡為什麼會又兩步呢,這兩步的作用分別是什麼呢?讓我們從源碼的角度來分析一下,這兩部都做了什麼:

webpack配置的webpack-isomorphic/plugin作用

其實配置webpack-isomorphic/plugin就是為了生成前面提到的webpack.assets.json文件,讓我們看下源碼和webpack.assets.json文件的內容:

webpack-isomorphic/plugin源碼:

var fs = require(fs);nvar path = require(path);nnfunction IsomorphicPlugin(options) {ntthis.extensions = options.extensions || [];n}nnIsomorphicPlugin.prototype.apply = function (compiler) {ntvar files = {};ntvar extensions = this.extensions || [];ntvar assets = {extensions: extensions, files: files};ntvar options = compiler.options;ntvar context = options.context;ntvar publicPath = options.output.publicPath || ;nntcompiler.plugin(emit, function (compilation, callback) {nnttcompilation.modules.forEach(function (module) {ntttvar userRequest = module[userRequest] || ;ntttvar ext = path.extname(userRequest).slice(1);ntttif (userRequest.indexOf(!) < 0 && extensions.indexOf(ext) >= 0) {nttttvar prefix = var __webpack_public_path__ = + publicPath + ;;nttttvar filename = path.relative(context, userRequest);nnttttif (module[_source]) {ntttttfiles[filename] = prefix + module[_source][_value];ntttt} else {ntttttfiles[filename] = undefined;ntttt}nttt}ntt});nnttvar json = JSON.stringify(assets);nttcompilation.assets[webpack.assets.json] = {ntttsource: function () {nttttreturn json;nttt},ntttsize: function () {nttttreturn json.length;nttt}ntt};nttcallback();nt});n};nnmodule.exports = IsomorphicPlugin;n

webpack.assets.json內容:

{n "extensions": [n "jpg",n "png",n "gif",n "css"n ],n "files": {n "css/style.css": "var __webpack_public_path__ = ;// removed by extract-text-webpack-pluginnmodule.exports = {"avatar":"_3_uBgFYhUQTwdp-0-ze7RD"};",n "img/avatar.jpg": "var __webpack_public_path__ = ;module.exports = __webpack_public_path__ + "img/avatar.e244ac.jpg";"n }n}n

通過源碼和webpack.assets.json內容可以知道,webpack-isomorphic/plugin的功能很簡單就是把需要處理的後綴和在前端import .css、.png和.jpg等文件而獲取的內容保存到webpack.assets.json文件中。

webpackIsomorphic.install的作用

exports.install = function (context, opts) {ntopts = opts || {};ntvar cache = opts[cache] !== false;nntvar assets;ntvar files;ntvar extensions;nntfunction loadAssets() {nttvar filename = path.join(context, webpack.assets.json);ntttry {ntttassets = JSON.parse(fs.readFileSync(filename));ntt} catch (e) {ntttconsole.warn(Warning: + filename + is not valid.);ntttreturn;ntt}nnttfiles = assets.files || {};nttextensions = assets.extensions || [];nnttextensions.forEach(function (ext) {ntttModule._extensions[. + ext] = function (module, filename) {nttttif (!cache) {ntttttdelete Module._cache[filename];ntttt}nttttvar relative = path.relative(context, filename);nttttmodule._compile(files[relative], filename);nttt}ntt});nt}nntvar original = Module._findPath;ntModule._findPath = function (request, paths) {nttif (!assets || !cache) {ntttloadAssets();ntt}nttvar ext = path.extname(request).slice(1);nttif (extensions.indexOf(ext) < 0) {ntttreturn original.apply(Module, arguments);ntt}nttfor (var i = 0, l = paths.length; i < l; ++i) {ntttvar filename = path.join(paths[i], request);ntttvar relative = path.relative(context, filename);ntttif (files[relative] !== undefined) {nttttreturn filename;nttt}ntt}nt};nn};n

通過源碼可以發現,webpackIsomorphic.install的作用就是通過擴展node中require的功能,使得在使用require獲取指定後綴文件內容的時候,從webpack.assets.json文件中獲取,對於如何擴展require對更多後綴文件的處理,請參考node源碼筆記——模塊載入。

總結

通過這段時間的學習初步了解了react伺服器端渲染,並通過對webpack-isomorphic源碼的學習,了解了在進行伺服器端渲染時如何解決css modules和file-loader帶來正確解析html的問題。

對於看到最後的讀者,真的非常的感激,如果您發現文章中有什麼問題或在react伺服器端渲染時還有其他問題是本文中沒有涉及到的請給出寶貴的意見,非常感謝。


推薦閱讀:

基於 webpack 的持久化緩存方案
基於gulp+webpack構建基礎
[webpack]源碼解讀:命令行輸入webpack的時候都發生了什麼?

TAG:React | webpack | Nodejs |