1. 程式人生 > >你一定喜歡看的 Webpack 2.× 入門實戰(轉載)

你一定喜歡看的 Webpack 2.× 入門實戰(轉載)

 最近在學習 Webpack,網上大多數入門教程都是基於 Webpack 1.x 版本的,我學習 Webpack 的時候是看了 zhangwang<<入門 Webpack,看這篇就夠了>> 寫的非常好,不過是基於 Webpack 1.x 版本的,語法上和 Webpack 2.x 有一點不同.我學習時是使用 Webpack 2.6.1 版本,所以我就尋思著基於 zhangwang<<入門 Webpack,看這篇就夠了>> 寫下這篇 Webpack 2.x 的入門實戰,是我學習 Webpack 的記錄.聽說 Webpack 3.x 版本快要出了,不得不感嘆前端領域發展的真是太快了!

Webpack 是什麼?

Webpack 是前端資源模組化管理和打包工具。
Webpack 可以將許多鬆散的模組按照依賴和規則打包成符合生產環境部署的前端資源。
Webpack 可以將按需載入的模組進行程式碼分隔,等到實際需要的時候再非同步載入。
Webpack 通過 loader 的轉換,任何形式的資源都可以視作模組,比如 CommonJs 模組、 AMD 模組、 ES6 模組、CSS、圖片、 JSON、Coffeescript、 LESS、 SASS 等。

一圖勝千言,下圖足夠說明上面巴巴拉拉一大堆是啥了!

對於模組的組織,通常有如下幾種方法:

  • 分開寫幾個 js 檔案,使用 script 標籤載入.
  • CommonJS 進行同步載入, Node.js 就使用這種方式.
  • AMD進行非同步載入, require.js 使用這種方式.
  • 新的 ES6 模組.

Webpack 的特點

  • 豐富的外掛,流行的外掛都有,方便進行開發工作.
  • 大量的載入器,便於處理和載入各種靜態資源.
  • 將按需載入的模組進行程式碼分隔,等到實際需要的時候再非同步載入.

Webpack 的優勢

  • Webpack 以 commonJS 的形式來書寫指令碼,但對 AMD / CMD / ES6 模組 的支援也很全面,方便舊專案進行程式碼遷移。
  • 所有資源都能模組化。
  • 開發便捷,能替代部分 Grunt / Gulp 的工作,比如打包、壓縮混淆、圖片轉 base64、SASS 解析成 CSS 等。
  • 擴充套件性強,外掛機制完善,特別是支援模組熱替換(見 模組熱替換 )的功能讓人眼前一亮。

Webpack 與 Grunt / Gulp

在沒有學習 Webpack 之前我對 Webpack、Grunt、Gulp 的認識很模糊,只知道好像這三個東西都是前端自動化工具,都是用來使前端自動化、模組化、工程化的,這三者是可以替代彼此的前端工具.

其實 Webpack 和 Gulp / Grunt 並沒有太多的可比性,Gulp / Grunt 是一種能夠優化前端開發流程的自動化工具,而 Webpack 是一種模組化的解決方案,不過 Webpack 的優點使得 Webpack 可以替代 Gulp / Grunt 一部分工作。

Grunt / Gulp 的工作方式是:在一個配置檔案中,指明對某些檔案需要進行哪些處理,例如:編譯、組合、壓縮等任務的具體步驟,Grunt / Gulp 之後可以自動替你完成這些任務。Grunt / Gulp的工作流程如下圖:

Webpack 的工作方式是:把你的專案當做一個整體,通過一個給定的主檔案( 如:index.js ),Webpack 將從這個檔案開始找到你的專案的所有依賴檔案,使用 loaders 處理它們,最後打包為一個瀏覽器可識別的 JavaScript 檔案。Webpack工作方式如下圖:

 

Webpack

如果實在要進行比較,Webpack 的處理速度更快更直接,因為 Webpack 的歷史包袱小.Webpack 還能打包更多不同型別的檔案。

開始使用 Webpack

初步瞭解 Webpack 後,就可以開始學習使用 Webpack。這裡會以一個小的 Demo 為例子來一步一步進行動手學習!

新建 Webpack 專案

1. 新建一個資料夾,命名為 webpack-demo,webpack-demo 就是你的專案名,專案名建議使用小寫字母,並且不帶空格,不能含有大寫字母.

2. 安裝 Webpack,Webpack 可以使用 npm 安裝,如果你還不知道 npm 為何物,請 Google,也可以參考 Node.js 安裝配置NPM 使用介紹快速瞭解、安裝 npm.

使用終端在該資料夾中執行下述指令就可以完成安裝,由於網路原因安裝過程可能需要一些時間。

//全域性安裝
npm install -g webpack
//安裝到你的專案目錄
npm install --save-dev webpack

Webpack 可以全域性安裝,也可以安裝到你的專案目錄.剛開始學習 Webpack 為了方便,建議同時全域性安裝和安裝到你的專案目錄.

