webpack 做爲前端領域的模塊化打包工具,相信你們都不陌生。如今很火的 react 和 vue 的一些腳手架都是基於 webpack 開發定製的,所以,瞭解並會配置 webpack 仍是頗有必要的(文章基於 webpack4.x 版原本講解)。css
PS:文章內容可能有點長,你們提早作好心理準備。html
官方定義:
webpack 是一個現代 JavaScript 應用程序的靜態模塊打包器。當 webpack 處理應用程序時,它會遞歸地構建一個依賴關係圖,其中包含應用程序須要的每一個模塊,而後將全部這些模塊打包成一個或多個 bundle。前端
我的理解:
webpack 做爲一個模塊化打包工具,根據入口文件(任何類型文件,不必定是 js 文件)遞歸處理模塊中引入的 js/css/scss/image 等文件,將其轉換打包爲瀏覽器能夠識別的基礎文件(js/css/image 文件等)。vue
與 grunt/gulp 等區別:
1.runt 與 gulp 屬於自動化流程工具,經過配置文件指明對哪些文件執行編譯、組合、壓縮等具體任務,由工具自動完成這些任務。
2.webpack 做爲模塊化打包工具,把項目做爲一個總體,經過入口文件,遞歸找到全部依賴文件,經過 loader 和 plugin 針對文件進行處理,最後打包生成不一樣的 bundle 文件。java
當你想使用 webpack 打包項目時,須要在項目目錄下新建 webpack.config.js,webpack 默認會讀取 webpack.config.js 做爲配置文件,進而執行打包構建流程。node
先來看一下 webpack 的基本配置項,留個印象先。react
webpack.config.jswebpack
const path = require('path');
module.exports = {
mode: 'production/development/none', // 打包模式,使用對應模式的內置優化
entry: './src/index.js', // 入口,支持單入口、多入口
output: { // 輸出相關配置
filename: 'xx.js', // 輸出文件的文件名
path: path.resolve(__dirname, 'dist') // 輸出文件的絕對路徑,默認爲dist
},
module: { // 針對不一樣類型文件的轉換
rules: [
{
test: /\.xx$/, // 針對某類型文件處理,使用正則匹配文件
use: [
{
loader: 'xx-loader', // 使用xx-loader進行轉換
options: {} // xx-loader的配置
}
]
}
]
},
plugins: [ // 插件,完成特定任務,如壓縮/拆分
new xxPlugin({ options });
]
};
複製代碼
webpack 有五個核心概念:入口(entry)、輸出(output)、模式(mode)、loader、插件(plugins)。git
入口指示 webpack 應該使用哪一個模塊,來做爲構建其內部依賴圖的開始。默認值爲./src
。es6
單入口是指 webpack 打包只有一個入口,單入口支持單文件和多文件打包。
一般像 vue/react spa 應用都屬於單入口形式,以
src/index.js
做爲入口文件。
(1)單文件打包
不指定入口文件的 entryChunkName 時,默認爲 main。
// webpack.config.js
module.exports = {
entry: "./src/index.js"
};
複製代碼
上面的單入口語法,是下面的簡寫:
module.exports = {
entry: {
main: "./src/index.js"
}
};
複製代碼
main 表示 entryChunkName 爲 main,打包後生成的文件 filename 爲 main。
webpack 打包後,dist 文件夾生成 main.js 文件。
也能夠將 entryChunkName 修改成其餘值,打包出的 filename 也會對應改變。
(2)多文件打包
多文件打包入口以數組形式表示,表示將多個文件一塊兒注入到 bundle 文件中。
module.exports = {
entry: ["./src/index.js", "./src/main.js"]
};
複製代碼
多入口是指 webpack 打包有多個入口模塊,多入口 entry 通常採用對象語法表示,應用場景:
(1)分離應用程序 app 和第三方入口(vendor)
module.exports = {
entry: {
app: "./src/index.js",
vendor: "./src/vendor.js"
}
};
複製代碼
webpack 打包後,生成應用程序 app.js 和 vendor.js。
(2)多頁面打包,通常指多個 html 文檔形式,每一個文檔只使用一個入口。
module.exports = {
entry: {
app: "./src/app.js",
home: "./src/home.js",
main: "./src/main.js"
}
};
複製代碼
webpack 打包後,dist 文件夾下生成 app.js、home.js、main.js 三個文件。
output 選項能夠控制 webpack 如何輸出打包文件,output 屬性包含 2 個屬性:
即便存在多個入口起點,webpack 只有一個輸出配置,不對 output 進行配置時,默認輸出到./dist 文件夾。
單入口打包經常使用配置以下:
const path = require("path");
module.exports = {
entry: {
app: "./src/app.js"
},
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist") // __dirname表示js文件執行的絕對路徑,使用path.resolve生成dist文件夾的絕對路徑
}
};
複製代碼
webpack 打包後,dist 文件夾下生成 bundle.js 文件
當存在多入口時,應該使用佔位符來確保每一個文件具備惟一的名稱,不然 webpack 打包會報錯。
佔位符 name 與 entry 對象中的 key 一一對應。
正確的寫法以下:
const path = require("path");
module.exports = {
entry: {
app: "./src/app.js",
main: "./src/main.js",
home: "./src/home.js"
},
output: {
filename: "[name].js", // 使用佔位符來表示
path: path.resolve(__dirname, "dist")
}
};
複製代碼
webpack 打包後,在 dist 文件夾下生成了 app.js、home.js、main.js 文件。
在揭祕 hash、chunkhash、contenthash 以前,咱們先看下 webpack 打包輸出信息。
Hash:與整個項目構建相關,當項目中不存在文件內容變動時,hash 值不變,當存在文件修改時,會生成新的一個 hash 值。
Version:webpack 版本
Time:構建時間
Build at:開始構建時間
Asset:輸出文件
Size:輸出文件大小
Chunks:chunk id
ChunkNames:對應 entryChunkName
Entrypoint:入口與輸出文件的對應關係
若是使用佔位符來表示文件,當文件內容變動時,仍然生成一樣的文件,沒法解決瀏覽器緩存文件問題。藉助於 hash、chunkhash、contenthash 能夠有效解決問題。
整個項目構建生成的一個 md5 值,項目文件內容不變,hash 值不變。
使用 hash 關聯輸出文件名稱
const path = require("path");
module.exports = {
entry: {
app: "./src/app.js",
main: "./src/main.js",
home: "./src/home.js"
},
output: {
filename: "[name].[hash].js",
path: path.resolve(__dirname, "dist")
}
};
複製代碼
filename: "[name].[hash:7].js"表示去 hash 值的前 7 位
webpack 打包,看到新生成文件帶上了 hash 值
當咱們修改 app.js 文件內容後,從新打包,發現能夠生成了新的 hash 值,全部文件的名稱都發生了變動。
問題:當我修改了項目中的任何一個文件時,致使未修改文件緩存都將失效。
webpack 構建時,根據不一樣的入口文件,構建對應的 chunk,生成對應的 hash 值,每一個 chunk 的 hash 值都是不一樣的。
使用 chunkhash 關聯文件名
const path = require("path");
module.exports = {
entry: {
app: "./src/app.js",
main: "./src/main.js",
home: "./src/home.js"
},
output: {
filename: "[name].[chunkhash].js",
path: path.resolve(__dirname, "dist")
}
};
複製代碼
使用 webpack 打包後,dist 目錄下,每一個 bundle 文件都帶有不一樣的 chunkash 值。
修改 app.js 內容,從新打包,只有 app 文件名稱發生了變動。
使用 chunkhash 能夠有效解決 hash 緩存失效問題,可是當在 js 文件裏面引入 css 文件時,將 js、css 分別打包,若 js 件內容變化時,css 文件名稱也會變動。
app.js 中引入了 css 文件
import "./css/style.css";
console.log("app");
複製代碼
webpack 配置
const path = require("path");
const miniCssExtractPlugin = require("mini-css-extract-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
entry: {
app: "./src/app.js",
main: "./src/main.js",
home: "./src/home.js"
},
module: {
rules: [
{
test: /\.css$/,
use: [miniCssExtractPlugin.loader, "css-loader"]
}
]
},
output: {
filename: "[name].[chunkhash].js",
path: path.resolve(__dirname, "dist")
},
plugins: [
new CleanWebpackPlugin(), // 清空dist目錄
new miniCssExtractPlugin({
// 抽離css文件
filename: "css/style.[chunkhash].css"
})
]
};
複製代碼
打包,dist 文件夾下生成了 css 與 js 文件,chunkhash 一致。
當咱們修改 app.js 文件內容後,從新打包,發現 css 文件名也變動了,css 文件緩存將失效,這顯然不是咱們想要的結果。
問題:js 引入 css 等其餘文件時,js 文件變動,css 等文件名也會變動,緩存失效。
contenthash 表示由文件內容產生的 hash 值,內容不一樣產生的 contenthash 值也不同。藉助於 contenthash 能夠解決上述問題,只要 css 文件不變,緩存一直有效。
修改 webpack 配置,css filename 使用 contenthash 表示。
const path = require("path");
const miniCssExtractPlugin = require("mini-css-extract-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
entry: {
app: "./src/app.js",
main: "./src/main.js",
home: "./src/home.js"
},
module: {
rules: [
{
test: /\.css$/,
use: [miniCssExtractPlugin.loader, "css-loader"]
}
]
},
output: {
filename: "[name].[contenthash].js",
path: path.resolve(__dirname, "dist")
},
plugins: [
new CleanWebpackPlugin(), // 清空dist目錄
new miniCssExtractPlugin({
// 抽離css文件
filename: "css/style.[contenthash].css"
})
]
};
複製代碼
打包後,dist 目錄下,生成了 css、js 文件,app 文件包含 chunkhash 值,css 文件包含 contenthash 值。
;修改 app.js 文件內容,從新打包,app 文件重命名了,css 文件沒變,緩存有效。
項目中 css 等非 js 文件抽離最好使用 contenthash。
webpack 提供了 mode 配置選項,用來選擇使用響應的內置優化,不配置 mode 選項時,默認使用 production 模式。
mode 選項有 3 個可選值:production(生產模式、默認)、development(開發模式)、none。
production 模式下,會自動開啓 Tree Shaking(去除無用代碼)和文件壓縮(UglifyJs)。
在 fun.js 中定義了 2 個函數
export function f1() {
console.log("f1");
}
export function f2() {
console.log("f2");
}
複製代碼
在 app.js 中只引入了 f1
import { f1 } from "./fun";
f1();
複製代碼
production 模式打包,查看打包後的文件,只引入了 f1,而且代碼進行了壓縮。
development 模式下,webpack 會啓用 NamedChunksPlugin 和 NamedModulesPlugin 插件。
一樣的代碼,development 模式下打包,將 f1 和 f2 都一塊兒打包了,並且代碼並無進行壓縮。
none 模式下,webpack 不會使用任何內置優化,這種模式不多使用。
loader 用於對模塊的源代碼進行轉換。loader 能夠實現文件內容的轉換,好比將 es6 語法轉換爲 es5 語法,將 scss 轉換爲 css 文件,將 image 轉換爲 base 64 位編碼。通常 loaderp 配置在 module 的 rules 選項中。
經常使用的 loader 有:
下面就以 scss 轉換的例子,描述如何使用 loader
app.js 中引入了 main.scss 文件
// app.js
import "./css/main.scss";
複製代碼
webpack 配置以下
const path = require("path");
const miniCssExtractPlugin = require("mini-css-extract-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
mode: "development",
entry: {
app: "./src/app.js"
},
module: { // 針對項目中不一樣類型模塊的處理
rules: [ // 匹配請求的規則數組
{
test: /\.scss$/, // 檢測scss文件結尾的文件
exclude: /node_modules/, // 排除查找範圍
include: [path.resolve(__dirname, "src/css")], // 限定查找範圍
use: [miniCssExtractPlugin.loader, "css-loader", "sass-loader"] // loader鏈式調用,從最右邊向左處理
}
]
},
output: {
filename: "[name].[chunkhash].js",
path: path.resolve(__dirname, "dist")
},
plugins: [
new CleanWebpackPlugin(), // 清空dist目錄
new miniCssExtractPlugin({
// 抽離css文件
filename: "css/style.[contenthash].css"
})
]
};
複製代碼
其中,sass-loader 用於將 scss 文件編譯成 css 文件,css-loader 用於解釋 import(),miniCssExtractPlugin 用於將 css 抽離到單獨的文件中。
關於 loader 有幾點說明:
1.loader 支持鏈式調用,一組鏈式 loader 按照相反的順序執行,loader 鏈中的前一個 loader 返回值給下一個 loader,最後一個 loader 輸出文件。
上面例子中,loader 執行順序:sass-loader => css-loader => miniCssExtractPlugin.loader。
2.loader 可使用 options 對象進行配置,像下面這樣:
module: {
//
rules: [
{
test: /\.scss$/, // 檢測scss文件結尾的文件
exclude: /node_modules/, // 排除查找範圍
include: [path.resolve(__dirname, "src/css")], // 限定查找範圍
use: [
miniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
modules: true
}
},
"sass-loader"
] // loader鏈式調用,從最右邊向左處理
}
]
},
複製代碼
插件是 webpack 的支柱功能,旨在解決 loader 沒法實現的其餘事。插件能夠攜帶參數,配置插件須要向 plugins 數組中傳入 new 實例。
經常使用的插件有:
clean-webpack-plugin:清空 dist 文件夾
clean-webpack-plugin:生成 html 文件
mini-css-extract-plugin:抽離 css 文件
optimize-css-assets-webpack-plugin:優化和壓縮 css
css-split-webpack-plugin:針對 css 大文件進行拆分
webpack-bundle-analyzer:webpack 打包結果分析
webpack.DllPlugin:建立 dll 文件和 manifest 文件
webpack.DllReferencePlugin:把只有 dll 的 bundle 引用到須要的預編譯的依賴。
SplitChunksPlugin:拆分代碼塊,在 optimization.splitChunks 中配置。
下面以 html-webpack-plugin 爲例說明 plugins 的用法,這裏只列出 plugins 部分的配置
plugins: [
new CleanWebpackPlugin(), // 清空dist目錄
new miniCssExtractPlugin({
// 抽離css文件
filename: "css/style.[contenthash].css"
}),
new HtmlWebpackPlugin({ // 生成插件實例
filename: "index.html", // 生成模板的名稱
minify: {
collapseWhitespace: true, // 去除空格
minifyCSS: true, // 壓縮css
minifyJS: true, // 壓縮js
removeComments: true, // 移除註釋
removeEmptyElements: true, // 移除空元素
removeScriptTypeAttributes: true, // 移除script type屬性
removeStyleLinkTypeAttributes: true // 移除link type屬性
}
})
]
複製代碼
打包後,生成了 index.html 文件
打開 index.html,看到 css 和 js 文件被引入了
接下來描述 webpack 其餘經常使用的一些配置 resolve、devServer、devtool。
resolve 選項設置模塊如何被解析。
建立 import 或 require 的別名,確保模塊引入變得簡單。
下面的例子針對 css、util 文件夾 設置了 alias 別名,引入文件夾下面的文件能夠直接使用相對地址。
resolve: {
alias: {
css: path.resolve(__dirname, "src/css"),
util: path.resolve(__dirname, "src/util")
}
},
複製代碼
app.js 文件中引入 util 文件夾下的 common.js 文件,就會引入 src/util/common.js 文件。
import fun1 from "util/common.js";
複製代碼
自動解析引入模塊的擴展,按照從左到右的順序解析。
resolve: {
extensions: [".js", ".json"]
}
複製代碼
在 app.js 中引入 common.js 能夠不攜帶後綴,由 webpack 自動解析。
import fun1 from "util/common";
複製代碼
解析目錄時要使用的文件名,默認
mainFiles: ["index"]
複製代碼
也能夠指定多個 mainFiles,會依次從左到右解析
mainFiles: ["index", "main"]
複製代碼
好比須要從 util 文件夾下引入 index.js 文件,import 只須要導入到 util 文件夾,webpack 會自動從 util 文件夾下引入 index.js 文件。
import index from "util";
複製代碼
extentsion 和 mainFiles 屬性雖然會方便開發者簡寫,可是會增長 webpack 額外的解析時間。
devServer 主要用於 development 模式配置本地開發服務器,須要安裝 webpack-dev-server。
npm i webpack-dev-server -g
複製代碼
devServer 經常使用的配置項以下:
devServer: {
contentBase: path.resolve(__dirname, "dist"), // 告訴服務器從哪裏提供內容
host: "localhost", // 制定一個host,默認localhost
port: 3000, // 請求端口號,默認8080
compress: true, // 啓用gzip壓縮
https: true, // 開啓http服務
hot: true, // 啓用模塊熱替換
open: true, // 自動打開默認瀏覽器
index: "index.html", // 頁面入口html文件,默認index.html
headers: {
// 全部響應中添加首部內容
"X-Custom-Foo": "bar"
},
proxy: {
"/api": "http://localhost:3000"
}
}
複製代碼
讀取配置文件,啓動開發服務器。
webpack-dev-server --config webpack.dev.js
複製代碼
source map 一個存儲源代碼與編譯代碼對應位置的映射信息文件,它是專門給調試器準備的,它主要用於 debug。
webpack 經過配置 devtool 屬性來選擇一種 source map 來加強調試過程。
如下是官方對於 devtool 的各類 source map 的比較:
development 模式下 devtool 設置爲 cheap-module-eval-source-map,production 模式下 devtool 設置爲 souce-map。
接下來將經過一個完整的例子實現 react 項目的完整 webpack 配置。 先全局安裝 webpack 和 webpack-dev-server。
npm i webpack webpack-dev-server -g
複製代碼
新建一個目錄,結構以下:
其中 public 文件夾下包含 index.html 入口 html 文件,src 文件夾下包含 index.js 入口 js 文件,css 文件夾、font 文件夾、image 文件夾。
webpack 配置以下:
// webpack.config.js
const path = require("path");
module.exports = {
entry: "./src/index.js",
output: {
filename: "[name].[chunkhash:7].js",
path: path.resolve(__dirname, "dist")
}
};
複製代碼
在目錄下使用npm init
新建 package.json 文件,設置 dev 和 build 的 script, 分別用於開發模式和生產模式。
在 react 項目中,咱們使用 jsx 和 es6 語法,爲了兼容低版本瀏覽器,須要經過 babel 轉換。
先安裝 babel 相關依賴包
npm i babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime @babel/preset-react @babel/polyfill @babel/runtime -D
複製代碼
babel-loader:處理 ES6 語法,將其編譯爲瀏覽器能夠執行的 js 語法
@babel/core-babel:babel 核心模塊
@babel/preset-env:轉換 es6 語法,支持最新的 javaScript 語法
@babel/preset-react:轉換 jsx 語法
@babel/plugin-transform-runtime: 避免 polyfill 污染全局變量,減少打包體積
@babel/polyfill: ES6 內置方法和函數轉化墊片
將 index.js 做爲入口文件,引入 App.jsx 組件
//index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./views/App";
console.log(App);
ReactDOM.render(<App />, document.getElementById("root"));
//App.jsx
import React, { Component } from "react";
class App extends Component {
render() {
return <h2>This is a react app.</h2>;
}
}
export default App;
複製代碼
webpack 配置以下:
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin"); // clean-webpack-plugin用來清空dist文件夾
module.exports = {
mode: "production",
entry: {
app: "./src/index.js"
},
module: {
rules: [
{
test: /\.js[x]?$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader"
}
]
}
]
},
resolve: {
extensions: [".jsx", ".js"]
},
output: {
filename: "[name].[chunkhash:7].js",
path: path.resolve(__dirname, "dist")
},
plugins: [new CleanWebpackPlugin()]
};
複製代碼
clean-webpack-plugin:清空 dist 文件夾
新建.babelrc 文件
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": ["@babel/plugin-transform-runtime"]
}
複製代碼
執行npm run build
,打包成功,在 dist 文件夾下生成了 app.js 文件
配置 html 模板表示配置 index.html 文件相關配置,將打包後的文件引入到 index.html 文件,經過 html-webpack-plugin 插件實現。
先安裝 html-webpack-plugin 插件
npm i html-webpack-plugin -D
複製代碼
webpack 配置以下:
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin"); // 引入html-webpack-plugin插件
module.exports = {
mode: "production",
entry: {
app: "./src/index.js"
},
module: {
rules: [
{
test: /\.js[x]?$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader"
}
]
}
]
},
resolve: {
extensions: [".jsx", ".js"]
},
output: {
filename: "[name].[chunkhash:7].js",
path: path.resolve(__dirname, "dist")
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
filename: "index.html", // 模板文件名
template: path.resolve(__dirname, "public/index.html"), // 模板文件源
minify: {
collapseWhitespace: true, // 壓縮空格
minifyCSS: true, // 壓縮css
minifyJS: true, // 壓縮js
removeComments: true, // 移除註釋
caseSensitive: true, // 去除大小寫
removeScriptTypeAttributes: true, // 移除script的type屬性
removeStyleLinkTypeAttributes: true // 移除link的type屬性
}
})
]
};
複製代碼
執行 npm run build,打包成功,在 dist 文件夾下生成了 index.html 和 app.js 打包文件
打開 index.html,引入了 app.js 打包文件
在 index.js 中引入 main.scss 文件
import React from "react";
import ReactDOM from "react-dom";
import App from "./views/App";
import "./css/main.scss";
ReactDOM.render(<App />, document.getElementById("root"));
複製代碼
當在 js 文件中引入 css/scss 文件時,須要通過 loader 轉換,才能引入到 index.html 文件中。
安裝相關依賴包
npm i css-loader sass-loader node-sass mini-css-extract-plugin optimize-css-assets-webpack-plugin css-split-webpack-plugin -D
複製代碼
sass-loader:將 scss/sass 文件編譯爲 css
css-loader:解析 import/require 導入的 css 文件
mini-css-extract-plugin:將 js 中引入的 css 文件抽離成單獨的 css 文件
optimize-css-assets-webpack-plugin:優化和壓縮 css 文件
css-split-webpack-plugin:css 文件拆分
webpack 配置以下:
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const CSSSplitWebpackPlugin = require("css-split-webpack-plugin").default;
module.exports = {
mode: "production",
entry: {
app: "./src/index.js"
},
module: {
rules: [
{
test: /\.js[x]?$/,
exclude: /node_modules/,
use: ["babel-loader"]
},
{
test: /\.(sa|sc|c)ss$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"]
}
]
},
resolve: {
extensions: [".jsx", ".js"]
},
output: {
filename: "[name].[chunkhash:7].js",
path: path.resolve(__dirname, "dist")
},
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: "css/[name].[hash:7].css",
chunkFilename: "[id].css"
}),
new OptimizeCSSAssetsPlugin({
assetNameRegExp: /\.css$/g,
cssProcessor: require("cssnano"), // //引入cssnano配置壓縮選項
cssProcessorPluginOptions: {
preset: [
"default",
{
discardComments: {
// 移除註釋
removeAll: true
},
normalizeUnicode: false
}
]
},
canPrint: true
}),
new CSSSplitWebpackPlugin({
size: 4000, // 超過4kb的css文件進行拆分
filename: "[name]-[part].[ext]"
}),
new HtmlWebpackPlugin({
filename: "index.html", // 模板文件名
template: path.resolve(__dirname, "public/index.html"), // 模板文件源
minify: {
collapseWhitespace: true, // 壓縮空格
minifyCSS: true, // 壓縮css
minifyJS: true, // 壓縮js
removeComments: true, // 移除註釋
caseSensitive: true, // 去除大小寫
removeScriptTypeAttributes: true, // 移除script的type屬性
removeStyleLinkTypeAttributes: true // 移除link的type屬性
}
})
]
};
複製代碼
執行npm run build
,在 dist 文件夾下生成了 css 文件夾和編譯的 css 文件
打開 index.html,css 文件被引入到 index.html 中。
在 index.js 中引入圖片,在 main.scss 文件中引入字體庫
// index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./views/App";
import "./css/main.scss";
import image from "./image/image1.png";
const newImage = new Image();
newImage.src = image;
newImage.style.cssText = "width: 100px; height: 100px;";
document.body.append(newImage);
ReactDOM.render(<App />, document.getElementById("root"));
複製代碼
在 main.scss 文件中引入字體庫
// main.scss
@font-face {
font-family: 'MyFont';
src: url('../font/icomoon.eot') format('eot'),
+ url('../font/icomoon.woff') format('woff');
font-weight: 600;
font-style: normal;
}
body {
background-color: blue;
}
複製代碼
當在 js 文件中引入圖片或字體文件時,須要經過 url-loader 和 file-loader 來處理。
file-loader:解析 import/require 導入的文件,將其輸出到生產目錄,併產生一個 url 地址。
url-loader:不超過限定 limit 時,轉換爲 base64 url。
安裝 file-loader 和 url-loader
npm i url-loader file-loader -D
複製代碼
webpack module 部分配置以下:
...
module: {
...
rules: [
{
test: /\.(png|jpg|jpeg|gif|svg)/,
use: [
{
loader: "url-loader",
options: {
name: "[name]_[hash].[ext]",
outputPath: "images/",
limit: 204800 // 小於200kb,進行base64轉碼
}
}
]
},
{
test: /\.(eot|woff2?|ttf)/,
use: [
{
loader: "url-loader",
options: {
name: "[name]-[hash:5].min.[ext]",
limit: 5000,
outputPath: "fonts/"
}
}
]
}
]
...
}
...
複製代碼
執行npm run build
打包,在 dist 目錄下生成了 image 文件夾和 font 文件夾
打開 index.html,圖片成功引入
webpack-dev-server 就是在本地爲搭建了一個小型的靜態文件服務器,有實時重加載的功能,爲將打包生成的資源提供了 web 服務,適用於本地開發模式。
devServer: {
contentBase: path.join(__dirname, "../dist"), // 資源目錄
host: 'localhost', // 默認localhost
port: 3000, // 默認8080
hot: true, // 支持熱更新
inline: true
}
複製代碼
執行npm run dev
,web 服務起在 localhost:3000
當咱們在代碼裏引入了第三方庫和公共代碼時,可使用 splitChunks 提取公共代碼,避免加載的包太大。
webpack 配置以下:
...
optimization: {
splitChunks: {
// 提取公共代碼
chunks: "all", // async(動態加載模塊),initital(入口模塊),all(所有模塊入口和動態的)
minSize: 3000, // 抽取出來的文件壓縮前最小大小
maxSize: 0, // 抽取出來的文件壓縮前的最大大小
minChunks: 1, // 被引用次數,默認爲1
maxAsyncRequests: 5, // 最大的按需(異步)加載次數,默認爲 5;
maxInitialRequests: 3, // 最大的初始化加載次數,默認爲 3;
automaticNameDelimiter: "~", // 抽取出來的文件的自動生成名字的分割符,默認爲 ~;
name: "vendor/vendor", // 抽取出的文件名,默認爲true,表示自動生成文件名
cacheGroups: {
// 緩存組
common: {
// 將node_modules模塊被不一樣的chunk引入超過1次的抽取爲common
test: /[\\/]node_modules[\\/]/,
name: "common",
chunks: "initial",
priority: 2,
minChunks: 2
},
default: {
reuseExistingChunk: true, // 避免被重複打包分割
filename: "common.js", // 其餘公共函數打包成common.js
priority: -20
}
}
}
},
...
複製代碼
執行npm run build
,在 dist 文件夾下生成了 vendor.js 包
打開 index.html,vendor.js 成功引入
因爲開發環境和生產環境下的 webpack 配置存在公共配置,所以最好將公共配置抽離成 webpack.common.js,而後針對開發環境和生產環境分別配置,經過 webpack-merge merge 配置,便可知足開發環境和生產環境不一樣的配置。
先安裝 webpack-merge,用來 merge webpack 配置項
npm i webpack-merge -D
複製代碼
在目錄下新建 tools 文件夾,存放 webpack 相關配置
新建 pathConfig.js 文件,返回 entry js、output 目錄及 index.html 模板目錄的絕對地址
// pathConfig.js
const path = require("path");
const fs = require("fs");
const appDirectory = fs.realpathSync(process.cwd()); // 獲取當前根目錄
const resolvePath = (relativePath) => path.resolve(appDirectory, relativePath);
module.exports = {
appHtml: resolvePath("public/index.html"), // 模板html
appBuild: resolvePath("dist"), // 打包目錄
appIndexJs: resolvePath("src/index.js") // 入口js文件
};
複製代碼
webpack 公共配置,引入 pathConfig.js 文件
// webpack.common.js
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const CSSSplitWebpackPlugin = require("css-split-webpack-plugin").default;
const { appIndexJs, appBuild, appHtml } = require("./pathConfig");
module.exports = {
entry: {
app: appIndexJs
},
output: {
filename: "[name].[hash:7].js",
path: appBuild
},
module: {
rules: [
{
test: /\.js[x]?$/, // jsx、js處理
exclude: /node_modules/,
use: ["babel-loader"]
},
{
test: /\.(sa|sc|c)ss$/, // scss、css處理
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"]
},
{
test: /\.(png|jpg|jpeg|gif|svg)/, // 圖片處理
use: [
{
loader: "url-loader",
options: {
name: "[name]_[hash].[ext]",
outputPath: "images/",
limit: 204800 // 小於200kb採用base64轉碼
}
}
]
},
{
test: /\.(eot|woff2?|ttf)/, // 字體處理
use: [
{
loader: "url-loader",
options: {
name: "[name]-[hash:5].min.[ext]",
limit: 5000, // 5kb限制
outputPath: "fonts/"
}
}
]
}
]
},
resolve: {
extensions: [".jsx", ".js"]
},
optimization: {
splitChunks: {
// 提取公共代碼
chunks: "all", // async(動態加載模塊),initital(入口模塊),all(所有模塊入口和動態的)
minSize: 3000, // 抽取出來的文件壓縮前最小大小
maxSize: 0, // 抽取出來的文件壓縮前的最大大小
minChunks: 1, // 被引用次數,默認爲1
maxAsyncRequests: 5, // 最大的按需(異步)加載次數,默認爲 5;
maxInitialRequests: 3, // 最大的初始化加載次數,默認爲 3;
automaticNameDelimiter: "~", // 抽取出來的文件的自動生成名字的分割符,默認爲 ~;
name: "vendor/vendor", // 抽取出的文件名,默認爲true,表示自動生成文件名
cacheGroups: {
// 緩存組
common: {
// 將node_modules模塊被不一樣的chunk引入超過1次的抽取爲common
test: /[\\/]node_modules[\\/]/,
name: "common",
chunks: "initial",
priority: 2,
minChunks: 2
},
default: {
reuseExistingChunk: true, // 避免被重複打包分割
filename: "common.js", // 其餘公共函數打包成common.js
priority: -20
}
}
}
},
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: "css/[name].[hash:7].css",
chunkFilename: "[id].css"
}),
new OptimizeCSSAssetsPlugin({
assetNameRegExp: /\.css$/g,
cssProcessor: require("cssnano"), // //引入cssnano配置壓縮選項
cssProcessorPluginOptions: {
preset: [
"default",
{
discardComments: {
// 移除註釋
removeAll: true
},
normalizeUnicode: false
}
]
},
canPrint: true
}),
new CSSSplitWebpackPlugin({
size: 4000, // 超過4kb進行拆分
filename: "[name]-[part].[ext]"
}),
new HtmlWebpackPlugin({
filename: "index.html", // 模板文件名
template: appHtml, // 模板文件源
minify: {
collapseWhitespace: true, // 壓縮空格
minifyCSS: true, // 壓縮css
minifyJS: true, // 壓縮js
removeComments: true, // 移除註釋
caseSensitive: true, // 去除大小寫
removeScriptTypeAttributes: true, // 移除script的type屬性
removeStyleLinkTypeAttributes: true // 移除link的type屬性
}
})
]
};
複製代碼
開發環境 webpack 配置
// webpack.dev.config.js
const path = require("path");
const merge = require("webpack-merge");
const baseConfig = require("./webpack.config");
module.exports = merge(baseConfig, {
mode: "development",
devtool: "cheap-module-eval-source-map",
devServer: {
contentBase: path.join(__dirname, "../dist"),
port: 3000,
historyApiFallback: true,
hot: true,
inline: true
}
});
複製代碼
生產環境配置,啓用 webpack-bundle-analyzer 進行打包分析,啓用 compression-webpack-plugin 生成 gzip 壓縮。
// webpack.prod.config.js
const merge = require("webpack-merge");
const baseConfig = require("./webpack.com.config");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const CompressionWebpackPlugin = require("compression-webpack-plugin");
module.exports = merge(baseConfig, {
mode: "production",
devtool: "source-map",
plugins: [
new CompressionWebpackPlugin({
filename: "[path].gz[query]", // path-原資源路徑,query-原查詢字符串
algorithm: "gzip", // 壓縮算法
threshold: 0, // 文件壓縮閾值
minRatio: 0.8 // 最小壓縮比例
}),
new BundleAnalyzerPlugin()
]
});
複製代碼
修改 package.json 中 script 中 dev 和 build,--config 表示讀取後面的文件做爲配置文件。
"scripts": {
"dev": "webpack-dev-server --config ./tools/webpack.dev.config.js",
"build": "webpack --config ./tools/webpack.prod.config.js"
},
複製代碼
執行npm run build
,項目打包成功
執行npm run dev
,啓動開發者模式,運行在 localhost:3000。
到此爲止,咱們的案例就完成了。
代碼地址:案例連接
看完這篇文章,相信你們對於 webpack 已經有了一個初步的瞭解,學習 webpack 最好的方式仍是多動手實踐,以爲不錯的小夥伴能夠點個贊(碼字不易,灰常感謝)。
webpack 官方連接:www.webpackjs.com/