# Chapter5: Webpack

## This section contain the following items:

* 1.Webpack Howto
* 2.Webpack.config.js   &#x20;
* 3.Webpack preloaders, loaders, postloaders
* 4.Webpack Plugins
* 5.Webpack & Angular
* 6.Webpack & React Hot Reloader

## 1.Webpack Howto

以下參考自<https://github.com/petehunt/webpack-howto>:

1.What is webpack?

```
    (1)可做到bowswerify可做到的, 但是可以分散封裝專案使用的程式碼，使載入頁面時只需載入當頁所需的程式碼以加速載入速度,以下兩行指令的效果是相同的:
        browserify main.js > bundle.js
        webpack main.js bundle.js
    Webpack 提供比 browserify 更多的功能,一般來說會創建一個名為 webpack.config.js 的檔案來做集中管理:
        // webpack.config.js
        module.exports = {
          entry: './main.js',
          output: {
            filename: 'bundle.js'       
          }
        };

    (2)用來取代grunt&gulp, 因為webpack可以build跟bundle css, preprocess css, compile-to-JS languages and images, among other things.

    (3)其他還有:
        ->可同時整合 CommonJS 和 AMD 模組
        ->轉換 JSX, Coffee Script, TypeScript 等
        ->整合樣式表 (css, sass, less 等)
        ->建置 production-ready 的程式碼 (壓縮)
```

2.How to invoke webpack?

```
      切換到有 webpack.config.js 檔案的目錄下並執行:
      webpack : 會在開發模式下開始一次性的建置
      webpack -p : 會建置 production-ready 的程式碼 (壓縮)
      webpack --watch : 會在開發模式下因應程式碼的變換持續更新建置 (快速!)
      webpack -d : 加入 source maps 檔案
```

3.Compile-to-JS languages:

```
       webpack與browserify transforms及RequireJS pluguns相同作用的是loaders, 以下示範如何使用webpackload Coffeescript, 以及Facebook的JSX+ES6 (必須先npm install babel-loader coffee-loader,npm install babel-core babel-preset-es2015 babel-preset-react):
      (1)先建置webpack.config.js, 並調整loaders的內容:
          // webpack.config.js
          module.exports = {
          entry: './main.js',
          output: {
            filename: 'bundle.js'       
          },
          module: {
            loaders: [
              { test: /\.coffee$/, loader: 'coffee-loader' },
              {
                test: /\.js$/,
                loader: 'babel-loader',
                query: {
                  presets: ['es2015', 'react']
                }
              }
            ]
          }
        };

       (2)如果需要require檔案而不指定特定的副檔名, 可以加入resolve.extensions:
          // webpack.config.js
          module.exports = {
            entry: './main.js',
            output: {
              filename: 'bundle.js'       
            },
            module: {
              loaders: [
                { test: /\.coffee$/, loader: 'coffee-loader' },
                {
                  test: /\.js$/,
                  loader: 'babel-loader',
                  query: {
                    presets: ['es2015', 'react']
                  }
                }
              ]
            },
            resolve: {
              // you can now require('file') instead of require('file.coffee')
              extensions: ['', '.js', '.json', '.coffee'] 
            }
        };
```

4.Stylesheets and images:

```
      (1)首先更新你的程式碼去require你的static assets:
          require('./bootstrap.css');
          require('./myapp.less');

          var img = document.createElement('img');
          img.src = require('./glyph.png');

     (2)當require css(或less等)時, webpack中CSS是可以被require()的,webpack 會自動生成一個 <style> 標籤並加入到 html的<head>中; 當require image, webpack將URL放到bundle中, 被require時再return回來, 但是必須給他指定loaders:
        // webpack.config.js
        module.exports = {
          entry: './main.js',
          output: {
            path: './build', // This is where images AND js will go
            publicPath: 'http://mycdn.com/', // This is used to generate URLs to e.g. images
            filename: 'bundle.js'
        },
        module: {
          loaders: [
            { test: /\.less$/, loader: 'style-loader!css-loader!less-loader' }, // use ! to chain loaders
            { test: /\.css$/, loader: 'style-loader!css-loader' },
            { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192' } // inline base64 URLs for <=8k images, direct URLs for the rest
          ]
        }
       };
```

