24 個例項入門並掌握「Webpack4」(一)

Zsh發表於2019-04-12

前言

此專案總共 24 小節,目錄:

  1. 搭建專案並打包 JS 檔案
  2. 生產和開發模式
  3. 覆蓋預設 entry/output
  4. 用 Babel 7 轉譯 ES6
  5. Code Splitting
  6. Lazy Loading、Prefetching
  7. 自動生成 HTML 檔案
  8. 處理 CSS/SCSS 檔案
  9. JS Tree Shaking
  10. CSS Tree Shaking
  11. 圖片處理彙總
  12. 字型檔案處理
  13. 處理第三方 js 庫
  14. 開發模式與 webpack-dev-server
  15. 開發模式和生產模式・實戰
  16. 打包自定義函式庫
  17. PWA 配置
  18. TypeScript 配置
  19. Eslint 配置
  20. 使用 DLLPlugin 加快打包速度
  21. 多頁面打包配置
  22. 編寫 loader
  23. 編寫 plugin
  24. 編寫 Bundle

前 15 節基於 Webpack4 漸進式教程 為基礎,加上自己的實踐和理解得出,感謝 godbmw ?

Webpack4 漸進式教程 的基礎上升級:

  • 使用 babel7
  • 配置 .browserslistrc 檔案
  • 使用 mini-css-extract-plugin 替代 extract-text-webpack-plugin
  • 使用 optimize-css-assets-webpack-plugin 壓縮 css
  • 使用 postcsscss 加上各個瀏覽器字首
  • 使用 image-webpack-loader 處理圖片

隨後的章節以 mooc手把手帶你掌握新版 Webpack4.0 整理的學習筆記,感謝 DellLee 老師 ?

環境如下:

OS: 「win10」

node: 「10.5.0」

npm: 「6.1.0」

webpack: 「4.29.6」

webpack-cli: 「3.2.3」

複製程式碼

每一個章節對應一個 demo ? 原始碼地址, clone 原始碼後注意執行 npm install 安裝依賴

第一次寫文章,錯誤的地方希望各位大佬指出,第一時間修改

一、搭建專案並打包 JS 檔案

demo1 原始碼地址

建立空資料夾,通過執行以下命令初始化 package.json

npm init -y
複製程式碼

npm init 用來初始化生成一個新的 package.json 檔案。它會向使用者提問一系列問題,如果你覺得不用修改預設配置,一路回車就可以了。 如果使用了 -y(代表 yes),則跳過提問階段,直接生成一個新的 package.json 檔案。

引入 webpack4:

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

還需要 webpack-cli ,作為一個單獨的包引入,如果不裝 webpack-cli 是無法在命令列裡使用 webpack 的

npm i webpack-cli --save-dev
複製程式碼

此專案 webpack 版本如下:

"webpack": "^4.29.6",
"webpack-cli": "^3.2.3"
複製程式碼

現在開啟 package.json 並新增一個 build(構建) 指令碼:

24 個例項入門並掌握「Webpack4」(一)

嘗試執行看看會發生什麼:

npm run build
複製程式碼

24 個例項入門並掌握「Webpack4」(一)

在 webpack4 以前的版本中,必須在名為 webpack.config.js 的配置檔案中 通過 entry 屬性定義 entry point(入口點),就像這樣:

24 個例項入門並掌握「Webpack4」(一)

從 webpack4 開始,不再必須定義 entry point(入口點) :它將預設為 ./src/index.js

測試這個新功能,首先建立 ./src/index.js 檔案

24 個例項入門並掌握「Webpack4」(一)

再執行 npm run build 試試

24 個例項入門並掌握「Webpack4」(一)

打包成功,並在當前的根目錄中得到打包後的資料夾,也就是 dist 資料夾

24 個例項入門並掌握「Webpack4」(一)

它將查詢 ./src/index.js 作為預設入口點。 而且,它會在 ./dist/main.js 中輸出模組包,目前程式碼量小,可以格式化看效果

24 個例項入門並掌握「Webpack4」(一)

至此,打包 JS 結束

參考:webpack 官網入門

二、生產和開發模式

demo2 原始碼地址

擁有 2 個配置檔案在 webpack 中是的常見模式。

一個典型的專案可能有:

  • 用於開發的配置檔案,配置熱更新、跨域配置、埠設定等
  • 用於生產的配置檔案,配置 js 壓縮、程式碼拆分等

雖然較大的專案可能仍然需要 2 個配置檔案,但在 webpack4 中,你可以在沒有一行配置的情況下完成

webpack4 引入了 production(生產) 和 development(開發) 模式。

細心的朋友會發現在 npm run build 打包後會有一段報警提示

'mode' 選項尚未設定,webpack 將回退到 'production'。 將 “mode” 選項設定為 'development' 或 'production' 以啟用每個環境的預設值。您還可以將其設定為 'none' 以禁用任何預設行為。 瞭解更多

24 個例項入門並掌握「Webpack4」(一)

  1. 開啟 package.json 並填充 script 部分,如下所示:
"dev": "webpack --mode development",
"build": "webpack --mode production"
複製程式碼
  1. 執行 npm run dev

開啟 ./dist/main.js 檔案,是一個 bundle(包) 檔案,並沒有壓縮!

24 個例項入門並掌握「Webpack4」(一)

  1. 執行 npm run build

可以看到 ./dist/main.js 檔案已經被壓縮了

24 個例項入門並掌握「Webpack4」(一)

其實在終端裡也能發現,看構建完的大小, dev 模式下檔案大小是 3.8 KB, prod 模式下檔案大小僅為 960 位元組

24 個例項入門並掌握「Webpack4」(一)

production mode(生產模式) 可以開箱即用地進行各種優化。 包括壓縮,作用域提升,tree-shaking 等。

另一方面,development mode(開發模式) 針對速度進行了優化,僅僅提供了一種不壓縮的 bundle

在 webpack4 中,可以在沒有一行配置的情況下完成任務! 只需定義 –mode 引數即可獲得所有內容!

三、覆蓋預設 entry/output

demo3 原始碼地址

1. 檢驗 webpack 規範支援

webpack 支援 ES6, CommonJS, AMD 規範

建立 vendor 資料夾,其中 minus.js、multi.js 和 sum.js 分別用 CommonJS、AMD 和 ES6 規範編寫

// minus.js
module.exports = function(a, b) {
  return a - b
}

// multi.js
define(function(require, factory) {
  'use strict'
  return function(a, b) {
    return a * b
  }
})

// sum.js
export default function(a, b) {
  return a + b
}
複製程式碼

app.js 檔案中引入以上三個 js 檔案

/**
 * webpack 支援 ES6、CommonJs 和 AMD 規範
 */

// ES6
import sum from './vendor/sum'
console.log('sum(1, 2) = ', sum(1, 2))

