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

Zsh發表於2019-04-12

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

九、JS Tree Shaking

demo9 原始碼地址

什麼是 Tree Shaking?

字面意思是搖樹,專案中沒有使用的程式碼會在打包的時候丟掉。JS 的 Tree Shaking 依賴的是 ES6 的模組系統(比如:import 和 export)

專案目錄如下:

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

在 util.js 檔案中寫入測試程式碼

// util.js
export function a() {
  return 'this is function "a"'
}

export function b() {
  return 'this is function "b"'
}

export function c() {
  return 'this is function "c"'
}
複製程式碼

在 app.js 中引用 util.js 的 function a() 函式,按需引入

// app.js
import { a } from './vendor/util'
console.log(a())
複製程式碼

命令列執行 webpack 打包後,開啟打包後生成的 /dist/app.bundle.js 檔案。查詢我們 a() 函式輸出的字串,如下圖所示:

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

如果將查詢內容換成 this is function "c" 或者 this is function "b", 並沒有相關查詢結果。說明 JS Tree Shaking 成功。

1. 如何處理第三方 JS 庫?

對於經常使用的第三方庫(例如 jQuery、lodash 等等),如何實現 Tree Shaking ?

下面以 lodash.js 為例,進行介紹。

安裝 lodash.js : npm install lodash --save

在 app.js 中引用 lodash.js 的一個函式:

// app.js
import { chunk } from 'lodash'
console.log(chunk([1, 2, 3], 2))
複製程式碼

命令列打包。如下圖所示,打包後大小是 70kb。顯然,只引用了一個函式,不應該這麼大。並沒有進行 Tree Shaking。

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

開頭講過,js tree shaking 利用的是 ES 的模組系統。而 lodash.js 沒有使用 CommonJS 或者 ES6 的寫法。所以,安裝對應的模組系統即可。

安裝 lodash.js 的 ES 寫法的版本:npm install lodash-es --save

修改一下 app.js:

// app.js
import { chunk } from 'lodash-es'
console.log(chunk([1, 2, 3], 2))
複製程式碼

再次打包,打包結果只有 3.5KB(如下圖所示)。顯然,tree shaking 成功。

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

在一些對載入速度敏感的專案中使用第三方庫,請注意庫的寫法是否符合 ES 模板系統規範,以方便 webpack 進行 tree shaking。

十、CSS Tree Shaking

demo10 原始碼地址

CSS Tree Shaking 並不像 JS Tree Shaking 那樣方便理解,首先要模擬一個真實的專案環境,來體現 CSS 的 Tree Shaking 的配置和效果。

此章節原始碼基於第八節處理 CSS 專案上做修改

我們首先編寫 /src/css/base.css 樣式檔案,在檔案中,我們編寫了 3 個樣式類。但在程式碼中,我們只會使用 .box 和 .box--big 這兩個類。程式碼如下所示:

/* base.css */
html {
  background: red;
}

.box {
  height: 200px;
  width: 200px;
  border-radius: 3px;
  background: green;
}

.box--big {
  height: 300px;
  width: 300px;
  border-radius: 5px;
  background: red;
}

.box-small {
  height: 100px;
  width: 100px;
  border-radius: 2px;
  background: yellow;
}
複製程式碼

按照正常使用習慣,DOM 操作來實現樣式的新增和解除安裝,是一貫技術手段。所以,入口檔案 /src/app.js 中建立了一個 <div> 標籤,並且將它的類設為 .box

// app.js
import base from './css/base.css'

// 給 app 標籤再加一個 div 並且類名為 box
var app = document.getElementById('app')
var div = document.createElement('div')
div.className = 'box'
app.appendChild(div)
複製程式碼

最後,為了讓環境更接近實際環境,我們在 index.html 的一個標籤,也引用了定義好的 box-big 樣式類。

<!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>CSS Tree Shaking</title>
  </head>

  <body>
    <div id="app">
      <div class="box-big"></div>
    </div>
  </body>
</html>
複製程式碼

PurifyCSS將幫助我們進行 CSS Tree Shaking 操作。為了能準確指明要進行 Tree Shaking 的 CSS 檔案,還有 glob-all (另一個第三方庫)。

glob-all 的作用就是幫助 PurifyCSS 進行路徑處理,定位要做 Tree Shaking 的路徑檔案。

安裝依賴:

npm i glob-all purify-css purifycss-webpack --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 PurifyCSS = require('purifycss-webpack')
const glob = require('glob-all')

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$/, // 針對 .scss 或者 .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
    new PurifyCSS({
      paths: glob.sync([
        // 要做 CSS Tree Shaking 的路徑檔案
        path.resolve(__dirname, './*.html'), // 請注意,我們同樣需要對 html 檔案進行 tree shaking
        path.resolve(__dirname, './src/*.js')
      ])
    })
  ]
}
複製程式碼

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

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

在 index.html 和 src/app.js 中引用的樣式都被打包了,而沒有被使用的樣式類–box-small,沒有被打包進去

十一、圖片處理彙總

demo11 原始碼地址

目錄結構:

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

webpack4 中的圖片常用的基礎操作:

  • 圖片處理和 Base64 編碼
  • 圖片壓縮
  • 合成雪碧圖

(一) 準備工作

如專案程式碼目錄展示的那樣,除了常見的 app.js 作為入口檔案,我們將用到的 3 張圖片放在 /src/assets/imgs/ 目錄下,並在樣式檔案 base.css 中引用這些圖片。

剩下的內容交給 webpack 打包處理即可。樣式檔案和入口 app.js 檔案的程式碼分別如下所示:

/* base.css */
*,
body {
  margin: 0;
  padding: 0;
}
.box {
  height: 400px;
  width: 400px;
  border: 5px solid #000;
  color: #000;
}
.box div {
  width: 100px;
  height: 100px;
  float: left;
}
.box .ani1 {
  background: url('./../assets/imgs/1.jpg') no-repeat;
}
.box .ani2 {
  background: url('./../assets/imgs/2.png') no-repeat;
}
.box .ani3 {
  background: url('./../assets/imgs/3.png') no-repeat;
}
複製程式碼

