webpack 從入門到放棄(一)

sunshine小小倩發表於2019-03-03

什麼是 webpack,為什麼要使用 webpack

什麼是 webpack

官網給出的概念是:

本質上,webpack 是一個現代 JavaScript 應用程式的靜態模組打包器(module bundler)。當 webpack 處理應用程式時,它會遞迴地構建一個依賴關係圖(dependency graph),其中包含應用程式需要的每個模組,然後將所有這些模組打包成一個或多個 bundle。
根據官網最直觀最出名的那個圖我們可以知道,webpack 可以打包/指令碼/圖片/樣式/表
從圖中我們可以看出左邊有依賴關係的模組(MODULES WITH DEPENDENCIES)通過 webpack 打包成了各種靜態資源(STATIC ASSETS)

為什麼要使用 webpack

通過上面的概念,你是不是已經大概知道了 webpack 是幹什麼的,那麼問題來了,為什麼要使用 webpack 呢?
這就說來話長了,那就長話短說,emmmm

為什麼使用 webpack,這應該是和前端的發展有關係的,我認為,webpack 是前端發展到一定階段的必然產物(貌似是一句廢話)。
因為計算機網路的飛速發展,導致 web 前端也在迅猛發展。最初的實踐方案已經不能滿足我們的需求,於是,越來越多的新技術新思想新框架孕育而生,比如:

前端模組化

隨著前端專案的複雜度越來越高,相互之前的依賴越來越多,以及為了更好的複用程式碼,前端也需要用模組化的思想來組織程式碼。

首先我們要明白模組化解決了前端的哪些痛點:

  • 命名衝突
  • 檔案依賴(js 載入順序)
  • 程式碼複用

我們這裡說的模組和 Java 的 package 的概念是類似的。邏輯上相關的程式碼放在一個包中,每一個包都是相互獨立的,不用擔心命名衝突的問題,如果其他人想要用這部分功能,直接 import 匯入包就好

所以前端程式碼模組化的實現,會幫我們解決命名衝突和程式碼複用的問題,那麼檔案依賴要怎麼處理呢?這就用到了我們的 webpack,稍後再做介紹。

所以有了模組,我們就可以方便的複用他人的程式碼,那麼問題來了,無規矩不成方圓,我們在使用他人程式碼的時候肯定是要遵循某種規範,所以就出現了 CommonJS、AMD 和 終極模組化方案 —— ES6 模組,這些都是前端模組化的規範。

我們來簡單瞭解一下:

CommonJS

node.js 採用的就是 CommonJS 規範,使用 require 的方法同步載入依賴,一個檔案就是一個模組,匯入匯出格式如下:

// 匯入
const moduleA = require(`./moduleA`);

// 匯出
module.exports = moduleA.someFunc;
複製程式碼

缺點是載入的模組是同步的,只有載入完才能執行後面的操作。因為 node.js 的模組檔案一般存在於本地硬碟,所以一般不會出現這個問題,但是在瀏覽器環境該規範就不那麼適用了。

AMD

原因如上,因為 CommonJS 不適用於瀏覽器環境,所以出現了 AMD 規範。該規範非同步載入依賴,可以再宣告的時候指定需要載入的依賴,並且需要當做引數傳入,對於依賴的模組提前執行,依賴前置。

寫法如下:

define("module", ["dep1", "dep2"], function(d1, d2) {
  return someExportedValue;
});
require(["module", "../file"], function(module, file) { /* ... */ });
複製程式碼

ES6 模組化

ES6 直接在語言層面上實現了模組化。我們現在用的最多的就是 ES6 模組化的實踐方式。

寫法如下:

// 匯入
import { readFile } from `fs`;
import React from `react`;
// 匯出
export function hello() {};
export default {
  // ...
};
複製程式碼

樣式模組化

現在越來越多人也開始使用模組化的思想寫樣式。比如現在大部分的 CSS 預編譯器都支援 @import 的寫法。將一些公用樣式放在一個檔案中,在其他檔案中匯入。


三大框架的出現,使我們不需要像傳統的 JQ 一樣操作 DOM,將精力集中在對資料的處理上。

