透視前端工程化之二:Webpack 基本介紹

1 Webpack 的特點

圖片來源於網絡
圖片來源於網絡

Webpack 是一款強大的打包工具。在 Webpack 中一切皆模塊。Webpack 官網的 Banner 圖完美地詮釋了這一理念。Webpack 從一個入口文件開始遞歸地分析模塊的依賴關係,根據依賴關係將這些模塊打包成一個或多個文件。

目前幾乎所有的前端構建和開發都是採用 Webpack 。因爲 Webpack 有強大的社區生態,每月 Webpack 的下載量超過百萬。通過 loader、plugin 支持 Webpack 與主流的前端框架和語言進行集成,比如 Vue、React、TypeScript。

  • 支持所有的模塊化 可以對 ES6 模塊、commonjs 模塊、AMD 模塊等所有標準的模塊進行打包。
  • code splitting 可以將代碼打成多個 chunk,按需加載,意味着我們的站點無需等待整個 js 資源下載完成之後才能交互,可以大大提升速度。
  • 強大靈活的插件系統 Webpack 提供了很多內置的插件,包括其自身也是架構在插件系統上可以滿足所有的打包需求。
  • loader 藉助 loader 預處理非 js 資源,Webpack 可以打包所有的靜態資源。

2 Webpack 構建流程

Webpack 的構建流程是一種事件流機制。整個構建流程可以看成是一個流水線,每個環節負責單一的任務,處理完將進入下一個環節。Webpack 會在每個環節上發佈事件,供內置的和自定義的插件有機會干預 Webpack 的構建過程,控制 Webpack 的構建結果。Webpack 的基本的構建流程圖如下:

graph LR
A(初始化)-->B(開始編譯)
B(開始編譯)-->C(編譯模塊)
C(編譯模塊)-->D(編譯完成)
D(資源輸出)-->E(完成)

  • 初始化 讀取 webpack 配置文件和 shell 腳本中的參數,將參數合併後初始化 Webpack ,生成 Compiler 對象。
  • 開始編譯 執行 Compiler 的 run 方法開始執行編譯。
  • 編譯完成 從入口文件開始,調用配置中的 loader 對模塊進行編譯,並梳理出模塊間的依賴關係,直至所有的模塊編譯完成。
  • 資源輸出 根據入口與模塊間的依賴關係,將上一步編譯完成的內容組裝成一個個的 chunk (代碼塊),然後把 chunk 加入到等待輸出的資源列表中。
  • 完成 確定好輸出資源後,根據指定的輸出路徑和文件名配置,將資源寫入到磁盤的文件系統中,完成整個構建過程。

3 核心概念

入口

入口是 Webpack 進行構建的起點,Webpack 在構建過程中從入口文件開始,遞歸地編譯模塊,並分析模塊間的依賴關係,最終得出依賴圖。Webpack 依據該依賴圖對模塊進行組裝,輸出到最終的 bundle 文件中。

我們可以在Webpack 的配置文件中配置 entry 屬性,來指定入口文件,入口文件可以是一個也可以指定多個。

我們來看一個例子:

// Webpack .config.js
module.exports = {
  entry: './src/app.js'
};

配置多個入口的場景常見於多頁應用中。如果配置多個入口可以這樣:

// Webpack .config.js
module.exports = {
  entry: {
    pageOne: './src/pageOne/app.js',
    pageTwo: './src/pageTwo/app.js'
  }
};
輸出

配置 output 選項可以指示 Webpack 如何去輸出、在哪裏輸出我們的靜態資源文件。

我們通過一個例子來看一下 output 如何使用:

// Webpack .config.js
module.exports = {
  output: {
    filename: 'bundle.js',
    path: './dist'
  }
};

上例中,我們指示 Webpack 最終的輸出文件名爲 bundle.js ,輸出的目錄爲 ./dist

loader

loader 的使用

Webpack 本身是不能處理非 js 資源的,但我們卻可以在 Webpack 中引入 css、圖片、字體等非 js 文件。例如:

// app.js
import Styles from './styles.css';

那麼 Webpack 是如何實現的呢?

Webpack 中使用 loader 對非 js 文件進行轉換。loader 可以在我們 import 或者加載模塊時,對文件進行預處理,將非 js 的文件內容,最終轉換成 js 代碼。