app.js

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

安裝依賴:

npm install url-loader file-loader --save-dev
複製程式碼

(二) 圖片處理和 base64 編碼

webpack.config.js 中的 module.rules 選項中進行配置,以實現讓 loader 識別圖片字尾名,並且進行指定的處理操作。

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpg|jpeg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              name: '[name]-[hash:5].min.[ext]',
              outputPath: 'images/', //輸出到 images 資料夾
              limit: 20000 //把小於 20kb 的檔案轉成 Base64 的格式
            }
          }
        ]
      }
    ]
  }
}
複製程式碼

完整的配置檔案

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$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader
          },
          'css-loader'
        ]
      },
      {
        test: /\.(png|jpg|jpeg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              name: '[name]-[hash:5].min.[ext]',
              outputPath: 'images/', //輸出到 images 資料夾
              limit: 20000 //把小於 20kb 的檔案轉成 Base64 的格式
            }
          }
        ]
      }
    ]
  },
  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'
    })
  ]
}
複製程式碼

打包專案,檢視打包結果,並在瀏覽器中開啟 index.html 檔案

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

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

可以看到除了 1.jpg,另外兩張圖片已經被打包成 base64 格式,在 app.css 檔案中

1.jpg 這個檔案超過我們在 url-loader 選項中設定的 limit 值,所以被單獨打包

這就是利用了 file-loader 的能力,如果在 url-loader 中設定了 limit 的值,卻沒有安裝 file-loader 依賴,會怎麼樣?來試試看,首先解除安裝 file-loader 依賴npm uninstall file-loader,再執行打包命令,npm run build

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

如果圖片較多,會發很多 http 請求,會降低頁面效能。

url-loader 會將引入的圖片編碼,轉為 base64 字串。再把這串字元打包到檔案中,最終只需要引入這個檔案就能訪問圖片了,節省了圖片請求。

但是,如果圖片較大,編碼會消耗效能。因此 url-loader 提供了一個 limit 引數,小於 limit 位元組的檔案會被轉為 base64,大於 limit 的使用 file-loader 進行處理,單獨打包。

url-loader 依賴 file-loader,url-loader 可以看作是增強版的 file-loader

(三) 圖片壓縮

圖片壓縮需要使用 img-loader 外掛,除此之外,針對不同的圖片型別,還要引用不同的外掛。比如,我們專案中使用的是 png 圖片,因此,需要引入 imagemin-pngquant,並且指定壓縮率。壓縮 jpg/jpeg 圖片為 imagemin-mozjpeg 外掛

這裡有個 bug,可以先不急著操作,先把這一小節看完,再決定!!

安裝依賴

npm i img-loader imagemin imagemin-pngquant imagemin-mozjpeg --save-dev
複製程式碼

在之前的配置上更改:

{
  test: /\.(png|jpg|jpeg|gif)$/,
  use: [
    {
      loader: 'url-loader',
      options: {
        name: '[name]-[hash:5].min.[ext]',
        outputPath: 'images/', // 輸出到 images 資料夾
        limit: 20000 //把小於 20kb 的檔案轉成 Base64 的格式
      }
    }
  ]
}
複製程式碼

更改為:

{
  test: /\.(png|jpg|jpeg|gif)$/,
  use: [
    {
      loader: 'url-loader',
      options: {
        name: '[name]-[hash:5].min.[ext]',
        limit: 1000, // size <= 1KB
        outputPath: 'images/'
      }
    },
    // img-loader for zip img
    {
      loader: 'img-loader',
      options: {
        plugins: [
          require('imagemin-pngquant')({
            quality: '80' // the quality of zip
          }),
          require('imagemin-mozjpeg')({
            quality: '80'
          })
        ]
      }
    }
  ]
}
複製程式碼

打包結果:

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

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

原因在 png 圖片上,jpg 圖片可以壓縮,但是去 imagemin-pngquant github 上也沒發現有人提出類似 issue ,百度、google 找了半天,還是沒發現怎麼解決 ?,於是使用另一種壓縮圖片的外掛 image-webpack-loader

首先解除安裝了之前的依賴:

npm uni img-loader imagemin imagemin-pngquant imagemin-mozjpeg

安裝依賴:

npm i image-webpack-loader --save-dev

這個依賴安裝的時間會比較久。。。可以先去做別的。。。

在之前的配置上更改:

{
  test: /\.(png|jpg|jpeg|gif)$/,
  use: [
    {
      loader: 'url-loader',
      options: {
        name: '[name]-[hash:5].min.[ext]',
        outputPath: 'images/', // 輸出到 images 資料夾
        limit: 20000 //把小於 20kb 的檔案轉成 Base64 的格式
      }
    }
  ]
}
複製程式碼

更改為:

{
  test: /\.(png|jpg|jpeg|gif)$/,
  use: [
    {
      loader: 'url-loader',
      options: {
        name: '[name]-[hash:5].min.[ext]',
        limit: 1000, // size <= 1KB
        outputPath: 'images/'
      }
    },
    // img-loader for zip img
    {
      loader: 'image-webpack-loader',
      options: {
        // 壓縮 jpg/jpeg 圖片
        mozjpeg: {
          progressive: true,
          quality: 65 // 壓縮率
        },
        // 壓縮 png 圖片
        pngquant: {
          quality: '65-90',
          speed: 4
        }
      }
    }
  ]
}
複製程式碼

這裡故意url-loaderlimit 屬性值設的很小,不讓它轉化 png 圖片為 base64,因為我們要測試壓縮 png 圖片

打包結果:

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

圖片壓縮成功,這裡我仔細看了下image-webpack-loader 的 github,其實這個 image-webpack-loader 外掛內建了好幾種圖片壓縮的外掛

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

這裡讓我很疑惑,為什麼我直接安裝 imagemin-pngquant 不行,反而使用 image-webpack-loader 卻可以,於是我去檢視 package-lock.json 檔案,搜尋 image-webpack-loader

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

我看了下我之前安裝的是最新的版本, ^7.0.0 !!!