5.Feature flags (特定功能標籤)

```
    (1)當想要在特定環境(dev)下,可先在code中定義全域變數
        if (__DEV__) {
          console.warn('Extra logging');
        }
        // ...
        if (__PRERELEASE__) {
          showSecretFeature();
        }
    (2)然後在webpack.config.js中定義不同環境時的行為
        // webpack.config.js
        // definePlugin 接收字串，因此你也可以直接寫入字串而不使用 JSON.stringify()
        var definePlugin = new webpack.DefinePlugin({
          __DEV__: JSON.stringify(JSON.parse(process.env.BUILD_DEV || 'true')),
          __PRERELEASE__: JSON.stringify(JSON.parse(process.env.BUILD_PRERELEASE || 'false'))
        });

        module.exports = {
          entry: './main.js',
          output: {
            filename: 'bundle.js'       
          },
          plugins: [definePlugin]
        };
    (3)設定完成後, 可以使用 BUILD_DEV=1 BUILD_PRERELEASE=1 webpack 來完成建置。調整指令的參數即可模擬各狀況下的建置。特別注意的是, webpack -p在壓縮時會將沒用到的程式碼刪除， 因此不用擔心會有不該出現的程式碼被加入到最終產出的程式碼內。
```

6.Multiple entrypoints (分批進入)

```
(1)如果你有profile page 及 feed page, 如果使用者只想要profile而不希望下載feed的code, 可以建置不同的bundle:
  // webpack.config.js
  module.exports = {
    entry: {
      Profile: './profile.js',
      Feed: './feed.js'
    },
    output: {
      path: 'build',
      filename: '[name].js' // Template based on keys in entry above
    }
  };

(2)For profile, insert <script src="build/Profile.js"></script> into your page. Do a similar thing for feed.
```

7.Optimizing common code

```
  (1)Feed與Profile間有許多共同的code, webpack分析出共通處並額外整合成一組在頁面間可快取的共用包:
    // webpack.config.js
    var webpack = require('webpack');
    var commonsPlugin =new webpack.optimize.CommonsChunkPlugin('common.js');
    module.exports = {
      entry: {
        Profile: './profile.js',
        Feed: './feed.js'
      },
      output: {
        path: 'build',
        filename: '[name].js'
        // [name] 會依據上面 entry 的屬性名稱變動
      },
      plugins: [commonsPlugin]
    };
  (2)Add <script src="build/common.js"></script> before the script tag you added in the previous step and enjoy the free caching.
```

8.Async loading

reference: 1. <https://github.com/petehunt/webpack-howto> 2. <https://rhadow.github.io/2015/03/23/webpackIntro/>

## 2.Webpack.config.js

```
  以下以https://github.com/preboot/angular-webpack中的範例來分析:
```

### 2.1 設定不同的環境變數: test, build(production)

```
  利用定義NODE_ENV變數, 此部份可參考npm life cycle (https://docs.npmjs.com/misc/scripts#current-lifecycle-event)來區分dev時或是production時應該做什麼事,例如在命令列下輸入:

  1.npm run build: run build scripts
  2.npm run test: run test server
  3.npm run start: run production server


 設定可以在package.json中設定:
 {
    "scripts": {
      "build": "rimraf dist && webpack --bail --progress --profile",
      "server": "webpack-dev-server --history-api-fallback --inline --progress",
      "test": "karma start",
      "test-watch": "karma start --auto-watch --no-single-run",
      "start": "npm run server"
    }
 }

  行為則在Webpack.config.js中定義:
  取得環境變數
    var ENV = process.env.npm_lifecycle_event;
    var isTest = ENV === 'test' || ENV === 'test-watch';
    var isProd = ENV === 'build';
```

reference:

1.<https://medium.com/@brianhan/use-this-npm-variable-as-a-flag-for-your-build-scripts-31069f5e2e57#.ae34blmqt>

2.<https://docs.npmjs.com/misc/scripts#current-lifecycle-event>

### 2.2 Export config