loader 有三種使用方式:

  • 配置 在 Webpack .config.js 文件中指定
  • 內聯 在每個 import 語句中線上指定
  • CLI 在 shell 命令中指定。

在實際的應用中,絕大數都是採用配置的方式來使用,一方面在配置文件中,可以非常直觀地看到某種類型的文件使用了什麼 loader,另一方面,在項目複雜的情況下,便於進行維護。

我們通過一個簡單的例子來看一下 loader 的使用:

// Webpack .config.js
module.exports = {
 module: {
    rules: [
      { test: /\.css$/, use: 'css-loader' }
    ]
  }
};

我們需要告訴 Webpack 當遇到 css 文件的時候,使用 css-loader 進行預處理。這裏由於 css-loader 是單獨的 npm 模塊,使用前我們需要先進行安裝:

npm install --save-dev css-loader

常用的 loader

Webpack 可以處理任何非 js 語言,得益於社區提供的豐富的 loader,日常開發中所使用到的 loader,都可以在社區找到。這裏對一些常用的 loader 進行簡要的說明。

  • babel-loader 將 ES2015+ 代碼轉譯爲 ES5。
  • ts-loader 將 TypeScript 代碼轉譯爲 ES5。
  • css-loader 解析 @importurl(),並對引用的依賴進行解析。
  • style-loader 在 HTML 中注入 <style> 標籤將 css 添加到 DOM 中。通常與 css-loader 結合使用。
  • sass-loader 加載 sass/scss 文件並編譯成 css。
  • postcss-loader 使用 PostCSS 加載和轉譯 CSS文件。
  • html-loader 將 HTML 導出爲字符串。
  • vue-loader 加載和轉譯 Vue 組件。
  • url-loaderfile-loader 一樣,但如果文件小於配置的限制值,可以返回 data URL
  • file-loader 將文件提取到輸出目錄,並返回相對路徑。
plugin

插件的使用

插件是 Webpack 的非常重要的功能,Webpack 本身也是建立在插件系統之上的。插件機制極大增強了 Webpack 的功能,爲 Webpack 增加了足夠的靈活性。通過插件,我們可以在 Webpack 的構建過程中,引入自己的操作,干預構建結果。

我們通過一個示例來看一下插件的使用:

// Webpack .config.js
const HtmlWebpack Plugin = require('html-Webpack -plugin'); 
const Webpack  = require('Webpack '); 

const config = {
  plugins: [
    new Webpack .optimize.UglifyJsPlugin(),
    new HtmlWebpack Plugin({template: './src/index.html'})
  ]
};

module.exports = config;

示例中,我們用到了兩個插件,一個是內置的 UglifyJsPlugin 插件,該插件對 js 進行壓縮,減小文件的體積。一個是外部插件 HtmlWebpack Plugin,用來自動生成入口文件,並將最新的資源注入到 HTML 中。

常用的插件

  • HtmlWebpack Plugin 自動生成入口文件,並將最新的資源注入到 HTML 中。
  • CommonsChunkPlugin 用以創建獨立文件,常用來提取多個模塊中的公共模塊。
  • DefinePlugin 用以定義在編譯時使用的全局常量。
  • DllPlugin 拆分 bundle 減少不必要的構建。
  • ExtractTextWebpack Plugin 將文本從 bundle 中提取到單獨的文件中。常見的場景是從 bundle 中將 css 提取到獨立的 css 文件中。
  • HotModuleReplacementPlugin 在運行過程中替換、添加或刪除模塊,而無需重新加載整個頁面。
  • UglifyjsWebpack Plugin 對 js 進行壓縮,減小文件的體積。
  • CopyWebpack Plugin 將單個文件或整個目錄複製到構建目錄。一個常用的場景是將項目中的靜態圖片不經構建直接複製到構建後的目錄。

4 如何使用 Webpack

下面我們通過一個簡單的例子來看一下 Webpack 的使用。這裏假定你已經安裝了最新版本的 nodejs 和 npm,因爲使用舊版本可能會遇到各種問題。

4.1 安裝

創建 Webpack -demo 目錄,初始化 npm,並且在 Webpack -demo 目錄中安裝 Webpack 和 Webpack -cli:

mkdir Webpack -demo && cd Webpack -demo
npm init -y
npm install Webpack  Webpack -cli --save-dev