阿西吧... 終於找到問題所在,新版本有些問題沒處理好,導致壓縮 png 圖片失敗,知道問題就好辦了,在 package.json 中,將 imagemin-pngquant 版本改為 ^6.0.0,重新 npm install

再按照之前的操作,就可以壓縮成了,對應版本如下:

{
  "devDependencies": {
    "imagemin": "^6.1.0",
    "imagemin-mozjpeg": "^8.0.0",
    "imagemin-pngquant": "^6.0.0",
    "img-loader": "^3.0.1"
  }
}
複製程式碼

如果使用 image-webpack-loader ,版本為 4.6.0 ,引入的依賴版本也在白框內

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

這次我還是使用 image-webpack-loader,朋友們可以自行選擇使用哪個外掛,只是 image-webpack-loader 引入了其他圖片格式壓縮的依賴,如 svg/webp/gif 等,只安裝 image-webpack-loader 就夠了,而另一種則是要一個個外掛裝過去,其實原理都一樣

經過這次除錯,明白並不是最新的版本就是最好的,新版本也許有哪些地方沒處理好,或者是不能相容其他外掛,導致報錯

所以安裝第三方依賴的時候,還是要謹慎一點npm install 預設是安裝最新版,如果出了問題,回滾到之前的穩定版,不僅僅適用於 webpack 外掛,對於其他軟體或者工具也是這樣

寫這一小節的時間為:2019-3-9,之後的版本變動出現報錯的話,可以不用安裝最新版,回滾到之前的版本試試

(四) 生成雪碧圖

安裝依賴:

npm i postcss-loader postcss-sprites --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 單獨打包成檔案

/*********** sprites config ***************/
let spritesConfig = {
  spritePath: './dist/images'
}
/******************************************/

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$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader
          },
          'css-loader',
          /*********** loader for sprites ***************/
          {
            loader: 'postcss-loader',
            options: {
              ident: 'postcss',
              plugins: [require('postcss-sprites')(spritesConfig)]
            }
          }
          /*********************************************/
        ]
      },
      {
        test: /\.(png|jpg|jpeg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              name: '[name]-[hash:5].min.[ext]',
              limit: 1000, // size <= 1KB
              outputPath: 'images/'
            }
          },
          // img-loader for zip img
          {
            loader: 'image-webpack-loader',
            options: {
              // 壓縮 jpg/jpeg 圖片
              mozjpeg: {
                progressive: true,
                quality: 65 // 壓縮率
              },
              // 壓縮 png 圖片
              pngquant: {
                quality: '65-90',
                speed: 4
              }
            }
          }
        ]
      }
    ]
  },
  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'
    })
  ]
}
複製程式碼

打包後檢視結果:

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

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

雪碧圖是為了減少網路請求,所以被處理雪碧圖的圖片多為各式各樣的 logo 或者大小相等的小圖片

而對於大圖片,不推薦使用雪碧圖。這樣會使得圖片體積很大

除此之外,雪碧圖要配合 css 程式碼進行定製化使用。要通過 css 程式碼在雪碧圖上精準定位需要的圖片

十二、字型檔案處理

demo12 原始碼地址

專案目錄為:

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

package.json 中使用的依賴如下:

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

app.js 中引入字型檔案

import './assets/fonts/iconfont.css'
複製程式碼

配置 webpack.config.js 檔案來處理字型

藉助 url-loader,可以識別並且處理 eot、woff 等結尾的字型檔案。同時,根據字型檔案大小,可以靈活配置是否進行 base64 編碼。

下面的 demo 就是當檔案大小小於 5000B 的時候,進行 base64 編碼。

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$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader
          },
          'css-loader'
        ]
      },
      {
        test: /\.(eot|woff2?|ttf|svg)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              name: '[name]-[hash:5].min.[ext]',
              limit: 5000, // fonts file size <= 5KB, use 'base64'; else, output svg file
              publicPath: 'fonts/',
              outputPath: 'fonts/'
            }
          }
        ]
      }
    ]
  },
  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'
    })
  ]
}
複製程式碼

在 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>
    <div id="app">
      <div class="box">
        <i class="iconfont icon-xiazai"></i>
        <i class="iconfont icon-shoucang"></i>
        <i class="iconfont icon-erweima"></i>
        <i class="iconfont icon-xiangshang"></i>
        <i class="iconfont icon-qiehuanzuhu"></i>
        <i class="iconfont icon-sort"></i>
        <i class="iconfont icon-yonghu"></i>
      </div>
    </div>
  </body>
</html>
複製程式碼

打包後檢視 index.html 檔案,打包成功

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

十三、處理第三方 js 庫

demo13 原始碼地址

專案目錄:

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

1. 如何使用和管理第三方 JS 庫?

專案做大之後,開發者會更多專注在業務邏輯上,其他方面則盡力使用第三方 JS 庫來實現。

由於 js 變化實在太快,所以出現了多種引入和管理第三方庫的方法,常用的有 3 中:

  • CDN:<script></script> 標籤引入即可
  • npm 包管理:目前最常用和最推薦的方法
  • 本地 js 檔案:一些庫由於歷史原因,沒有提供 ES6 版本,需要手動下載,放入專案目錄中,再手動引入。

針對第三種方法,如果沒有 webpack,則需要手動引入 import 或者 require 來載入檔案;但是,webpack 提供了 alias 的配置,配合 webpack.ProvidePlugin 這款外掛,可以跳過手動入,直接使用!

2. 編寫入口檔案

如專案目錄圖片所展示的,我們下載了 jquery.min.js,放到了專案中。同時,我們也通過 npm 安裝了 jquery

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

為了儘可能模仿生產環境,app.js 中使用了 $ 來呼叫 jq,還使用了 jQuery 來呼叫 jq。

因為正式專案中,由於需要的依賴過多,掛載到 window 物件的庫,很容易發生命名衝突問題。此時,就需要重新命名庫。例如:$ 就被換成了 jQuery。

在 app.js 中進行修改

// app.js
$('div').addClass('new')

jQuery('div').addClass('old')