```
  module.exports = function makeWebpackConfig () {
    var config = {};
    return config;
  }();

  config的內容大致如下:

  1.entry: webpack的進入點:
    example: 設定進入點為./src/app/app.js
      config.entry = isTest ? {} : {
        app: './src/app/app.js'
      };
  2.output: 設定打包後的輸出檔
       example:
        config.output = isTest ? {} : {
         // 輸出檔的輸出路徑
         path: __dirname + '/dist',

         // require()時參考的路徑，例如圖片的路徑
         // 在dev環境下, 使用webpack-dev-server 
         publicPath: isProd ? '/' : 'http://localhost:8080/',

         // 輸出的bundle檔名
         // build mode下加入hash
         filename: isProd ? '[name].[hash].js' : '[name].bundle.js',

         // 未被列在entry裡, 卻又希望被輸出的budle
         // build mode下加入hash
         chunkFilename: isProd ? '[name].[hash].js' : '[name].bundle.js'
       };
  3.devtool
  4.module:
        preloaders, loaders, postloaders
        example:
          preloaders: 圖片壓縮 
          loaders:配置文件, 如coffie-script轉換成js 
          postloaders: 代碼覆蓋率測試等
  5.plugins
  6.devServer: 
        contentBase: 啟動webpack-dev-server的路徑
        example:
          config.devServer = {
            contentBase: './src/public',
            stats: 'minimal'
          };
```

## 3.Webpack preloaders, loaders, postloaders

Using loaders: <http://webpack.github.io/docs/using-loaders.html>

1.preloaders,

2.loaders

```
    1.babel loader
    2.css loader
    3.file loader
    4.raw-loader
    5.ngtemplate-loader: https://github.com/WearyMonkey/ngtemplate-loader
    6.raw-loader: https://github.com/webpack/raw-loader
```

## 4.Webpack Plugins

* Extract Text Plugin:
  * webpack中CSS是可以被require()的,webpack 會自動生成一個style標籤並加入到 html的head中
  * ExtractTextPlugin可輸出打包成一包的css, 一般來說會用在production的時候

    ```
    example:
       new ExtractTextPlugin('[name].[hash].css', {disable: !isProd})
    ```
* HtmlWebpackPlugin: 可以動態組成index.html

  ```
    example:
      new HtmlWebpackPlugin({
        template: './src/public/index.html',
        inject: 'body'
      })
      template: 動態產生檔案所存放的位置
  ```

  * CopyWebpackPlugin:

    copies individual files or entire directories to the build directory.
  * NoErrorsPlugin
  * DedupePlugin
  * UglifyJsPlugin

    **5.Webpack & Angular**

    **5.1 Environment**

    **5.2 Webpack.config.js**

    (1)Add 2 things into webpack.config.js: 'use strict'; var webpack = require('webpack'); (2)Add 3 thing in module.exports = {}: (2.1) Entry: 告訴webpack打包的進入點 (2.2) Output: filename: 指定webpack打包後所產出的檔案名稱 path: 指定webpack打包後所產出的檔案的路徑 (2.3) Module: loaders: 指定Loader的種類 (3)Add loaders to webpack.config.js (4)Example: var webpack = require('webpack');

    ```
    module.exports = {
      entry: [
        './Angular/app.js'
      ],
      output: {
        path: __dirname + '/public',
        filename: 'bundle.js'
      },
      module: {
      loaders: [
          { test: /\.js$/, loader: 'babel', exclude: [/node_modules/] },
          { test: /\.html$/, loader: 'html-loader', exclude: [/node_modules/] },
          { test: /\.css$/, loader: "style!css", exclude: [/node_modules/] },
          { test: /\.scss$/, loader: "style!css!sass", exclude: [/node_modules/] }
        ]
      }
    };
    ```

### 5.3 Directive and Webpack

## 6.Webpack & React Hot Reloader

* 1.Introduction
  * 雖然使用webpack有很多優點, 但比較不便利的地方是更新完必須重新build bundle.js, 再重新啟動node.js, 重新reload, 造成開發上的不方便.
  * Hot Reloader的目的在於一旦有更新檔案, Webpack可以自己重build, Browser可接到訊息並reload.&#x20;
  * 要讓Webpack做到Hot Reload有兩種方式:&#x20;
    * 一種是使用webpack-dev-server, 可以提供獨立的standalone server.
    * 另一種則是使用webpack提供給express整合的middleware: webpack-dev-middleware及webpack-hot-middleware, 本段所記錄的是使用這種方式.
