webpack使用優化(react篇)

原文連接css

前言

《React移動web極致優化》也提到了,構建工具是前端優化的重要一環。而React的推薦構建工具則是Webpack。這篇文章咱們就來聊聊如何在Webpack構建的過程當中如何針對React的應用作一些優化。html

若是還沒看過《webpack使用優化(基本篇)》這篇文章,建議去看看,由於針對React的優化每每也離不開Webpack那些最基本的優化點。此外,在這裏將Webpack視做構建可能招來一些人的反對,他們會將Webpack定位成打包的工具。但實際項目中,除了合圖之外,家校羣項目已將Webpack將爲最核心的構建工具。前端

本篇文章的成果最終都會轉化成一個開源的boilerplate, steamer-reactnode

目錄結構

15

構建工具離不開目錄的設計,咱們須要安排號文件存放的位置才便於構建工程的開展。在src目錄下一級的文件,除了page文件夾是react的主體邏輯文件以外,其它的像img, js, css, libs,都屬於各個頁面都會用到的公共文件,如utils, 上報等。react

page目錄下,common文件夾主要旋轉跟React相關的一些公共的文件,如公共的component,中間件等。而其它的文件夾就是每一個頁面的主體邏輯和資源,另外就是頁面對應的html文件。webpack

因爲家校羣採用的是React+ Redux這套方案,咱們文件夾的名字也很能體現這套方案的特點。首先組件Component方面,咱們比較認同Redux推崇的smart component(container)和dump component,所以它們分別放置在container和component文件夾下。那container和component文件夾下面放在什麼呢?咱們放置了組件相關的邏輯js和樣式scss文件。咱們暫時沒將圖片放在組件這一層,而是放在頁面這一層,是由於咱們業務不一樣組件間共用了很多圖片,所以放在更上一層更爲合適。git

而store, reducer, action, connect都是跟Redux這個數據處理框架相關。像root這樣的文件夾則是項目的主入口,裏面有root.dev.js和root.prod.js,用於區分開發環境與生產環境對應須要引入的組件。github

若是你還用到React-Router,可能你還須要多加一個route的文件夾,裏面用存放項目route的配置文件。web

這套文件架構比較傳統的gulp和grunt複雜,但卻更符合React + Redux這套方案的開發思路。npm

針對React的優化點

須要維護兩套構建配置

Webpack跟Gulp和Grunt不一樣,前者屬於配置型構建(固然也能夠經過插件去作一些流程),後二者屬於任務型的構建。之前在用Gulp開發的時候,也會寫一些任務專門針對開發或者生產的環境,分別再建兩條任務流,分別去處理開發與生產環境的構建。同理Webpack也須要去處理開發與生產環境的構建,所以也須要兩套配置去實現。

若是搞不清楚什麼任務應該放在開發環境,什麼應該放在生產環境,能夠參考《性能優化三部曲之一——構建篇》,裏有有詳情參考;若是不知道如何去區分開發與生產環境,能夠參考《webpack使用優化(基本篇)》(https://github.com/lcxfs1991/blog/issues/2)

這裏想提出來講的有2點:

  • 第一,是建議開發環境的配置是生產環境的子集。
    這樣順着寫下來比較流暢,畢竟咱們是先考慮開發,等開發完以後纔會去構思生產環境的部署。這時咱們能夠直接用Object.assign去複製開發環境寫好的配置,進行修改即是。也能夠添加一些方法方便處理更新配置,例如生產環境想去添加新的插件,我參考了以前看過的一個boilerplate:

devConfig.addPlugins = function(plugin, opt) {
    devConfig.plugins.push(new plugin(opt));
};
  • 第二,是React是否使用外鏈問題。在開發環境下,建議直接引入node_modules包的,由於裏面有許多有用的報錯和提示,方便開發時快速發現問題。而生產環境天然是建議外鏈,不然Webpack就會自做主線地把React和你的業務邏輯打包到一塊兒,比分開打包要大得多。

React的ES2015編譯

ES2015近2年很火熱,咱們也來嚐嚐鮮。用ES2015的最大好處就是可使用許多方便的特性,但有一個小小的壞處就是,你可能忽略ES5的寫法,而ES5的寫法不少時候可以清楚地表示出React的實現方式,對理解框架和原理更有幫助。

另外就是,用這些新的特性,會有一些不穩定的因素,就是不知道轉換以後會成什麼樣子,轉換後的代碼兼容性如何(具體可參《babel到底將代碼轉換成什麼鳥樣?》])。果不其然,咱們發佈列表頁以後發現一個報錯:

Uncaught TypeError: Cannot assign to read only property '__esModule' of #<Object>。

解決辦法,就是babel編譯使用ES2015-Loose而不是ES2015的preset。具體轉換的代碼以下:

16

具體在Webpack的loaders裏能夠這樣寫你的編譯配置

{ 
    test: /\.js?$/,
    loader: 'babel',
    query: {
        plugins: ['transform-decorators-legacy'],   /// 使用decorator寫法
        presets: [
            'es2015-loose',    // ES2015 loose mode
            'react',
        ]
    },
    exclude: /node_modules/,
}

除此以外,咱們也發現每一個Webpack打包的模塊,最終編譯都會生成一堆相似的代碼:

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

    ......

這些是Babel對ES2015轉義生成的代碼片斷。多的時候,每一個模塊生成的這些代碼都接近1kb。若是你的component多,例如像家校羣有超過20組件,後面的詳情頁的組件可能更多,這麼多組件合起來重複的代碼可能就近20kb。在PC端20kb沒有所謂,但到移動端卻對bundle的大小錙銖必較。建議寫一個Webpack plugin/loader對這些代碼進行去重(反正沒看到Webpack有插件,機會就留給大家了)。

如何熱替換css

打包css的時候,咱們習慣使用ExtractTextPlugin讓css單獨生成一個文件。但若是你想讓css也可以熱替換,在開發環境的時候請去掉這個插件讓樣式內聯。

Webpack慎用devtools的inline-source-map模式

使用此模式會內聯一大段便於定位bug的字符串,查錯時能夠開啓,不是查錯時建議
關閉,不然開發時加載的包會很是大。

若是不使用devtools查錯,你看到的會是合成以後的bundle,上萬行代碼,也不知道是哪一個文件:
17

使用了以後,你就很很清晰是在哪一個文件,哪一行了:
18

若是沒法使用服務器構建,開發時請讓小夥伴統一開發路徑

webpack的bug致使若是本地開發目錄路徑不一致,編譯出來的md5會不一致。因此推薦使用服務器構建。

React項目的合圖

在搭項目構建的時候,曾經嘗試過用Webpack一個合圖插件,但因不夠成熟而棄用,轉而考慮轉投向gulp的合圖插件的懷抱。因爲家校羣功能頁面是一個多頁項目,每一個頁面都會有合圖,所以咱們選用了gulp.spritesmith-multi

之前使用gulp的構建項目,css都同一放在一層,引用圖片的路徑都放在一個css裏面。但面對React的項目,咱們每個component都有本身對應的index.js(處理邏輯)和index.scss(處理樣式),因爲這個合圖插件只能標出一個圖片路徑,所以若是合圖的引用發生在不一樣層次的component,絕對會發生找不到文件的報錯,所以咱們統一將引用放在container的樣式文件中,權宜地解決這個問題,如下是大體gulpfile寫法:

gulp.task('sprites', function (cb) {
    var spriteData = gulp.src(Config.filePath.src + 'img/sprites/**/*.png')
                        .pipe(spritesmith({
                            spritesmith: function(options) {
                                options.imgPath = Config.sprites.imgPath + options.imgName;

                                options.cssName = options.cssName.replace('.css', '.scss');
                                // customized generated css template
                                options.cssTemplate = './config/scss.template.handlebars';
                            }
                          }));
    
      // Pipe image stream through image optimizer and onto disk
      var imgStream = spriteData.img
    // DEV: We must buffer our stream into a Buffer for `imagemin`
    .pipe(gulp.dest(Config.sprites.imgDest));

      // Pipe CSS stream through CSS optimizer and onto disk
      var cssStream = spriteData.css
    .pipe(gulp.dest(Config.sprites.cssDest));

      // Return a merged stream to handle both `end` events
      return merge(imgStream, cssStream);
});

如下是樣式引用辦法:

@import"../../../css/sprites/list_s";

......

.info-right.tiku-status3 {
    @include sprite($remark);
}

若有錯誤,懇請斧正!

相關文章
相關標籤/搜索