// 執行webpack後
// 瀏覽器開啟 index.html, 檢視 div 標籤的 class
複製程式碼
  1. 編寫配置檔案

webpack.ProvidePlugin 引數是鍵值對形式,鍵就是我們專案中使用的變數名,值就是鍵所指向的庫。

webpack.ProvidePlugin 會先從 npm 安裝的包中查詢是否有符合的庫

如果 webpack 配置了 resolve.alias 選項(理解成 “別名”),那麼 webpack.ProvidePlugin 就會順著這條鏈一直找下去。

const path = require('path')
const webpack = require('webpack')

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' // 程式碼拆分後的檔名
  },
  resolve: {
    alias: {
      jQuery$: path.resolve(__dirname, 'src/vendor/jquery.min.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 檔案
      chunks: ['app'] // entry中的 app 入口才會被打包
    }),
    new webpack.ProvidePlugin({
      $: 'jquery', // npm
      jQuery: 'jQuery' // 本地Js檔案
    })
  ]
}
複製程式碼

修改 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>處理第三方 js 庫</title>
  </head>
  <body>
    <div></div>
  </body>
</html>
複製程式碼

打包並在 Chrome 中開啟 index.html。如下圖所示,<div> 標籤已經被新增上了 oldnew 兩個樣式類。證明在 app.js 中使用的 $ 和 jQuery 都成功指向了 jquery 庫。

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

十四、開發模式與 webpack-dev-server

demo14 原始碼地址

1. 為什麼需要開發模式?

這十幾節來我們使用最多的就是生產環境,也就是執行 npm run build 命令,打包專案中的各種檔案及壓縮

而開發模式就是指定 mode 為 development。對應我們在 package.json 中配置的,就是 npm run dev,在第二小節也涉及到了這一點

在開發模式下,我們需要對程式碼進行除錯。對應的配置就是:devtool 設定為 source-map。在非開發模式下,需要關閉此選項,以減小打包體積。詳情見: devtool

在開發模式下,還需要熱過載路由重定向設定代理等功能,webpack4 已經提供了 devServer 選項,啟動一個本地伺服器,讓開發者使用這些功能。

目錄結構:

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

安裝依賴

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

修改 package.json

{
  "scripts": {
    "dev": "webpack-dev-server --open",
    "build": "webpack --mode production"
  },
  "devDependencies": {
    "clean-webpack-plugin": "^2.0.0",
    "html-loader": "^0.5.5",
    "html-webpack-plugin": "^3.2.0",
    "jquery": "^3.3.1",
    "webpack": "^4.29.6",
    "webpack-cli": "^3.2.3",
    "webpack-dev-server": "^3.2.1"
  }
}
複製程式碼

因為我們在 package.json 中配置了 script,所以開啟開發模式直接 npm run dev 即可

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

雖然控制檯輸出了打包資訊(假設我們已經配置了熱過載),但是磁碟上並沒有建立 /dist/ 資料夾和打包檔案。控制檯的打包檔案的相關內容是儲存在記憶體之中的。

修改 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>webpack-dev-server</title>
  </head>

  <body>
    This is Index html
  </body>
</html>
複製程式碼

按照專案目錄,簡單封裝下 /vendor/ 下的三個 js 檔案,以方便 app.js 呼叫:

// 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) {
  console.log('I am sum.js')
  return a + b
}
複製程式碼

app.js 中使用三種引入方式引入 js 檔案:

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

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

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

現在開始更改 webpack.config.js, 完整的配置檔案如下:

const webpack = require('webpack')
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' // 程式碼拆分後的檔名
  },
  mode: 'development', // 開發模式
  devtool: 'source-map', // 開啟除錯
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    port: 8000, // 本地伺服器埠號
    hot: true, // 熱過載
    overlay: true, // 如果程式碼出錯,會在瀏覽器頁面彈出“浮動層”。類似於 vue-cli 等腳手架
    proxy: {
      // 跨域代理轉發
      '/comments': {
        target: 'https://m.weibo.cn',
        changeOrigin: true,
        logLevel: 'debug',
        headers: {
          Cookie: ''
        }
      }
    },
    historyApiFallback: {
      // HTML5 history模式
      rewrites: [{ from: /.*/, to: '/index.html' }]
    }
  },
  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 webpack.HotModuleReplacementPlugin(), // 熱部署模組
    new webpack.NamedModulesPlugin(),
    new webpack.ProvidePlugin({
      $: 'jquery', // npm
      jQuery: 'jQuery' // 本地Js檔案
    })
  ]
}
複製程式碼

對上面的配置進行單獨分析:

  • 模組熱更新

模組熱更新需要 HotModuleReplacementPluginNamedModulesPlugin 這兩個外掛,並且順序不能錯,並且指定 devServer.hot 為 true

const webpack = require('webpack') // 引入 webpack

module.exports = {
  plugins: [
    new webpack.HotModuleReplacementPlugin(), // 熱部署模組
    new webpack.NamedModulesPlugin()
  ]
}
複製程式碼

有了這兩個外掛,在專案的 js 程式碼中可以針對偵測到變更的檔案並且做出相關處理,也就不用寫完程式碼重新重新整理頁面,它會自動檢測變更的程式碼並且在頁面上更改

注意是 js 程式碼,如果你去改動 index.html 檔案,儲存後,頁面並不會更改,反之你去修改了 js 檔案,儲存後,頁面會更新

比如,我們啟動開發模式後,修改了 vendor/sum.js 這個檔案,此時,需要在瀏覽器的控制檯列印一些資訊。那麼,app.js 中就可以這麼寫:

if (module.hot) {
  // 檢測是否有模組熱更新
  module.hot.accept('./vendor/sum.js', function() {
    // 針對被更新的模組, 進行進一步操作
    console.log('/vendor/sum.js is changed')
  })
}
複製程式碼

每當 sum.js 被修改後,都可以自動執行回撥函式。

瀏覽器控制檯輸出資訊如下:

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

