「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战」。
上一篇文章《webpack5教程一:初识webpack》中我们讲解了webpack简单使用,成功加载并编译了js模块,本文将通过 资源模块(asset module) 与 加载器(Loader) 来实现其它类型模块的加载与编译。
Webpack默认只支持js模块的加载,如果在没有进行额外配置前加载其它类型模块,则会在运行进会报错,如下代码加载一张logo.png图片:
// index.js
import logoUrl from './assets/logo.png'
const logo = new Image();
logo.src = logoUrl
document.body.appendChild(logo)
直接运行webpack时控制台会报以下错误,因为此时Webpack并不能识别图片资源
Webpack把所有资源文件当做一个模块来处理,而除js以外的其它模块都需要设置相应的加载规则Rule才能正确处理,所以我们先大概了解一下每一条Rule的写法:
{
module:{
rules:[
{
// 模块加载规则
}
]
}
}
每一条Rule规则的常用属性如下:
test: 匹配资源文件的正则表达式,上面的代码是匹配png图片type: 设置资源模块类型use/loader: 设置加载器,类型可以是String | Object | Arraygenerator: 设置资源生成信息filename: 设置资源文件输出名称和保存的路径(只适用于type属性为asset和asset/resource的场景)
include/exclude:手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)
接下来我们使用最简单的方式——资源模块(asset module) 来解决以上问题
资源模块(asset module)
资源模块(asset module) 是一种模块类型,它允许在js代码中引入其它资源文件(字体,图片、样式等),以前只能通过 loader 加载器来实现,而Webpack5中通过asset module轻松实现。
Webpack5 实现了 4 种新的模块类型,通过Rule的type属性设置:
asset/resource输出一个单独的文件并导出 URL。之前通过使用file-loader实现。asset/inline导出一个资源的 data URI。之前通过使用url-loader实现。asset/source导出资源的源代码。之前通过使用raw-loader实现。asset在导出一个 dataURI 和发送一个单独的文件之间自动选择。之前通过使用url-loader,并且配置资源体积限制实现。
接下来我们用资源模块来解决上面报错的问题,添加一条Rule:匹配png图片并输出单独文件
// webpack.config.js
module.exports = {
// ...
module:{
rules:[
{
test:/\.png$/,
type:'asset/resource'
}
]
}
}
基于以上配置运行webpack,控制台成功编译,并在输出目录dist中得到一个图片文件
如果设置成asset/inline,则会把图片对应的dataURI(base64编码)写入到打包后的js文件中,这样的好处是减少页面的http请求数量,加快页面渲染速度
但也并不是所有的图片都适合使用这种方式,因为越是复杂的图片生成的base64编码量就越多,整体大小甚至会超过图片本身的大小(如上图72.7KB,图片本身只有52kB),所以有时候我们希望Webpack能帮助我们自动调整适配,这时就可以使用type:"asset"配置了。
如果设置成asset,则会根据图片的大小在asset/inline 和 asset/resource 中自动选择
默认大于
8KB导出单独文件,小于等于8KB导出dataURI(可通过Rule的parser.dataUrlCondition.maxSize去设置,单位是:Byte字节)
module:{
rules:[
{
test:/\.png$/,
type:'asset',
parser:{
dataUrlCondition:{
maxSize:1024*20, // 20KB
}
`}`
}
]
}
其它类型效果各位小伙伴可以自行测试,怎么样,是不是爽到没朋友?
修改资源模块输出文件名与路径
如上图,图片文件虽然已经输出,但文件名是一段哈希字符,如果想修改特定的文件名称,可以通过generator.filename设置资源文件名和保存的路径,与output.filename的作用类似,也可以通过[name]原文件名、[hash]哈希字符、[ext]扩展名等变量控制文件名
该属性与
output.assetModuleFilename相同,但generator.filename的优先级更高
module:{
rules:[
{
test:/\.png$/,
type:'asset/resource',
generator:{
filename:'img/[name]-[hash][ext]'
}
}
]
}
加载器(Loader)
除了资源模块方式,你也可以使用Loader实现其它类型资源的加载,不同的资源应用的不同的loader(loader需要单独安装),然后在webpack配置文件中通过module.rules来配置,Rule中设置 use 或 loader 属性即可(loader为use的简写)
使用加载器一般需要两步,步骤如下:
- 安装loader
如加载器依赖其它模块,则一并安装
- 编写Rule并配置loader
如需对加载器进行一些细节配置,则添加
options属性
1、CSS加载器
css-loader
以css文件模块为例,假设代码中需要引入CSS文件样式,如下代码:
// index.js
import './css/home.css'
样式中随便写几条简单的css属性:
/*./css/home.css*/
body {
padding: 20px;
margin: 0;
color:#f00;
}
要正确处理该样式文件,需要先安装该模块css-loader:
npm install css-loader
webpack.config.js中配置样式加载器,文件代码如下:
module.exports = {
// ...
module:{
rules:[
{
test:/\.css$/,
use:'css-loader', // 或 loader:'css-loader'
}
]
}
}
webpack执行后样式代码最终会打包到js文件中
style-loader
但实际开发中,如果我们仅仅这样配置,最终的页面我们是无法看到样式效果的,原因很简单,现在的样式只是写入js文件当中,并没在页面中使用,样式要想在页面中生效,我们还得使用style-loader加载器把css代码写入页面style标签,同理,先安装该模块
npm install style-loader
由于有两个loader,需要使用到数组,使用时注意先后顺序:webpack使用加载器的顺序是从后往前调用,最终的配置代码如下:
module.exports = {
// ...
module:{
rules:[
{
test:/\.css$/,
use:['style-loader','css-loader'], // 先使用css-loader再使用style-loader
}
]
}
}
这时我们用一个html文件引入打包后的main.js文件就可以看到效果了,样式被写入到页面的 style标签 中
css模块(css module)
如需要实现一些高级功能,还需要对这些加载器配置相应参数,这时加载器就需要使用对象来设置,如需要实现css模块(css module)可以通过以下设置
rules:[
{
test:/\.css$/,
use:[
'style-loader',
{
loader:'css-loader',
options:{
modules:true
}
}
]
}
]
为了看到css模块效果,给home.css文件添加几条样式
body {
padding: 20px;
margin: 0;
color:#f00;
}
.box{color:#58bc58;}
.msg{background-color:#efefef;}
设置后在引入样式时我们就可以使用以下代码加载样式并使用
import homeStyle from './css/home.css
console.log(homeStyle)
输出后的效果如下:
在加载器中通过这些高级设置,我们就可以在js中我们可以通过homeStyle.msg把样式应用到页面,从而实现局部样式效果
PS: css-loader默认支持*.module.css格式的样式文件实现模块化,也就是说如果你的文件采用该命名方式,哪不配置modules:true,也能实现css模块化
2、postcss-loader
配合autoprefixer与browserslist自动添加浏览器内核前缀,从而实现样式兼容写法(不知道什么是browserslist?点这里)
- 先安装加载器与依赖
npm install postcss-loader autoprefixer
- 配置
browserslist(可在package.json或.browserslistrc中配置)
// package.json
{
"browserslist": [
"last 2 versions",
"> 1%",
"not IE 11"
]
}
# .browserslistrc
last 2 versions
> 1%
not IE 11
- webpack配置
rules:[
{
test:/\.css$/,
use:[
'style-loader',
'css-loader',
{
loader:'postcss-loader',
options:{
postcssOptions: {
plugins: ['autoprefixer']
}
}
}
]
}
]
- 编写样式
.box{display:flex;color:#58bc58;border-radius: 5px;}
编译后的效果就自动加上了前缀,如图:
3、sass加载器
如果是使用sass来写样式,则需要配置sass-loader加载器,该加载器依赖sass模块,所以需要一并安装
npm install sass-loader sass
webpack配置如下:
rules:[
{
test:/\.scss$/,
use:['sass-loader']
}
]
运行webpack控制台会抛出一个错误
正如错误提示所说,原因很简单,我们需要一个加载器处理sass结果,即sass编译成css后并没有添加css加载器去处理它,所以解决方案也很简单:添加css-loader
rules:[
{
test:/\.scss$/,
use:['css-loader','sass-loader']
}
]
如果需要把css输出到页面style标签,继续添加style-loader
rules:[
{
test:/\.scss$/,
use:['style-loader','css-loader','sass-loader']
}
]
以上就解决了sass代码编译的问题,less同理(需要less-loader与less),这里不再赘述
4、babel-loader
用于处理js模块,照说Webpack本身已经默认支持js模块的加载了,为什么我们还要额外配置js加载器呢?原因很简单,因为我们在写js代码时可能会用到一些ES6+的新特性,而这些新特性不一定会被浏览器所支持,所以需要使用Babel对这些代码进行转换,一般转换成ES5代码(不知道babel的童鞋请自行查看Babel官网)
首先还是先要安装babel-loader,它需要依赖@babel/core
npm install babel-loader @babel/core
接下来就是配置加载器了,但如果只是指定loader的名称,那就没什么意义,因为Webpack已经默认js模块的加载,所以我们使用对象来控制加载器细节配置(options属性)
rules:[
{
test:/\.js$/,
// use:'babel-loader',
use:{
loader:'babel-loader',
options:{
// babel-loader选项
}
}
}
]
至于需要如何配置就看个人需求了,比如希望把ES6+代码转成所有浏览器支持的ES5代码,这时我们就需要babel插件来实现,但ES6+新特性有很多,每个新特性都需要一个babel插件来转换,显然非常繁琐,所以Babel推出preset(预设)来解决这个问题,preset其实就是插件集合(内部包含多个插件),@babel/preset-env就是用来解决浏览器兼容问题的预设,使用也很简单,先安装
npm install @babel/preset-env
- 什么是plugin(插件)? Babel 构建在插件之上。使用现有的或者自己编写的插件可以组成一个转换管道。通过使用一个preset即可轻松使用一组插件
- 什么是preset(预设)? 可以理解为babel插件集合;
配置babel选项,并设置env预设,设置后默认会转换ES6+(ES2015~ES2022)所有新特性
rules:[
{
test:/\.js$/,
use:{
loader:'babel-loader',
options:{
presets:['@babel/preset-env']
}
}
}
]
以下为设置@babel/preset-env与不设置的输出效果
我们还可以根据指定环境编译,这就得使用@babel/preset-env的options配置选项了(需要使用数组的第二个参数)
rules:[
{
test:/\.js$/,
use:{
loader:'babel-loader',
options:{
presets:[
// 使用数组
[
'@babel/preset-env',
{
// 数组的第二项为env选项
},
]
]
}
}
}
]
@babel/preset-env常用选项如下:
targets: 指定目标环境(String|Object|Array),不指定时会根据browserlist配置(.browserlistrc或package.json)去兼容目标环境Object 类型下可指定浏览器或其它环境,如:
chrome,opera,edge,firefox,safari,ie,ios,android,node,electron{ // 指定市场份额超过0.25%的浏览器(不推荐,会导致对IE6的支持) targets:"> 0.25%, not dead", // 推荐写法:兼容所有浏览器最后两个版本 targets:"last 2 versions" // 对象写法 targets:{ // 指定浏览器版本 "chrome": "58", "ie":"11", "edge":"last 2 versions", "firefox":"> 5%", // 指定nodejs版本, current为当前运行的node版本 "node":"current" } },spec: 启用更符合规范的转换,但速度会更慢,默认为 falseloose:是否启动松散模式(默认:false)如:是否让原型中的属性具有可枚举性,loose模式下使用点语法添加属性,正常模式下通过Object.defineProperty()添加属性以确保属性的不可枚举
modules:将 ES Modules 转换为其他模块规范,值可以为:adm | umd | systemjs | commonjs | cjs | false(默认)
debug:是否启用debug模式,默认 falseuseBuiltIns:配置@babel/preset-env如何处理polyfills,共有以下3个选项默认情况下,
@babel/preset-env无法编译一些内建对象(如:Promise、Map、Set等),结果是原样输出,但在一些不支持Promise、Map、Set的环境下运行代码就会抛出错误,故需要使用core-js这样的库进行处理(Babel@7.4.0前使用@babel/polyifll,现在推荐使用core-js)entry:根据配置的浏览器兼容,引入浏览器不兼容的polyfill。需要在入口文件手动添加import '@babel/polyfill',会自动根据browserslist替换成浏览器不兼容的所有polyfill// 如果指定`corejs: 3`, 则入口文件中的 `import '@babel/polyfill'` 需要改成 import 'core-js/stable'; import 'regenerator-runtime/runtime';usage: 会根据配置的浏览器兼容,以及你代码中用到的 API 来进行polyfill,实现了按需添加,好处就是最终打包的文件会比较小false(默认):此时不对polyfill做操作。如果引入@babel/polyfill,则无视配置的浏览器兼容,引入所有的polyfill
corejs:指定corejs版本(默认:2.0),目前使用core-js@2或core-js@3版本,需要手动安装{ //... corejs:'3.8', // 或对象形式:{version:'3.8'} }
实操代码
如我现在想兼容浏览器最后2个版本,且按需兼容Promise等内建对象,步骤如下:
-
安装
core-js@3npm install core-js@3 -
webpack配置:这里只列表
babel-loader的配置
{
test:/\.js$/,
use:{
loader:'babel-loader',
options:{
presets:[
[
'@babel/preset-env',
{
targets:'last 2 versions',
useBuiltIns:'usage',
corejs:3
}
]
]
}
}
}
-
入口文件添加以下代码
import 'core-js/stable'; import 'regenerator-runtime/runtime'; -
编译代码
然后通过通过命令行执行以下命令就能输出最终代码(由于代码量太多,这里就不截图了)
npx webpack
Babel还很多插件和预设(如:@babel/preset-react、@babel/preset-typescript等),能帮助我们实现更多的需求,大家可以自行查阅官网,也可以关注我的其它关于Babel的文章。
5、其它加载器
在Webpack的生态中,这样的资源加载器有很多,在此我只是做个抛砖引玉,其它的加载器的使用与css-loader、style-loader、sass-loader、babel-loader类似,以下列出常用的几个加载器,小伙伴们可以根据自己的需求自行查找
- url-loader:对资源文件进行优化的加载器,可以解决图片等资源文件的引用路径问题,并可以根据limit设置把小图片一dataURI的方式保存,依赖
file-loader - ts-loader: 加载并编译typescript,依赖
typescript - less-loader:加载并编译less,依赖
less - vue-loader: 加载并编译
.vue单文件组件 - ...
结语
本文我们学会了使用资源模块和加载器解决资源文件加载与编译的问题,从文中可以看出,资源模块是Webpack开箱即用的方式,是一种处理资源文件比较简单的方式,但如果需要对资源文件进行更特殊的处理就建议使用加载器的方式。
到目前为止,我们只是能把资源模块编译打包到一个js文件中,如何展示打包效果,我们将在下一篇文章中详细说明,敬请关注