预处理器 (loader) ,的主要功能是赋予了 Webpack 可处理不同资源类型的能力,极大丰富了其可扩展性。
一个Web工程通常会包含HTML、JS、CSS、模板、图片、字体等多种类型的静态资源,并且这些资源之间都存在着某种联系。对于 Webpack 来说,所有这些静态资源都是模块,可以像加载一个JS文件一样去加载它们,如在index.js中加载style.css:
dart
//index.js
import './style.css'
这个style.css虽然是被加载在js文件中,但其实是可以被打包并生成在输出资源目录下,对index.js 文件也不会产生质性的影响。这句引用的实际意义是描述了JS 文件与 CSS 文件之间的依赖关系。
在实际开发中,有时某个页面用到了组件,不光要加载JS文件,还要加载样式,而通过Webpack我们可以采用一种更简洁的方式来表达这种依赖关系
dart
// 。/u1/calendar/index.js
1mport './style.scss'; //引用组件自身样式
// ./page/home/index.js
import Calendar from './ui/calendar/index.js';
import './styles.css' ;// 引用页面自身样式
可以看到,在calendar的JS中加载了其组件自身的样式,而对于页面来说只要载calendar/index.js 即可(以及页面自身的样式),不需要额引入组件的样式。让Webpack维护模块间的关系可以使工程结构更加直观,代码的可维护性更强。
loader 概述
每个loader本质上都是一个函数。在 Webpack 4之前,函数的输人和输出都必须为字符串; 在Webpack 4之后,loader也同时支持抽象语法树 (AST)的传递,通过这种方法来减少重复的代码解析。用公式表达 loader的本质则为以下形式:
output = loader(input)
举一个例子,当我们使用babel-loader将ES6+的代码转化为ES5时,上面的公式如下:
dart
ES5 =babel-loader(ES6+)
loader 可以是链式的。我们可以对一种资源设置多个loader,第一个loader 的输入是文件源码,之后所有 loader 的输入都为上一个loader 的输出。公式如下:
output = loaderA(loaderB(loaderC(input)))
如在工程中编译SCSS时,我们可能需要如下loader:
Style 标签=style-loader(css-loader(sass-loader(SCSS)))
loader 的配置
要注意loader 做的实际上是一个预处理的工作。
loader的引入
假设我们要处理CSS,首先依照 Webpack"一切皆模块"的思想,从一个JS文件加载一个CSS文件。
dart
// app.js
import './style.css';
// style.css
body
text-align; center;
padding: 100px;
color: #fff;
background-color: #09c;
}
此时工程中还没有任何 loader,如果直接打包会看到报错提示
Webpack 无法处理 CSS语法,因此抛出了一个错误,并提示需要使用一个合适loader来处理这种文件。
下面把css-loader加到工程中。loader都是一些第三方npm模块,Webpack身并不包含任何loader,因此使用loader的第一步就是先从npm安装它。在工程目下执行命令:
dart
npm install css-loader
接下来我们将 loader引人工程中,具体配置如下:
dart
module.exports = {
// ...
module: {
rules: {
test: /\.csss/,
use: {'css-loader'},
}
}
}
与loader相关的配置都在module对象中 ,其中module.rules 代表了模块的处理规则 。每条规则内部可以包含很多配置项,这里我们只使用了最重要的两项一test 和use。
test
test 可接收一个正则表达式或者一个元素为正则表达式的数组,只有正则配上的模块才会使用这条规则。在本例中是匹配所有以css 结尾的文件。
use
use可接收一个数组,数组包含该规则所使用的 loader。在本例中只配置了一个css-loader,在只有一个loader 时也可以简化为字符串"css-loader"。
此时我们再进行打包,之前的错误应该已经消失了,但是 CSS 的样式仍然没有在页面上生效。这是因为css-loader 的作用仅仅是处理CSS 的各种加载语法 ,如果要使样式起作用还需要 style-loader来把样式插人页面。css-loade与style-loader通常是配合在一起使用的
链式loader
在处理某一类资源时我们都需要使用多个 loader。就比如上面的例子,用css-loader处理CSS的各类加载语法,再使用style-loader 来将样式字符串包装成style标签插入页面。
配置如下:
dart
module.exports = {
// ...
module: {
rules: {
test: /\.csss/,
use: ['style-loader','css-loader'],
}
}
}
我们把style-loader加到了css-loader前面,这是因为在Webpack打包时是按照数组从后往前的顺序将资源交给 loader 处理的,因此要把最后生效的放在前面。
loader options
loader作为预处理器通常会给开发者提供一些配置项,在引入loader 的时候可以通过options将它们传人。
下面是一些场景下loader的相关配置
exclude与 include
exclude 与 include是用来排除或包含指定目录下的模块,可接收正则表达式或者字符串(文件绝对路径),以及由它们组成的数组。如下例:
dart
rules: {
test: /\.csss/,
use: ['style-loader','css-loader'],
exclude: /node_modules/,
}
上面配置的含义是node_modules 中的模块不会执行这条规则。该配置项通常是必加的,否则可能拖慢体的打包速度。
include的例子如下:
dart
rules: {
test: /\.csss/,
use: ['style-loader','css-loader'],
include: /src/,
}
include代表该规则只对正则匹配到的模块生效。
exclude和include 同时存在时,exclude的优先级更高 。
由于exclude优先级更高,我们可以对include中的子目录进行排除。请看下面的例子:
dart
rules: {
test: /\.csss/,
use: ['style-loader','css-loader'],
exclude:/src\/lib/,
include: /src/,
}
通过include,我们将该规则配置为仅对 src 目录生效,但是仍然可以通过exclude排除其中的src/lib目录。
resource与issuer
resource 与issuer可用于更加精确地确定模块规则的作用范围。就比如下面的例子:
dart
// index.js
import ./style.css';
在Webpack 中,一般认为被加载模块是 resource ,而加载者是 issuer。如上面的例子中,resource为/path/of/app/style.css,issuer 是/path/of/app/index.js。
如果想要对 issuer 加载者也增加条件限制,则要额外写一些配置。比如,如果我们只让/src/pages目录下的JS可以引用CSS,应该如何设置呢?可以这样:
dart
issuer: {
test: /\.js$/,
include: /src/pages/,
}
可以看到,我们添加了issuer 配置对象,其形式与之前对resource条件的配置并无太大差异。
enforce
enforce用来指定一个loader的种类,只接收"pre"或"post"两种字符电类型的值。
请看下面的例子:
dart
rules:[
{
test: /\.js$/,
enforce: 'pre',
use:'eslint-loader',
}
]
可以看到,在配置中添加了一个eslint-loader来对源码进行质量检测,其enforce的值为"pre",代表它将在所有正常 loader之前执行 ,这样可以保证其检测的代码是被其他loader 更改过的。
类似的,如果某一个loader 是需要在所有 loader 之后执行的也可以指定其enforce为"post"
事实上,我们也可以不使用enforce而只要保证loader顺序是正确的即可 。
配置enforce 主要的目的是使模块规则更加清晰,可读性更强 ,尤其是在实际工程中,置文件可能达到上百行的情况,难以保证各个loader 都按照预想的方式工作,使用enforce可以强制指定 loader的作用顺序。