但是我們日常開發中使用 vue 腳手架根本沒有寫過這樣的程式碼,也能熱更新,是因為 vue-loader 中內建了這種方法,css-loader 中也有,所以我們改完 js 和 css 程式碼就能直接看到更新

  • 跨域代理

隨著前後端分離開發的普及,跨域請求變得越來越常見。為了快速開發,可以利用 devServer.proxy 做一個代理轉發,來繞過瀏覽器的跨域限制。

devServer 模組的底層是使用了 http-proxy-middleware,能配置的東西非常多

按照前面的配置檔案,如果想呼叫微博的一個介面:m.weibo.cn/comments/ho… /comments/hotflow 進行請求即可,在 app.js 中新增如下程式碼:

$.get(
  '/comments/hotflow',
  {
    id: '4263554020904293',
    mid: '4263554020904293',
    max_id_type: '0'
  },
  function(data) {
    console.log(data)
  }
)
複製程式碼

上面程式碼是使用 jQuery 傳送 get 請求,如果是在 vue 專案中,一般是使用 axios 來傳送請求

修改完 app.js 後儲存,開啟之前的 localhost:8000 網頁,可以看到 Network 傳送的請求

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

  • HTML5–History

當專案使用 HTML5 History API 時,任意的 404 響應都可能需要被替代為 index.html。

在 SPA(單頁應用)中,任何響應直接被替代為 index.html。

在 vuejs 官方的腳手架 vue-cli 中,開發模式下配置如下:

historyApiFallback: {
  // HTML5 history模式
  rewrites: [{ from: /.*/, to: '/index.html' }]
}
複製程式碼

最終 app.js 中的程式碼如下:

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

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

require(['./vendor/multi'], function(multi) {
  console.log('multi(1, 2) = ', multi(1, 2))
})

$.get(
  '/comments/hotflow',
  {
    id: '4263554020904293',
    mid: '4263554020904293',
    max_id_type: '0'
  },
  function(data) {
    console.log(data)
  }
)

if (module.hot) {
  // 檢測是否有模組熱更新
  module.hot.accept('./vendor/sum.js', function() {
    // 針對被更新的模組, 進行進一步操作
    console.log('/vendor/sum.js is changed')
  })
}
複製程式碼

開啟控制檯,可以看到程式碼都正常執行沒有出錯。除此之外,由於開啟了 source-map,所以可以定位程式碼位置(下圖紅框內):

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

參考文章: webpack4 系列教程 (十五):開發模式與 webpack-dev-server

十五、開發模式和生產模式・實戰

demo15 原始碼地址

首先,新建一個資料夾:demo15,執行 npm init -y 初始化 package.json,生成後的檔案如下:

{
  "name": "example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
複製程式碼

我們先將無用的程式碼清除掉,只留下關鍵程式碼:

{
  "scripts": {}
}
複製程式碼

首先安裝 webpack 所需依賴

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

安裝 babel7,因為目前主要是用 ES6 來編寫程式碼,所以需要轉譯

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

現在 package.json 中的依賴為:

{
  "scripts": {},
  "devDependencies": {
    "@babel/core": "^7.3.4",
    "@babel/plugin-transform-runtime": "^7.3.4",
    "@babel/preset-env": "^7.3.4",
    "babel-loader": "^8.0.5",
    "webpack": "^4.29.6",
    "webpack-cli": "^3.2.3",
    "webpack-dev-server": "^3.2.1"
  },
  "dependencies": {
    "@babel/polyfill": "^7.2.5",
    "@babel/runtime": "^7.3.4"
  }
}
複製程式碼

新建 .babelrc 來配置 babel 外掛,程式碼如下:

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

新建 .browserslistrc 檔案配置該專案所支援的瀏覽器版本

# 所支援的瀏覽器版本

> 1% # 全球使用情況統計選擇的瀏覽器版本
last 2 version # 每個瀏覽器的最後兩個版本
not ie <= 8 # 排除小於 ie8 及以下的瀏覽器
複製程式碼

在開始配置 webpack.config.js 檔案之前,需要注意一下,因為現在我們是有兩種模式,production(生產)  和 development(開發)  模式。

安裝自動生成 html 依賴

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

安裝 css/字型圖示處理依賴

npm i css-loader style-loader mini-css-extract-plugin optimize-css-assets-webpack-plugin --save-dev
複製程式碼

安裝 scss 處理依賴

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

為不同核心的瀏覽器加上 CSS 字首

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

圖片及字型處理:

npm i url-loader file-loader image-webpack-loader --save-dev
複製程式碼

第三方 js 庫

npm i jquery
複製程式碼

現在 package.json 為:

{
  "scripts": {},
  "devDependencies": {
    "@babel/core": "^7.3.4",
    "@babel/plugin-transform-runtime": "^7.3.4",
    "@babel/preset-env": "^7.3.4",
    "autoprefixer": "^9.4.10",
    "babel-loader": "^8.0.5",
    "clean-webpack-plugin": "^2.0.0",
    "css-loader": "^2.1.1",
    "file-loader": "^3.0.1",
    "html-loader": "^0.5.5",
    "html-webpack-plugin": "^3.2.0",
    "image-webpack-loader": "^4.6.0",
    "mini-css-extract-plugin": "^0.5.0",
    "node-sass": "^4.11.0",
    "optimize-css-assets-webpack-plugin": "^5.0.1",
    "postcss-loader": "^3.0.0",
    "sass-loader": "^7.1.0",
    "style-loader": "^0.23.1",
    "url-loader": "^1.1.2",
    "webpack": "^4.29.6",
    "webpack-cli": "^3.2.3",
    "webpack-dev-server": "^3.2.1"
  },
  "dependencies": {
    "@babel/polyfill": "^7.2.5",
    "@babel/runtime": "^7.3.4",
    "jquery": "^3.3.1"
  }
}
複製程式碼

之前我們大多都是寫生產模式,也就是經常說的打包,但是我們日常開發專案,用的是開發模式。

只有在專案做完後,要部署到 nginx 上的時候才使用生產模式,將程式碼打包後放到 nginx

之所以要分兩種模式是因為,開發模式下,需要加快編譯的速度,可以熱更新以及設定跨域地址,開啟原始碼除錯(devtool: 'source-map')

而生成模式下,則需要壓縮 js/css 程式碼,拆分公共程式碼段,拆分第三方 js 庫等操作

所以這裡的配置我們分成三個檔案來寫,一個是生產配置,一個是開發配置,最後一個是基礎配置

即:webpack.base.conf.js(基礎配置)、webpack.dev.conf.js(開發配置)、webpack.prod.conf.js(生產配置)

新建 build 資料夾,建立上述三個檔案,專案結構為:

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

這裡需要使用到一個外掛,webpack-merge 用來合併配置,比如開發環境就合併開發配置 + 基礎配置,生產就合併生產配置 + 基礎配置

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

先簡單寫個 webpack.base.conf.js 的示例程式碼

const merge = require('webpack-merge')

const productionConfig = require('./webpack.prod.conf') // 引入生產環境配置檔案
const developmentConfig = require('./webpack.dev.conf') // 引入開發環境配置檔案

const baseConfig = {} // ... 省略

module.exports = env => {
  let config = env === 'production' ? productionConfig : developmentConfig
  return merge(baseConfig, config) // 合併 公共配置 和 環境配置
}
複製程式碼
  • 引入 webpack-merge 外掛來合併配置
  • 引入生產環境和開發環境
  • 編寫基礎配置
  • 匯出合併後的配置檔案

在程式碼中區分不同環境:

module.exports = env => {
  let config = env === 'production' ? productionConfig : developmentConfig
  return merge(baseConfig, config) // 合併 公共配置 和 環境配置
}
複製程式碼

這裡的 env 在 package.json 中進行配置,修改 scripts,新增 "dev" 和 "build" 命令

注意,這裡有個 --env 欄位,與 webpack.base.conf.js 中的 env 是聯動的,告訴它當前是什麼環境,然後合併成什麼環境

{
  "scripts": {
    "dev": "webpack-dev-server --env development --open --config build/webpack.base.conf.js",
    "build": "webpack --env production --config build/webpack.base.conf.js"
  }
}
複製程式碼

(一) 編寫基礎配置

const webpack = require('webpack')
const merge = require('webpack-merge')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin') // 將 css 單獨打包成檔案
const CleanWebpackPlugin = require('clean-webpack-plugin')

const path = require('path')

const productionConfig = require('./webpack.prod.conf.js') // 引入生產環境配置檔案
const developmentConfig = require('./webpack.dev.conf.js') // 引入開發環境配置檔案

/**
 * 根據不同的環境,生成不同的配置
 * @param {String} env "development" or "production"
 */
const generateConfig = env => {
  // 將需要的 Loader 和 Plugin 單獨宣告

  let scriptLoader = [
    {
      loader: 'babel-loader'
    }
  ]

  let cssLoader = [
    'style-loader',
    'css-loader',
    'sass-loader', // 使用 sass-loader 將 scss 轉為 css
    'postcss-loader' // 使用 postcss 為 css 加上瀏覽器字首
  ]

  let cssExtractLoader = [
    {
      loader: MiniCssExtractPlugin.loader
    },
    'css-loader',
    'sass-loader', // 使用 sass-loader 將 scss 轉為 css
    'postcss-loader' // 使用 postcss 為 css 加上瀏覽器字首
  ]

  let fontLoader = [
    {
      loader: 'url-loader',
      options: {
        name: '[name]-[hash:5].min.[ext]',
        limit: 5000, // fonts file size <= 5KB, use 'base64'; else, output svg file
        publicPath: 'fonts/',
        outputPath: 'fonts/'
      }
    }
  ]

  let imageLoader = [
    {
      loader: 'url-loader',
      options: {
        name: '[name]-[hash:5].min.[ext]',
        limit: 10000, // size <= 10KB
        outputPath: 'images/'
      }
    },
    // 圖片壓縮
    {
      loader: 'image-webpack-loader',
      options: {
        // 壓縮 jpg/jpeg 圖片
        mozjpeg: {
          progressive: true,
          quality: 50 // 壓縮率
        },
        // 壓縮 png 圖片
        pngquant: {
          quality: '65-90',
          speed: 4
        }
      }
    }
  ]

  let styleLoader =
    env === 'production'
      ? cssExtractLoader // 生產環境下壓縮 css 程式碼
      : cssLoader // 開發環境:頁內樣式嵌入

  return {
    entry: { app: './src/app.js' },
    output: {
      publicPath: env === 'development' ? '/' : './',
      path: path.resolve(__dirname, '..', 'dist'),
      filename: '[name]-[hash:5].bundle.js',
      chunkFilename: '[name]-[hash:5].chunk.js'
    },
    module: {
      rules: [
        { test: /\.js$/, exclude: /(node_modules)/, use: scriptLoader },
        { test: /\.(sa|sc|c)ss$/, use: styleLoader },
        { test: /\.(eot|woff2?|ttf|svg)$/, use: fontLoader },
        { test: /\.(png|jpg|jpeg|gif)$/, use: imageLoader }
      ]
    },
    plugins: [
      // 開發環境和生產環境二者均需要的外掛
      new HtmlWebpackPlugin({
        title: 'webpack4 實戰',
        filename: 'index.html',
        template: path.resolve(__dirname, '..', 'index.html'),
        // chunks: ['app'],
        minify: {
          collapseWhitespace: true
        }
      }),
      new webpack.ProvidePlugin({ $: 'jquery' }),
      new CleanWebpackPlugin()
    ]
  }
}

module.exports = env => {
  let config = env === 'production' ? productionConfig : developmentConfig
  return merge(generateConfig(env), config) // 合併 公共配置 和 環境配置
}
複製程式碼

以上配置建議多看幾遍熟悉熟悉,為什麼要這樣寫

(二) 編寫開發環境配置檔案

const webpack = require('webpack')

const path = require('path')

module.exports = {
  mode: 'development',
  devtool: 'source-map', // 除錯原始碼
  devServer: {
    contentBase: path.join(__dirname, '../dist/'),
    port: 8000,
    hot: true,
    overlay: true,
    proxy: {
      '/comments': {
        target: 'https://m.weibo.cn',
        changeOrigin: true,
        logLevel: 'debug',
        headers: {
          Cookie: ''
        }
      }
    },
    historyApiFallback: true
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NamedModulesPlugin()
  ]
}
複製程式碼

開發配置主要是設定跨域、開啟原始碼除錯、熱更新

(三) 編寫生產環境配置檔案

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

module.exports = {
  mode: 'production',
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        jquery: {
          name: 'chunk-jquery', // 單獨將 jquery 拆包
          priority: 15,
          test: /[\\/]node_modules[\\/]jquery[\\/]/
        }
      }
    }
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[id].css'
    }),
    // 壓縮 css
    new OptimizeCssAssetsPlugin({
      assetNameRegExp: /\.css$/g, //一個正規表示式,指示應優化/最小化的資產的名稱。提供的正規表示式針對配置中ExtractTextPlugin例項匯出的檔案的檔名執行,而不是源CSS檔案的檔名。預設為/\.css$/g
      cssProcessor: require('cssnano'), //用於優化\最小化 CSS 的 CSS處理器,預設為 cssnano
      cssProcessorOptions: { safe: true, discardComments: { removeAll: true } }, //傳遞給 cssProcessor 的選項,預設為{}
      canPrint: true //一個布林值,指示外掛是否可以將訊息列印到控制檯,預設為 true
    })
  ]
}
**生產配置主要是拆分程式碼,壓縮 css**
複製程式碼

