【webpack系列】從核心概念到上手設定

2023-06-20 15:00:54

前言

作為前端開發者,相信大家或多或少都接觸過webpack,現如今webpack已經滲透在了前端的各個方面,所以我們有必要來了解並學習webpackwebpack 是一種用於構建 JavaScript 應用程式的靜態模組打包器,它能夠以一種相對一致且開放的處理方式,載入應用中的所有資原始檔(圖片、CSS、視訊、字型檔案等),並將其合併打包成瀏覽器相容的 Web 資原始檔。webpack相比其它構建工具功能更加強大,可延伸性也更強,它能夠融合多種工程化工具,將開發階段的應用程式碼編譯、打包成適合網路分發、使用者端執行的應用產物。

核心概念

輸入輸出

entry

webpack的構建入口,入口起點(entry point) 指示 webpack 應該使用哪個模組,來作為構建其內部 依賴圖(dependency graph) 的開始。進入入口起點後,webpack 會找出有哪些模組和庫是入口起點(直接和間接)依賴的。

// 單入口
module.exports = {
  entry: './src/main.js'
}
​
// 多入口
module.exports = {
  entry: {
    a: './src/a.js',
    b: './src/b.js'
  }
}

output

output 屬性告訴 webpack 在哪裡輸出它所建立的 bundle,以及如何命名這些檔案。主要輸出檔案的預設值是 ./dist/main.js,其他生成檔案預設放置在 ./dist 資料夾中。

// 單入口
module.exports = {
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',   
    path: path.resolve(__dirname, 'dist'),
  }
}
// 多入口
module.exports = {
  entry: {
    a: './src/a.js',
    b: './src/b.js'
  },
  output: {
    filename: '[name].[hash:6].js', // 通過預留位置確保檔名唯一,考慮快取問題,還可以為檔名加上hash
    path: __dirname + '/dist',
    publicPath: '/',    // 生產環境一般是CDN地址,開發環境設定為/或不設定
  }
}

模組處理

loader

webpack 只能理解 JavaScript 和 JSON 檔案,這是 webpack 開箱可用的自帶能力。loader 讓 webpack 能夠去處理其他型別的檔案,並將它們轉換為有效 模組,以供應用程式使用,以及被新增到依賴圖中。

比如:設定webpack為css檔案應用css-loader

module.exports = {
  module: {
    rules: [
      {test: /.css$/, use: 'css-loader'}
    ]
  }
}

module.rules 允許你在 webpack 設定中指定多個 loader。 這種方式是展示 loader 的一種簡明方式,並且有助於使程式碼變得簡潔和易於維護。

plugin

外掛 是 webpack 的 支柱 功能。Webpack 自身也是構建於你在 webpack 設定中用到的 相同的外掛系統 之上!

外掛目的在於解決 loader 無法實現的其他事。Webpack 提供很多開箱即用的 外掛

比如:為編譯過程新增進度報告外掛

const Webpack = require('webpack')
module.exports = {
  plugins: [new Webpack.ProgressPlugin()]
}

resolve

用於設定模組路徑解析規則,可用於幫助 Webpack 更精確、高效地找到指定模組

比如設定別名:

建立 importrequire 的別名,來確保模組引入變得更簡單。例如,一些位於 src/ 資料夾下的常用模組:

module.exports = {
  resolve: {
    alias: {
      node_modules: path.resolve(__dirname, './node_modules'),
      '@': path.resolve(__dirname, './src'),
      api: path.resolve(__dirname, './src/api'),
      components: path.join(__dirname, './src/components'),
    }
  }
}

module

這些選項決定了如何處理專案中的不同型別的模組

比如我們常見的loader就是在module.rules內設定的。

module.exports = {
  module: {
    rules: [
      {test: /.css$/, use: 'css-loader'}
    ]
  }
}

externals

用於宣告外部資源,Webpack 會直接忽略這部分資源,跳過這些資源的解析、打包操作

比如防止將某些 import 的包(package)打包到 bundle 中,而是在執行時(runtime)再去從外部獲取這些擴充套件依賴(external dependencies)

比如:從CDN引入Vue

<!-- index.html -->
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.3/vue.min.js"></script>
// webpack.config.js
module.exports = {
  externals: {
    vue: 'vue'
  }
}

後處理

optimization

用於控制如何優化產物包體積,內建 Dead Code Elimination、Scope Hoisting、程式碼混淆、程式碼壓縮等功能

從 webpack 4 開始,會根據你選擇的 mode 來執行不同的優化, 不過所有的優化還是可以手動設定和重寫。

module.exports = {
  //...
  optimization: {
    chunkIds: 'named',
  },
};

target

用於設定編譯產物的目標執行環境,支援 web、node、electron 等值,不同值最終產物會有所差異

