網頁

WebPack入門教學筆記

WebPack入門教學

what is webpack

Webpack與其他前端打包工具(watchify、Browserify)定位不太同,它是一個模組(module)系統,透過這些豐富的模組來實現更多的功能,它有:

  • 將CSS、圖片與其他資源打包至一個.js之中。
  • 打包之前預處理(less、coffeescript、jsx等)。
  • 依entry文件不同,把.js折分為多個.js
  • 豐富的模組元件

安裝webpack

參考 https://webpack.github.io/docs/tutorials/getting-started/

首先你必須安裝Node.js,我們需要使用npm進行套件的安裝與管理。

專案:npm install webpack --save-dev (儲存於專案目錄下)

全域:npm install webpack -g (global,存儲於本機之下)

webpack指令

開啟cmd.exe輸入指令:webpack main.js bundle.js

通常會建立一個webpack.config.js檔案並且進組態。

以是常用的指令:

  1. webpack:會在開發模式下開始一次性的建置
  2. webpack -p:會建置 production-ready 的程式碼 (壓縮)
  3. webpack --watch:會在開發模式下因應程式碼修改(儲存時有異動)持續更新建置
  4. webpack -d:加入 source maps 檔案
  5. webpack --progress --colors:含處理進度與顏色

常用指令:webpack --progress --colors --watch

Loader

webpack僅能處理Javascript所以需要一些Loader來幫忙。例如,處理CSS檔案,我們需要使用style-loader

安裝loader:npm install css-loader style-loader

JSXloader:npm install jsx-loader

webpack.config.js

    module.exports = {
       entry: './main.js',
       output: {
         filename: 'bundle.js'       
       },
       module: {
         loaders: [
           { test: /\.css$/, loader: "style!css" },
           { test: /\.js$/, loader: 'jsx-loader?harmony' } 
           // loaders 可以像 querystring 一樣接收參數
         ]
       },
       resolve: {
         // 設定後只需要寫 require('file') 而不用寫成 require('file.jsx')
         extensions: ['', '.js', 'jsx', '.json'] 
       }
    };
    
  • loaders 指定要載入的loader,loaders 可以像 querystring 一樣接收參數。
  • resolve 如果希望在 require()不需要加入副檔名可以像下面範例加入一個 resolve.extensions 屬性並告訴 webpack 哪些副檔名是可以省略的。

開發伺服器

安裝:npm install webpack-dev-server -g

啟動:webpack-dev-server --progress --colors

webpack-dev-server會讀取webpack.config.js,依照畫面輸出訊息可以得到伺服器位置,現在你可以一邊修改程式碼一邊看到即時更新網頁內容

package.json

參考:https://github.com/ruanyf/webpack-demos

切換至專案目錄下,於 cmd.exe 執行 npm init 並回答問題會產生可以幫忙產生預設package.json。package.json主要用於管理套件。在scripts段落可以設計多個指令,並使用npm run [指令名稱]來執行指令。

        // package.json
        {
        // ...
        "scripts": {
        "dev": "webpack-dev-server --devtool eval --progress --colors",
        "deploy": "NODE_ENV=production webpack -p"
        },
        // ...
        }
    

cmd.exe輸入npm run dev。我們可以在scripts段落設計各種所需要的指令。

Demo:多檔案輸出

很多情況,我們會希望不合併為一個.js檔案,希望分別輸出,我們可以這樣設計:

    module.exports = {
       entry: {
         bundle1: './main1.js',
         bundle2: './main2.js'
       },
       output: {
         filename: '[name].js'
       }
    };
    

bundle1、bundle2 會成為 [name] 的參數,main1.js 會輸出為 bundle1.js,main2.js 會輸出為 bundle2.js。

Demo:Babel-loader

Babel-loader 可以幫助你將JSX/ES6檔案轉換為JS檔案。

安裝:npm install react --save-dev (我們會使用 React 的 JSX 語法,所以需要它)

安裝:npm install babel-loader --save-dev

main.jsx是一個JSX檔案

    var React = require('react');
 
    React.render(
      <h1>Hello, world!</h1>,
      document.body
    );
    

webpack.config.js

    module.exports = {
      entry: './main.jsx',
      output: {
        filename: 'bundle.js'       
      },
      module: {
        loaders: [
          { test: /\.js[x]?$/, exclude: /node_modules/, loader: 'babel-loader' },
        ]
      }
    };
    

loaders指定了使用 Babel-loader 來將 main.jsx 轉換至 bundle.js。

Demo:css-loader

webpack 允許你在JS檔案中參考CSS。例如:

main3.js

    require('./app.css');
 
    document.write('<h1>Hello world!</h1>');
    

透過 require() 載入 app.css。如果 webpack.config.js 有設置 resolve 段落,可省略副檔名。

app.css

    body {
     background-color: blue;
    }
    