(四) 測試開發模式

執行 npm run dev

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

並且自動開啟瀏覽器,圖片和字型都出來了,開啟控制檯也能看到跨域成功、原始碼定位,因為將 devtool 設定為 'source-map',所以就會生成 map 檔案,體積較大

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

(五) 測試生產模式

執行 npm run build

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

開啟 dist/index.html 檔案

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

生產模式下跨域失敗是很正常的,而且如果是 vue 專案打包完之後是無法直接開啟 index.html 檔案檢視效果的,必須要放在伺服器上,一般都是將打包後的檔案放入 nginx 中,在 nginx 中配置跨域地址

還有一種配置 webpack 開發和生產環境的方式,會比較常用:

修改 webpack.base.conf.js

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

module.exports = {
  entry: {
    app: './src/app.js'
  },
  output: {
    path: path.resolve(__dirname, '..', 'dist')
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader'
          }
        ]
      },
      {
        test: /\.(png|jpg|jpeg|gif)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              name: '[name]-[hash:5].min.[ext]',
              limit: 1000, // size <= 1KB
              outputPath: 'images/'
            }
          },
          // img-loader for zip img
          {
            loader: 'image-webpack-loader',
            options: {
              // 壓縮 jpg/jpeg 圖片
              mozjpeg: {
                progressive: true,
                quality: 65 // 壓縮率
              },
              // 壓縮 png 圖片
              pngquant: {
                quality: '65-90',
                speed: 4
              }
            }
          }
        ]
      },
      {
        test: /\.(eot|ttf|svg)$/,
        use: {
          loader: 'url-loader',
          options: {
            name: '[name]-[hash:5].min.[ext]',
            limit: 5000, // fonts file size <= 5KB, use 'base64'; else, output svg file
            publicPath: 'fonts/',
            outputPath: 'fonts/'
          }
        }
      }
    ]
  },
  plugins: [
    // 開發環境和生產環境二者均需要的外掛
    new HtmlWebpackPlugin({
      title: 'webpack4 實戰',
      filename: 'index.html',
      template: path.resolve(__dirname, '..', 'index.html'),
      minify: {
        collapseWhitespace: true
      }
    }),
    new webpack.ProvidePlugin({ $: 'jquery' }),
    new CleanWebpackPlugin()
  ],
  performance: false
}
複製程式碼

修改 webpack.dev.conf.js

const webpack = require('webpack')
const merge = require('webpack-merge')
const commonConfig = require('./webpack.base.conf.js')

const path = require('path')

const devConfig = {
  mode: 'development',
  output: {
    filename: '[name].js',
    chunkFilename: '[name].js'
  },
  module: {
    rules: [
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2 // 在一個 css 中引入了另一個 css,也會執行之前兩個 loader,即 postcss-loader 和 sass-loader
            }
          },
          'sass-loader', // 使用 sass-loader 將 scss 轉為 css
          'postcss-loader' // 使用 postcss 為 css 加上瀏覽器字首
        ]
      }
    ]
  },
  devtool: 'cheap-module-eval-soure-map',
  devServer: {
    contentBase: path.join(__dirname, '../dist/'),
    port: 8000,
    hot: true,
    overlay: true,
    proxy: {
      '/comments': {
        target: 'https://m.weibo.cn',
        changeOrigin: true,
        logLevel: 'debug',
        headers: {
          Cookie: ''
        }
      }
    },
    historyApiFallback: true
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NamedModulesPlugin()
  ]
}

module.exports = merge(commonConfig, devConfig)
複製程式碼

修改 webpack.prod.conf.js

const merge = require('webpack-merge')
const commonConfig = require('./webpack.base.conf.js')

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

const prodConfig = {
  mode: 'production',
  output: {
    filename: '[name].[contenthash].js',
    chunkFilename: '[name].[contenthash].js'
  },
  devtool: 'cheap-module-source-map',
  module: {
    rules: [
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader
          },
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2 // 在一個 css 中引入了另一個 css,也會執行之前兩個 loader,即 postcss-loader 和 sass-loader
            }
          },
          'sass-loader', // 使用 sass-loader 將 scss 轉為 css
          'postcss-loader' // 使用 postcss 為 css 加上瀏覽器字首
        ]
      }
    ]
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        jquery: {
          name: 'jquery', // 單獨將 jquery 拆包
          priority: 15,
          test: /[\\/]node_modules[\\/]jquery[\\/]/
        },
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors'
        }
      }
    }
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name]-[contenthash].css',
      chunkFilename: '[id]-[contenthash].css'
    }),
    // 壓縮 css
    new OptimizeCssAssetsPlugin({
      assetNameRegExp: /\.css$/g, //一個正規表示式,指示應優化/最小化的資產的名稱。提供的正規表示式針對配置中ExtractTextPlugin例項匯出的檔案的檔名執行,而不是源CSS檔案的檔名。預設為/\.css$/g
      cssProcessor: require('cssnano'), //用於優化\最小化 CSS 的 CSS處理器,預設為 cssnano
      cssProcessorOptions: { safe: true, discardComments: { removeAll: true } }, //傳遞給 cssProcessor 的選項,預設為{}
      canPrint: true //一個布林值,指示外掛是否可以將訊息列印到控制檯,預設為 true
    })
  ]
}