// CommonJs
var minus = require('./vendor/minus')
console.log('minus(1, 2) = ', minus(1, 2))

// AMD
require(['./vendor/multi'], function(multi) {
  console.log('multi(1, 2) = ', multi(1, 2))
})
複製程式碼

2. 編寫配置檔案覆蓋 entry/output

webpack.config.js 是 webpack 預設的配置檔名,在根目錄下建立

const path = require('path')

module.exports = {
  entry: {
    app: './app.js' // 需要打包的檔案入口
  },
  output: {
    publicPath: __dirname + '/dist/', // js 引用的路徑或者 CDN 地址
    path: path.resolve(__dirname, 'dist'), // 打包檔案的輸出目錄
    filename: 'bundle.js' // 打包後生產的 js 檔案
  }
}
複製程式碼

path.resolve() 方法會把一個路徑或路徑片段的序列解析為一個絕對路徑。

__dirname: 當前模組的資料夾名稱。

可以使用 console.log 輸出一下就明白了

const path = require('path')

console.log('__dirname: ', __dirname)
console.log('path.resolve: ', path.resolve(__dirname, 'dist'))

module.exports = {
  entry: {
    app: './app.js' // 需要打包的檔案入口
  },
  output: {
    publicPath: __dirname + '/dist/', // js 引用的路徑或者 CDN 地址
    path: path.resolve(__dirname, 'dist'), // 打包檔案的輸出目錄
    filename: 'bundle.js' // 打包後生產的 js 檔案
  }
}
複製程式碼

24 個例項入門並掌握「Webpack4」(一)

執行 npm run build 打包 js 檔案

會發現生成了 dist 資料夾,並生成了兩個打包後的檔案

24 個例項入門並掌握「Webpack4」(一)

這跟 AMD 的引入方式有關,如果在 app.js 中註釋掉 AMD 的寫法,則只會打包出一個 bundle.js 檔案

在實際寫程式碼的時候,最好使用 ES6 和 CommonJS 的規範來寫

當你註釋 AMD 後,打包完 dist 中有多個檔案,這是因為打包的時候,沒有先刪除 dist 檔案,再打包,我們需要使用一個外掛來幫我們實現,GitHub 連結:clean-webpack-plugin

① 安裝外掛

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

② 修改 webpack 配置檔案

const path = require('path')

const CleanWebpackPlugin = require('clean-webpack-plugin')

module.exports = {
  entry: {
    app: './app.js' // 需要打包的檔案入口
  },
  output: {
    publicPath: __dirname + '/dist/', // js 引用的路徑或者 CDN 地址
    path: path.resolve(__dirname, 'dist'), // 打包檔案的輸出目錄
    filename: 'bundle.js' // 打包後生產的 js 檔案
  },
  plugins: [
    new CleanWebpackPlugin() // 預設情況下,此外掛將刪除 webpack output.path目錄中的所有檔案,以及每次成功重建後所有未使用的 webpack 資產。
  ]
}
複製程式碼

之後再執行 npm run build 就可以了

打包後的 js 檔案會按照我們的配置放在 dist 目錄下,建立一個 html 檔案,引用打包好的 js 檔案,開啟 F12 就能看到效果了

參考文章

webpack4 系列教程 (一): 打包 JS

Webpack4 教程:從零配置到生產模式

四、用 Babel 7 轉譯 ES6

demo4 原始碼地址

(一) 瞭解 Babel 及生態

現代 Javascript 主要是用 ES6 編寫的。但並非每個瀏覽器都知道如何處理 ES6。 我們需要某種轉換,這個轉換步驟稱為 transpiling(轉譯)。transpiling(轉譯) 是指採用 ES6 語法,轉譯為舊瀏覽器可以理解的行為。

Webpack 不知道如何進行轉換但是有 loader(載入器) :將它們視為轉譯器。

babel-loader 是一個 webpack 的 loader(載入器),用於將 ES6 及以上版本轉譯至 ES5

要開始使用 loader ,我們需要安裝一堆依賴項,以下已 Babel7 為主,升級建議

如果是用 babel7 來轉譯,需要安裝 @babel/core@babel/preset-env@babel/plugin-transform-runtime,而不是 babel-core、babel-preset-env 和 babel-plugin-transform-runtime,它們是用於 babel6 的

使用 @babel/plugin-transform-runtime 的原因:Babel 使用非常小的助手來完成常見功能。預設情況下,這將新增到需要它的每個檔案中。這種重複有時是不必要的,尤其是當你的應用程式分佈在多個檔案上的時候。 transform-runtime 可以重複使用 Babel 注入的程式程式碼來節省程式碼,減小體積

使用 @babel/polyfill 的原因:Babel 預設只轉換新的 JavaScript 句法(syntax),而不轉換新的 API,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全域性物件,以及一些定義在全域性物件上的方法(比如 Object.assign)都不會轉碼。必須使用 @babel/polyfill,為當前環境提供一個墊片。 所謂墊片也就是墊平不同瀏覽器或者不同環境下的差異

(二) 安裝依賴並配置

① 安裝依賴

npm i @babel/core babel-loader @babel/preset-env @babel/plugin-transform-runtime --save-dev
複製程式碼
npm i @babel/polyfill @babel/runtime
複製程式碼

② 在專案的根目錄中建立名為 .babelrc 的新檔案來配置 Babel:

{
  "presets": ["@babel/preset-env"],
  "plugins": ["@babel/plugin-transform-runtime"]
}

複製程式碼

感謝評論區 xcsweb 的指出,如果遇到如下報錯


WARNING: We noticed you're using the `useBuiltIns` option without declaring a core-js version. Currently, we assume version 2.x when no version is passed. Since this default version will likely change in future versions of Babel, we recommend explicitly setting the core-js version you are using via the `corejs` option. 
 
You should also be sure that the version you pass to the `corejs` option matches the version specified in your `package.json`'s `dependencies` section. If it doesn't, you need to run one of the following commands: 
 
  npm install --save core-js@2    npm install --save core-js@3 
  yarn add core-js@2              yarn add core-js@3
複製程式碼

不僅僅要安裝 npm install --save core-js@3 還需要設定 .babelrc 設定 "corejs": 3

{
 "presets": [
   [
     "@babel/preset-env",
     {
       "useBuiltIns": "usage",
       "corejs": 3
     }
   ]
 ],
 "plugins": ["@babel/plugin-transform-runtime"]
}

複製程式碼

③ webpack 配置 loader(載入器)

module: {
  rules: [
    {
      test: /\.js$/, // 使用正則來匹配 js 檔案
      exclude: /node_modules/, // 排除依賴包資料夾
      use: {
        loader: 'babel-loader' // 使用 babel-loader
      }
    }
  ]
}
複製程式碼

webpack.config.js 最終配置:

24 個例項入門並掌握「Webpack4」(一)