3. 在 webpack-demo 資料夾中建立一個 package.json 檔案,這是一個標準的 npm 說明檔案,裡面蘊含了豐富的資訊,包括當前專案的依賴模組,自定義的指令碼任務等等。在終端中使用 npm init 命令可以自動建立這個 package.json 檔案.

npm init

 

輸入這個命令後,終端會問你一系列諸如專案名稱,專案版本,專案描述,入口檔案,作者等資訊,不過不用擔心,如果你不準備在 npm 中釋出你的模組,這些問題的答案都不重要,回車預設即可.這些資訊今後都可以更改 package.json 來修改,所以不用擔心.

4. 在 webpack-demo 資料夾中建立兩個資料夾 app 資料夾和 public 資料夾, app 資料夾用來存放原始資料,例如: SASS 檔案、LESS 檔案、JavaScript 模組等,public 資料夾用來存放經過 Webpack 處理過的 app 資料夾資料,這也是準備給瀏覽器讀取的資料,其中包括使用 Webpack 打包後的 js 檔案等。在這裡還需要在 public 資料夾中建立 index.html 檔案.在 app 資料夾中建立 Greeter.js 和 main.js 檔案,此時專案結構如下圖所示:

5. 在 public 資料夾中的 index.html 檔案只有最基礎的 html 程式碼,它唯一的目的就是載入打包後的 js 檔案 bundle.js.

 

// index.html

<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <title>webpack-demo</title>
</head>

<body>
    <div id='root'>
    </div>
    <script type="text/javascript" src="bundle.js"></script>
</body>

</html>

6. 在 app 資料夾中的 Greeter.js 只包括一個用來返回問候資訊的 html 元素的函式。

// Greeter.js

module.exports = function() {
    var greet = document.createElement('div');
    greet.textContent = "Hi there and greetings!";
    return greet;
}


7. 在 app 資料夾中的 main.js 用來把 Greeter 模組(其實可以簡單的把它看作 Greeter.js)返回的節點插入頁面。

// main.js

var greeting = require('./Greeter.js');
document.getElementById('root').appendChild(greeting());

Webpack 配置檔案

Webpack 配置檔案其實也是一個簡單的 JavaScript 模組,可以把所有與專案構建相關的資訊放在裡面。在 webpack-demo 資料夾根目錄下新建一個名為 webpack.config.js 的檔案,並在其中進行最簡單的配置.如下所示,它包含入口檔案路徑和存放打包後文件的地方路徑。

// webpack.config.js

module.exports = {
    entry: __dirname + "/app/main.js", //入口檔案路徑
    output: {
        path: __dirname + "/public/", //存放打包後文件的地方路徑
        filename: "bundle.js" //打包後的檔名
    }
}
注:__dirname 是 node.js 中的一個全域性變數,它指向當前 js 檔案所在的目錄.

現在只需要在終端裡執行 webpack 命令就可以了,這條命令會自動參考 webpack.config.js 檔案中的配置選項打包你的專案,輸出結果如下:

 

此時專案的 public 資料夾下也會出現打包好的 bundle.js 檔案.此時專案結構如下圖所示:

可以看出 webpack 同時編譯了 main.js 和 Greeter.js,開啟 public 目錄下的 index.html 檔案,就可以看到最終效果,如下圖:

利用 npm 更快捷的執行打包任務

通過 Webpack 配置檔案和執行 webpack 命令其實是比較煩人且容易出錯的,不過值得慶幸的是 npm 可以引導任務執行,對其進行配置後可以使用簡單的 npm start 命令來代替這些繁瑣的命令。在 package.json 中對 npm 的指令碼部分進行相關設定,相當於把 npm 的 start 命令指向 webpack 命令,設定方法如下:

// package.json

{
    "name": "webpack-demo",
    "version": "1.0.0",
    "description": "",
    "scripts": {
        "start": "webpack"
    },
    "author": "",
    "license": "ISC"
}

執行 npm start 後命令行的輸出顯示:

現在只需要使用 npm start 就可以打包檔案了.開啟 public 目錄下的 index.html 檔案,看到的最終效果是不是與之前的一樣.

利用 Webpack 生成 Source Maps

簡單說,Source Maps 就是一個資訊檔案,裡面儲存著位置資訊。也就是說,轉換後的程式碼的每一個位置,所對應的轉換前的位置.有了它,出錯的時候,除錯工具將直接顯示原始程式碼,而不是轉換後的程式碼。這無疑給開發者帶來了很大方便.為了方便除錯可以利用 Webpack 生成 Source Maps.