以及 ES6/7/8 和 TS 的使用越來普及,無疑使我們的開發效率提高了很多。

但是出現的問題是:這些新興的技術並不是在所有的瀏覽器上都適用,都需要將原始碼轉化為可以直接在瀏覽器上執行的程式碼

所以,webpack 就解決了這個問題。

Gulp/Grunt、Rollup 和 webpack 的比較

Gulp/Grunt

其實,Gulp/Gunt 和 webpack 應該是沒有可比性的,但是他們都可以稱為前端自動化構建工具(讓我們不再做機械重複的事情,解放我們的雙手)

但是 Gulp/Gunt 和 webpack 確實乾的不是一件事

Gulp 本質是 task runner,Webpack 是 module bundler

我認為 Gulp 正如他的定義一樣:基於流的自動化構建工具,定義每一個任務,然後自動將一個個任務執行。

而 webpack 是模組化地組織,模組化地依賴,然後模組化地打包。相對來上,場景侷限在前端模組化打包上。

推薦知乎 寸志 大佬的回答:gulp 有哪些功能是 webpack 不能替代的?

Rollup

Rollup 是一個和 Webpack 很類似但專注於 ES6 的模組打包工具。 Rollup 的亮點在於能針對 ES6 原始碼進行 Tree Shaking 以去除那些已被定義但沒被使用的程式碼,以及 Scope Hoisting 以減小輸出檔案大小提升執行效能。 然而 Rollup 的這些亮點隨後就被 Webpack 模仿和實現。 由於 Rollup 的使用和 Webpack 差不多,這裡就不詳細介紹如何使用了,而是詳細說明它們的差別:

Rollup 是在 Webpack 流行後出現的替代品;
Rollup 生態鏈還不完善,體驗不如 Webpack;
Rollup 功能不如 Webpack 完善,但其配置和使用更加簡單;
Rollup 不支援 Code Spliting,但好處是打包出來的程式碼中沒有 Webpack 那段模組的載入、執行和快取的程式碼。
Rollup 在用於打包 JavaScript 庫時比 Webpack 更加有優勢,因為其打包出來的程式碼更小更快。 但功能不夠完善,很多場景都找不到現成的解決方案。

安裝與使用

建立 package.json 檔案

可以手動的建立,也可以使用命令自動建立

npm init
複製程式碼

安裝

webpack 可以直接使用 npm 安裝,可以安裝到全域性,也可以安裝到專案

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

使用

推薦大家閱讀這篇文章:
入門 Webpack,看這篇就夠了

我就是跟著這篇文章做的

新建檔案

首先新建一個資料夾,然後在終端開啟該資料夾並執行來初始化你的專案,在初始過程中,會有一些問題幫助你建立 package.json 檔案。

npm init
複製程式碼

初始化之後我們還要建立幾個檔案來存放我們的專案檔案。

建立一個 app 資料夾來存放我們打包之前的原始檔

建立一個 public 資料夾來存放一個入口檔案 index.html 和通過 webpack 打包之後瀏覽器可直接執行的 js 檔案

比如我們在 app 資料夾下建立一個 Greeter.js 檔案,裡面有一個方法可以再頁面顯示文字資訊 Hi there and greetings!

module.exports = function() {
  var greet = document.createElement(`div`);
  greet.textContent = "Hi there and greetings!";
  return greet;
};
複製程式碼

然後我們建立一個 main.js 來引入 Greeter.js 這個檔案,

瀏覽器的入口 index.html 檔案內容如下(其中 bundle.js 是打包之後的檔案):

<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Webpack Sample Project</title>
  </head>
  <body>
    <div id=`root`>
    </div>
    <script src="bundle.js"></script>
  </body>
</html>
複製程式碼

此時的檔案路徑如下

.
├── app
│   ├── Greeter.js
│   └── main.js
├── package.json
├── public
│   ├── bundle.js
│   └── index.html
複製程式碼

安裝 webpack

初始化專案之後,要安裝 webpack

npm install --save-dev webpack
複製程式碼