webpack.config.js

    module.exports = {
      entry: './main3.js',
      output: {
        filename: 'bundle.js'       
      },
      module: {
        loaders: [
          { test: /\.css$/, loader: 'style-loader!css-loader' },
        ]
      }
    };
    

Demo:Image loader

安裝:npm install url-loader --save-dev

某些場合,需要載入圖片,那麼可以使用 url-loader

main4.js

      var img1 = document.createElement("img");
      img1.src = require("./small.png");
      document.body.appendChild(img1);
      var img2 = document.createElement("img");
      img2.src = require("./big.png");
      document.body.appendChild(img2);
    

webpack.config.js

    module.exports = {
      entry: './main.js',
      output: {
        filename: 'bundle.js'
      },
      module: {
        loaders:[
          { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192' }
        ]
      }
    };
    

url-loader 會轉換圖片,依照圖片的大小(預設 8192 bytes)來決定轉換後的類別。小於 8k 會轉換為 Data URL。

    <img src="data:image/png;base64,iVBOR...uQmCC">
    <img src="4853ca667a2b8b8844eb2693ac1b2578.png">
    

Loader

參考:http://webpack.github.io/docs/using-loaders.html

安裝loader

npm install xxx-loader --save

npm install xxx-loader --save-dev

在使用時,xxx-loader可簡寫為xxx,例如,json-loader簡寫為json

使用有三種方式:

  • 明確的require敘述
  • 經由組態檔
  • 經由CLI

使用require

盡量避免。

組態檔

透過 RegExp 在組態檔中去綁定副檔名與處理的loader關係。(建議使用)

    {
     module: {
         loaders: [
             { test: /\.jade$/, loader: "jade" },
             // ".jade" 副檔名使用 "jade" loader
             { test: /\.css$/, loader: "style!css" },
             // ".css" 副檔名使用 "style" 和 "css" loader
             // ! 是 chain 的概念
             // 也可使用陣列語法
             { test: /\.css$/, loaders: ["style", "css"] },
         ]
     }
    }
    

CLI

webpack --module-bind jade --module-bind 'css=style!css'


loader 可以指定參數,採用一般QueryString方法:?key=value&key2=value2,也接受JSON物件:?{"key":"value","key2":"value2"}

CSS Module

css-loader?modules 可以啟用 CSS Modules 功能。

UglifyJS

Webpack 的 plugin 系統可以擴充它的功能。例如,使用http://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin來進行JS壓縮。

web.config.js

     var webpack = require('webpack');
     var uglifyJsPlugin = webpack.optimize.UglifyJsPlugin;
     module.exports = {
       entry: './main.js',
       output: {
         filename: 'bundle.js'
       },
       plugins: [
         new uglifyJsPlugin({
           compress: {
             warnings: false
           }
         })
       ]
     };
    

輸出的 bundle.js 就會進行壓縮處理。

Code Splitting

一般,我們在大型應用上把所有程式碼壓縮為單一檔案並不是有效的。這樣會造成檔案過於肥大。webpack也能讓我們可以來分割它們。需要的地方分別載入。

首先,使用require.ensure來定義分割點(split point)。

main8.js

    require.ensure(['./a'], function(require) {
     // 內容由 a 模組取得
     var content = require('./a');
     document.open();
     document.write('<h1>' + content + '</h1>');
     document.close();
    });
    

require.ensure是向webpack說明會使用到那些模組,這些模組就是我們的分割點。

如果是多個模組

    require(["module-a", "module-b"], function(a, b) {
     // ...
    });
    

a.js (a模組)

    module.exports = 'Hello World';
    

web.config.js

    module.exports = {
     entry: './main.js',
     output: {
       filename: 'bundle.js'
     }
    };
    

index.html

     <html>
       <body>
         <script src="bundle.js"></script>
       <body>
     </html>
    

編譯後會除了預設的bundle.js,還會產生1.bundle.jsmain.jsbundle.jsa.js1.bundle.js,最後,bundle.js內去會參考1.bundle.js

Code Splitting with bundle-loader

安裝:npm install bundle-loader --dev-save

另一種Code Splitting的方式是使用bundle-loader.

mail9.js

      var load = require('bundle-loader!./a.js');
     load(function(fileContent) {
       document.open();
       document.write('<h1>' + fileContent + '</h1>');
       document.close();
     });
    

首先透過bundle-loadera.js模組載入,在load()去使用載入的內容。

共同區塊

當多個腳本檔案有共同區塊時,你可以將共同部分提取出來為一支共用檔。

main10-x.js

      // main10-1.jsx
      var React = require('react');
      React.render(
        <h1>Hello World</h1>,
        document.getElementById('a')
      );
      
      // main10-2.jsx
      var React = require('react');
      React.render(
        <h2>Hello Webpack</h2>,
        document.getElementById('b')
      );
    

init.js 是預設提取出來的JS檔。

    <html>
      <body>
        <div id="a"></div>
        <div id="b"></div>
        <script src="init.js"></script>
        <script src="bundle1.js"></script>
        <script src="bundle2.js"></script>
      </body>
    </html>
    

web.config.js

      var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
      module.exports = {
        entry: {
          bundle1: './main10-1.jsx',
          bundle2: './main10-2.jsx'
        },
        output: {
          filename: '[name].js'
        },
        module: {
          loaders:[
            { test: /\.js[x]?$/, exclude: /node_modules/, loader: 'babel-loader' },
          ]
        },
        plugins: [
          new CommonsChunkPlugin('init.js')
        ]
      }
    

指定使用CommonsChunkPlugin,並指定產出的名稱為init.js

Vendor 區塊

在CommonsChunkPlugin裡,你可以加入額外的vendor函式庫到分割檔中。

安裝:npm install jquery --dev-save

這裡我們安裝jquery模組,並在main11.js載入使用。

main11.js

      var $ = require('jquery');
   
      $('h1').text('Hello World');
    

index.html

    <html>
     <body>
       <h1></h1>
       <script src="vendor.js"></script>
       <script src="bundle.js"></script>
     </body>
    </html>
    

web.config.js

    var webpack = require('webpack');
 
    module.exports = {
      entry: {
        app: './main.js',
        vendor: ['jquery'],
      },
      output: {
        filename: 'bundle.js'
      },
      plugins: [
        new webpack.optimize.CommonsChunkPlugin(/* chunkName= */'vendor', /* filename= */'vendor.js')
      ]
    };
    

使用上一個範例的宣告方式也行:

    var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin");
    // ...
      plugins: [
        new CommonsChunkPlugin('vendor', 'vendor.js')
      ]
    

如果你想將(jquery)模組使用在每一個模組中,像是讓$jQuery都是使用在每一模組中,但不需要寫required('jquery'),你應該使用ProvidePlugin。文件:http://webpack.github.io/docs/shimming-modules.html

main11.js

    //var $ = require('jquery');
    $('h1').text('Hello World');
    

註解require()的使用。

web.config.js

    module.exports = {
      entry: {
        app: './main.js'
      },
      output: {
        filename: 'bundle.js'
      },
      plugins: [
        new webpack.ProvidePlugin({
          $: "jquery",
          jQuery: "jquery",
          "window.jQuery": "jquery"
        })
      ]
    };
    

Webpack就是幫我們把jQuery模組包裝在bundle.js之中。

index.html

    <html>
      <body>
        <h1></h1>
        <script src="bundle.js"></script>
      </body>
    </html>
    

這樣就能刪除(或取消)vendor.js的使用。

全域變數

如果你想使用某些全域變數,但你不想包含在Webpack的bundle.js之中,你在web.config.js中去啟用externals。文件:http://webpack.github.io/docs/library-and-externals.html

Notifier

安裝:npm install webpack-notifier --save-dev

        var path = require('path');
        var WebpackNotifierPlugin = require('webpack-notifier');
    
        module.exports = {
          context: path.join(
              dirname, 'App'),
              entry: './main.js',
              output: {
              path: path.join(dirname, 'Built'),
              filename: '[name].bundle.js'
              },
          plugins: [
            new WebpackNotifierPlugin()
          ],
          module: {
         loaders: [
           { test: /.css$/, loader: "style!css" },
           { test: /.jpe?g$|.gif$|.png$|.svg$|.woff$|.ttf$|.eot$/, loader: "url" }
            ]
          }
        };
    

我們觸發webpack時可以看到桌面會跳出一個提醒訊息。

讀者可以從上面範例看到,它不只是一個打包、壓縮的功能,webpack功能更多元,當然,強大代表的複雜性,希望這份簡單的入門教學可以讓各位可以快速對webpack上手。

6 則留言:

  1. 謝謝分享這麼完整的整理,讓我可以很快速的對webpack有完整的了解。

    回覆刪除
  2. Bruce大, 請問如果沒有自動產生webpack.config.js , 我應該怎麼做, 謝謝您 (剛發錯篇了, 抱歉)

    回覆刪除
    回覆
    1. 它只是一個純文字的組態檔,你可以複製此頁面上的設置去修改即可。

      刪除
    2. 謝謝大大回覆,不好意思再請教一個可能不是很相關的問題。 我是用Asp.net MVC 5 搭配 reactjs.net 來開發~ 使用webpack 打包與babel轉譯, 我在使用範例的時候, require('xxx') 一直被認為是undefined , 是因為這是node.js的語法嗎? 如果我在asp.net mvc 把component切成分開的檔案想互相引用的話, 請問該怎麼處理, 謝謝您。

      刪除

感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。