在 Webpack 的配置檔案中配置 Source Maps,需要配置 devtool,它有以下四種不同的配置選項,各有優缺點,描述如下:

  • source-map 在一個單獨的檔案中產生一個完整且功能完全的檔案。這個檔案具有最方便除錯的 Source Maps,但是這個檔案會比較大,會減慢打包檔案的構建速度.
  • cheap-module-source-map 在一個單獨的檔案中生成一個不帶列對映的 Source Maps,不帶列對映能夠提高專案構建速度,但這也使得瀏覽器開發者工具只能對應到具體的行,不能對應到具體的列,會對除錯造成不便.
  • eval-source-map 在同一個檔案中生成乾淨的完整的 Source Maps。這個選項可以在不影響構建速度的前提下生成完整的 Source Maps,但是對打包後輸出的 js 檔案的執行具有效能和安全的隱患。不過在開發階段這是一個非常好的選項,但是在生產階段一定不要用這個選項.
  • cheap-module-eval-source-map 這是在打包檔案時最快的生成 Source Maps 的方法,生成的Source Map 會和打包後的 js 檔案同行顯示,沒有列對映,和 eval-source-map 選項具有相似的缺點,檔案的執行具有效能和安全的隱患.

上述選項由上到下打包速度越來越快,不過同時也具有越來越多的負面作用,較快的構建速度的後果就是對打包的檔案執行有一定影響。在學習階段以及在小到中型的專案上,eval-source-map是一個很好的選項,不過記得只在開發階段使用它.

編輯 webpack-demo 資料夾下的 webpack.config.js 檔案配置 devtool 選項,生成 Source Maps 檔案.配置 devtool 後的 webpack.config.js 檔案如下:

// webpack.config.js

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口檔案路徑
    output: {
        path: __dirname + "/public/", //存放打包後文件的地方路徑
        filename: "bundle.js" //打包後的檔名
    }
}

執行 npm start 命令後就能生成對應的 Source Maps,終端輸出資訊如下圖:

 

此時專案中 public 資料夾下也生成了名為 bundle.js.map 的 Source Maps 檔案.此時專案結構如下圖所示:

使用 Webpack 構建本地伺服器

想不想讓你的瀏覽器監測你修改的程式碼,並自動重新整理修改後的結果.其實 Webpack 提供一個可選的本地開發伺服器,這個本地伺服器基於 node.js 構建,可以實現你想要的這些功能,不過它是一個單獨的元件,在 Webpack 中進行配置之前需要單獨安裝它作為專案依賴.在終端中輸入下面的指令安裝對應元件.建議同時全域性安裝和安裝到你的專案目錄.

//全域性安裝
npm install -g webpack-dev-server
//安裝到你的專案目錄
npm install --save-dev webpack-dev-server

devserver 作為 Webpack 配置選項中的一項,具有以下配置選項

  • contentBase 預設 webpack-dev-server 會為根資料夾提供本地伺服器,如果想為另外一個目錄下的檔案提供本地伺服器,應該在這裡設定其所在目錄(本例設定到“public"資料夾下).
  • port 設定預設監聽埠,如果省略,預設為"8080".
  • inline 設定為 true,當原始檔改變時會自動重新整理頁面.
  • historyApiFallback 在開發單頁應用時非常有用,它依賴於 HTML5 history API,如果設定為 true,所有的跳轉將指向 index.html.

編輯 webpack-demo 資料夾下的 webpack.config.js 檔案配置以上選項.

// webpack.config.js

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口檔案路徑
    output: {
        path: __dirname + "/public/", //存放打包後文件的地方路徑
        filename: "bundle.js" //打包後的檔名
    },
    devServer: {
        contentBase: "./public",
        port: "9000",
        inline: true,
        historyApiFallback: true,
    }
}

在終端中輸入如下命令,構建本地伺服器:

webpack-dev-server

終端輸出資訊如下圖,表示 Webpack 構建的本地伺服器已啟動.

在瀏覽器中開啟 http://localhost:9000/ 就可以看到像之前一樣的問候語頁面.

也可以使用 npm 更快捷的執行任務,編輯 webpack-demo 資料夾下的 package.json 檔案 scripts 選項.

 

// package.json

{
    "name": "webpack-demo",
    "version": "1.0.0",
    "description": "",
    "scripts": {
        "start": "webpack",
        "dev": "webpack-dev-server --devtool eval --progress --content-base build"
    },
    "author": "",
    "license": "ISC"
}

在終端中執行 npm run dev 命令,輸出資訊如下圖,一樣可以啟動的本地伺服器.

 

 

Ctrl + C 即可退出本地伺服器.

一切皆模組

Webpack 有一個不可不說的優點,它把所有的檔案都可以當做模組處理,包括你的 JavaScript 程式碼,也包括 CSS 和 fonts 以及圖片等等,只要通過合適的 Loaders,它們都可以被當做模組被處理.

Loaders

webpack 可以使用 loader 來預處理檔案。這允許你打包除 JavaScript 之外的任何靜態資源.通過使用不同的 loader,Webpack 通過呼叫外部的指令碼或工具可以對任何靜態資源進行處理,比如說分析 JSON 檔案並把它轉換為 JavaScript 檔案,或者說把 ES6 / ES7 的 JS 檔案轉換為現代瀏覽器可以識別的 JS 檔案.對 React 開發而言,合適的 Loaders 可以把 React 的 JSX 檔案轉換為 JS 檔案.