在安裝完 webpack 之後,可以再 package.json 檔案中看到增加了 webpack 的依賴。

配置 webpack

安裝完 webpack 之後,我們就要配置 webpack 了,首先建立配置檔案 webpack.config.js 檔案內容如下:

module.exports = {
  entry:  __dirname + "/app/main.js",//已多次提及的唯一入口檔案
  output: {
    path: __dirname + "/public",//打包後的檔案存放的地方
    filename: "bundle.js"//打包後輸出檔案的檔名
  }
}
複製程式碼

然後我們在該專案的終端輸入

webpack
複製程式碼

就可以看到如下資訊:

Hash: 4e6a6b5eb88a83b29e02
Version: webpack 4.12.0
Time: 551ms
Built at: 2018-06-24 14:53:39
    Asset      Size  Chunks             Chunk Names
bundle.js  6.82 KiB       0  [emitted]  main
[3] ./node_modules/css-loader!./app/main.css 190 bytes {0} [built]
[4] ./app/main.css 1.04 KiB {0} [built]
[5] ./app/Greeter.js 143 bytes {0} [built]
[6] ./app/main.js 119 bytes {0} [built]
    + 3 hidden modules

WARNING in configuration
The `mode` option has not been set, webpack will fallback to `production` for this value. Set `mode` option to `development` or `production` to enable defaults for each environment.
You can also set it to `none` to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/
複製程式碼

看到這樣的資訊,那麼恭喜你,你的第一個 webpack 專案就完成了!

開啟 public 資料夾下面的 index.html,你就可以再瀏覽器上看到如下的效果。

Loader

loader 用於對模組的原始碼進行轉換。loader 可以使你在 import 或”載入”模組時預處理檔案。因此,loader 類似於其他構建工具中“任務(task)”,並提供了處理前端構建步驟的強大方法。loader 可以將檔案從不同的語言(如 TypeScript)轉換為 JavaScript,或將內聯影像轉換為 data URL。loader 甚至允許你直接在 JavaScript 模組中 import CSS檔案!

因為 webpack 本身只能處理 JavaScript,如果要處理其他型別的檔案,就需要使用 loader 進行轉換,loader 本身就是一個函式,接受原始檔為引數,返回轉換的結果。

舉個 ? —— css-loader

例如,我們想在剛剛的頁面增加樣式,使文字居中顯示,那麼我在 app 資料夾下面新建一個 main.css 檔案。在 webpack 中,所有的檔案都是模組,所以要使用這個 css 檔案,就必須要先引入。

引入 css 檔案

所以我就在 app 資料夾下面的 main.js 中引入該 css 檔案

const greeter = require(`./Greeter.js`);
require (`./main.css`)
document.querySelector("#root").appendChild(greeter());
複製程式碼

重新打包

然後我重新打包一遍,藍後,發現居然報錯了!

Hash: 179c18498fac6de89a96
Version: webpack 4.12.0
Time: 533ms
Built at: 2018-06-24 15:00:24
 1 asset
[0] ./app/Greeter.js 143 bytes {0} [built]
[1] ./app/main.js 119 bytes {0} [built]

WARNING in configuration
The `mode` option has not been set, webpack will fallback to `production` for this value. Set `mode` option to `development` or `production` to enable defaults for each environment.
You can also set it to `none` to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