* 2.Install packages
  * **需要的套件有webpack, webpack-dev-middleware, webpack-hot-middleware, react-hot-loader, react, babel-loader**:

    ```
      npm install webpack webpack-dev-middleware, webpack-hot-middleware react-hot-loader react babel-loader
    ```

    * 1.react, babel-loader便不多作解釋
    * 2.webpack-dev-middleware, webpack-hot-middleware:讓我們用 express 客製一個有熱替換功能的 webpack 開發伺服器
    * 3.react-hot-loader:可以在不改變 React 元件的 state 下，將更改過程式碼的元件直接更新到畫面上
* 3.Webpack.config.js
  * 要修改的地方有五處: cache, entry, output, plugins, module
    * 1.cache: enable cache

      ```
          cache: true,
      ```
    * 2.entry: 加&#x5165;**'webpack-hot-middleware/client', 'webpack/hot/dev-server**

      ```
           entry: [
           './js/mainentry.js', 'webpack-hot-middleware/client', 'webpack/hot/dev-server'
           ], 
      ```
    * 3.output: 加入publicPath

      ```
          var publicPath = 'http://localhost:3000/assets';
          output: {
           path: __dirname + '/assets',
           publicPath: publicPath,
           filename: 'mainbundle.js'
       },
      ```
    * 4.plugins: 修改如下:

      ```
      plugins: [
           new webpack.optimize.OccurenceOrderPlugin(),
           new webpack.HotModuleReplacementPlugin(),
           new webpack.NoErrorsPlugin()
           ],
      ```
    * 5.module:

      ```
          module: {
          loaders: [{
            test: /\.jsx?$/, // Match both .js and .jsx files
            loaders: ['react-hot', 'babel?presets[]=es2015,presets[]=react']
            }]
           }
      ```
* 4.index.dev.js
  * Create一個index.dev.js: 使用webpack-dev-middleware及webpack-hot-middleware,以及導入webpack.config.js

    ```
         var webpack = require('webpack'),
          webpackDevMiddleware = require('webpack-dev-middleware'),
          webpackHotMiddleware = require('webpack-hot-middleware'),
          webpackDevConfig = require('./webpack.config.js');

          var compiler = webpack(webpackDevConfig);

          function useWebpackMiddleware(app) {
              app.use(webpackDevMiddleware(compiler, {
            // public path should be the same with webpack config
              publicPath: webpackDevConfig[0].output.publicPath,
              noInfo: true,
              hot: true,
              inline: true,
              stats: {
              colors: true
              }
              }));
              app.use(webpackHotMiddleware(compiler, {
                  log: console.log
              }));
              return app;
            }

          module.exports = {
            useWebpackMiddleware: useWebpackMiddleware
          };
    ```
* 5.app.js
  * 目的是分為develope版本及production版本, 當環境變數不是production時,就會切到index.dev.js

    ```
        var webpackDevHelper = require('./index.dev.js');
        if (process.env.NODE_ENV !== 'production') {
            console.log('DEVOLOPMENT ENVIRONMENT: Turning on WebPack Middleware...');
            webpackDevHelper.useWebpackMiddleware(app);
        } else {
            console.log('PRODUCTION ENVIRONMENT');
        }
    ```
* 6.package.json
  * 在package.json的scripts中放入**dev, production**兩個script
    * production: 將環境變數改為production, 並執行node.js
    * dev: 如果mainbundle.js存在則刪除, 並執行node.js

      ```
      "scripts": {
        "production": "export NODE_ENV=production && webpack && node app.js",
        "dev": "rm -f ./assets/mainbundle.js && node app.js"
      },
      ```
  * 執行dev: npm run dev
  * 執行production:

    ```
    npm run production
    ```
* 7.index.html
  * 記得埋入script source

    ```
      <script src="./assets/mainbundle.js"></script>
    ```
