webpack 5.x 快速入門

totravel發表於2021-02-28

簡介

透過具體的示例,結合 Bootstrap 4.x、Vue.js 3.x 和 Electron 11.x,全面介紹 webpack 5.x 的配置、用法。

本文原始碼 在此

起步

檢查 node.js 和 npm 的版本。

$ node -v
v15.10.0
$ npm  -v
7.6.0

新建一個目錄,進入。新建 package.json 檔案。

$ mkdir webpack-demo
$ cd webpack-demo
$ npm init -y

為了提高下載速度,安裝 cnpm。

$ npm install -g cnpm --registry=https://registry.npm.taobao.org

用 cnpm 安裝 webpack 及其命令列工具。檢查安裝的版本。

$ cnpm i -D webpack webpack-cli
$ npx webpack -v
webpack 5.24.2
webpack-cli 4.5.0

package.jsonscripts 中新增 "build": "webpack"

  "scripts": {
    "build": "webpack"
  }

這樣就可以用 npm run build 命令來執行一次打包。

新建兩個子目錄 srcdist,分別用來放置原始檔和打包輸出。

$ mkdir src dist

新建 ./src/app.js 檔案,作為打包入口。

$ touch ./src/app.js
alert('Hello webpack!');

新建 index.html 檔案,假設打包輸出為 ./dist/bundle.js

$ touch index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <title>Hello World!</title>
  </head>
  <body>
    <p>Hello webpack, Bootstrap 4.x, Vue.js 3.x and Electron 11.x!</p>

    <script src="./dist/bundle.js"></script>
  </body>
</html>

最終的目錄結構:

$ tree -L 2 -I "node_modules"
.
|-- dist
|-- index.html
|-- package.json
`-- src
    `-- app.js

2 directories, 3 files

基本配置

習慣上將 webpack 的配置檔案命名為 webpack.config.js,須手動建立,放在專案根目錄下,webpack 會自動引用此配置檔案。

$ touch webpack.config.js
const path = require('path');

module.exports = {
  // production 生產環境
  // 或 development 開發環境
  mode: 'production',

  // 打包的起點
  entry: './src/app.js',

  // 打包的輸出
  output: {
    filename: 'bundle.js',
    // 輸出目錄的絕對路徑和相對路徑(相對網站目錄)
    path: path.resolve(__dirname, 'dist'),
    publicPath: '/'
  },

  // 模組,裝載器等,見下文
  module: {},

  // 外掛,`.css` 的剝離和壓縮、`.html` 的動態生成等,見下文
  plugins: []
};

嘗試進行一次打包:

$ npm run build

> webpack-demo@1.0.0 build
> npx webpack

asset bundle.js 24 bytes [compared for emit] [minimized] (name: main)
./src/app.js 28 bytes [built] [code generated]
webpack 5.24.2 compiled successfully in 278 ms

$ tree -L 2 -I "node_modules"
.
|-- dist
|   `-- bundle.js
|-- index.html
|-- package.json
|-- src
|   `-- app.js
`-- webpack.config.js

2 directories, 5 files

嘗試用瀏覽器開啟 index.html,看是否有彈窗。

認識裝載器

裝載器在 webpack.config.jsmodule.rules 中列出。

  module: {
    rules: [
    ]
  }

rules 中的元素是物件型別,通常包含 test use 兩個屬性。test 匹配檔名,use 列出裝載器,由此形成一條流水線(呼叫順序與書寫順序相反)。

  module: {
    rules: [
      {
        test: /\.less$/,
        use: [
            'style-loader',
            {
                loader: 'css-loader',
                options: { importLoaders: 1 }
            },
            {
                loader: 'less-loader',
                options: { noIeCompat: true }
            }
        ]
      }
    ]
  }

上例表示以 .less 結尾的檔案依次用 less-loader css-loader style-loader 處理。

只需一個裝載器時,用 loader 代替 use

      {
        test: /\.(png|svg|jpg|gif)$/,
        loader: 'file-loader'
      }

新增轉載器

安裝上述提到的 3 個的裝載器:

$ cnpm i -D style-loader css-loader file-loader
  • css-loader 負責解析 .js 檔案中的 import '.css' 以及 .css 檔案中的 @import '.css'
  • style-loader 負責生成 <style> 標籤並追加到 <head> 標籤中。

webpack.config.jsmodule.rules 中新增:

  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        loader: 'file-loader'
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        loader: 'file-loader'
      }
    ]
  }

新建 ./src/app.css 檔案。

$ touch ./src/app.css
p {
  background-color: blue;
}

./src/app.js 中引入:

import './app.css';
$ rm -f ./dist/* && npm run build
$ tree -L 2 -I "node_modules"
.
|-- dist
|   `-- bundle.js
|-- index.html
|-- package.json
|-- src
|   |-- app.css
|   `-- app.js
`-- webpack.config.js