ERROR in ./app/main.js
Module not found: Error: Can`t resolve `style-loader` in `/Users/cherry/Workspace/webpack-demo`
 @ ./app/main.js 2:0-22
複製程式碼

根據報錯資訊,我們很明顯能發現是提示我們專案缺少 style-loader,這是因為 webpack 原生只支援解析 js 檔案,要支援非 js 型別的檔案,就需要使用 loader

安裝 loader

所以我們要安裝 style-loadercss-loader

npm i -D style-loader css-loader
複製程式碼

修改配置檔案

然後修改 webpack 的配置檔案 webpack.config.js

module.exports = {
  entry:  __dirname + "/app/main.js",//已多次提及的唯一入口檔案
  output: {
    path: __dirname + "/public",//打包後的檔案存放的地方
    filename: "bundle.js"//打包後輸出檔案的檔名
  },
  module: {
    rules: [
      {
        test: /.css$/,
        use: [`style-loader`, `css-loader`]
      }
    ]
  }
}
複製程式碼

配置檔案增加了 module.rules 陣列,該陣列是一些配置規則,告訴 webpack 符合 test 的檔案需要使用 use 後面的 loader 處理。所以該規則就是對所有 .css 結尾的檔案使用 style-loadercss-loader 進行處理。

loader 特性

我們來看一下 loader 有哪些特性:

  • loader 支援鏈式傳遞。能夠對資源使用流水線(pipeline)。一組鏈式的 loader 將按照相反的順序執行。loader 鏈中的第一個 loader 返回值給下一個 loader。在最後一個 loader,返回 webpack 所預期的 JavaScript。
  • loader 可以是同步的,也可以是非同步的。
  • loader 執行在 Node.js 中,並且能夠執行任何可能的操作。
  • loader 接收查詢引數。用於對 loader 傳遞配置。
  • loader 也能夠使用 options 物件進行配置。
  • 除了使用 package.json 常見的 main 屬性,還可以將普通的 npm 模組匯出為 loader,做法是在 package.json 裡定義一個 loader 欄位。
  • 外掛(plugin)可以為 loader 帶來更多特性。
  • loader 能夠產生額外的任意檔案。

使用 webpack 的三種姿勢

webpack 中使用 loader 有三種姿勢

通過 CLI

命令列中執行

webpack --module-bind jade  --module-bind `css=style!css`   
複製程式碼

jade,style,css後面可省略-loader,他們分別對.jade使用jade-loader,對.css使用style-loader和css-loader

通過require

可以直接在原始碼中指定使用什麼 loader 去處理檔案。

require(`style-loader!css-loader?minimize!./main.css`)
複製程式碼

這樣就是對 ./main.css 檔案先使用 css-loader 再使用 style-loader 進行轉換

使用配置檔案 webpack.config.js

最常用的方式就是使用本文所使用的配置檔案的方式

常見的 loader

常見的 loader

loader name loader des
babel-loader 載入 ES2015+ 程式碼,然後使用 Babel 轉譯為 ES5
buble-loader 使用 Bublé 載入 ES2015+ 程式碼,並且將程式碼轉譯為 ES5
cache-loader 在一些效能開銷較大的 loader 之前新增此 loader,以將結果快取到磁碟裡。
coffee-loader CoffeeScript 轉化為 JS
css-loader css-loader 是將 @importurl() 引入的 css 轉換為 import/require() 的方式然後在解析他們
exports-loader 通過新增 exports[...] = ... 語句匯出檔案中的變數。
expose-loader expose-loader 將模組新增到全域性物件上
file-loader file-loader 可以解析專案中的url引入(不僅限於css),根據我們的配置,

將圖片拷貝到相應的路徑,再根據我們的配置,修改打包後檔案引用路徑,使之指向正確的檔案。
gzip-loader | 可以載入 gzip 壓縮之後的資源
html-loader | 將 html 輸出為字串,也可以根據配置進行壓縮
imports-loader | imports-loader 允許使用依賴於特定全域性變數的模組,這對於依賴於像 $ 這樣的全域性變數的第三方模組非常有用
jshint-loader | 為載入的模組使用 jshint
json-loader | 由於 webpack >= v2.0.0 預設支援匯入 JSON 檔案。如果你使用自定義副檔名,你可能仍然需要使用此 loader
json5-loader | 將 json5 檔案解析成 js 物件
less-loader | 將 less 轉化為 css
null-loader | 返回一個空模組
postcss-loader | 將 postcss 轉化為 css
raw-loader | 載入檔案原始內容(utf-8格式)
sass-loader | 將 SASS/SCSS 轉換為 css
source-map-loader | 從現有原始檔(原始碼源URL)中提取源對映,方便除錯
style-loader | 將 CSS 放在 <style> 標籤中注入到 DOM 中
script-loader | 在全域性上下文中執行一次 JavaScript 檔案(如在 script 標籤),不需要解析
svg-inline-loader | 將 SVG 作為模組嵌入
url-loader | 將檔案載入為 Base64 編碼的 URL

row 2 col 1 | row 2 col 2

Plugin

外掛(plugin)是 webpack 的支柱功能。webpack 自身也是構建於,你在 webpack 配置中用到的相同的外掛系統之上!
外掛目的在於解決 loader 無法實現的其他事

Plugin 是用來擴充套件 Webpack 功能的,通過在構建流程裡注入鉤子實現,它給 Webpack 帶來了很大的靈活性。

通過plugin(外掛)webpack可以實 loader 所不能完成的複雜功能,使用 plugin 豐富的自定義 API 以及生命週期事件,可以控制 webpack 打包流程的每個環節,實現對 webpack 的自定義功能擴充套件。

舉個 ? —— ExtractTextPlugin

webpack4 已經不支援用extract-text-webpack-plugin來優化 css, 需要改成optimize-css-assets-webpack-plugin和mini-css-extract-plugin

在剛剛的例子中,我們檢視打包之後的 index.html 檔案可以看到我們剛剛寫的 css 程式碼放在了 head 中的 style 標籤中,這是 style-loader 幫我們處理的

但是,如果你希望打包之後 css 在單獨的檔案中,那麼你就需要 ExtractTextPlugin 這個 plugin 了。

安裝 ExtractTextPlugin

npm i -D ExtractTextPlugin
複製程式碼

修改配置檔案

我們需要修改配置檔案:

const path = require(`path`)
const ExtractTextPlugin = require(`extract-text-webpack-plugin`)
module.exports = {
  entry:  __dirname + "/app/main.js",//已多次提及的唯一入口檔案
  output: {
    path: __dirname + "/public",//打包後的檔案存放的地方
    filename: "bundle.js",//打包後輸出檔案的檔名
  },
  module: {
    rules: [
      {
        test: /.css$/,
        // 轉換 .css 需要的 loader
        loaders: ExtractTextPlugin.extract({
          use: [`css-loader`],
        })
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin({
      filename: `[name]-[contenthash:8].css`
    })
  ]
}
複製程式碼

然後我們再重新打包,就可以發現在 public 資料夾下面多了一個 main-493a2c3c.css 檔案,下面我們要在 index.html 中自動引入這個 css 檔案

html-webpack-plugin

html-webpack-plugin 可以根據你設定的模板,在每次執行後生成對應的模板檔案,同時所依賴的 CSS/JS 也都會被引入,如果 CSS/JS 中含有 hash 值,則 html-webpack-plugin 生成的模板檔案也會引入正確版本的 CSS/JS 檔案。

安裝 html-webpack-plugin

安裝方式我們已經很熟悉了

npm i -D html-webpack-plugin
複製程式碼

修改配置檔案

const path = require(`path`)
const ExtractTextPlugin = require(`extract-text-webpack-plugin`)
const HtmlWebpackPlugin = require(`html-webpack-plugin`);  

module.exports = {
  entry:  __dirname + "/app/main.js",//已多次提及的唯一入口檔案
  output: {
    path: __dirname + "/public",//打包後的檔案存放的地方
    filename: "bundle.js",//打包後輸出檔案的檔名
  },
  module: {
    rules: [
      {
        test: /.css$/,
        loaders: ExtractTextPlugin.extract({
          use: [`css-loader`],
        })
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin({
      filename: `[name]-[contenthash:8].css`
    }),
    new HtmlWebpackPlugin(),
  ]
}
複製程式碼

藍後我們刪除之前我們在 punlic 資料夾下面 index.html 的內容,在打包一次,就會生成一個 html 模板

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Webpack App</title>
  <link href="main-493a2c3c.css" rel="stylesheet"></head>
  <body>
  <script type="text/javascript" src="bundle.js"></script></body>
</html>
複製程式碼

new HtmlWebpackPlugin 的時候,我們可以進行一系列的配置

    new HtmlWebpackPlugin({  
      // 生成的HTML模板的title,如果模板中有設定title的名字,則會忽略這裡的設定
      title: "This is the webpack config", 
      
      // 生成的模板檔案的名字
      filename: "/index.html", 
      
      // 模板來原始檔 
      template: "index.html", 
      
      // 引入模組的注入位置;取值有true/false/body/head 
      // true 預設值,script標籤位於html檔案的 body 底部
      // body script標籤位於html檔案的 body 底部
      // head script標籤位於html檔案的 head中
      // false 不插入生成的js檔案,這個幾乎不會用到的
      inject: "body", 
      
      // 指定頁面圖示; 
      favicon: "", 
      
      // 是html-webpack-plugin中整合的 html-minifier ,生成模板檔案壓縮配置 
      minify: {  
          caseSensitive: false, //是否大小寫敏感  
          collapseBooleanAttributes: true, // 省略布林屬性的值  
          collapseWhitespace: true //刪除空格 
      }, 
      
      // 是否生成hash新增在引入檔案地址的末尾,類似於我們常用的時間戳,避免快取帶來的麻煩
      hash: true,
      
      // 是否需要快取,如果填寫true,則檔案只有在改變時才會重新生成
      cache: true,  
      
      // 是否將錯誤資訊寫在頁面裡,預設true,出現錯誤資訊則會包裹在一個pre標籤內新增到頁面上
      
      showErrors: true,  
      
      // 引入的模組,這裡指定的是entry中設定多個js時,在這裡指定引入的js,如果不設定則預設全部引入
      chunks: "",  
      
      // 引入模組的排序方式
      // 預設四個選項: none auto dependency {function}
      // `dependency` 檔案的依賴關係
      // `auto` 預設值,外掛的內建的排序方式
      // `none` 無序
      // {function} 自定義
      chunksSortMode: "auto",  
      
      // 排除的模組
      excludeChunks: "",  
      
      // 生成的模板文件中標籤是否自動關閉,針對xhtml的語法,會要求標籤都關閉,預設false
      xhtml: false  
    
    }),
複製程式碼

常見的 plugin

摘自:http://www.css88.com/doc/webpack/plugins/

Name Description
AggressiveSplittingPlugin 將原來的 chunk 分成更小的 chunk
BabiliWebpackPlugin 基於 Babel 的裁剪工具:Babili
BannerPlugin 在每個生成的 chunk 頂部新增 banner
CommonsChunkPlugin 提取 chunks 之間共享的通用模組
ComponentWebpackPlugin 通過 webpack 使用元件
CompressionWebpackPlugin 預先準備的資源壓縮版本,使用 Content-Encoding 提供訪問服務
ContextReplacementPlugin 重寫 require 表示式的推斷上下文
DefinePlugin 允許在編譯時(compile time)配置的全域性常量
DllPlugin 為了極大減少構建時間,進行分離打包
EnvironmentPlugin DefinePlugin 中 process.env 鍵的簡寫方式。
ExtractTextWebpackPlugin 從 bundle 中提取文字(CSS)到單獨的檔案
HotModuleReplacementPlugin 啟用模組熱替換(Enable Hot Module Replacement – HMR)
HtmlWebpackPlugin 簡單建立 HTML 檔案,用於伺服器訪問
I18nWebpackPlugin 為 bundle 增加國際化支援
IgnorePlugin 從 bundle 中排除某些模組
LimitChunkCountPlugin 設定 chunk 的最小/最大限制,以微調和控制 chunk
LoaderOptionsPlugin 用於從 webpack 1 遷移到 webpack 2
MinChunkSizePlugin 確保 chunk 大小超過指定限制
NoEmitOnErrorsPlugin 在輸出階段時,遇到編譯錯誤跳過
NormalModuleReplacementPlugin 替換與正規表示式匹配的資源
NpmInstallWebpackPlugin 在開發時自動安裝缺少的依賴
ProvidePlugin 不必通過 import/require 使用模組
SourceMapDevToolPlugin 對 source map 進行更細粒度的控制
UglifyjsWebpackPlugin 可以控制專案中 UglifyJS 的版本
ZopfliWebpackPlugin 通過 node-zopfli 將資源預先壓縮的版本

參考文章:

相關文章