Loaders 需要單獨安裝並且需要 在webpack.config.js 下的 modules 關鍵字下進行配置,Loaders 的配置選項包括以下幾方面:

  • test:一個匹配 Loaders 所處理的檔案的拓展名的正則表示式(必須)
  • loader:loader 的名稱(必須)
  • include/exclude: 手動新增必須處理的檔案/資料夾,或遮蔽不需要處理的檔案/資料夾(可選)
  • query:為 Loaders 提供額外的設定選項(可選)

繼續動手實踐,修改 app 資料夾下的 Greeter.js 檔案,把問候訊息放在一個單獨的 JSON 檔案裡,通過 loader 的使 Greeter.js 可以讀取該 JSON 檔案.

1. 在 app 資料夾下建立 config.json 檔案,並輸入如下程式碼:

//config.json

{
    "greetText": "Hi there and greetings from JSON!"
}

2. 編輯 app 資料夾下的 Greeter.js 檔案,修改後如下:

// Greeter.js

var config = require('./config.json');

module.exports = function() {
    var greet = document.createElement('div');
    greet.textContent = config.greetText;
    return greet;
}

3. 安裝支援匯入 JSON 檔案的 json-loader .由於 webpack 2.× 預設支援匯入 JSON 檔案.如果你使用自定義副檔名,可能仍然需要使用此 loader.在終端中執行如下命令,安裝 json-loader 到你的專案中.

//安裝到你的專案中
npm install --save-dev json-loader

因為 json-loader 安裝到你的專案目錄裡了,所以 webpack-demo 專案下會新增一個 node_modules 資料夾用於存放安裝的 json-loader.此時的專案結構如下:

                                                                                 

4. 編輯 webpack.config.js 檔案配置 modules 選項,新增 json-loader,編輯後的檔案如下:

 

// webpack.config.js

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口檔案路徑
    output: {
        path: __dirname + "/public/", //存放打包後文件的地方路徑
        filename: "bundle.js" //打包後的檔名
    },
    devServer: {
        contentBase: "./public",
        port: "9000",
        inline: true,
        historyApiFallback: true,
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }]
    }
}

在終端中輸入 npm start 重新編譯打包,再在瀏覽器中開啟 public 資料夾下的 index.html 檔案,如果看到和下圖一樣的,就說明 json-loader 配置成功了.

Babel

Babel 其實是一個編譯 JavaScript 的平臺,它的強大之處表現在可以通過編譯幫你達到以下目的:

  • 把 ES6 / ES7 標準的 JavaScript 轉化為瀏覽器能夠解析的 ES5 標準的 JavaScript.
  • 使用基於 JavaScript 進行了拓展的語言,比如 React 的 JSX.

Babel的安裝與配置

Babel 其實是幾個模組化的包,其核心功能位於稱為 babel-core 的 npm 包中,不過 Webpack 把它們整合在一起使用,但是對於每一個你需要的功能或拓展,你都需要安裝單獨的包.用得最多的是解析 ES6 的 babel-preset-es2015 包和解析 JSX 的 babel-preset-react 包.

先來安裝這些依賴包,輸入如下命令,把這些依賴包安裝到你的專案中.

// 利用 npm 一次性安裝多個依賴模組,模組之間用空格隔開
npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react

//安裝 React 和 React-DOM
npm install --save react react-dom

編輯 webpack.config.js 檔案配置 modules 選項,新增 Babel 配置,編輯後的檔案如下:

// webpack.config.js

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口檔案路徑
    output: {
        path: __dirname + "/public/", //存放打包後文件的地方路徑
        filename: "bundle.js" //打包後的檔名
    },
    devServer: {
        contentBase: "./public",
        port: "9000",
        inline: true,
        historyApiFallback: true,
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }, {
            test: /\.js$/,
            exclude: /node_modules/, //編譯打包時需要排除 node_modules 資料夾
            loader: "babel-loader",
            query: {
                presets: ['es2015', 'react']
            }
        }]
    }
}

使用 ES6 的語法,更新 app 資料夾下的 Greeter.js 檔案,並返回一個 React 元件,修改後的程式碼如下:

// Greeter.js

import React, { Component } from 'react';
import config from './config.json';

class Greeter extends Component {
    render() {
        return (<div> { config.greetText } </div>);
        }
    }

    export default Greeter;

使用 ES6 的模組定義和渲染 Greeter 模組,修改 app 資料夾下的 main.js 檔案,修改後的程式碼如下:

// main.js

import React from 'react';
import {render} from 'react-dom';
import Greeter from './Greeter';

render(<Greeter />, document.getElementById('root'));

在終端中執行 npm start 命令重新編譯打包,終端輸出資訊如下:

 

在瀏覽器中開啟 public 資料夾下的 index.html 檔案,如果看到和下圖一樣的,就說明已經成功配置了 Babel.

 

Babel的配置選項

Babel 其實可以完全在 webpack.config.js 檔案中進行配置,但是考慮到 babel 具有非常多的配置選項,在單一的 webpack.config.js 檔案中進行配置往往使得這個檔案顯得太複雜,因此一些開發者支援把 babel 的配置選項放在一個單獨的名為 ".babelrc" 的配置檔案中。我們現在的 babel 的配置並不算複雜,不過之後我們會再加一些東西,因此現在我們就提取出相關部分,分兩個配置檔案進行配置, Webpack 會自動呼叫 .babelrc 裡的 babel 配置選項.