④ 在 app.js 全域性引入 @babel/polyfill 並寫入 ES6 語法,並執行 npm run build 打包

// 全域性引入
import '@babel/polyfill'

// 測試 ES6 語法是否通過 babel 轉譯
const array = [1, 2, 3]
const isES6 = () => console.log(...array)

isES6()

const arr = [new Promise(() => {}), new Promise(() => {})]

arr.map(item => {
  console.log(item)
})
複製程式碼

24 個例項入門並掌握「Webpack4」(一)

⑤ 打包完之後使用 IE 瀏覽器開啟 index.html 檔案,看控制檯是否有輸出,如果是新版的 chrome,是可以使用 es6 語法的,所以要用 IE 這個萬惡之源試試

全域性引入 @babel/polyfill 的這種方式可能會匯入程式碼中不需要的 polyfill,從而使打包體積更大

更改 .babelrc,只轉譯我們使用到的

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage"
      }
    ]
  ],
  "plugins": ["@babel/plugin-transform-runtime"]
}
複製程式碼

同時,將全域性引入這段程式碼註釋掉,再次打包

// 全域性引入
// import '@babel/polyfill'
複製程式碼

24 個例項入門並掌握「Webpack4」(一)

體積就減小了很多,但是更多的情況是我們並不確切的知道專案中引發相容問題的具體原因,所以還是全域性引入比較好

(三) 瞭解 .browserslistrc 配置檔案

browserslistrc 用於在不同前端工具之間共享目標瀏覽器和 Node.js 版本的配置

可以看看 browserslist 相容瀏覽器的頁面

當您將以下內容新增到 package.json 時,所有工具都會自動找到目標瀏覽器:

"browserslist": [
  "> 1%",
  "last 2 version",
  "not ie <= 8"
]
複製程式碼

24 個例項入門並掌握「Webpack4」(一)

也可以建立 .browserslistrc 檔案單獨寫配置

# 所支援的瀏覽器版本

> 1% # 全球使用情況統計選擇的瀏覽器版本

last 2 version # 每個瀏覽器的最後兩個版本

not ie <= 8 # 排除小於 ie8 以下的瀏覽器
複製程式碼

該專案還是使用單獨建立配置檔案的方式,便於理解,如果覺得配置檔案不好,也可以寫在 package.json

參考文章

webpack4 系列教程 (二): 編譯 ES6

babel 7 的使用的個人理解

babel 7 升級建議

browserslist

五、Code Splitting

demo5 原始碼地址

package.json 檔案所用依賴,npm install 安裝:

{
  "scripts": {
    "dev": "webpack --mode development",
    "build": "webpack --mode production"
  },
  "devDependencies": {
    "clean-webpack-plugin": "^2.0.0",
    "webpack": "^4.29.6",
    "webpack-cli": "^3.2.3"
  },
  "dependencies": {
    "lodash": "^4.17.11"
  }
}
複製程式碼

我們在 src/ 資料夾下建立 index.js 檔案

import _ from 'lodash'

console.log(_.join(['a', 'b', 'c']))
複製程式碼

目錄結構為:

24 個例項入門並掌握「Webpack4」(一)

配置 webpack.config.js 檔案

const path = require('path')

const CleanWebpackPlugin = require('clean-webpack-plugin')

module.exports = {
  entry: {
    main: './src/index.js'
  },
  output: {
    publicPath: __dirname + '/dist/', // js 引用的路徑或者 CDN 地址
    path: path.resolve(__dirname, 'dist'), // 打包檔案的輸出目錄
    filename: '[name].bundle.js', // 程式碼打包後的檔名
    chunkFilename: '[name].js' // 程式碼拆分後的檔名
  },
  plugins: [new CleanWebpackPlugin()]
}
複製程式碼

執行 npm run build 打包

24 個例項入門並掌握「Webpack4」(一)

在 index.html 中使用打包後的檔案

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>程式碼分割</title>
  </head>

  <body>
    <script src="./dist/main.bundle.js"></script>
  </body>
</html>
複製程式碼

使用瀏覽器開啟 index.html 檔案,進入控制檯,可以看到如下資訊:a,b,c

如果我們再改動業務程式碼,將 index.js 中的程式碼改為

import _ from 'lodash'

console.log(_.join(['a', 'b', 'c'], '***'))
複製程式碼

再打包,重新整理頁面可以看到 a***b*****c**

我們引用的第三方框架和我們的業務程式碼一起被打包,這樣會有一個什麼問題?

假設 lodash 為 1M,業務程式碼也為 1M,打包後假設為 2M

瀏覽器每次開啟頁面,都要先載入 2M 的檔案,才能顯示業務邏輯,這樣會使得載入時間變長,

業務程式碼更新會比較頻繁,第三方程式碼基本不會更改,這樣重新打包後,假設為 2M,使用者重新開啟網頁後,又會再載入 2M 檔案

瀏覽器是有快取的,如果檔案沒變動的話,就不用再去傳送 http 請求,而是直接從快取中取,這樣在重新整理頁面或者第二次進入的時候可以加快網頁載入的速度。

怎麼解決呢,可以利用 webpack 中的程式碼分割

在 webpack4 之前是使用 commonsChunkPlugin 來拆分公共程式碼,v4 之後被廢棄,並使用 splitChunksPlugins

在使用 splitChunksPlugins 之前,首先要知道 splitChunksPlugins 是 webpack 主模組中的一個細分模組,無需 npm 引入

現在我們來配置 webpack.config.js 檔案

const path = require('path')

const CleanWebpackPlugin = require('clean-webpack-plugin')

module.exports = {
  entry: {
    main: './src/index.js'
  },
  output: {
    publicPath: __dirname + '/dist/', // js 引用的路徑或者 CDN 地址
    path: path.resolve(__dirname, 'dist'), // 打包檔案的輸出目錄
    filename: '[name].bundle.js', // 程式碼打包後的檔名
    chunkFilename: '[name].js' // 程式碼拆分後的檔名
  },
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  },
  plugins: [new CleanWebpackPlugin()]
}
複製程式碼

上面高亮的程式碼段就是告訴 webpack,要做程式碼分割了,這裡的 chunks: 'all' 是分割所有程式碼,包括同步程式碼和非同步程式碼,webpack 預設是 chunks: 'async' 分割非同步程式碼

我們使用 npm run dev 來打包開發環境下的程式碼,這樣程式碼就不會壓縮,方便我們來觀察,可以看到程式碼被分割成兩個檔案了

24 個例項入門並掌握「Webpack4」(一)

開啟 dist/main.bundle.js 檔案,在最底部可以看到 src/index.js 檔案,裡面放的是業務邏輯的程式碼,但是並沒有 lodash 的程式碼

24 個例項入門並掌握「Webpack4」(一)

開啟 dist/vendors~main.js 檔案,在最上面可以看到 lodash 模組