比如:target設定為node,webpack將在node環境下進行編譯

module.exports = {
  target: 'node'
}

mode

提供 mode 設定選項,告知 webpack 使用相應模式的內建優化。

string = 'production': 'none' | 'development' | 'production'
module.exports = {
  mode: 'development',
};

或者從cli--mode 引數進行傳遞

webpack --mode development

開發效率

watch

啟用 Watch 模式。這意味著在初始構建之後,webpack 將繼續監聽任何已解析檔案的更改。

module.exports = {
  watch: true
}

⚠️注意:webpack-dev-serverwebpack-dev-middleware預設是開啟watch模式的

devtool

此選項控制是否生成,以及如何生成 source map。

string = 'eval' | false

選擇一種 source map 風格來增強偵錯過程。不同的值會明顯影響到構建(build)和重新構建(rebuild)的速度。

devServer

用於設定與 HMR 強相關的開發伺服器功能

通過 webpack-dev-server 的這些設定,能夠以多種方式改變其行為,這裡比較常見的設定有:porthostproxy

module.exports = {
  devServer: {
    static: {
      directory: path.join(__dirname, 'public'),
    },
    compress: true,
    port: 9000,
  }
}

cache

Webpack 5 之後,該項用於控制如何快取編譯過程資訊與編譯結果

快取生成的 webpack 模組和 chunk,來改善構建速度。cache 會在開發 模式被設定成 type: 'memory' 而且在 生產 模式 中被禁用。 cache: truecache: { type: 'memory' } 設定作用一致。 傳入 false 會禁用快取:

module.exports = {
  cache: false
}

上手設定

瞭解完上面這些webpack核心概念,我們可以嘗試來手動設定好一個Vue開發環境

初始化專案

首先npm init -y初始化package.json檔案

接著安裝好我們的webpackwebpack-cli

npm i webpack webpack-cli -D

⚠️注意: 我這裡的webpack是5版本的

"webpack": "^5.85.1",
"webpack-cli": "^4.7.2",

處理Vue程式碼

原生 Webpack 並不能處理這種內容格式的檔案,為此我們需要引入專用於 Vue SFC 的載入器:vue-loader

npm i vue-loader
// webpack.config.js

const Webpack = require('webpack')
const {VueLoaderPlugin}  = require('vue-loader')
module.exports = {
    entry: './src/main.js',
    output: {
        filename: 'bundle.[hash:6].js',
        path: __dirname + '/dist',
    },
    module: {
        rules:[
            {test: /.vue$/, use: 'vue-loader'}, 
        ]
    },
    plugins:[
        new Webpack.ProgressPlugin(),
        new VueLoaderPlugin(),
    ],
}

提示:vue-loader 庫同時提供用於處理 SFC 程式碼轉譯的 Loader 元件,與用於處理上下文相容性的 Plugin 元件,兩者需要同時設定才能正常執行。

此時我們的檔案結構是這樣的,大致與Vue專案結構一致

嘗試啟動看一下:

// package.json
"dev": "webpack --mode development",
npm run dev

由於我們的vue檔案中有css內容,而webpack預設是不理解css內容的,所以導致報錯了

處理CSS內容

這裡需要安裝style-loadercss-loader來進行處理。

修改webpack設定

// webpack.config.js
const Webpack = require('webpack')
const {VueLoaderPlugin}  = require('vue-loader')
module.exports = {
    entry: './src/main.js',
    output: {
        filename: 'bundle.[hash:6].js',
        path: __dirname + '/dist',
    },
    module: {
        rules:[
            {test: /.vue$/, use: 'vue-loader'},
            {
                test: /.css$/,
                use: ['style-loader', 'css-loader']
            }
        ]
    },
    plugins:[
        new Webpack.ProgressPlugin(),
        new VueLoaderPlugin(),
    ],
}

此時再跑起來,發現沒有報錯了。

處理JS內容

我們平時在開發中肯定會用到ES6語法,這裡我們也需要設定對應的loader來進行處理

安裝babel-loader

npm i babel-loader @babel/preset-env @babel/core

設定

module.exports = {
  module: {
    rules: [
      {
        test: /.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        },
        exclude: /node_modules/
      }
    ]
  }
}

當然這裡的options設定你也可以在.babelrcbabel.config.js中單獨設定。

處理圖片資源

與CSS一樣,webpack也是預設不理解圖片的,所以這裡也需要設定loader進行處理。

webpack4

在webpack4中,我們常用的處理圖片的loader有:file-loaderurl-loader

// webpack.config.js
module.exports = {
  // ...
  module: {
    rules: [{
      test: /.(png|jpg|gif|jpeg)$/,
      use: ['file-loader']
    }],
  },
};