module.exports = merge(commonConfig, prodConfig)
複製程式碼

修改 package.json 的 script 命令

{
  "scripts": {
    "dev": "webpack-dev-server --open --config ./build/webpack.dev.conf.js",
    "build": "webpack --config ./build/webpack.prod.conf.js"
  }
}
複製程式碼

在之前的基礎又修改了一下配置,重新打包即可

十六、打包自定義函式庫

demo16 原始碼地址

新建 index.jsmath.jsstring.js

// index.js
import * as math from './math'
import * as string from './string'

export default { math, string }

// math.js
export function add(a, b) {
  return a + b
}

export function minus(a, b) {
  return a - b
}

export function multiply(a, b) {
  return a * b
}

export function division(a, b) {
  return a / b
}

// string.js
export function join(a, b) {
  return a + ' ' + b
}
複製程式碼

程式碼寫完,使用 webpack 打包,安裝 webpack,-D 表示 --save-dev 的簡寫

npm i webpack webpack-cli -D
複製程式碼

修改 package.json

{
  "name": "library",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "xh",
  "license": "MIT",
  "devDependencies": {
    "webpack": "^4.29.6",
    "webpack-cli": "^3.3.0"
  }
}
複製程式碼

"license": "MIT"表示完全開源的協議,name 表示你的元件庫的名稱

新建 webpack.config.js 並配置

const path = require('path')

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'library.js'
  }
}
複製程式碼

執行打包命令,生成 library.js 檔案

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

這個檔案就可以在專案中用了,但是我們現在是要做一個開源庫,是給別人用的,別人可能會這麼用

// ES module
import library from 'library'

// CommonJS
const library = require('library')

// AMD
require(['library'], function() {})
複製程式碼

如果我們要支援這三種形式的使用,可以在 webpack 裡配置,加上 libraryTarget 引數

const path = require('path')

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'library.js',
    libraryTarget: 'umd'
  }
}
複製程式碼

當然,如果你希望使用者還可以使用 script 標籤的形式引入

<script src="library.js"></script>

使用者希望可以通過 library 全域性變數來使用,比如 library.math 要怎麼辦

可以再配置一個引數,叫 library

const path = require('path')

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'library.js',
    library: 'root', // root 可以隨便更換,不是固定值
    libraryTarget: 'umd'
  }
}
複製程式碼

umd 是支援前面三種語法,但是不支援全域性變數這種用法,如果配置了 library,打包之後就會將程式碼掛載到 root 這個全域性變數上,通過 script 來引入 library,現在來打包一下,打包完之後來測試用 script 標籤來引入我們寫的庫

在 dist 目錄下新建個 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>
    <script src="./library.js"></script>
  </head>
  <body></body>
</html>
複製程式碼

在控制檯中輸入 root,回車,就能看到我們前面封裝的函式了

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

libraryTarget: 'umd' 如果將 umd 改為 this

再去打包,在控制檯輸入 this.root 也能看到效果

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

libraryTarget 也可以填 window,如果在 node 環境下也可以使用 global

不過一般我們都是使用 umd

還有一種情況要注意,我們現在寫的 string.js 我覺得寫的不好,lodash 寫的更好,我要引入這個第三方庫,來代替我寫的一些功能

npm install lodash

// string.js
import _ from 'lodash'

export function join(a, b) {
  return _.join([a, b], ' ')
}
複製程式碼

重新打包,體積為 70kb,因為我們也把 lodash 也打包進去了

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

別人要使用我們的庫的話,需要這樣 import library from 'library',也許別人也會用到 lodash 庫,結果變成了這樣:

import _ from 'lodash'
import library from 'library'
複製程式碼

終端使用者的程式碼中就會存在兩份 lodash 的程式碼,這種情況就要再去更改一下我們的 webpack 配置

const path = require('path')

module.exports = {
  mode: 'production',
  entry: './src/index.js',
  externals: ['lodash'],
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'library.js',
    library: 'root',
    libraryTarget: 'umd'
  }
}
複製程式碼

externals 會在打包的過程中,如果遇到了 lodash 這個庫,就不會打包進去,可以寫成陣列形式也可以是字串,更改完後再次打包

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

可以發現我們庫裡使用的 lodash 並沒有被打包進去,體積只有 1kb

這個時候別人再次使用我們的 library 這個庫,如果不引入 lodash,則會失敗,別人在使用 library 之前要先引入 lodash

如果改為 externals: 'lodash',則使用的時候為,import lodash from lodash,而不能用 _ 下劃線來代替 lodash, import _ from lodash

如果要讓別人使用你的庫,其實就是使用你打包後的檔案,需要先在 package.json,將 main: index.js 改為 main: ./dist/library.js,通過 npm 釋出之前,你要確保你的庫的 name 不會和別人上線的 name 衝突,改一個有特點的 name,來確保能釋出成功,如 library-xh-2019,感興趣的可以自己去研究一下如何通過 npm 釋出

{
  "name": "library-xh-2019",
  "version": "1.0.0",
  "description": "",
  "main": "./dist/library.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "xh",
  "license": "MIT",
  "devDependencies": {
    "webpack": "^4.29.6",
    "webpack-cli": "^3.3.0"
  },
  "dependencies": {
    "lodash": "^4.17.11"
  }
}
複製程式碼

個人部落格

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

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

相關文章