24 個例項入門並掌握「Webpack4」(一)

再次開啟頁面,控制檯也輸出了內容,這樣就實現了 Code Splitting(程式碼分割)

其實沒有 webpack 的時候,也是有程式碼分割的,不過是需要我們自己手動的分割,而現在使用了 webpack,通過這種配置項的方式,它會自動幫我們去做程式碼分割

仔細看分割完的程式碼名稱,vendors~main.js,我們對分割完的名稱進行更改

還是在 splitChunks 的配置項中,新增 cacheGroups 物件

optimization: {
  splitChunks: {
    chunks: 'all',
    cacheGroups: {
      vendors: {
        name: 'vendors'
      }
    }
  }
}
複製程式碼

再次打包就可以看到效果了,cacheGroups 的預設配置會定義 vendorsdefault

24 個例項入門並掌握「Webpack4」(一)

test: /[\\/]node_modules[\\/]/, 使用正則過濾,只有 node_modules 引入的第三方庫會被分割

24 個例項入門並掌握「Webpack4」(一)

為了驗證預設配置,我們將 splitChunks 屬性設定為空物件,再次打包

24 個例項入門並掌握「Webpack4」(一)

打包完發現只有一個檔案,這是為什麼?

24 個例項入門並掌握「Webpack4」(一)

因為 chunks 預設為 async,只會分割非同步的程式碼,而之前我們寫的都是同步的程式碼,先 import lodash,再去寫業務邏輯,現在使用非同步的方式來做,將 index.js 中的程式碼改為以下:

function getComponent() {
  // 使用 非同步的形式匯入 lodash,default: _ 表示用 _ 代指 lodash
  return import('lodash').then(({ default: _ }) => {
    var element = document.createElement('div')
    element.innerHTML = _.join(['hello', 'world'], '-')
    return element
  })
}

getComponent().then(element => {
  document.body.appendChild(element)
})
複製程式碼

24 個例項入門並掌握「Webpack4」(一)

這裡分割出了 0.jsmain.bundle.js,0 是以 id 為編號來命名

所以一般我們設定 chunks 為 all,非同步、同步程式碼都打包

現在我們將 webpack 官網上的預設配置拷貝到我們的 webpack.config.js 中來分析一下

optimization: {
  splitChunks: {
    chunks: 'async',
    minSize: 30000,
    maxSize: 0,
    minChunks: 1,
    maxAsyncRequests: 5,
    maxInitialRequests: 3,
    automaticNameDelimiter: '~',
    name: true,
    cacheGroups: {
      vendors: {
        test: /[\\/]node_modules[\\/]/,
        priority: -10
      },
      default: {
        minChunks: 2,
        priority: -20,
        reuseExistingChunk: true
      }
    }
  }
}
複製程式碼

webpack 程式碼分割的配置是這樣的,比如我們要分割 jQuery 和 lodash 這樣的第三方庫,它會先經過 chunks、minSize、maxSize、minChunks 等等,滿足條件後生成 jQuery 和 lodash 兩個檔案,然後放入 cacheGroup 中快取著,再根據你在 cacheGroup 中配置的來決定是將兩個檔案整合到一個檔案打包,還是單獨分開打包,比如上面程式碼中的 vendors,就是將 node_modules 中所有的第三方庫都打包到 vendors.js 檔案中,如果你還想繼續分割可以這麼做

cacheGroups: {
  lodash: {
    name: 'lodash',
    test: /[\\/]node_modules[\\/]lodash[\\/]/,
    priority: 5  // 優先順序要大於 vendors 不然會被打包進 vendors
  },
  vendors: {
    test: /[\\/]node_modules[\\/]/,
    priority: -10
  },
  default: {
    minChunks: 2,
    priority: -20,
    reuseExistingChunk: true
  }
}
複製程式碼

感謝評論區 anlinsir 的指出,如果打包有報錯 Support for the experimental syntax ‘dynamicImport’ isn't currently enabled,這是因為 dynamicImport 還是實驗性的語法,webpack 不支援,需要安裝外掛來支援,具體步驟可以參考: www.cnblogs.com/chaoyueqi/p…

再次打包,就可以看到 lodash 被分割出來了,以後使用第三方庫都可以用這種配置來單獨分割成一個 js 檔案,比如 element-ui注意設定 priority 的值很重要,優先順序越高的會越先被打包

24 個例項入門並掌握「Webpack4」(一)

如果 index.js 引入了 A.js 和 B.js,同時 A、B 又引入了 common,common 被引入了兩次,可以被稱為公共模組

目錄結構為:

24 個例項入門並掌握「Webpack4」(一)

程式碼如下:

// a,js
import './common'
console.log('A')
export default 'A'

// b.js
import './common'
console.log('B')
export default 'B'

// common.js
console.log('公共模組')
export default 'common'

// index.js
import './a.js'
import './b.js'

// 非同步程式碼
function getComponent() {
  // 使用非同步的形式匯入 lodash,default: _ 表示用 _ 代指 lodash
  return import('lodash').then(({ default: _ }) => {
    var element = document.createElement('div')
    element.innerHTML = _.join(['hello', 'world'], '-')
    return element
  })
}

getComponent().then(element => {
  document.body.appendChild(element)
})
複製程式碼

上面那種非同步的寫法可能比較繞,現在精簡一下,並且 webpack 對非同步程式碼通過註釋可以直接修改打包後的名稱,以下程式碼全部以非同步的形式引入

// 非同步程式碼
import(/* webpackChunkName: 'a'*/ './a').then(function(a) {
  console.log(a)
})

import(/* webpackChunkName: 'b'*/ './b').then(function(b) {
  console.log(b)
})

import(/* webpackChunkName: 'use-lodash'*/ 'lodash').then(function(_) {
  console.log(_.join(['1', '2']))
})
複製程式碼

將 minChunks 設定為 2,最小公用 2 次才分割

optimization: {
  splitChunks: {
    chunks: 'all',
    minSize: 30000,
    maxSize: 0,
    minChunks: 1,
    maxAsyncRequests: 5,
    maxInitialRequests: 3,
    automaticNameDelimiter: '~',
    name: true,
    cacheGroups: {
      lodash: {
        name: 'lodash',
        test: /[\\/]node_modules[\\/]lodash[\\/]/,
        priority: 10
      },
      commons: {
        name: 'commons',
        minSize: 0, //表示在壓縮前的最小模組大小,預設值是 30kb
        minChunks: 2, // 最小公用次數
        priority: 5, // 優先順序
        reuseExistingChunk: true // 公共模組必開啟
      },
      vendors: {
        test: /[\\/]node_modules[\\/]/,
        priority: -10
      },
      default: {
        minChunks: 2,
        priority: -20,
        reuseExistingChunk: true
      }
    }
  }
}
複製程式碼

24 個例項入門並掌握「Webpack4」(一)