經過 file-loader 處理後,原始圖片會被重新命名並複製到產物資料夾,同時在程式碼中插入圖片 URL 地址

  • url-loader 將檔案作為 data URI 內聯到 bundle 中,它有兩種表現,對於小於閾值 limit 的影象直接轉化為 base64 編碼;大於閾值的影象則呼叫 file-loader 進行載入
module.exports = {
  // ...
  module: {
    rules: [{
      test: /.(png|jpg|gif|jpeg)$/,
      use: [{
        loader: 'url-loader',
        options: {
          limit: 1024
        }
      }]
    }],
  },
};

經過 url-loader 處理後,小於 limit 引數即 1024B 的圖片會被轉譯為 Base64 編碼,對於超過 limit 值的圖片則直接呼叫 file-loader 完成載入。

webpack5

file-loaderurl-loader並不侷限於處理圖片,它們還可以被用於載入任意型別的多媒體或文字檔案,使用頻率極高,幾乎已經成為標配元件!所以 Webpack5 直接內建了這些能力,開箱即可使用。

用法上,原本需要安裝、匯入 Loader,Webpack5 之後只需要通過 module.rules.type 屬性指定資源型別即可

比如:

module.exports = {
  // ...
  module: {
    rules: [{
      test: /.(png|jpg|gif|jpeg)$/,
      type: 'asset/resource'
    }],
  },
};

執行頁面

設定了這麼多內容,我們卻還不能看到頁面的內容,心裡肯定不樂意,上面這幾步操作其實就相當於翻譯 Vue SFC 檔案的內容,接下來我們還需要讓頁面真正執行起來。

粗暴方案

有一種快速驗證我們的打包設定是否正確:我們只需要新建一個html檔案,將打包產物引入進去,並建立好掛載節點就可以

<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    
</head>
<body>
    <div id="app"></div>
    <script src="../dist/bundle.42603d.js"></script>
</body>
</html>

我們再把這個html檔案在瀏覽器開啟,就能看到我們vue專案能夠正常開啟了。

這種方案有一種弊端就是:我們打包出來的檔案一般都會帶有hash,那就需要我們每次在打包完後去html檔案修改引入的檔案,這樣是不是有點太費勁了,你們能忍嗎?

優雅方案

上面那種方案在日常開發中顯然是不能接受的,身為程式設計師能偷懶的地方必須得偷懶!

我們可以利用下面兩個工具讓這個過程變得更加智慧化、自動化。

html-webpack-plugin: 自動生成 HTML 頁面

HtmlWebpackPlugin 簡化了 HTML 檔案的建立,以便為你的 webpack 包提供服務。這對於那些檔名中包含雜湊值,並且雜湊值會隨著每次編譯而改變的 webpack 包特別有用。

webpack-dev-server :讓頁面真正執行起來,並具備熱更新能力。

webpack-dev-server 主要提供兩種功能:

  • 結合 Webpack 工作流,提供基於 HTTP(S) 協定的靜態資源服務;
  • 提供資源熱更新能力,在保持頁面狀態前提下自動更新頁面程式碼,提升開發效率。

安裝

npm i html-webpack-plugin webpack-dev-server

修改設定

​
const Webpack = require('webpack')
const {VueLoaderPlugin}  = require('vue-loader')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
    entry: './src/main.js',
    output: {
        filename: 'bundle.[hash:6].js',
        path: __dirname + '/dist',
    },
    module: {
        rules:[
            {test: /.vue$/, use: 'vue-loader'},
            {
                test: /.css$/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /.js$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env']
                    }
                },
                exclude: /node_modules/
            },
            {
                test: /.(png|jpg|gif|jpeg)$/,
                type: 'asset/resource',
            }
        ]
    },
    plugins:[
        new Webpack.ProgressPlugin(),
        new VueLoaderPlugin(),
        new HtmlWebpackPlugin({
            template: './public/index.html',
            filename: 'index.html'
        })
    ],
    devServer: {
        hot: true,
        open: true
    }
}

修改啟動指令碼

"dev": "webpack serve --mode development"

執行

npm run dev

這時webpack就能自動幫我們開啟瀏覽器執行頁面了

vue檔案內容如下:

<template>
    <div class="title">webpack + vue -- {{ name }}</div>
    <img src="../asset/1.png" class="top_bg" />
</template>
​
<script setup>
import { ref } from 'vue'
const name = ref('前端南玖')
</script>
​
<style>
.title {
    font-size: 16px;
    font-weight: bold;
    color: salmon;
}
.top_bg {
    width: 100%;
    height: auto;
}
</style>

如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公眾號首發,關注 前端南玖 第一時間獲取最新文章~