「Webpack5 專題(二)」靜態(tài)資源的處理

一、前言
上一篇講解了?webpack5 的基礎(chǔ)配置。這一篇文章將會介紹如何利用 webpack 中的?Loaders?對靜態(tài)資源做處理,Loaders 是 webpack 的核心概念之一。
靜態(tài)資源主要包括以下幾方面的內(nèi)容:
樣式文件(CSS) 圖片(Images) 字體(Fonts) 數(shù)據(jù)文件(Data) ...
文件結(jié)構(gòu):
webpack
|-?/dist
?|-?index.html
|-?/src
?|-?/assets
??|-?/fonts
???|-?font-a.otf
???|-?font-b.ttf
???|-?font-c.woff
???|-?font-d.woff2
???|-?font-e.svg
??|-?/icons
???|-?icon-a.png
???|-?incon-b.svg
??|-?/images
???|-?img-a.png
???|-?img-b.jpg
???|-?img-gif.jpg
??|-?/js
???|-?js-a.js
??|-?/others
???|-?o-a.txt
???|-?o-b.xml
??|-?/styles
???|-?global.scss
???|-?reset.css
?|-?/components
??|-?content.js
??|-?header.js
??|-?sidebar.js
|-?index.js
|-?package.json
|-?webpack.config.js
復(fù)制代碼
assets 文件夾中存放了若干靜態(tài)文件(例如:字體、圖標(biāo)、圖片、文本、樣式以及 JS 文件),中間根據(jù)文件的不同,又分了幾個文件夾。這里為了方便起見,文件名均已作簡化處理。
index.js
//?js?模塊
const?Header?=?require("./components/header");
const?Sidebar?=?require("./components/sidebar");
const?Content?=?require("./components/content");
//?圖片模塊?(這?5?個都是小圖)
const?icon1?=?require("./assets/icons/mac-os.png");
const?icon2?=?require("./assets/icons/apple-tv.png");
const?icon3?=?require("./assets/icons/apple-contacts.png");
const?iconSvg1?=?require("./assets/icons/arrow-up.svg");
const?iconSvg2?=?require("./assets/icons/arrow-down.svg");
//?圖片模塊?(這?3?個都是大圖)
const?dog1?=?require("./assets/images/animal-dog-1.png");
const?avatar1?=?require("./assets/images/avatar-1.jpg");
const?cat1?=?require("./assets/images/express-cat-1.gif");
//?數(shù)據(jù)模塊
const?fileTxt?=?require("./assets/data/notes.txt");
const?fileXml?=?require("./assets/data/hello.xml");
//?字體模塊
const?font?=?require("./assets/fonts/Calibre-Regular.otf");
const?iconFont1?=?require("./assets/fonts/iconfont.ttf");
const?iconFont2?=?require("./assets/fonts/iconfont.woff");
const?iconFont3?=?require("./assets/fonts/iconfont.woff2");
//?樣式模塊
//?const?reset?=?require('./assets/styles/reset.css');
//?const?global?=?require('./assets/styles/global.scss');
const?dom?=?document.getElementById("root");
//?header
new?Header(dom);
//?side-bar
new?Sidebar(dom);
//?content
new?Content(dom);
復(fù)制代碼
引入了各種文件模塊并放入變量中,每一個模塊在打包以后都有值,可以正常使用。
二、Loaders — 加載器
webpack enables use of?loaders[2]?to preprocess files. This allows you to bundle any static resource way beyond JavaScript. You can easily write your own loaders using Node.js.
簡單來說,loaders 可以幫我們預(yù)處理任何靜態(tài)文件并打包。
例如:圖片文件,樣式文件、html 文件,甚至是一些數(shù)據(jù)文件:txt 文件、xml 文件……
那么,如何配置呢?
三、Asset Modules — 靜態(tài)資源模塊
根據(jù) Webpack5 的文檔,它簡化了之前版本對于文件方面的配置,提供了?Asset Modules[3]?(靜態(tài)資源模塊),替代了之前常用的?raw-loader、url-loader、file-loader。
也就是說不用再安裝這幾個 loader 了,直接通過一行type: 'asset'?搞定,書寫更加方便!(注意 module 中 type 的設(shè)置?。?/strong>
//?webpack?配置文件
const?path?=?require("path");
module.exports?=?{
??mode:?"production",
??entry:?{
????main:?"./src/index.js",
??},
??output:?{
????path:?path.resolve(__dirname,?"dist"),
????filename:?"bundle.js",
??},
??//?***?模塊選項中匹配的文件會通過 loaders 來轉(zhuǎn)換!
??module:?{
????rules:?[
??????//?圖片文件
??????{
????????test:?/\.(jpe?g|png|gif|svg)$/i,
????????type:?"asset",?//?一般會轉(zhuǎn)換為?"asset/resource"
??????},
??????//?字體文件
??????{
????????test:?/\.(otf|eot|woff2?|ttf|svg)$/i,
????????type:?"asset",?//?一般會轉(zhuǎn)換為?"asset/inline"
??????},
??????//?數(shù)據(jù)文件
??????{
????????test:?/\.(txt|xml)$/i,
????????type:?"asset",?//?一般會轉(zhuǎn)換成?"asset/source"
??????},
????],
??},
};
復(fù)制代碼
每次打包后,文件都會根據(jù) output 的配置放進輸出文件夾,但是對于壓縮處理的文件則不會被打包到文件夾,只會以base64 編碼格式或者 URI 編碼等格式存在于 bundle.js 的源碼中。
源碼中的 base64

源碼中的 URI

默認(rèn)情況下,當(dāng)文件小于 8 kb 則會采用 base64 編碼,那么上面出現(xiàn)的 URI 編碼怎么設(shè)置的呢?
1. 特殊處理
某些文件需要特殊處理。例如,SVG 文件轉(zhuǎn)換為 URI 編碼后,與 base64 相比,體積會更??;
處理 txt、xml 文件?type: 'asset/source'?更好,它會導(dǎo)出資源的源碼,這樣就能看到全貌,而非 base64 格式的編碼字符串。
最小化 SVG 文件需要安裝一個第三方庫:mini-svg-data-uri[4].
npm?i?-D?mini-svg-data-uri
復(fù)制代碼
安裝之后,重新配置如下:
//?webpack?配置文件
const?path?=?require("path");
const?miniSVGDataURI?=?require("mini-svg-data-uri");
module.exports?=?{
??//?...
??module:?{
????rules:?[
??????//?圖片文件
??????{
????????test:?/\.(jpe?g|png|gif)$/i,
????????type:?"asset",
??????},
??????//?svg文件
??????{
????????test:?/\.svg$/i,
????????type:?"asset/inline",
????????generator:?{
??????????dataUrl(content)?{
????????????content?=?content.toString();
????????????return?miniSVGDataURI(content);
??????????},
????????},
??????},
??????//?字體文件
??????{
????????test:?/\.(otf|eot|woff2?|ttf|svg)$/i,
????????type:?"asset",
??????},
??????//?數(shù)據(jù)文件
??????{
????????test:?/\.(txt|xml)$/i,
????????type:?"asset/source",
??????},
????],
??},
};
復(fù)制代碼
至此,打包完成?,F(xiàn)在,看看這些文件模塊究竟被打包成什么數(shù)據(jù)了,在 index.js 文件中添加如下代碼:
//?js?模塊
const?Header?=?require("./components/header");
const?Sidebar?=?require("./components/sidebar");
const?Content?=?require("./components/content");
//?圖片模塊?(這?5?個都是小圖)
const?icon1?=?require("./assets/icons/mac-os.png");
const?icon2?=?require("./assets/icons/apple-tv.png");
const?icon3?=?require("./assets/icons/apple-contacts.png");
const?iconSvg1?=?require("./assets/icons/arrow-up.svg");
const?iconSvg2?=?require("./assets/icons/arrow-down.svg");
//?圖片模塊?(這?3?個都是大圖)
const?dog1?=?require("./assets/images/animal-dog-1.png");
const?avatar1?=?require("./assets/images/avatar-1.jpg");
const?cat1?=?require("./assets/images/express-cat-1.gif");
//?其他文件模塊
const?fileTxt?=?require("./assets/data/notes.txt");
const?fileXml?=?require("./assets/data/hello.xml");
//?字體模塊
const?font?=?require("./assets/fonts/Calibre-Regular.otf");
const?iconFont1?=?require("./assets/fonts/iconfont.ttf");
const?iconFont2?=?require("./assets/fonts/iconfont.woff");
const?iconFont3?=?require("./assets/fonts/iconfont.woff2");
//?樣式模塊
//?const?reset?=?require('./assets/styles/reset.css');
//?const?global?=?require('./assets/styles/global.scss');
//?在控制臺中打印這些模塊:
console.log("icon-png:?",?icon1);
console.log("icon-svg:?",?iconSvg1);
console.log("png:?",?dog1);
console.log("jpg:?",?avatar1);
console.log("gif:?",?cat1);
console.log("txt:?",?fileTxt);
console.log("xml:?",?fileXml);
console.log("font:?",?font);
console.log("iconFont:?",?iconFont1);
const?dom?=?document.getElementById("root");
//?插入一張圖片
const?img?=?new?Image();
img.src?=?dog1;?//?圖片模塊?dog1?作為?img?變量的?src?屬性值
img.width?=?200;
root.append(img);
//?header
new?Header(dom);
//?side-bar
new?Sidebar(dom);
//?content
new?Content(dom);
復(fù)制代碼
瀏覽器頁面展示如下:
可以看到 SVG 文件?(icon-svg)?是被處理成 URI 編碼的!
2. 配置靜態(tài)文件名
默認(rèn)情況下,打包以后的文件是散在 dist 文件夾中的,難以區(qū)分和維護。
現(xiàn)在,需要將他們分門別類地放進對應(yīng)的文件夾中,就需要對文件名做統(tǒng)一的管理。
webpack.config.js
const?path?=?require("path");
const?miniSVGDataURI?=?require("mini-svg-data-uri");
module.exports?=?{
??mode:?"production",
??entry:?{
????main:?"./src/index.js",
??},
??output:?{
????path:?path.resolve(__dirname,?"dist"),
????filename:?"bundle.js",
????//?靜態(tài)文件打包后的路徑及文件名(默認(rèn)是走全局的,如果有獨立的設(shè)置就按照自己獨立的設(shè)置來。)
????assetModuleFilename:?"assets/[name]_[hash][ext]",
??},
??module:?{
????rules:?[
??????//?圖片文件
??????{
????????test:?/\.(jpe?g|png|gif)$/i,
????????type:?"asset",
????????generator:?{
??????????filename:?"images/[name]_[hash][ext]",?//?獨立的配置
????????},
??????},
??????//?svg?文件
??????{
????????test:?/\.svg$/i,
????????type:?"asset",
????????generator:?{
??????????dataUrl(content)?{
????????????content?=?content.toString();
????????????return?miniSVGDataURI(content);
??????????},
????????},
??????},
??????//?字體文件
??????{
????????test:?/\.(otf|eot|woff2?|ttf|svg)$/i,
????????type:?"asset",
????????generator:?{
??????????filename:?"fonts/[name]_[hash][ext]",
????????},
??????},
??????//?數(shù)據(jù)文件
??????{
????????test:?/\.(txt|xml)$/i,
????????type:?"asset/source",?//?exports?the?source?code?of?the?asset
??????},
????],
??},
};
復(fù)制代碼
第 23 - 25 行,用于單獨配置靜態(tài)文件名;
generator:?{
??filename:?'images/[name]_[hash][ext]'?//?單獨配置打包的路徑及文件名
},
復(fù)制代碼
第 12 行,assetModuleFilename: 'assets/[name][ext]',?用于設(shè)置全局的靜態(tài)文件路徑及文件名。如果文件模塊沒有單獨進行配置,就按照這個來設(shè)置文件名。
其中,[name]?表示原來的文件名,[hash]?表示散列值,[ext]?表示文件后綴。這些都屬于占位符(placeholders),在 webpack4 中有提到:v4.webpack.js.org/loaders/fil…[5]
3. asset 類型
當(dāng) type 設(shè)置為'asset',就會按照以下的策略去打包文件:
如果一個模塊大小超過 8 kb(這個值是默認(rèn)的),就使用? asset/resource,被打包進輸出文件夾中。(類似于 file-loader)否則,就使用? asset/inline,內(nèi)聯(lián)到打包文件中。(類似于 url-loader)
區(qū)別在于:前者會被單獨放進輸出文件夾中,后者被處理成 base64 編碼字符串內(nèi)斂進打包出的 JS 文件中。
后者的好處在于減少一次 http 請求,但是過長的字符串也會加重 js 的體積導(dǎo)致加載變慢,因此需要根據(jù)實際情況來確定到底采用哪一種方式去處理文件。
注意,當(dāng)被作為后者處理時,是可以設(shè)置編碼方式的,例如上面提到的特殊處理。
手動通過?`Rule.parser.dataUrlCondition.maxSize`[6]?去設(shè)置兩者的界限:
{
??test:?/\.(jpe?g|png|gif)$/i,
??type:?'asset',
??generator:?{
????filename:?'images/[name]_[hash][ext]',
??},
??parser:?{
????dataUrlCondition:?{
??????maxSize:?8?*?1024?//?8kb?(低于8kb都會壓縮成?base64)
????}
??},
},
{
??test:?/\.svg$/i,
??type:?'asset',
??generator:?{
????filename:?'icons/[name]_[hash][ext]',
????dataUrl(content)?{
??????content?=?content.toString();
??????return?miniSVGDataURI(content);?//?通過插件提供的編碼算法處理文件
????}
??},
??parser:?{
????dataUrlCondition:?{
??????maxSize:?2?*?1024?//?2kb?(低于2kb都會壓縮)
????}
??},
},
復(fù)制代碼
另外,除了asset/resource、asset/inline,還有一個上文提到的,用于處理 txt、xml 文件的asset/source,它相當(dāng)于 webpack4 的 raw-loader。
三、樣式文件的處理
1. style-loader & css-loader
style-loader 和 css-loader 是相輔相成的。
style-loader:將? ?標(biāo)簽插入到 DOM 中。css-loader:解析通過 @import、url()、import/require() 這些方式引入的樣式文件。
安裝 loaders:
npm?install?--save-dev?css-loader?style-loader
復(fù)制代碼
webpack.config.js
module.exports?=?{
??module:?{
????rules:?[
??????//?...
??????{
????????test:?/\.css$/i,
????????use:?["style-loader",?"css-loader"],
??????},
????],
??},
};
復(fù)制代碼
注意兩個 loader 的位置,要反過來寫。
index.js
//?...
const?reset?=?require("./assets/styles/reset.css");
console.log("css:?",?reset);
import?"./assets/styles/reset.css";
const?dom?=?document.getElementById("root");
dom.innerHTML?+=?'This?a?text.
';
復(fù)制代碼
reset.css
@font-face?{
??font-family:?"iconfont";
??/*?Project?id?1947684?*/
??src:?url("../fonts/iconfont.woff2?t=1627306378388")?format("woff2"),?url("../fonts/iconfont.woff?t=1627306378388")?format("woff"),
????url("../fonts/iconfont.ttf?t=1627306378388")?format("truetype");
}
.iconfont?{
??font-family:?"iconfont"?!important;
??font-size:?16px;
??font-style:?normal;
??-webkit-font-smoothing:?antialiased;
??-moz-osx-font-smoothing:?grayscale;
}
.icon-mianxingshezhi:before?{
??content:?"\e6ad";
}
/*?...?*/
復(fù)制代碼
解釋:reset.css 中引入了字體圖標(biāo)(來自 iconfont),index.js 中通過import的方式引入了這個 css 文件,并使用了這個字體圖標(biāo)。
其中,
import './assets/styles/reset.css'、
onst reset = require('./assets/styles/reset.css');
這兩段語句背后的 CSS 代碼就是通過 css-loader 去解析的。
控制臺展示如下:
上圖中,在 CSS 樣式被 css-loader 解析完成后,??標(biāo)簽通過 style-loader 插入到 DOM 中。
2. sass-loader
sass-loader:加載一個 Sass/SCSS 文件,并編譯成 CSS。
安裝:
npm?install?sass-loader?sass?webpack?--save-dev
復(fù)制代碼
webpack.config.js
module.exports?=?{
??module:?{
????rules:?[
??????//?...
??????{
????????test:?/\.s[ac]ss$/i,
????????use:?[
??????????//?Creates?`style`?nodes?from?JS?strings
??????????"style-loader",
??????????//?Translates?CSS?into?CommonJS
??????????"css-loader",
??????????//?Compiles?Sass?to?CSS
??????????"sass-loader",
????????],
??????},
????],
??},
};
復(fù)制代碼
index.js
const?global?=?require("./assets/styles/global.scss");
console.log("scss:?",?global);
import?"./assets/styles/global.scss";
const?dom?=?document.getElementById("root");
//?insert?an?image
const?img?=?new?Image();
img.src?=?dog1;
//?img.width?=?200;
img.classList.add("avatar");
root.append(img);
復(fù)制代碼
global.scss
//?自定義變量
$color:?#ff4200;
$fs:?14px;
$ls:?1.2;
//?自定義mixin
@mixin?size($w,?$h:?$w)?{
??width:?$w;
??height:?$h;
}
body?{
??font-size:?$fs;
??background-color:?#eaeaea;
??.avatar?{
????@include?size(150px);
????transform:?translateX(50px);
??}
}
復(fù)制代碼
控制臺展示如下:
上圖第二個??標(biāo)簽內(nèi)的樣式代碼就是 global.scss 中轉(zhuǎn)譯成 css,并通過 style-loader 插入到 DOM 中的。
3. postcss-loader
PostCSS is a tool for transforming styles with JS plugins. These plugins can lint your CSS, support variables and mixins, transpile future CSS syntax, inline images, and more.
postcss 倉庫:github.com/postcss/pos…[7]
autoprefixer:github.com/postcss/aut…[8]
postcss-preset-env:github.com/csstools/po…[9]
a) 安裝
安裝:
npm?install?--save-dev?postcss-loader?postcss
復(fù)制代碼
webpack.config.js
module.exports?=?{
??module:?{
????rules:?[
??????//?...
??????{
????????test:?/\.s?css$/i,
????????use:?["style-loader",?"css-loader",?"sass-loader",?"postcss-loader"],
??????},
????],
??},
};
復(fù)制代碼
注意,postcss-loader 要放在最后。
b) 配置
Autoprefixer
PostCSS[10]?plugin to parse CSS and add vendor prefixes to CSS rules using values from?Can I Use[11].
PostCSS Preset Env
PostCSS Preset Env[12]?lets you convert modern CSS into something most browsers can understand, determining the polyfills you need based on your targeted browsers or runtime environments.
PostCSS SCSS Syntax
A?SCSS[13]?parser for?PostCSS[14].
This module does not compile SCSS.?It simply parses mixins as custom at-rules & variables as properties, so that PostCSS plugins can then transform SCSS source code alongside CSS.
插件需要安裝一下:
npm?install?postcss-preset-env?autoprefixer?postcss-scss?--save-dev
復(fù)制代碼
在根目錄下,新建:postcss.config.js
module.exports?=?{
??syntax:?"postcss-scss",
??plugins:?[require("autoprefixer"),?"postcss-preset-env"],
};
復(fù)制代碼
webpack.config.js 不需要修改。
四、補充:css-loader 的 options
不同的 loader 都會有各自的 options,css-loaders 有兩個實用的 options。
importLoaders:The option? importLoaders?allows you to configure how many loaders before?css-loader?should be applied to?@imported resources and CSS modules/ICSS imports. 簡單來說,就是允許你在執(zhí)行 css-loader 前,讓某些資源要經(jīng)過前面的 loaders 去處理的個數(shù)。modules:Allows to enable/disable CSS Modules or ICSS and setup configuration. 允許你開啟?CSS Module。
webpack.config.js
module.exports?=?{
??module:?{
????rules:?[
??????{
????????test:?/\.sc?ss$/i,
????????use:?[
??????????"style-loader",
??????????{
????????????loader:?"css-loader",
????????????options:?{
??????????????importLoaders:?2,
??????????????//?0?=>?no?loaders?(default);
??????????????//?1?=>?postcss-loader;
??????????????//?2?=>?postcss-loader,?sass-loader
??????????????modules:?true,?//?默認(rèn)是?false?***
????????????},
??????????},
??????????"sass-loader",
??????????"postcss-loader",
????????],
??????},
????],
??},
};
復(fù)制代碼
注意,上面?importLoaders 設(shè)置為 2,表示匹配到樣式文件時,都會去執(zhí)行一開始的兩個 loader,也就是 'sass-loader' 以及 'postcss-loader'.
至于?modules 設(shè)置為 true,則保證了樣式模塊的獨立性,不會被互相覆蓋。具體看下面的例子:
a) 不設(shè)置 modules 選項
src/assets/styles/global.scss
//?自定義變量
$color:?#ff4200;
$fs:?14px;
$ls:?1.2;
//?自定義mixin
@mixin?size($w,?$h:?$w)?{
??width:?$w;
??height:?$h;
}
body?{
??font-size:?$fs;
??background-color:?#eaeaea;
??.avatar?{
????@include?size(150px);
????transform:?translateX(50px);
??}
}
復(fù)制代碼
src/assets/js/createImg.js
import?avatar?from?"../images/avatar-2.jpg";
import?dog1?from?"../images/animal-dog-1.png";
function?createImg(root)?{
??const?img?=?new?Image();
??img.src?=?dog1;
??img.classList.add("avatar");?//?添加?avatar?類名
??root.append(img);
}
export?default?createImg;
復(fù)制代碼
src/index.js
import?"./assets/styles/reset.css";
import?"./assets/styles/global.scss";
import?dog1?from?"./assets/images/animal-dog-1.png";
import?createImg?from?"./assets/js/createImg.js";
const?dom?=?document.getElementById("root");
//?第一張圖的創(chuàng)建
createImg(dom);
//?第二張圖的創(chuàng)建
//?insert?an?image
const?img?=?new?Image();
img.src?=?dog1;
img.classList.add("avatar");?//?//?添加?avatar?類名
root.append(img);
復(fù)制代碼
在這種情況下,import './assets/styles/global.scss'這一句對全局的文件都有效果,因此它的后一句?import createImg from './assets/js/createImg.js';?就會受到影響。影響的結(jié)果就是,createImg.js?文件中的第 7 行代碼的樣式添加,添加的就是 global.scss 中的第 15 行的選擇器 'avatar' 的樣式。
打包后,結(jié)果如圖所示:

b) 將 modules 設(shè)置為 true
設(shè)置完后,代碼不變的情況下,打包完成后,查看結(jié)果如圖所示:
前后兩張秋田犬均變成了超大的圖,原因是樣式?jīng)]有作用到圖片上。
當(dāng)應(yīng)用?CSS Module[15]?時,要通過以下的方式添加樣式:
(注意:這里僅對 index.js 中的代碼作修改,使之恢復(fù),而 createImg.js 模塊中的代碼不變,從而形成對比。)
src/index.js
import?"./assets/styles/global.scss";
import?style?from?"./assets/styles/global.scss";?//?自定義變量名稱,作為?css?module?導(dǎo)入
import?dog1?from?"./assets/images/animal-dog-1.png";
import?createImg?from?"./assets/js/createImg.js";
const?dom?=?document.getElementById("root");
//?第一張圖的創(chuàng)建
createImg(dom);
//?第二張圖的創(chuàng)建
//?insert?an?image
const?img?=?new?Image();
img.src?=?dog1;
// css module 的使用:通過自定義變量名稱 style 加 . 的形式訪問到 avatar 類名
img.classList.add(style.avatar);?
root.append(img);
復(fù)制代碼
打包后,結(jié)果如圖所示:
通過對比,就可以發(fā)現(xiàn),通過模塊方式設(shè)置的秋田犬的樣式是有的,而第一張則沒有。
這種利用 CSS Module 的方式來添加樣式的方法,解決了全局覆蓋同名樣式(一般是類名和 id)的尷尬情況。
閱讀參考:
github.com/css-modules…[16] github.com/css-modules…[17] webpack.js.org/loaders/css…[18] webpack.js.org/loaders/css…[19]
小結(jié)
以上,是本篇的所有內(nèi)容。在 Webpack5 中,對于靜態(tài)資源的處理,我們只要簡單地設(shè)置 type 就能處理文件,非常方便。但是有些特殊情況還是需要去單獨處理的,例如資源輸出的路徑及文件名的設(shè)置、URI 編碼格式的設(shè)置、轉(zhuǎn) base64 的文件大小限制的設(shè)置。樣式文件中,為了防止全局的樣式污染,可以開啟 CSS Module 來避免。
關(guān)于本文
本系列來自:EricKnight
https://juejin.cn/user/2154698521972423/posts