這裡分割出了 lodash 和我們在註釋中定義的 use-lodash,前者是第三庫,後者是使用第三庫寫的業務程式碼,也能被分割出來

24 個例項入門並掌握「Webpack4」(一)

這裡之所以會自動引入分割後的依賴,可以檢視打包後的 main.bundle.js 檔案

24 個例項入門並掌握「Webpack4」(一)
甚至我們可以打斷點來看它是怎麼執行的,以下為 gif 動圖演示
24 個例項入門並掌握「Webpack4」(一)

常用的配置項在下面的表格中,更多配置詳情見官網

配置項 說明 示例
chunks 匹配的塊的型別 initial(初始塊),async(按需載入的非同步塊),all(所有塊)
name 用以控制分離後程式碼塊的命名 chunk-libs
test 用於規定快取組匹配的檔案位置 /[\/]node_modules[\/]/
priority 分離規則的優先順序,優先順序越高,則優先匹配 priority: 20
minSize 超過多少大小就進行壓縮 minSize: 30000 預設值是 30kb
minChunks 分割前必須共享模組的最小塊數 minChunks: 2
reuseExistingChunk 如果當前塊已從主模組拆分出來,則將重用它而不是生成新的塊 true

參考文章

webpack4 系列教程 (三): 多頁面解決方案 -- 提取公共程式碼

webpack 官網

六、Lazy Loading、Prefetching

demo6 原始碼地址

在 demo5 的基礎上修改 index.js 檔案,並刪除了多餘的 js 檔案

document.addEventListener('click', function() {
  import(/* webpackChunkName: 'use-lodash'*/ 'lodash').then(function(_) {
    console.log(_.join(['3', '4']))
  })
})
複製程式碼

這段程式碼表示的是,當點選頁面的時候,非同步載入 lodash 並輸出內容,打包後開啟 index.html 檔案,演示如下:

24 個例項入門並掌握「Webpack4」(一)

第一次進入頁面的時候,並沒有載入 lodash 和 use-lodash,當我點選網頁的時候,瀏覽器再去載入,並且控制檯輸出內容,這就是程式碼懶載入,如果有用過 vue-router 的朋友應該會知道路由懶載入,並且官方也推薦使用懶載入的寫法,就是為了結合 webpack,下圖是 vue-cli3 生成的專案

24 個例項入門並掌握「Webpack4」(一)

其實懶載入就是通過 import 去非同步的載入一個模組,具體什麼時候載入,這個要根據業務來寫,比如彈窗元件,模態框元件等等,都是點選按鈕後再出現。

懶載入能加快網頁的載入速度,如果你把詳情頁、彈窗等頁面全部打包到一個 js 檔案中,使用者如果只是訪問首頁,只需要首頁的程式碼,不需要其他頁面的程式碼,加入多餘的程式碼只會使載入時間變長,所以我們可以對路由進行懶載入,只有當使用者訪問到對應路由的時候,再去載入對應模組

懶載入並不是 webpack 裡的概念,而是 ES6 中的 import 語法,webpack 只是能夠識別 import 語法,能進行程式碼分割而已。

import 後面返回的是一個 then,說明這是一個 promise 型別,一些低端的瀏覽器不支援 promise,比如 IE ,如果要使用這種非同步的程式碼,就要使用 babel 以及 babel-polyfill 來做轉換,因為我使用的是 73 版本的 chrome 瀏覽器,對 ES6 語法是支援的,所以我並沒有安裝 babel 也能使用

更改 index.js 檔案

document.addEventListener('click', function() {
  const element = document.createElement('div')
  element.innerHTML = 'Hello World'
  document.body.appendChild(element)
})
複製程式碼

重新打包,並開啟 index.html ,開啟瀏覽器控制檯,按 ctrl + shift + p ,輸入 coverage

24 個例項入門並掌握「Webpack4」(一)

就能看到當前頁面載入的 js 程式碼未使用率,紅色區域表示未被使用的程式碼段

24 個例項入門並掌握「Webpack4」(一)

演示:

24 個例項入門並掌握「Webpack4」(一)

開啟 coverage 如果沒出現分析的檔案,記得重新整理一下

這裡一開始紅色區域的程式碼未被使用,當我點選了頁面後,變成綠色,頁面出現了 Hello World,說明程式碼被使用了

頁面剛載入的時候,非同步的程式碼根本就不會執行,但是我們卻把它下載下來,實際上就會浪費頁面執行效能,webpack 就希望像這樣互動的功能,應該把它放到一個非同步載入的模組去寫

新建一個 click.js 檔案

function handleClick() {
  const element = document.createElement('div')
  element.innerHTML = 'Dell Lee'
  document.body.appendChild(element)
}

export default handleClick
複製程式碼

並且將 index.js 檔案改為非同步的載入模組:

document.addEventListener('click', () => {
  import('./click.js').then(({ default: func }) => {
    func()
  })
})
複製程式碼

重新打包,使用 coverage 分析

演示:

24 個例項入門並掌握「Webpack4」(一)

當載入頁面的時候,沒有載入業務邏輯,當點選網頁的時候,才會載入 1.js 模組,也就是我們在 index.js 中非同步引入的 click.js

這麼去寫程式碼,才是使頁面載入最快的一種方式,寫高效能前端程式碼的時候,不關是考慮快取,還要考慮程式碼使用率

所以 webpack 在打包過程中,是希望我們多寫這種非同步的程式碼,才能提升網站的效能,這也是為什麼 webpack 的 splitChunks 中的 chunks 預設是 async,非同步的

24 個例項入門並掌握「Webpack4」(一)

非同步能提高你網頁開啟的效能,而同步程式碼是增加一個快取,對效能的提升是非常有限的,因為快取一般是第二次開啟網頁或者重新整理頁面的時候,快取很有用,但是網頁的效能一般是使用者第一次開啟網頁,看首屏的時候。

當然,這也會出現另一個問題,就是當使用者點選的時候,才去載入業務模組,如果業務模組比較大的時候,使用者點選後並沒有立馬看到效果,而是要等待幾秒,這樣體驗上也不好,怎麼去解決這種問題

一:如果訪問首頁的時候,不需要載入詳情頁的邏輯,等使用者首頁載入完了以後,頁面展示出來了,頁面的頻寬被釋放出來了,網路空閒了,再「偷偷」的去載入詳情頁的內容,而不是等使用者去點選的時候再去載入

這個解決方案就是依賴 webpack 的 Prefetching/Preloading 特性

修改 index.js

document.addEventListener('click', () => {
  import(/* webpackPrefetch: true */ './click.js').then(({ default: func }) => {
    func()
  })
})
複製程式碼

webpackPrefetch: true 會等你主要的 JS 都載入完了之後,網路頻寬空閒的時候,它就會預先幫你載入好

重新打包後重新整理頁面,注意看 Network