Webpack -cli 用來在命令行中運行 Webpack 。這裏建議本地安裝 Webpack 和 Webpack -cli,因爲全局安裝的話,Webpack 的升級會影響到所有的項目。

接下來我們先在項目中新增一些目錄和文件:

Webpack -demo
├── package.json
├── dist
├── index.html
└── src
    └── index.js

index.html 內容如下:

<!doctype html>
<html>
  <head>
    <title>Webpack -demo</title>
  </head>
  <body>
    <script src="./src/index.js"></script>
  </body>
</html>

src/index.js 內容如下:

function createEl() {
  var element = document.createElement('div')
  element.innerHTML = 'hello world'

  return element;
}

document.body.appendChild(createEl());

4.2 第一次構建

在命令行運行:

./node_modules/.bin/Webpack 

Hash: 2353b0d3d427eaa8a18a
Version: Webpack  4.29.6
Time: 175ms
Built at: 2019-04-03 22:08:36
  Asset   Size  Chunks             Chunk Names
main.js  1 KiB       0  [emitted]  main
Entrypoint main = main.js
[0] ./src/index.js 175 bytes {0} [built]

大家可以發現,我們並沒有在配置文件中指定打包的入口和輸出的出口,也沒有在命令行中指定配置參數,但可以看到在 ./dist 目錄下新增了一個 main.js。這是因爲 Webpack 配置中 entry 的默認值爲 ./src,出口的默認目錄是 ./dist。

Webpack -demo
├── package.json
├── dist
|    └── main.js
├── index.html
└── src
     └── index.js

構建後的項目目錄中新增了 main.js。

<!doctype html>
<html>
  <head>
    <title>Webpack -demo</title>
  </head>
  <body>
    <script src="./dist/main.js"></script>
  </body>
</html>

我們現在將 index.html 中的腳本引用修改爲構建後的文件 ./dist/main.js,在瀏覽器預覽,如果一切正常應該可以看到頁面上會輸出文本 hello world

4.3 使用配置文件

對於簡單的構建,Webpack 基本可以做到零配置。但對於複雜的單頁應用而言,則需要使用 Webpack 的配置文件來提供個性化的功能。

首先我們在項目根目錄下新增 Webpack .config.js 文件:

// Webpack .config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};

在配置文件中,通過 entry 指定了入口文件爲 ./src/index.js,通過 output 指定了輸出的目錄爲 ./dist,輸出的文件名爲 bundle.js。目錄結構更新如下:

Webpack -demo
├── package.json
├── Webpack .config.js
├── index.html
├── dist
|    └── bundle.js
└── src
     └── index.js

同時爲了調用簡單,我們在 package.json 文件中設置快捷命令來調用 ./node_modules/.bin/Webpack

// package.json
{
  "scripts": {
    "build": "Webpack "
  }
}


再次執行構建命令:

npm run build
> Webpack [email protected] build C:\work\tech\Webpack -demo
> Webpack 

Hash: d0fa6b1e011af414e622
Version: Webpack  4.29.6
Time: 157ms
Built at: 2019-04-03 22:42:50
 Asset   Size  Chunks             Chunk Names
bundle.js  1 KiB       0  [emitted]  main
Entrypoint main = bundle.js
[0] ./src/index.js 175 bytes {0} [built]

index.html 中的 script 引用鏈接修改爲 ./dist/bundle.js,在瀏覽器中預覽頁面,不出意外的話會輸出文本 hello world

4.4 使用插件

我們發現在構建的過程中,如果構建後的資源名稱發生了變化,index.html 中對資源的引用會被動地跟着修改,非常不方便,我們引入 HtmlWebpack Plugin 來幫助我們自動生成入口文件,自動將生成的資源文件注入 index.html 中。

安裝:

npm install --save-dev html-Webpack -plugin

配置:

const path = require("path");
const HtmlWebpack Plugin = require("html-Webpack -plugin");
module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist")
  },
  plugins: [new HtmlWebpack Plugin()]
};


在配置文件中,我們引入插件,並在 plugins 選項中,將插件實例化後添加到數組中。該插件會自動生成 index.html,因此原目錄中的 index.html 文件可以刪除。

Webpack -demo
├── package.json
├── Webpack .config.js
├── dist
|    └── bundle.js
└── src
     └── index.js

再次執行構建命令:

$ npm run build

