前言
近期對自己負責的項目進行優化,但 webpack 這塊一直有着茫然的不熟悉,本着對 webpack 打包配置及項目優化的進一步理解和學習,所以記錄下自己學習的流程,加深印象,有深入的理解后再進行補充。另外,對不同版本的 webpack(比如 webpack4 和 webpack5),在一些配置的參考上會略有不同。
此測試項目已上傳至gitee,有錯誤及不准確之處,歡迎評論指正~
一、初始化及安裝
1. 初始化項目
新建文件夾 demo_webpack
,在此目錄下執行命令 npm init
,執行后項目文件夾里會生成包管理器 package.json
。
$ npm init
2. 安裝打包工具 webpack
# 安裝webpack腳手架
$ npm i webpack-cli -D
# 安裝webpack
$ npm i webpack -D
二、基礎配置
1. 配置出入口
(1)配置入口
新建 build/webpack.common.js
// webpack.common.js
const path = require('path')
module.exports = {
entry: path.resolve(__dirname, '../src/main.js'), // 入口文件
}
(2)配置出口
// webpack.common.js
const path = require('path')
module.exports = {
output: {
path: path.join(__dirname, '../dist'), // 打包后生成的文件夾
filename: '[name].[contenthash:8].js', // js文件名稱
clean: true, // 每次構建都清除dist包
},
}
2. 添加 webpack 構建腳本
// package.json
"scripts": {
"build":"webpack --config build/webpack.common.js",
}
最后可以運行打包命令
$ npm run build
打包后目錄結構
三、插件(plugins)
插件(Plugins)是用來拓展 Webpack 功能的,包括:打包優化、資源管理、注入環境變量。插件使用:只需要 require()它,然后把它添加到 plugins 數組中。
1. html-webpack-plugin
該插件將為你生成一個 HTML5 文件, 其中包括使用 script 標簽的 body 中的所有 webpack 包。 只需添加插件到你的 webpack 配置如下
(1)安裝
$ npm i html-webpack-plugin -D
(2)新建 index.html
<!-- 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>demo_webpack</title>
</head>
<body>
<div id="app">
<div>這是一段測試文字...</div>
</div>
</body>
</html>
(3)配置
// webpack.common.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../index.html'), // html模板
filename: 'index.html',
}),
],
}
(4)運行
$ npm run build
打包完成后可以看到 dist 下多了 index.html 文件,並且打包生成的 js 文件在 index.html 中通過 script 標簽被引入。
2. progress-bar-webpack-plugin
打包時可以顯示打包進度
(1)安裝
$ npm i progress-bar-webpack-plugin -D
(2)配置
// webpack.common.js
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
module.exports = {
plugins: [
new ProgressBarPlugin({
complete: '█',
}),
],
}
(3)運行
$ npm run build
可以看到執行打包命令后顯示出了打包進度...
四、loader
webpack 使 loader 能夠對文件進行預處理,用於對模塊的源代碼進行轉換,loader 都在 module 下的 rules 中配置。
rules 是一個數組,數組每一項是一個 js 對象,該對象有兩個關鍵屬性 test 和 use。loader 配置項:
- test: 是一個正則表達式或正則表達式數組,模塊文件名與正則匹配的會被 use 里的 loader 處理(必選)
- loader 調用 loader 的名稱 / use 鏈式調用 loader (必選,兩種調用方式選其一)
- exclude: 有一些文件不想被正則匹配到的 loader 處理。值是字符串或正則表達式,字符串需要是絕對路徑(可選)
- include: 表示的意思正好與 exclude 相反,它表示只對匹配到的文件處理(可選)
- options: 為 loader 提供額外的參數配置(可選)
1.css-loader 和 style-loader
用於加載解析 css
css-loader: 解釋(interpret) @import 和 url() ,會 import/require() 后再解析(resolve)它們
style-loader: 將 CSS 注入到 JavaScript 中,通過 DOM 操作控制 css
(1)安裝
$ npm i css-loader style-loader -D
(2)配置
// webpack.common.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'], //從右向左解析
},
],
},
}
2.url-loader 和 file-loader
加載 images 圖像,字體,視頻資源
url-loader: 功能類似於 file-loader,但是在文件大小(單位 byte)低於指定的限制時,可以返回一個 DataURL
file-loader: 默認情況下,生成的文件的文件名就是文件內容的 MD5 哈希值並會保留所引用資源的原始擴展名
(1)安裝
$ npm i url-loader file-loader -D
(2)配置
// webpack.common.js
module.exports = {
module: {
rules: [
{
test: /\.(png|jpe?g|gif|svg|ico)(\?.*)?$/, // 圖片、gif、svg、ico資源
type: 'javascript/auto', // 解決asset重復
loader: 'url-loader', // 將文件轉為base64內聯到bundle中,如果超出限制的大小,則使用file-loader將文件移動到輸出的目錄中
options: {
esModule: false, // 關閉es6模塊化解析
limit: 10000, // 圖片大於10kb,就會被base64處理
name: 'img/[name].[hash:7].[ext]', // [hash:10]取圖片的hash的前10位, [ext]取文件原來擴展名
},
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, // 視頻資源
loader: 'url-loader',
options: {
limit: 10000,
name: 'media/[name].[hash:7].[ext]',
},
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字體資源
loader: 'url-loader',
options: {
limit: 10000,
name: 'fonts/[name].[hash:7].[ext]',
},
},
],
},
}
(3)拓展
在 webpack5 中,內置了資源模塊(asset module),代替了 file-loader 和 url-loader
例如,處理 png 圖片資源,如下配置:
module.exports = {
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource',
generator: {
filename: 'img/[name].[hash:7].[ext]',
},
},
],
},
}
3. babel-loader
解析 es6,jsx
Babel 其實是幾個模塊化的包,使用@區分其他非官方包:
- @babel/core:babel 核心庫
- babel-loader:webpack 的 babel 插件,讓我們可以在 webpack 中運行 babel
- @babel/preset-env:將 ES6 轉換為向后兼容的 JavaScript
- @babel/plugin-transform-runtime:處理 async,await、import()等語法關鍵字的幫助函數
(1)安裝
$ npm i @babel/core babel-loader @babel/preset-env @babel/plugin-transform-runtime -D
(2)配置
// webpack.common.js
module.exports = {
module: {
rules: [
{
test: /(\.jsx|\.js)$/,
use: ['babel-loader'],
exclude: /node_modules/,
},
],
},
}
(3)添加 babel 額外配置項
在根目錄下新建.babelrc
,配置如下
{
"presets": [
"@babel/preset-env"
],
"plugins": [
"@babel/plugin-transform-runtime"
]
}
3. html-loader
將 HTML 導出為字符串,處理 HTML 中引入的靜態資源。當編譯器需要時,將壓縮 HTML 字符串
(1)安裝
$ npm i html-loader -D
(2)配置
// webpack.common.js
module.exports = {
module: {
rules: [
{
test: /\.html$/i,
loader: 'html-loader',
options: {
esModule: false, // 在開發環境中啟用false
},
},
],
},
}
五、搭建環境
1. 搭建本地環境服務器
(1)安裝
$ npm i webpack-dev-server -D
(2)配置
// webpack.common.js
module.exports = {
devServer: {
hot: true,
open: false,
port: 8088,
compress: true, // 開啟gzip壓縮
static: {
// 托管靜態資源文件, 可通過數組的方式托管多個靜態資源文件
directory: path.join(__dirname, '../public'),
},
client: {
progress: false, // 在瀏覽器端打印編譯速度
},
},
}
(3)添加啟動命令
// package.json
"scripts": {
"dev":"webpack serve --config build/webpack.common.js",
}
2. 區分生產環境與開發環境
build 下新建 webpack.dev.js
和 webpack.prod.js
。根目錄下新建文件夾config
,在config
中新建dev.env.js
和prod.env.js
// dev.env.js
module.exports = {
NODE_ENV: 'development',
}
// prod.env.js
module.exports = {
NODE_ENV: 'prodction',
}
(1)安裝
$ npm i -D webpack-merge
(2)更改公共配置文件 webpack.common.js
// webpack.common.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: path.resolve(__dirname, '../src/main.js'),
output: {
path: path.join(__dirname, '../dist'), // 打包后生成的文件夾
filename: '[name].[contenthash:8].js', // js文件名稱
clean: true, // 每次構建都清除dist包
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../index.html'),
filename: 'index.html',
}),
],
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /(\.jsx|\.js)$/,
use: ['babel-loader'],
exclude: /node_modules/,
},
{
test: /\.(png|jpe?g|gif|svg|ico)(\?.*)?$/,
type: 'javascript/auto',
loader: 'url-loader',
options: {
esModule: false,
limit: 10000,
name: 'img/[name].[hash:7].[ext]',
},
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: 'media/[name].[hash:7].[ext]',
},
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
loader: 'url-loader',
options: {
limit: 10000,
name: 'fonts/[name].[hash:7].[ext]',
},
},
],
},
}
(3)更改開發環境配置文件 webpack.dev.js
// webpack.dev.js
const { merge } = require('webpack-merge')
const common = require('./webpack.common.js')
const path = require('path')
const env = require('../config/dev.env')
const webpack = require('webpack')
module.exports = merge(common, {
stats: 'errors-only', // 去除控制台webpack打印的無用信息
devServer: {
hot: true,
open: false,
port: 8088,
compress: true, // 開啟gzip壓縮
static: {
// 托管靜態資源文件, 可通過數組的方式托管多個靜態資源文件
directory: path.join(__dirname, '../public'),
},
client: {
progress: false, // 在瀏覽器端打印編譯速度
},
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(env.NODE_ENV),
},
}),
],
})
(4)更改生產環境配置文件 webpack.prod.js
// webpack.prod.js
/*
* @Date: 2021-10-22 15:49:07
* @information: 生產配置
*/
const webpack = require('webpack')
const { merge } = require('webpack-merge')
const common = require('./webpack.common.js')
const env = require('../config/prod.env.js')
const path = require('path')
// 打包進度顯示
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
module.exports = merge(common, {
output: {
path: path.resolve(__dirname, '../dist'),
filename: 'js/[name].[chunkhash].js', // 此選項決定了每個輸出 bundle 的名稱
chunkFilename: 'js/[id].[chunkhash].js', // 此選項決定了非入口(non-entry) chunk 文件的名稱
},
module: {
rules: [
{
test: /\.html$/i, // 將HTML導出為字符串,處理HTML中引入的靜態資源
loader: 'html-loader',
},
],
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(env.NODE_ENV),
},
}),
new ProgressBarPlugin({
complete: '█',
}),
],
})
(5)修改 package.json 中啟動和打包命令
"scripts": {
"dev": "webpack serve --config build/webpack.dev.js",
"build": "webpack --config build/webpack.prod.js",
},
3. 配置別名
// webpack.common.js
module.exports = {
resolve: {
extensions: ['.js', '.jsx', '.json', '.vue'], // 省略文件后綴
alias: {
// 配置別名
'@': path.resolve(__dirname, '../src'),
},
},
}
六、代碼分離
1. mini-css-extract-plugin
分離 css 文件
(1)安裝
$ npm i mini-css-extract-plugin -D
(2)配置
// webpack.common.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name]_[contenthash:8].css', // 抽離整的css文件名稱
}),
],
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
{
test: /\.(scss|sass)$/, // 解析scss、sass,需安裝sass-loader
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
},
],
},
}
七、打包優化
1. 開啟 gzip 壓縮
(1)安裝
$ npm i compression-webpack-plugin -D
(2)配置
//webpack.prod.js
const CompressionPlugin = require('compression-webpack-plugin')
module.exports = {
plugins: [
new CompressionPlugin({
asset: '[path].gz[query]', // 目標資源名稱。[file] 會被替換成原資源。[path] 會被替換成原資源路徑,[query] 替換成原查詢字符串
algorithm: 'gzip',
// test: new RegExp('\\.(js|css)$'),
test: /\.(js|css)$/,
threshold: 10240, // 只處理比這個值大的資源。按字節計算, 默認為0
minRatio: 0.8, // 只有壓縮率比這個值小的資源才會被處理, 默認為0.8
}),
],
}
2. externals
防止將外部資源包打包到自己的 bundle 中,例如從 cdn 引入的資源防止進行打包處理
(1)cdn 引入資源
<!-- index.html -->
<!DOCTYPE html>
<html lang="zh_CN">
<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>demo_webpack</title>
</head>
<body>
<div id="app">
<div>這是一段測試文字...</div>
<div class="green">這是一段綠色的字啊222</div>
<div class="red">這是一段紅色的字啊</div>
</div>
<!-- 引入cdn -->
<script
src="https://code.jquery.com/jquery-3.1.0.js"
integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
crossorigin="anonymous"
></script>
</body>
</html>
(2)配置
//webpack.common.js
module.exports = {
externals: {
jquery: 'jQuery',
},
}
(3)在業務 js 文件中引入
import $ from 'jquery'
八、 引入 Vue
1. 安裝
vue-loader,用於解析.vue 文件
vue-template-compiler,用於模板編譯
$ npm i -D vue-template-compiler vue-loader vue-style-loader
2. 配置
// webpack.common.js
const { VueLoaderPlugin } = require('vue-loader')
module.exports = {
plugins: [new VueLoaderPlugin()],
module: {
rules: [
// vue-loader 要放在匹配規則的第一個,否則會報錯
{
test: /\.vue$/,
loader: 'vue-loader',
include: [path.resolve(__dirname, '../src')],
},
],
},
}
3. 配置 externals
(1)在 index.html 文件中引入 vue、vue-router
<!-- index.html -->
<script src="https://cdn.bootcss.com/vue/2.6.12/vue.min.js"></script>
<script src="https://cdn.bootcss.com/vue-router/3.5.1/vue-router.min.js"></script>
(2)在 webpack.common.js 中引入 vue、vue-router
// webpack.common.js
module.exports = {
externals: {
vue: 'Vue',
'vue-router': 'VueRouter',
},
}
4. 使用
此時可以正常使用 vue 和 vue-router 了...
比如新建 src/App.vue、src/Home.vue、src/Mine.vue 等頁面,新建 src/routers/router.js 路由文件。並寫入內容及配置,此處省略,可參考gitee代碼。
(1)配置 main.js
// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './routers/router'
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>',
})
九、一些資源處理及優化插件
1. copy-webpack-plugin
拷貝插件,將一些靜態資源(如圖片、視頻、文件)拷貝至打包后的文件夾中
$ npm i -D copy-webpack-plugin
// webpack.common.js
const CopyPlugin = require("copy-webpack-plugin")
module.exports = {
new CopyPlugin({
patterns: [
// 說明:在此測試項目中,`public/static`中的內容打包時會被復制到`dist/static`中...
{
from: path.resolve(__dirname, '../public'), // 定義要拷貝的源目錄
to: '', // 定義要拷貝到的目標目錄,非必填,不填寫則拷貝到打包的output輸出地址中
},
],
}),
}
2. friendly-errors-webpack-plugin
友好的終端錯誤顯示方式,項目啟動后可以在終端中提示一些自定義信息
$ npm i -D friendly-errors-webpack-plugin
// webpack.dev.js
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
module.exports = {
plugins: [
new FriendlyErrorsWebpackPlugin({
// 運行成功
compilationSuccessInfo: {
messages: ['Your Application is: http://localhost:8088'],
notes: ['有些附加說明要在成功編輯時顯示'],
},
// 運行錯誤 - //您可以收聽插件轉換和優先級的錯誤, 嚴重性可以是'錯誤'或'警告'
onErrors: utils.createNotifierCallback(),
clearConsole: true, // 是否每次編譯之間清除控制台, 默認為true
}),
],
}
// build/utils.js
exports.createNotifierCallback = () => {
const notifier = require('node-notifier')
return (severity, errors) => {
if (severity !== 'error') return
const error = errors[0]
const filename = error.file && error.file.split('!').pop()
notifier.notify({
title: packageConfig.name,
message: severity + ': ' + error.name,
subtitle: filename || '',
// icon: path.join(__dirname, 'logo.png')
})
}
}
3. terser-webpack-plugin
壓縮 js,去除注釋等..(PS: webpack5+已內置壓縮 js 的插件功能,但若想自定義,需額外引包寫配置。)
$ npm i -D terser-webpack-plugin
// webpack.prod.js
const TerserPlugin= require('terser-webpack-plugin')
module.exports = {
plugins: [
new TerserPlugin({
parallel: true, // 多進程
terserOptions: {
ecma: undefined,
warnings: false,
parse: {},
compress: {
drop_console: true,
drop_debugger: true,
pure_funcs: ['console.log'], // 移除console
},
},
}),
],
}
附 packag.json 部分配置
使用 cross-env
進行啟動和打包項目的命令,設置環境變量等。
$ npm i -D cross-env
// packag.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "npm run dev",
"dev": "cross-env NODE_ENV=development webpack server --progress --config build/webpack.dev.js",
"build": "cross-env NODE_ENV=production webpack --config build/webpack.prod.js"
},
文件結構
打包后的文件結構