24 個例項入門並掌握「Webpack4」(一)

當網頁開啟的時候,main.bundle.js 被載入完了,網路空閒了,就會預先載入 1.js 耗時 14ms,等我去點選頁面的時候,Network 又多了一個 1.js,耗時 2ms,這是因為第一次載入完了 1.js,被瀏覽器給快取起來了,等我點選的時候,瀏覽器直接從快取中取,響應速度非常快

這裡我們使用的是 webpackPrefetch,還有一種是 webpackPreload

與 prefetch 相比,Preload 指令有很多不同之處

Prefetch 會等待核心程式碼載入完之後,有空閒之後再去載入。Preload 會和核心的程式碼並行載入,還是不推薦

針對優化,不僅僅是侷限於快取,快取能帶來的程式碼效能提升是非常有限的,而是如何讓程式碼的使用率最高,有一些互動後才用的程式碼,可以寫到非同步元件裡面去,通過懶載入的形式,去把程式碼邏輯載入進來,這樣會使得頁面訪問速度變的更快,如果你覺得懶載入會影響使用者體驗,可以使用 Prefetch 這種方式來預載入,不過在某些遊覽器不相容,會有相容性的問題,重點不是在 Prefetch 怎麼去用,而是在做前端程式碼效能優化的時候,快取不是最重要的點,最重要的是程式碼使用的覆蓋率上(coverage)

七、自動生成 HTML 檔案

demo7 原始碼地址

經過上面幾個小節的操作,有沒有覺得每次要去更改 index.html 中引入 js 檔案很麻煩,一旦打包的名字變更後,也要對應的去修改 index.html 引入的 js 名稱,這個時候就要使用一個外掛來幫助我們,打包完之後自動生成 HTML 檔案並自動引入打包後的 js 檔案

(一) 安裝依賴

npm i html-webpack-plugin html-loader --save-dev
複製程式碼

package.json 如下:

{
  "scripts": {
    "dev": "webpack --mode development",
    "build": "webpack --mode production"
  },
  "devDependencies": {
    "clean-webpack-plugin": "^2.0.0",
    "html-loader": "^0.5.5",
    "html-webpack-plugin": "^3.2.0",
    "webpack": "^4.29.6",
    "webpack-cli": "^3.2.3"
  },
  "dependencies": {
    "lodash": "^4.17.11"
  }
}
複製程式碼

(二) 更改配置檔案

module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      // 打包輸出HTML
      title: '自動生成 HTML',
      minify: {
        // 壓縮 HTML 檔案
        removeComments: true, // 移除 HTML 中的註釋
        collapseWhitespace: true, // 刪除空白符與換行符
        minifyCSS: true // 壓縮內聯 css
      },
      filename: 'index.html', // 生成後的檔名
      template: 'index.html' // 根據此模版生成 HTML 檔案
    })
  ]
}
複製程式碼

HtmlWebpackPlugin 是在 plugin 這個選項中配置的。常用引數含義如下:

  • title: 打包後生成 html 的 title
  • filename:打包後的 html 檔名稱
  • template:模板檔案(例子原始碼中根目錄下的 index.html)
  • chunks:和 entry 配置中相匹配,支援多頁面、多入口
  • minify:壓縮選項

由於使用了 title 選項,則需要在 template 選項對應的 html 的 title 中加入 <%= htmlWebpackPlugin.options.title %>

24 個例項入門並掌握「Webpack4」(一)

const path = require('path')

const CleanWebpackPlugin = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin') // 引入外掛

module.exports = {
  entry: {
    page: './src/page.js'
  },
  output: {
    publicPath: __dirname + '/dist/', // js 引用的路徑或者 CDN 地址
    path: path.resolve(__dirname, 'dist'), // 打包檔案的輸出目錄
    filename: '[name].bundle.js', // 程式碼打包後的檔名
    chunkFilename: '[name].js' // 程式碼拆分後的檔名
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      // 打包輸出HTML
      title: '自動生成 HTML',
      minify: {
        // 壓縮 HTML 檔案
        removeComments: true, // 移除 HTML 中的註釋
        collapseWhitespace: true, // 刪除空白符與換行符
        minifyCSS: true // 壓縮內聯 css
      },
      filename: 'index.html', // 生成後的檔名
      template: 'index.html' // 根據此模版生成 HTML 檔案
    })
  ],
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        lodash: {
          name: 'chunk-lodash', // 單獨將 lodash 拆包
          priority: 10, // 優先順序要大於 commons 不然會被打包進 commons
          test: /[\\/]node_modules[\\/]lodash[\\/]/
        },
        commons: {
          name: 'chunk-commons',
          minSize: 1, //表示在壓縮前的最小模組大小,預設值是 30kb
          minChunks: 2, // 最小公用次數
          priority: 5, // 優先順序
          reuseExistingChunk: true // 公共模組必開啟
        }
      }
    }
  }
}
複製程式碼

(三) 打包並測試

執行 npm run build

24 個例項入門並掌握「Webpack4」(一)

開啟 dist 資料夾裡自動生成的 index.html

24 個例項入門並掌握「Webpack4」(一)

在瀏覽器中開啟 index.html 檔案,開啟控制檯也發現有輸出,OK,自動生成 HTML 檔案成功

24 個例項入門並掌握「Webpack4」(一)

細心的朋友可能會發現一個問題,生成後的 HTML 檔案引入的 JS 為絕對路徑,但是真實的專案打完包之後都是部署在伺服器上,用絕對路徑肯定不行,因為你本地電腦的絕對路徑放在伺服器上肯定找不到

24 個例項入門並掌握「Webpack4」(一)

我們要將引入的 js 檔案從絕對路徑改為相對路徑

修改 webpack.config.js 檔案

24 個例項入門並掌握「Webpack4」(一)

找到 output 輸出配置,更改 publicPath 公共路徑,修改為 ./ 絕對路徑

  output: {
    publicPath: './', // js 引用的路徑或者 CDN 地址
    path: path.resolve(__dirname, 'dist'), // 打包檔案的輸出目錄
    filename: '[name].bundle.js', // 程式碼打包後的檔名
    chunkFilename: '[name].js' // 程式碼拆分後的檔名
  },
複製程式碼

再次打包,看打包後的 index.html 檔案,開啟瀏覽器測試,也是沒問題的

24 個例項入門並掌握「Webpack4」(一)

八、處理 CSS/SCSS 檔案

demo8 原始碼地址

(一) 準備工作

CSS 在 HTML 中的常用引入方法有 <link> 標籤和 <style> 標籤兩種,所以這次就是結合 webpack 特點實現以下功能:

  • 將 css 通過 link 標籤引入
  • 將 css 放在 style 標籤裡

下圖展示了這次的目錄程式碼結構:

24 個例項入門並掌握「Webpack4」(一)