編輯 webpack.config.js 檔案配置 modules 選項,新增 Babel 配置,編輯後的檔案如下:

// webpack.config.js

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口檔案路徑
    output: {
        path: __dirname + "/public/", //存放打包後文件的地方路徑
        filename: "bundle.js" //打包後的檔名
    },
    devServer: {
        contentBase: "./public",
        port: "9000",
        inline: true,
        historyApiFallback: true,
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }, {
            test: /\.js$/,
            exclude: /node_modules/, //編譯打包時需要排除 node_modules 資料夾
            loader: "babel-loader"
        }]
    }
}

 在 webpack-demo 資料夾下新建 .babelrc 檔案,新增如下程式碼:

// .babelrc

{
    "presets": ['es2015', 'react']
}

在終端中執行 npm start 命令重新編譯打包,終端輸出資訊如下:

在瀏覽器中開啟 public 資料夾下的 index.html 檔案,如果看到和下圖一樣的,就說明已經成功配置了 Babel.

CSS

Webpack 提供兩個工具處理樣式表,css-loader 和 style-loader.

  • css-loader 使你能夠使用類似 @import 和 url(...) 的方法實現 require() 的功能
  • style-loader 將所有的計算後的樣式加入頁面中

二者組合在一起使你能夠把樣式表嵌入 Webpack 打包後的 JS 檔案中。

先來安裝 css-loader, style-loader,輸入如下命令,把這些依賴包安裝到你的專案中.

npm install --save-dev style-loader css-loader

編輯 webpack.config.js 檔案配置 modules 選項,新增處理樣式表配置,編輯後的檔案如下:

// webpack.config.js

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口檔案路徑
    output: {
        path: __dirname + "/public/", //存放打包後文件的地方路徑
        filename: "bundle.js" //打包後的檔名
    },
    devServer: {
        contentBase: "./public",
        port: "9000",
        inline: true,
        historyApiFallback: true,
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }, {
            test: /\.js$/,
            exclude: /node_modules/, //編譯打包時需要排除 node_modules 資料夾
            loader: "babel-loader"
        }, {
            test: /\.css$/,
            loader: 'style-loader!css-loader' //新增對樣式表的處理,感嘆號的作用在於使同一檔案能夠使用不同型別的 loader
        }]
    }
}

接下來,在 app 資料夾裡建立一個名為 main.css 的檔案,在檔案中新增如下程式碼,對一些元素設定樣式.

// main.css

html {
    box-sizing: border-box;
    -ms-text-size-adjust: 100%;
    -webkit-text-size-adjust: 100%;
}

*,
*:before,
*:after {
    box-sizing: inherit;
}

body {
    margin: 0;
    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

h1,
h2,
h3,
h4,
h5,
h6,
p,
ul {
    margin: 0;
    padding: 0;
}
Webpack 只有單一的入口,其它的模組需要通過 import, require, url 等匯入相關位置,為了讓 Webpack 能找到 main.css 檔案,我們把它匯入 app 資料夾下的 main.js 中.修改 app 資料夾下的 main.js 檔案,修改後的程式碼如下:
// main.js

import React from 'react';
import {render} from 'react-dom';
import Greeter from './Greeter';
import './main.css'; //匯入css檔案

render(<Greeter/>, document.getElementById('root'));

在終端中執行 npm start 命令重新編譯打包,終端輸出資訊如下:

 

在瀏覽器中開啟 public 資料夾下的 index.html 檔案,如果看到和下圖一樣的,就說明已經配置成功了.

 

通常情況下,css 會和 js 打包到同一個檔案中,並不會打包為一個單獨的 css 檔案,不過通過合適的配置 Webpack 也可以把 css 打包為單獨的檔案的。
不過這也只是 Webpack 把 css 當做模組而已,繼續看一個真的 CSS 模組的實踐.

CSS module

在過去的一些年裡,JavaScript 通過一些新的語言特性、更好的工具、更好的實踐方法(比如說模組化)發展得非常迅速。模組使得開發者把複雜的程式碼轉化為小的、乾淨的、依賴宣告明確的單元,且基於優化工具,依賴管理和載入管理可以自動完成。

不過前端的另外一部分,CSS 的發展就相對慢一些,大多的樣式表卻依舊是巨大且充滿了全域性類名,這使得維護和修改都非常困難和複雜。

CSS modules 的技術就能夠把 JS 的模組化思想帶入 CSS 中來,通過 CSS 模組,所有的類名,動畫名預設都只作用於當前模組.

Webpack 從一開始就對 CSS 模組化提供了支援,在 CSS loader 中進行配置後,你所需要做的一切就是把 modules 傳遞到需要的地方,然後就可以直接把 CSS 的類名傳遞到元件的程式碼中,且這樣做只對當前元件有效,不必擔心在不同的模組中具有相同的類名可能會造成的問題。

編輯 webpack.config.js 檔案配置 modules 選項,新增處理樣式表配置,編輯後的檔案如下:

// webpack.config.js

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口檔案路徑
    output: {
        path: __dirname + "/public/", //存放打包後文件的地方路徑
        filename: "bundle.js" //打包後的檔名
    },
    devServer: {
        contentBase: "./public",
        port: "9000",
        inline: true,
        historyApiFallback: true,
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }, {
            test: /\.js$/,
            exclude: /node_modules/, //編譯打包時需要排除 node_modules 資料夾
            loader: "babel-loader"
        }, {
            test: /\.css$/,
            loader: 'style-loader!css-loader?modules' //跟前面相比就在後面加上了 ?modules
        }]
    }
}