2 directories, 6 files

檢查 <p> 標籤的背景色。

使用 Bootstrap 4.x

安裝 Bootstrap 及其依賴包。

$ cnpm i -D bootstrap jquery popper.js

./src/app.js 的頂部插入:

import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';

index.html 中使用 Bootstrap 的樣式類 text-success

    <p class="text-danger">Hello webpack, Bootstrap 4.x, Vue.js 3.x and Electron 11.x!</p>

重新打包,檢查字型的顏色。

省略字尾名

.js 檔案中用 import 匯入模組時,可以省略模組的字尾名,但必須在 webpack.config.jsresolve.extensions 中列出。

  resolve: {
    extensions: ['.js', '.json', '.css', '.sass', '.vue']
  }

多入口

新建 JS 檔案。

$ touch ./src/admin.js
alert('Hello admin!');

webpack.config.js 中,entry 改成物件型別,output.filename[name] 拼接。

  entry: {
    app: './src/app.js',
    admin: './src/admin.js'
  },
  output: {
    filename: '[name].bundle.js',
  },

相應修改 index.html

    <script src="./dist/app.bundle.js"></script>
    <script src="./dist/admin.bundle.js"></script>
$ rm -f ./dist/* && npm run build
$ tree -L 2 -I "node_modules"
.
|-- dist
|   |-- admin.bundle.js
|   |-- app.bundle.js
|   |-- app.bundle.js.LICENSE.txt
|-- index.html
|-- package.json
|-- src
|   |-- admin.js
|   |-- app.css
|   `-- app.js
`-- webpack.config.js

2 directories, 9 files

重新打包,檢查是否有兩次彈窗。

第二次彈窗

分離第三方庫

可以單獨打包第三方庫,而不在 .js 檔案中引入,只須在 entry.vendors 中列出第三方庫,這會得到 vendors.bundle.js 檔案(檔名取決於 output.filename)。

  entry: {
    vendors: ['bootstrap', 'jquery', 'popper.js']
  }

index.html 中新增:

    <script src="./dist/vendors.bundle.js"></script>

註釋 ./src/app.js 中的 import 'bootstrap'

// import 'bootstrap';

剝離 CSS

剝離 .js 引入的 .css,合併成單獨的 .css 檔案,每個入口對應一個。

安裝外掛 mini-css-extract-plugin

$ cnpm i -D mini-css-extract-plugin

plugins 中例項化外掛,在 module.rules 中應用外掛。同時,為方便演示:

  • 刪除 ./src/admin.js,相應修改 entry
  • 修改 output.filename
$ rm -f ./src/admin.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

  entry: {
    // admin: './src/admin.js',
  },
  output: {
    // filename: '[name].bundle.js',
    filename: '[name].js',
  },
  module: {
    rules: [
      {
        test: /\.css/,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      },
    ]
  },
  plugins: [
    // 將輸出 `[name].css`
    new MiniCssExtractPlugin({ filename: '[name].css' })
  ]

修改 index.html

    <link rel="stylesheet" href="./dist/app.css">

    <script src="./dist/vendors.js"></script>
    <script src="./dist/app.js"></script>
$ rm -f ./dist/* && npm run build
$ tree -L 2 -I "node_modules"
.
|-- dist
|   |-- app.css
|   |-- app.js
|   |-- vendors.js
|   `-- vendors.js.LICENSE.txt
|-- index.html
|-- package.json
|-- src
|   |-- app.css
|   `-- app.js
`-- webpack.config.js

2 directories, 9 files

壓縮 CSS

安裝外掛 css-minimizer-webpack-plugin

$ cnpm i -D css-minimizer-webpack-plugin

optimization.minimizer 中例項化即可。

const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

  optimization: {
    minimizer: ['...', new CssMinimizerPlugin()]
  }

... 表示對 minimizer 進行擴充套件而不是覆蓋,以保留內建的 .js 壓縮外掛。

自動清理打包輸出

安裝外掛 clean-webpack-plugin

$ cnpm i -D clean-webpack-plugin

plugins 中例項化即可。

const { CleanWebpackPlugin } = require('clean-webpack-plugin');

  plugins: [
    new CleanWebpackPlugin()
  ]

帶 hash 的檔名、自動生成 index.html

安裝外掛 html-webpack-plugin

$ cnpm i -D html-webpack-plugin

檔名用 [fullhash] 拼接。在 plugins 中呼叫 HtmlWebpackPlugin 指定模板 .ejs、輸出檔名、是否將 .css .js 直接嵌入模板而不透過 <link> <script> 引用。

const HtmlWebpackPlugin = require('html-webpack-plugin');

  output: {
    filename: '[name].[fullhash].js',
  },
  plugins: [
    new MiniCssExtractPlugin({ filename: '[name].[fullhash].css' }),
    new HtmlWebpackPlugin({
      template: './src/index.ejs',
      filename: 'index.html',
      inject: false
    })
  ]

刪除沒用的 index.html,新建模板 ./src/index.ejs

$ rm -f ./index.html
$ touch ./src/index.ejs
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <title>Hello World!</title>

    <link rel="stylesheet" href="<%= htmlWebpackPlugin.files.css[0] %>">
  </head>
  <body>
    <p class="text-danger">Hello webpack, Bootstrap 4.x, Vue.js 3.x and Electron 11.x!</p>

    <script src="<%= htmlWebpackPlugin.files.js[0] %>"></script>
    <script src="<%= htmlWebpackPlugin.files.js[1] %>"></script>
  </body>
</html>

重新打包,檢查是否正確生成 ./dist/index.html

$ npm run build
$ tree -L 2 -I "node_modules"
.
|-- dist
|   |-- app.7faf852820591e38bbd0.css
|   |-- app.7faf852820591e38bbd0.js
|   |-- index.html
|   |-- vendors.7faf852820591e38bbd0.js
|   `-- vendors.7faf852820591e38bbd0.js.LICENSE.txt
|-- package.json
|-- src
|   |-- app.css
|   |-- app.js
|   `-- index.ejs
`-- webpack.config.js

2 directories, 10 files

開發時配置

安裝 webpack-merge

$ cnpm i -D webpack-merge

新增配置檔案 webpack.dev.config.js

$ touch webpack.dev.config.js
const { merge } = require('webpack-merge');
const common = require('./webpack.config.js');

module.exports = merge(common, {
  // 開發環境
  mode: 'development',
  // 到原始碼的對映
  devtool: 'inline-source-map'
});

用處見下文。

使用 webpack-dev-server

webpack-dev-server 可以快速開啟一個 Web 服務,並在檔案發生改變時重新編譯並通知瀏覽器。

安裝 webpack-dev-server

$ cnpm i -D webpack-dev-server

webpack.dev.config.js 中新增配置項 devServer

  devServer: {
    host: "127.0.0.1",
    port: 8080,
    contentBase: __dirname + '/assets', // 圖示、圖片等靜態資源的位置
    historyApiFallback: true // 相容 HTML5 history API
  }

修改 package.jsonscripts

  "scripts": {
    "dev": "webpack serve -c webpack.dev.config.js --open"
  }

執行 npm run dev 檢視效果。

$ tree -L 2 -I "node_modules"
.
|-- dist
|-- package.json
|-- src
|   |-- app.css
|   |-- app.js
|   `-- index.ejs
|-- webpack.config.js
`-- webpack.dev.config.js

2 directories, 6 files

使用 Vue.js 3.x

安裝。

$ cnpm i -D vue@next vue-loader@next @vue/compiler-sfc

修改 webpack.config.jsmodule.rulesplugins

const webpack = require('webpack');
const { VueLoaderPlugin } = require("vue-loader");

  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
    ]
  },
  plugins: [
    new VueLoaderPlugin(),
    new webpack.DefinePlugin({
      "__VUE_OPTIONS_API__": true,
      "__VUE_PROD_DEVTOOLS__": false,
    })
  ],

修改 ./src/index.ejs

  <body>
    <div id="app" v-cloak></div>

  </body>

修改 ./src/app.css

[v-cloak] {
  display: none;
}

新增檔案 ./src/app.vue

$ touch ./src/app.vue
<template>
  <p @click="onClick" class="text-danger">
    Hello webpack, Bootstrap 4.x, Vue.js 3.x and Electron 11.x!
  </p>
</template>

<script>
  export default {
    methods: {
      onClick() {
        alert('clicked');
      }
    }
  };
</script>

./src/app.js 中新增:

import { createApp } from 'vue';
import app from './app.vue';

createApp(app).mount('#app');
$ tree -L 2 -I "node_modules"
.
|-- dist
|-- package.json
|-- src
|   |-- app.css
|   |-- app.js
|   |-- app.vue
|   `-- index.ejs
|-- webpack.config.js
`-- webpack.dev.config.js

2 directories, 7 files

執行 npm run dev 檢視效果。

使用 Electron 11.x

webpack.config.js 中增加 target 並修改 output

  target: 'electron-renderer',

  output: {
    // filename: '[name].[fullhash].js',
    filename: '[name].js',
    // publicPath: '/'
    publicPath: './'

  plugins: [
    // new MiniCssExtractPlugin({ filename: '[name].[fullhash].css' }),
    new MiniCssExtractPlugin({ filename: '[name].css' }),

必須設定 target,否則不能在渲染程式中使用 fs path 等模組。

安裝 Electron。

$ cnpm i -D electron electron-packager
$ npx electron -v
v11.3.0
$ npx electron-packager --version
Electron Packager 15.2.0
Node v15.10.0
Host Operating system: win32 10.0.19042 (x64)

新建 main.js 作為主程式。

$ touch main.js
const { app, BrowserWindow } = require('electron');

function createWindow() {
    const options = {
        width: 400,
        height: 300,
        webPreferences: { nodeIntegration: true }
    };
    const win = new BrowserWindow(options);
    win.loadFile('./dist/index.html');
}

app.whenReady().then(createWindow);

app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') app.quit();
});

app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
});

win.loadFile('./dist/index.html'); 取決於 electron 的工作目錄。

修改 package.json

  "main": "main.js",
  "scripts": {
    "build": "webpack",
    "dev": "electron .",
    "test": "webpack && electron .",
    "make": "electron-packager . --ignore='\\.gitignore|webpack*\\.js|node_modules|src' --overwrite --download.mirrorOptions.mirror=https://npm.taobao.org/mirrors/electron/"
  }
  • --ignore 忽略 .gitignore webpack*.js node_modules/ src/ 等檔案。
  • --overwrite 如存在舊的軟體包,直接覆蓋。
  • --download 從淘寶 NPM 映象下載。

./src/index.ejs 新增:

    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';">

用 webpack 打包,用 Electron 執行。

$ npm run build
$ tree -L 2 -I "node_modules"
.
|-- dist
|   |-- app.css
|   |-- app.js
|   |-- index.html
|   |-- vendors.js
|   `-- vendors.js.LICENSE.txt
|-- main.js
|-- package.json
|-- src
|   |-- app.css
|   |-- app.js
|   |-- app.vue
|   `-- index.ejs
|-- webpack.config.js
`-- webpack.dev.config.js

2 directories, 13 files

$ npm run dev

可以用 npm test 代替以上兩個命令。

打包成 .exe 檔案。

$ npm run make

> webpack-demo@1.0.0 make
> electron-packager . --ignore='\\.gitignore|webpack*\\.js|node_modules|src' --overwrite --download.mirrorOptions.mirror=https://npm.taobao.org/mirrors/electron/

Packaging app for platform win32 x64 using electron v11.3.0
Wrote new app to...
本作品採用《CC 協議》,轉載必須註明作者和本文連結