這次我們需要用到 css-loaderstyle-loader 等 loader,跟 babel 一樣,webpack 不知道將 CSS 提取到檔案中。需要使用 loader 來載入對應的檔案

css-loader:負責解析 CSS 程式碼,主要是為了處理 CSS 中的依賴,例如 @import 和 url() 等引用外部檔案的宣告

style-loader 會將 css-loader 解析的結果轉變成 JS 程式碼,執行時動態插入 style 標籤來讓 CSS 程式碼生效。

(二) 安裝依賴

npm i css-loader style-loader --save-dev
複製程式碼

package.json 如下:

{
  "scripts": {
    "dev": "webpack --mode development",
    "build": "webpack --mode production"
  },
  "devDependencies": {
    "clean-webpack-plugin": "^2.0.0",
    "css-loader": "^2.1.0",
    "html-loader": "^0.5.5",
    "html-webpack-plugin": "^3.2.0",
    "mini-css-extract-plugin": "^0.5.0",
    "style-loader": "^0.23.1",
    "webpack": "^4.29.6",
    "webpack-cli": "^3.2.3"
  }
}
複製程式碼

更改配置檔案

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/, // 針對 .css 字尾的檔案設定 loader
        use: ['style-loader', 'css-loader']
      }
    ]
  }
}
複製程式碼

配置 module 中的 rules 屬性,和配置 babel 一樣,首先在 test 中使用正則來過濾 .css 檔案,對 .css 檔案使用 loader,'style-loader', 'css-loader'

在 base.css 中寫入樣式

*,
body {
  margin: 0;
  padding: 0;
}
html {
  background: red;
}
複製程式碼

並在 app.js 中引入 base.css

import style from './css/base.css'
複製程式碼

配置檔案完整程式碼:

const path = require('path')

const CleanWebpackPlugin = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin') // 引入外掛

module.exports = {
  entry: {
    app: './src/app.js'
  },
  output: {
    publicPath: './', // js 引用的路徑或者 CDN 地址
    path: path.resolve(__dirname, 'dist'), // 打包檔案的輸出目錄
    filename: '[name].bundle.js', // 程式碼打包後的檔名
    chunkFilename: '[name].js' // 程式碼拆分後的檔名
  },
  module: {
    rules: [
      {
        test: /\.css$/, // 針對 .css 字尾的檔案設定 loader
        use: ['style-loader', 'css-loader'] // 使用 loader
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      // 打包輸出HTML
      title: '自動生成 HTML',
      minify: {
        // 壓縮 HTML 檔案
        removeComments: true, // 移除 HTML 中的註釋
        collapseWhitespace: true, // 刪除空白符與換行符
        minifyCSS: true // 壓縮內聯 css
      },
      filename: 'index.html', // 生成後的檔名
      template: 'index.html', // 根據此模版生成 HTML 檔案
      chunks: ['app'] // entry中的 app 入口才會被打包
    })
  ]
}
複製程式碼

專案打包,檢視 dist 資料夾

24 個例項入門並掌握「Webpack4」(一)

發現並沒有生成 CSS 檔案,但是開啟 index.html 是有樣式的

原因是:style-loader, css-loader 兩個 loader 的處理後,CSS 程式碼會轉變為 JS,和 index.js 一起打包

24 個例項入門並掌握「Webpack4」(一)

可以發現是通過 <style> 標籤注入的 css

如果需要單獨把 CSS 檔案分離出來,我們需要使用 mini-css-extract-plugin 外掛。

之前是使用 extract-text-webpack-plugin 外掛,此外掛與 webpack4 不太匹配,現在使用 mini-css-extract-plugin

確保將 webpack 更新到 4.2.0 版及以上。否則 mini-css-extract-plugin 將無效!

24 個例項入門並掌握「Webpack4」(一)

目前還不支援熱更新,也就是在開發環境下更改了 css,需要手動的重新整理頁面才會看到效果,目前這個外掛一般在生產環境中使用,開發環境下還是使用 'style-loader',具體見官網配置

npm i mini-css-extract-plugin --save-dev
複製程式碼

更改配置檔案:

const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/, // 針對 .css 字尾的檔案設定 loader
        use: [
          {
            loader: MiniCssExtractPlugin.loader
          },
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[id].css'
    })
  ]
}
複製程式碼

完整程式碼:

const path = require('path')

const CleanWebpackPlugin = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

const MiniCssExtractPlugin = require('mini-css-extract-plugin') // 將 css 單獨打包成檔案

module.exports = {
  entry: {
    app: './src/app.js'
  },
  output: {
    publicPath: './', // js 引用的路徑或者 CDN 地址
    path: path.resolve(__dirname, 'dist'), // 打包檔案的輸出目錄
    filename: '[name].bundle.js', // 程式碼打包後的檔名
    chunkFilename: '[name].js' // 程式碼拆分後的檔名
  },
  module: {
    rules: [
      {
        test: /\.css$/, // 針對 .css 字尾的檔案設定 loader
        use: [
          {
            loader: MiniCssExtractPlugin.loader
          },
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      // 打包輸出HTML
      title: '自動生成 HTML',
      minify: {
        // 壓縮 HTML 檔案
        removeComments: true, // 移除 HTML 中的註釋
        collapseWhitespace: true, // 刪除空白符與換行符
        minifyCSS: true // 壓縮內聯 css
      },
      filename: 'index.html', // 生成後的檔名
      template: 'index.html', // 根據此模版生成 HTML 檔案
      chunks: ['app'] // entry中的 app 入口才會被打包
    }),
    new MiniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[id].css'
    })
  ]
}
複製程式碼

這樣只是生成了單獨的 css 檔案,但是並沒有壓縮,引入 optimize-css-assets-webpack-plugin 外掛來實現 css 壓縮

npm install optimize-css-assets-webpack-plugin --save-dev
複製程式碼

完整程式碼:

const path = require('path')

const CleanWebpackPlugin = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

const MiniCssExtractPlugin = require('mini-css-extract-plugin') // 將 css 單獨打包成檔案
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin') // 壓縮 css

module.exports = {
  entry: {
    app: './src/app.js'
  },
  output: {
    publicPath: './', // js 引用的路徑或者 CDN 地址
    path: path.resolve(__dirname, 'dist'), // 打包檔案的輸出目錄
    filename: '[name].bundle.js', // 程式碼打包後的檔名
    chunkFilename: '[name].js' // 程式碼拆分後的檔名
  },
  module: {
    rules: [
      {
        test: /\.css$/, // 針對 .css 字尾的檔案設定 loader
        use: [
          {
            loader: MiniCssExtractPlugin.loader
          },
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      // 打包輸出HTML
      title: '自動生成 HTML',
      minify: {
        // 壓縮 HTML 檔案
        removeComments: true, // 移除 HTML 中的註釋
        collapseWhitespace: true, // 刪除空白符與換行符
        minifyCSS: true // 壓縮內聯 css
      },
      filename: 'index.html', // 生成後的檔名
      template: 'index.html', // 根據此模版生成 HTML 檔案
      chunks: ['app'] // entry中的 app 入口才會被打包
    }),
    new MiniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[id].css'
    }),
    new OptimizeCssAssetsPlugin({
      assetNameRegExp: /\.css$/g,
      cssProcessor: require('cssnano'), //用於優化\最小化 CSS 的 CSS處理器,預設為 cssnano
      cssProcessorOptions: { safe: true, discardComments: { removeAll: true } }, //傳遞給 cssProcessor 的選項,預設為{}
      canPrint: true //布林值,指示外掛是否可以將訊息列印到控制檯,預設為 true
    })
  ]
}
複製程式碼