接下來,在 app 資料夾裡建立一個名為 Greeter.css 的檔案,在檔案中新增如下程式碼,對一些元素設定樣式.

// Greeter.css

.root {
    background-color: #eee;
    padding: 10px;
    border: 3px solid #ccc;
}

匯入 .root 到 Greeter.js 中,修改 app 資料夾下的 Greeter.js 檔案,修改後的程式碼如下:

// Greeter.js

import React, { Component } from 'react';
import config from './config.json';
import styles from './Greeter.css'; //匯入 .root 到 Greeter.js 中

class Greeter extends Component {
    render() {
        return ( <div className={styles.root}> { config.greetText } </div>);
        }
    }

    export default Greeter;

在終端中執行 npm start 命令重新編譯打包,終端輸出資訊如下:

在瀏覽器中開啟 public 資料夾下的 index.html 檔案,如果看到和下圖一樣的,就說明已經配置成功了.

 

CSS modules 也是一個很大的主題,有興趣的話可以去官方文件檢視更多訊息.下面兩篇文章也可以看看:

CSS 前處理器

CSS 前處理器可以將 SASS、LESS 檔案轉化為瀏覽器可識別的 CSS 檔案,以下是常用的CSS 前處理器 loaders.

  • Less Loader
  • Sass Loader
  • Stylus Loader

其實也存在一個 CSS 的處理平臺 PostCSS,它可以幫助你的 CSS 實現更多的功能,可以看看<<PostCSS 是個什麼鬼東西?>>.

舉例來說如何使用 PostCSS,我們使用 PostCSS 來為 CSS 程式碼自動新增適應不同瀏覽器,不同版本的 CSS 字首。首先安裝 postcss-loader 和 autoprefixer(自動新增字首的外掛),安裝到你的專案中.

npm install --save-dev postcss-loader autoprefixer

 

 編輯 webpack.config.js 檔案配置 modules 選項,新增 postcss-loader 處理樣式表配置,編輯後的檔案如下:

// webpack.config.js

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口檔案路徑
    output: {
        path: __dirname + "/public/", //存放打包後文件的地方路徑
        filename: "bundle.js" //打包後的檔名
    },
    devServer: {
        contentBase: "./public",
        port: "9000",
        inline: true,
        historyApiFallback: true,
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }, {
            test: /\.js$/,
            exclude: /node_modules/, //編譯打包時需要排除 node_modules 資料夾
            loader: "babel-loader"
        }, {
            test: /\.css$/,
            loader: 'style-loader!css-loader?modules!postcss-loader' //跟前面相比就在後面加上了 !postcss-loader
        }]
    }
}

在 webpack-demo 資料夾下新建 postcss.config.js 檔案,新增如下程式碼:

// postcss.config.js

module.exports = {
    plugins: [
        //呼叫autoprefixer外掛,還可以配置選項新增需要相容的瀏覽器版本.
        require("autoprefixer")({ browsers: ['ie>=8', '>1% in CN'] })
    ]
}

現在你寫的樣式會自動根據 Can i use 裡的資料新增不同字首了.在終端中執行 npm start 命令重新編譯打包,終端輸出資訊如下:

 

在瀏覽器中開啟 public 資料夾下的 index.html 檔案,如果看到和下圖一樣的,就說明已經成功配置了 PostCSS.

 

 

 

外掛(Plugins)

外掛(Plugins)是用來拓展 Webpack 功能的,它會在整個構建過程中生效,執行相關的任務。
Loaders 和 Plugins 常常被弄混,但是他們其實是完全不同的東西,可以這麼說,Loaders 是在打包構建過程中用來處理原始檔的(JSX,Scss,Less..),一次處理一個;外掛並不直接操作單個檔案,它直接對整個構建過程其作用。

Webpack 有很多內建外掛,同時也有很多第三方外掛,可以讓我們完成更加豐富的功能。

使用外掛的方法

要使用某個外掛,我們需要通過 npm 安裝它,然後要做的就是在 Webpack 配置中的 Plugins 關鍵字部分新增該外掛的一個例項.

編輯 webpack.config.js 檔案配置 Plugins 選項,新增一個實現版權宣告的 BannerPlugin 外掛,BannerPlugin 是內建外掛不需要使用 npm 安裝.編輯後的檔案如下:

// webpack.config.js

var webpack = require("webpack");
module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口檔案路徑
    output: {
        path: __dirname + "/public/", //存放打包後文件的地方路徑
        filename: "bundle.js" //打包後的檔名
    },
    devServer: {
        contentBase: "./public",
        port: "9000",
        inline: true,
        historyApiFallback: true,
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }, {
            test: /\.js$/,
            exclude: /node_modules/, //編譯打包時需要排除 node_modules 資料夾
            loader: "babel-loader"
        }, {
            test: /\.css$/,
            loader: 'style-loader!css-loader?modules!postcss-loader' //跟前面相比就在後面加上了 !postcss-loader
        }]
    },
    plugins: [
        new webpack.BannerPlugin("Copyright Flying Unicorns inc.")//在這個陣列中new一個例項就可以了
    ]
}

在終端中執行 npm start 命令重新編譯打包,終端輸出資訊如下:

在瀏覽器中開啟 public 資料夾下的 index.html 檔案,如果看到和下圖一樣的,就說明已經成功配置了 BannerPlugin 外掛.

 

常用外掛

給大家推薦幾個常用的外掛

HtmlWebpackPlugin

這個外掛的作用是依據一個簡單的模板,幫你生成最終的 html 檔案,這個檔案中自動引用了你打包後的 JS 檔案。每次編譯都在檔名中插入一個不同的雜湊值。

安裝 HtmlWebpackPlugin 到你的專案中
npm install --save-dev html-webpack-plugin

在使用 HtmlWebpackPlugin 之前,需要對 webpack-demo 專案結構做一些改變.

1. 移除 public 資料夾.

2. 在 app 目錄下,建立一個檔名為 index.tmpl.html 模板檔案,在編譯過程中,HtmlWebpackPlugin 外掛會依據此模板生成最終的 html 頁面,會自動新增所依賴的 css、 js、favicon等檔案.index.tmpl.html 模板檔案程式碼如下:

// index.tmpl.html

<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <title>webpack-demo</title>
</head>

<body>
    <div id="root"></div>
</body>

</html>

3. 在 webpack-demo 資料夾下新建一個 build 資料夾用來存放最終的輸出檔案.

4. 編輯 webpack.config.js 檔案配置 Plugins 選項,新增 HtmlWebpackPlugin 外掛,修改 output 選項.編輯後的檔案如下:

// webpack.config.js

var webpack = require("webpack");
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口檔案路徑
    output: {
        path: __dirname + "/build/", //存放打包後文件的地方路徑
        filename: "bundle.js" //打包後的檔名
    },
    devServer: {
        contentBase: "./public",
        port: "9000",
        inline: true,
        historyApiFallback: true,
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }, {
            test: /\.js$/,
            exclude: /node_modules/, //編譯打包時需要排除 node_modules 資料夾
            loader: "babel-loader"
        }, {
            test: /\.css$/,
            loader: 'style-loader!css-loader?modules!postcss-loader' //跟前面相比就在後面加上了 !postcss-loader
        }]
    },
    plugins: [
        new webpack.BannerPlugin("Copyright Flying Unicorns inc."), //在這個陣列中new一個例項就可以了
        new HtmlWebpackPlugin({
            template: __dirname + "/app/index.tmpl.html" //new一個外掛的例項,並傳入相關的引數
        })
    ]
}

此時專案結構如下圖所示:


                                                         

 

在終端中執行 npm start 命令重新編譯打包,終端輸出資訊如下:
 

此時專案結構已經發生改變,build 資料夾下存放了最終的輸出的檔案,專案結構如下圖所示:

                               

 

在瀏覽器中開啟 build 資料夾下的 index.html 檔案,如果看到和下圖一樣的,就說明已經成功配置了 HtmlWebpackPlugin 外掛.

Hot Module Replacement

Hot Module Replacement(HMR)也是 Webpack 裡很有用的一個外掛,它允許你在修改元件程式碼後,自動重新整理實時預覽修改後的效果。
在 Webpack 中使用 HMR 也很簡單,只需要做兩項配置.

  • 在 Webpack 配置檔案中新增 HMR 外掛
  • 在 Webpack Dev Server 中新增 hot 引數

不過配置完這些後,JS 模組其實還是不能自動熱載入的,還需要在你的 JS 模組中執行一個 Webpack 提供的 API 才能實現熱載入,雖然這個 API 不難使用,但是如果是 React 模組,使用我們已經熟悉的 Babel 可以更方便的實現功能熱載入。

整理下思路,具體實現方法如下

  • Babel 和 Webpack 是獨立的工具,二者可以一起工作,二者都可以通過外掛拓展功能.
  • HMR 是一個 Webpack 外掛,它讓你能瀏覽器中實時觀察模組修改後的效果,但是如果你想讓它工作,需要對模組進行額外的配額.
  • Babel 有一個叫做 react-transform-hrm 的外掛,可以在不同 React 模組進行額外的配置下讓 HMR 正常工作.