> Webpack [email protected] build C:\work\tech\Webpack -demo
> Webpack 

Hash: 39dc7567ef99a69140e7
Version: Webpack  4.29.6
Time: 1241ms
Built at: 2019-04-03 22:53:44
     Asset       Size  Chunks             Chunk Names
 bundle.js      1 KiB       0  [emitted]  main
index.html  182 bytes          [emitted]
Entrypoint main = bundle.js
[0] ./src/index.js 175 bytes {0} [built]


命令執行後我們發現我們的 ./dist 下多了一個 index.html 文件,並且 index.html 中的資源引用被自動更新爲了 <script type="text/javascript" src="bundle.js"></script>

4.5 使用 loader 處理 css 文件

爲了使 Webpack 可以處理 import 進來的 css 文件,我們需要安裝並配置 style-loadercss-loader

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

修改 Webpack 的配置如下:

const path = require("path");
const HtmlWebpack Plugin = require("html-Webpack -plugin");
module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist")
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"]
      }
    ]
  },
  plugins: [new HtmlWebpack Plugin()]
};


如此一來,當 Webpack 匹配到後綴爲 .css 的文件都會使用 css-loader 和 style-loader 進行處理。

接下來我們在 ./src 目錄下新增一個樣式文件 main.css。在樣式中,設置文本的字體顏色爲紅色。

// main.css
div{color: red}

緊接着我們在 ./src/index.js 中引用 main.css:

import "./main.css";

function createEl() {
  var element = document.createElement("div");
  element.innerHTML = "hello world";

  return element;
}

document.body.appendChild(createEl());

執行構建命令:

$ npm run build

> Webpack [email protected] build C:\work\tech\Webpack -demo
> Webpack 

Hash: f9fcb8cfd689f4b96ce6
Version: Webpack  4.29.6
Time: 2672ms
Built at: 2019-04-03 23:24:40
     Asset       Size  Chunks             Chunk Names
 bundle.js   6.85 KiB       0  [emitted]  main
index.html  182 bytes          [emitted]
Entrypoint main = bundle.js
[0] ./src/index.js 199 bytes {0} [built]
[1] ./src/main.css 1.05 KiB {0} [built]
[2] ./node_modules/css-loader/dist/cjs.js!./src/main.css 170 bytes {0} [built]
    + 3 hidden modules

在瀏覽器預覽,不出意外字體的顏色已經變成了紅色,打開瀏覽器調試工具,可以看到在 <head> 標籤裏插入了一個 <style> 標籤。

<style type="text/css">
  div {
    color: red;
  }
</style>

通過以上完整的示例,我們演示了 Webpack 的核心的幾個配置的使用方式,我們對 Webpack 的使用應該有了一個基本的認識。

Webpack 中還有很多其他有用的配置項,篇幅原因不做詳細的介紹。大家可以查閱 官方文檔 自行配置和練習。

總結

本節我們對 Webpack 進行了總體的介紹。藉助 loader、Webpack 可以處理一切資源,JS 的、非 JS 的,都可以。通過插件,我們可以在 Webpack 的構建過程中的每個事件節點加入自己的行爲,來影響 Webpack 的構建。對 Webpack 的使用有了認識,可以爲我們搭建起項目的基本框架提供一個基礎。

筆者有一個前端工程化的實踐型課程剛剛上線——《透視前端工程化》

點擊瞭解《透視前端工程化》

以 Vue 爲例,結合筆者在團隊中的工程化實踐,帶領大家從零開始搭建一個腳手架,將搭建腳手架用到的技術點逐一拆解,希望大家看完後,每個人都對腳手架和工程化思想有個較深入地理解。

相信在學完本課程後,大家至少有以下幾點收穫:

  • 對前端工程化有一個系統認知;
  • 能獨立設計一套前端工程化解決方案;
  • 知識廣度上有大幅提升;
  • 進入更好的平臺,獲得更好的薪酬。

感興趣的話,還望大家多多支持!


作者簡介:

王超,現任快狗打車(原58速運)前端負責人。

先後任職於人人網、奇虎360,8 年知名互聯網工作經驗。

從 0 到 1 組建了快狗前端團隊,負責團隊技術體系的搭建,形成了以 Webpack 和 Vue 爲基礎、 Node.js 中間層爲補充的,自動化、工程化、組件化的快狗前端技術體系。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章