再開啟 css 檔案可以發現已經被壓縮了,並且開啟 index.html 也是有樣式的

(三) 處理 SCSS 檔案

安裝 sass 依賴:

npm i node-sass sass-loader --save-dev
複製程式碼

在 src 資料夾下新增 scss 資料夾及 main.scss 檔案

main.scss 引入樣式

$bgColor: black !default;
*,
body {
  margin: 0;
  padding: 0;
}
html {
  background-color: $bgColor;
}
複製程式碼

在 app.js 中引入 main.scss 檔案

import './css/base.css'
import './scss/main.scss'
複製程式碼

修改配置檔案

const path = require('path')

const CleanWebpackPlugin = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

const MiniCssExtractPlugin = require('mini-css-extract-plugin') // 將 css 單獨打包成檔案
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin') // 壓縮 css

module.exports = {
  entry: {
    app: './src/app.js'
  },
  output: {
    publicPath: './', // js 引用的路徑或者 CDN 地址
    path: path.resolve(__dirname, 'dist'), // 打包檔案的輸出目錄
    filename: '[name].bundle.js', // 程式碼打包後的檔名
    chunkFilename: '[name].js' // 程式碼拆分後的檔名
  },
  module: {
    rules: [
      {
        test: /\.(scss|css)$/, // 針對 .scss 或者 .css 字尾的檔案設定 loader
        use: [
          {
            loader: MiniCssExtractPlugin.loader
          },
          'css-loader',
          'sass-loader' // 使用 sass-loader 將 scss 轉為 css
        ]
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      // 打包輸出HTML
      title: '自動生成 HTML',
      minify: {
        // 壓縮 HTML 檔案
        removeComments: true, // 移除 HTML 中的註釋
        collapseWhitespace: true, // 刪除空白符與換行符
        minifyCSS: true // 壓縮內聯 css
      },
      filename: 'index.html', // 生成後的檔名
      template: 'index.html', // 根據此模版生成 HTML 檔案
      chunks: ['app'] // entry中的 app 入口才會被打包
    }),
    new MiniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[id].css'
    }),
    new OptimizeCssAssetsPlugin({
      assetNameRegExp: /\.css$/g,
      cssProcessor: require('cssnano'), //用於優化\最小化 CSS 的 CSS處理器,預設為 cssnano
      cssProcessorOptions: { safe: true, discardComments: { removeAll: true } }, //傳遞給 cssProcessor 的選項,預設為{}
      canPrint: true //布林值,指示外掛是否可以將訊息列印到控制檯,預設為 true
    })
  ]
}
複製程式碼

module.rules.use 陣列中,loader 的位置。根據 webpack 規則:放在最後的 loader 首先被執行,從上往下寫的話是下面先執行,從左往右寫的話是右邊先執行

['style-loader', 'css-loader', 'sass-loader']
複製程式碼

執行順序為 sass-loader --> css-loader --> style-loader

首先應該利用 sass-loader 將 scss 編譯為 css,剩下的配置和處理 css 檔案相同。

打包後再開啟 index.html 檔案會發現樣式已經被 main.scss 中寫的覆蓋了,處理 scss 成功

(四) 為 CSS 加上瀏覽器字首

安裝 postcss-loaderautoprefixer 依賴

npm install postcss-loader autoprefixer --save-dev
複製程式碼

src/scss/main.css 中新增這段程式碼

.example {
  display: grid;
  transition: all 0.5s;
  user-select: none;
  background: linear-gradient(to bottom, white, black);
}
複製程式碼

有兩種方式來配置 postcss,第一種是直接寫在 webpack.config.js 中

module: {
  rules: [
    {
      test: /\.(sa|sc|c)ss$/, // 針對 .sass .scss 或者 .css 字尾的檔案設定 loader
      use: [
        {
          loader: MiniCssExtractPlugin.loader
        },
        'css-loader',
        'sass-loader', // 使用 sass-loader 將 scss 轉為 css
        // 使用 postcss 為 css 加上瀏覽器字首
        {
          loader: 'postcss-loader',
          options: {
            plugins: [require('autoprefixer')]
          }
        }
      ]
    }
  ]
}
複製程式碼

打包完之後,檢視 dist/app.css 檔案

24 個例項入門並掌握「Webpack4」(一)

第二種方式,在 webpack.config.js 同級目錄下,新建 postcss.config.js 配置檔案

module.exports = {
  plugins: [require('autoprefixer')]
}
複製程式碼

同時在 webpack.config.js 中

module: {
  rules: [
    {
      test: /\.(sa|sc|c)ss$/, // 針對 .sass .scss 或者 .css 字尾的檔案設定 loader
      use: [
        {
          loader: MiniCssExtractPlugin.loader
        },
        'css-loader',
        'sass-loader', // 使用 sass-loader 將 scss 轉為 css
        'postcss-loader' // 使用 postcss 為 css 加上瀏覽器字首
      ]
    }
  ]
},
複製程式碼

由於 module 中的 rules 是倒著執行的,以上的執行順序是 postcss-loader -> sass-loader -> css-loader -> `MiniCssExtractPlugin.loader``

``postcss-loader` 要放在最下面,也就是第一個執行的 loader

補充:

在 css-loader 中使用 importLoaders 屬性

module: {
  rules: [
    {
      test: /\.(sa|sc|c)ss$/, // 針對 .sass .scss 或者 .css 字尾的檔案設定 loader
      use: [
        {
          loader: MiniCssExtractPlugin.loader
        },
        {
          loader: css - loader,
          options: {
            importLoaders: 2
          }
        },
        'sass-loader', // 使用 sass-loader 將 scss 轉為 css
        'postcss-loader' // 使用 postcss 為 css 加上瀏覽器字首
      ]
    }
  ]
}
複製程式碼

importLoaders: 2 表示:在一個 css 中引入了另一個 css,也會執行之前兩個 loader,即 postcss-loader 和 sass-loader

參考:webpack 官網指南

個人部落格

24 個例項入門並掌握「Webpack4」(二)

24 個例項入門並掌握「Webpack4」(三)

相關文章