編輯 webpack.config.js 檔案配置 Plugins / devServer 選項,新增 Hot Module Replacement 外掛.編輯後的檔案如下:

// webpack.config.js

var webpack = require("webpack");
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    devtool: "source-map", //配置生成 Source Maps 的選項
    entry: __dirname + "/app/main.js", //入口檔案路徑
    output: {
        path: __dirname + "/build/", //存放打包後文件的地方路徑
        filename: "bundle.js" //打包後的檔名
    },
    devServer: {
        port: "9000",
        inline: true,
        historyApiFallback: true,
        hot: true
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }, {
            test: /\.js$/,
            exclude: /node_modules/, //編譯打包時需要排除 node_modules 資料夾
            loader: "babel-loader"
        }, {
            test: /\.css$/,
            loader: 'style-loader!css-loader?modules!postcss-loader' //跟前面相比就在後面加上了 !postcss-loader
        }]
    },
    plugins: [
        new webpack.BannerPlugin("Copyright Flying Unicorns inc."), //在這個陣列中new一個例項就可以了
        new HtmlWebpackPlugin({
            template: __dirname + "/app/index.tmpl.html" //new一個外掛的例項,並傳入相關的引數
        }),
        new webpack.HotModuleReplacementPlugin() //熱載入外掛
    ]
}

安裝 react-transform-hmr 外掛

npm install --save-dev babel-plugin-react-transform react-transform-hmr

編輯在 webpack-demo 資料夾下的 .babelrc 檔案,編輯後的檔案如下:

// .babelrc

{
  "presets": ["react", "es2015"],
  "env": {
    "development": {
    "plugins": [["react-transform", {
       "transforms": [{
         "transform": "react-transform-hmr",
         "imports": ["react"],
         "locals": ["module"]
       }]
     }]]
    }
  }
}

編輯 webpack-demo 資料夾下的 package.json 檔案 scripts 選項,新增 --hot 選項開啟程式碼熱替換.

// package.json

{
    "name": "webpack-demo",
    "version": "1.0.0",
    "description": "",
    "scripts": {
        "start": "webpack",
        "dev": "webpack-dev-server --devtool eval --progress --content-base build --hot"
    },
    "author": "",
    "license": "ISC"
}

在終端中執行 npm run dev 命令,輸出資訊如下圖,一樣可以啟動自動熱載入.

 

在瀏覽器中開啟 http://localhost:9000/ 就可以看到像之前一樣的問候語頁面.

現在當你使用 React 時,就可以熱載入模組了.按 Ctrl + C 即可退出自動熱載入.

產品階段的構建

我們已經使用 Webpack 構建了一個完整的開發環境.但是在產品階段,可能還需要對打包的檔案進行額外的處理,比如說優化、壓縮、快取以及分離 CSS 和 JS.

對於複雜的專案來說,需要複雜的配置,這時候分解配置檔案為多個小的檔案可以使得事情井井有條,以 webpack-demo 專案來說,我們在 webpack-demo 資料夾下建立一個名為 webpack.production.config.js 的檔案,在裡面加上基本的配置程式碼,如下:

// webpack.production.config.js

var webpack = require("webpack");
var HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: __dirname + "/app/main.js", //入口檔案路徑
    output: {
        path: __dirname + "/build/", //存放打包後文件的地方路徑
        filename: "bundle.js" //打包後的檔名
    },
    module: {
        loaders: [{
            test: /\.json$/,
            loader: "json-loader"
        }, {
            test: /\.js$/,
            exclude: /node_modules/, //編譯打包時需要排除 node_modules 資料夾
            loader: "babel-loader"
        }, {
            test: /\.css$/,
            loader: 'style-loader!css-loader?modules!postcss-loader' //跟前面相比就在後面加上了 !postcss-loader
        }]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: __dirname + "/app/index.tmpl.html" //new一個外掛的例項,並傳入相關的引數
        })
    ]
}

 

編輯 webpack-demo 資料夾下的 package.json 檔案 scripts 選項,新增 build 選項,編輯後的檔案如下:
// package.json

{
    "name": "webpack-demo",
    "version": "1.0.0",
    "description": "",
    "scripts": {
        "start": "webpack",
        "dev": "webpack-dev-server --devtool eval --progress --content-base build --hot",
        "build": "webpack --config ./webpack.production.config.js --progress"
    },
    "author": "",
    "license": "ISC"
}

在終端中執行 npm run build 命令,輸出資訊如下圖:

說明分解配置檔案為多個小的檔案成功了.

優化外掛

Webpack 提供了一些在釋出階段非常有用的優化外掛,它們大多來自於 Webpack 社群,可以通過 npm 安裝,通過以下外掛可以完成產品釋出階段所需的功能.

  • OccurrenceOrderPlugin: 為元件分配 ID,通過這個外掛 Webpack 可以分析和優先考慮使用最多的模組,併為它們分配最小的 ID.
  • UglifyJsPlugin:壓縮JS程式碼.
  • ExtractTextPlugin: