一、loader是什么
A loader is just a JavaScript module that exports a function.
从语法角度看,loader是一个普通的Node.js模块,只是必须以函数格式导出来供使用。如果有必要可以使用一切Node.js功能模块。
从功能角度看,loader是在Webpack中作用于指定格式的资源文件并将其按照一定格式转换输出。例如:less-loader将less文件转换为css文件输出。
二、loader的特点
单一职责,一个Loader只做一件事情,正因为职责越单一,所以Loaders的组合性强,可配置性好。
loader支持链式调用,上一个loader的处理结果可以传给下一个loader接着处理,上一个Loader的参数options可以传递给下一个loader,直到最后一个loader,返回Webpack所期望的JavaScript。
三、loader的配置
在学习loader的配置时,最好搭个简易的Webpack Demo,执行webpack
命令打包,可以验证一下配置是否有误。
loader在Webpack中的配置有多种写法,下面一一详解。
先来看一个简单的loader配置。
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader','css-loader']
},
],
},
}
loader是配置在module.rules中,module.rules的含义是创建模块的规则,module.rules的值是一个数组,其中每一项都是一项规则。loader是用来生成符合Webpack的模块的。然后Webpack把这些模块打包起来生成对应的js文件。loader是在打包前执行的。
如下图所示,这是用style-loader
和css-loader
两个loader生成的模块。
在这里称module.rules中每一项规则为Rule,下面来讲配置规则Rule的条件和配置规则Rule的loader。
1、配置规则Rule的条件
Rule.test
在简单的loader配置中,test:/\.css$/
,是筛选到名称以.css
结尾的文件后,交给user
选项里面的loader处理一下。
那么test
选项的作用就是筛选资源,符合条件的资源让这项规则中的loader处理。
test
的值可以是字符串、正则表达式、函数、数组。
- 值为字符串时,可以为资源所在目录绝对路径 、资源的绝对路径。
const path = require('path');
module.exports = {
module: {
rules: [
{
test: path.resolve(__dirname, 'src/css'),
//test: path.resolve(__dirname, 'src/css/index.css'),
use: ['style-loader','css-loader']
},
],
},
}
- 值为函数时,接收的参数为资源的绝对路径。返回true表示该资源可以交给
user
选项里面的loader处理一下。
\project\03personal\05Webpack_demo\src\css\index.css
module.exports = {
module: {
rules: [
{
test: function (path) {
return path.indexOf('.css') > -1
},
use: ['style-loader','css-loader']
},
],
},
}
- 值为数组时,数组每一项可以为字符串、正则表达式、函数,只要符合数组中任一项条件的资源就可以交给
user
选项里面的loader处理一下。
const path = require('path');
module.exports = {
module: {
rules: [
{
test: [/\.css$/,path.resolve(__dirname, 'src/css')]
use: ['style-loader','css-loader']
},
],
},
}
Rule.include
符合条件的资源让这项规则中的loader处理,用法和Rule.test一样。
const path = require('path');
module.exports = {
module: {
rules: [
{
include:/\.css$/,
//include: path.resolve(__dirname, 'src/css'),
//include: path.resolve(__dirname, 'src/css/index.css'),
//include: [/\.css$/,path.resolve(__dirname, 'src/css')],
//include:function (content) {
//return content.indexOf('src/css') > -1
//},
use: ['style-loader','css-loader']
},
],
},
}
Rule.exclude
符合条件的资源要排除在外,不能让这项规则中的loader处理,用法和Rule.test一样。例如排除node_modules中的css文件。
const path = require('path');
module.exports = {
module: {
rules: [
{
exclude:/node_modules/,
//exclude: path.resolve(__dirname, 'node_modules'),
//exclude: [/node_modules/ , path.resolve(__dirname, 'node_modules')],
//exclude:function (content) {
//return content.indexOf('node_modules') > -1
//},
use: ['style-loader','css-loader']
},
],
},
}
Rule.issuer
用法和Rule.test一样,但是要注意是匹配引入资源的文件路径,
如在main.js中引入css/index.css
const path = require('path');
module.exports = {
module: {
rules: [
{
issuer: /\main\.js$/,
//issuer: path.resolve(__dirname, 'main.js'),
//issuer: [/\main\.js$/ , path.resolve(__dirname, 'main.js')],
//issuer:function (content) {
//return content.indexOf('main.js') > -1
//},
use: ['style-loader', 'css-loader']
},
],
},
}
Rule.issuer 和 Rule.test、Rule.include 、Rule.exclude同时使用时候,也是“与”的关系。
Rule.resource
此选项也可筛选资源,符合条件的资源让这项规则中的loader处理。
但配置resource
选项后,test
、include
、exclude
选项不能使用。issuer
选项不生效。
resource
选项中有以下子选项
test
选项,用法和Rule.test一样。exclude
选项,用法和Rule.exclude一样。include
选项,用法和Rule.include一样。not
选项,值为数组,数组每一项可以为字符串、正则表达式、函数,只要符合数组中任一项条件的资源就不能交给user
选项里面的loader处理一下。and
选项,值为数组,数组每一项可以为字符串、正则表达式、函数,必须符合数组中每一项条件的资源才能交给user
选项里面的loader处理一下。or
选项,值为数组,数组每一项可以为字符串、正则表达式、函数,只要符合数组中任一项条件的资源就可以交给user
选项里面的loader处理一下。
const path = require('path');
module.exports = {
module: {
rules: [
{
resource:{
test:/\.css$/,
include: path.resolve(__dirname, 'src/css'),
exclude: path.resolve(__dirname, 'node_modules'),
},
use: ['style-loader', 'css-loader']
},
],
},
}
Rule.resourceQuery
匹配资源引入路径上从问号开始的部分。例
import './ass/main.css?inline'
上面代码中Rule.resourceQuery要匹配?inline
,例
const path = require('path');
module.exports = {
module: {
rules: [
{
resourceQuery:/inline/,
// resourceQuery:function (content) {
//return content.indexOf('inline') > -1
// },
//resourceQuery:[/inline/],
use: ['style-loader', 'css-loader']
},
],
},
}
注意
- Rule.test、Rule.include、Rule.exclude、Rule.issuer、Rule.resourceQuery同时使用时候,是“与”的关系,必须同时符合以上所有配置的条件才可以让这项规则中的loader处理。
- Rule.issuer、Rule.resourceQuery、Rule.resource同时使用时候,也是“与”的关系。必须同时符合以上所有配置的条件才可以让这项规则中的loader处理。
2、配置规则Rule的loader
Rule.use
在上面已经提到过Rule.use的用法。意思是使用哪些loader处理符合条件的资源。
use: ['style-loader']
其实是use: [ { loader: 'style-loader'} ]
的简写。
还可以通过options
传入loader,可以理解为loader的选项。
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1
}
},
]
},
],
},
}
use
的值还可以是函数,返回一个数组,参数为info,info中有以下内容
compiler
:当前webpack的编译器(可以是undefined值)。issuer
:引入被处理资源的所在文件的绝对路径。realResource
:被处理资源的绝对路径。resource
:被处理资源的绝对路径,它常常与realResource替代,只有当资源名称被请求字符串中的!=!覆盖时才不近似。resourceQuery
:被处理资源的绝对路径中?后面的部分。
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: (info) =>{
console.log(info)
return [
'style-loader',
{
"loader": 'css-loader',
},
]
},
},
],
},
}
参数info打印如下图所示
Rule.loader
module.exports = {
module: {
rules: [
{
test: /\.css$/,
loader: 'css-loader',
},
],
},
}
loader: 'css-loader'
是 use: [ { loader: 'css-loader'} ]
的简写。
Rule.oneOf
当规则匹配时,只使用第一个匹配规则。
例如说要处理css文件资源时,one.css要用url-loader处理,two.css要用file-loader处理。可以用Rule.oneOf来配置,其用法和module.rules一样。
module.exports = {
module: {
rules: [
{
test: /\.css$/,
oneOf: [
{
resourceQuery: /one/, // one.css?one
//test: /one\.css/,
use: 'url-loader'
},
{
resourceQuery: /two/, // two.css?two
//test: /one\.css/,
use: 'file-loader'
}
]
},
],
},
}
四、loader的执行顺序
从右到左,从下到上执行。换句话来说,就是后写的先执行,跟栈一样后进先出。
rules: [
{
test: /\.less$/,
use: ['style-loader','css-loader','less-loader']
},
],
以上配置中,less-loader先执行,再执行css-loader,最后执行style-loader。
rules: [
{
test: /\.less$/,
use:[
{
loader:'style-loader'
},
{
loader:'css-loader'
},
{
loader:'less-loader'
}
]
},
],
以上配置中,less-loader先执行,再执行css-loader,最后执行style-loader。
rules: [
{
test: /\.less$/,
loader:'style-loader',
},
{
test: /\.less$/,
loader:'css-loader',
},
{
test:/\.less$/,
loader:'less-loader'
}
],
以上配置中,less-loader先执行,再执行css-loader,最后执行style-loader。
由以上三个例子,可以得知,在同一个规则Rule的条件下,其规则Rule中的loader都是后写的先执行。从空间上来看,就是从右到左,从下到上执行。
五、控制loader的执行顺序
用Rule.enforce来控制,其有两个值:
pre
:优先执行post
:最后执行
rules: [
{
test:/\.less$/,
loader:'less-loader'
},
{
test: /\.less$/,
loader:'css-loader',
},
{
test: /\.less$/,
loader:'style-loader',
},
],
如果按上面的书写顺序,style-loader先执行,再执行css-loader,最后执行less-loader。结果肯定会报错。可以用Rule.enforce来控制loader的执行顺序。既不改变loader的书写顺序,也可以正确执行。
rules: [
{
test:/\.less$/,
loader:'less-loader',
enforce:'pre'
},
{
test: /\.less$/,
loader:'css-loader',
},
{
test: /\.less$/,
loader:'style-loader',
enforce:'post'
},
],
此时,less-loader先执行,再执行css-loader,最后执行style-loader。
其实loader还有一种“内联”的用法。例
import 'style-loader!css-loader!less-loader!./index.css';
使用 ! 将资源中的 loader 分开。分开的每个部分都相对于当前目录解析。
在这里可以把loader分为四种
- pre
- normal
- inline
- post
其执行顺序 pre -> normal -> inline ->post
尽可能使用 module.rules,因为这样可以减少源码中的代码量,并且可以在出错时,更快地调试和定位 loader 中的问题。
Webpack官网中不推荐大家使用“内联”loader,所以在讲loader的执行顺序时把inline类型的loader排除掉了。
六、在Vue Cli3中配置loader
在Vue Cli3中配置loader,有两种方法,一是通过configureWebpack
选项来配置,二是通过chainWebpack
选项来配置。
在配置中,可以使用vue-cli-service inspect
来审查一个 Vue CLI 项目的 webpack config。
在项目中package.json文件中scripts
中添加一条命令
"scripts": {
"dev": "vue-cli-service serve",
"build": "vue-cli-service build",
"inspect": "vue-cli-service inspect --mode production > output.js"
},
inspect
这条命令的意思是把这个项目的生产环境下的解析好的 webpack 配置输出到output.js这个文件中。
如果是--mode development
,就是开发环境下的webpack config。
configureWebpack配置
configureWebpack
选项的值可以是对象,也可以是函数
-
值为对象。
最后通过webpack-merge合并到最终的配置中。也就是说在这里,只能新增loader配置,不能修改loader配置或者删除lodaer配置。
例如在vue.config.js中配置
module.exports = { configureWebpack:{ module:{ rules:[ { test:/\.less$/, use:['style-loader','css-loader','less-loader'] } ] } }, }
执行
npm run inspect
后,在output.js中会发现,如下图所示 -
值为函数。
函数接收config作为参数,参数内容是webpack 配置,此时可以通过config参数来修改webpack的配置,也可以返回一个对象来通过webpack-merge合并到最终的配置中。
例如在vue.config.js中配置
module.exports = { configureWebpack:config =>{ config.module.rules[10]={ test:/\.less$/, use:['style-loader','css-loader','less-loader'] } }, }
执行
npm run inspect
后,在output.js中会发现,如下图所示,原先处理.less文件的loader配置已经被替成后面修改的。但是用这种方法去修改loader的配置,太粗放了,如果要进行更细粒度的修改loader配置,可以使用
chainWebpack
来配置。
chainWebpack配置
chainWebpack
选项的值是一个函数,会接收一个基于webpack-chain 的 ChainableConfig 实例。采用链式写法来配置Webpack。
用法文档点这里we。
这里只讲关于loader配置的新增、修改、删除的用法。
新增一个规则Rule
module.exports = {
chainWebpack: config =>{
config.module
.rule('myRule')
},
}
添加规则Rule的条件
module.exports = {
chainWebpack: config =>{
config.module
.rule('myRule')
//添加test选项
.test(/\.less$/)
//添加include选项,其值是数组
.include.add('/src/').add('/view/').end()
//添加exclude选项,其值是数组
.exclude.add('/node_modules/').end()
//添加issuer选项
.issuer('/\main\.js$/')
//添加resourceQuery选项
.resourceQuery('/inline/')
},
}
执行npm run inspect
后,在output.js中会发现,如下图所示,就是上面配置生成的。
也可以使用Rule.resource来配置规则的条件,在chainWebpack中这样配置:
module.exports = {
chainWebpack: config =>{
config.module
.rule('myRule')
.issuer('/\main\.js$/')
.resourceQuery('/inline/')
//添加resource选项
.resource({
test:/\.less$/,
include:['/src/','/view/'],
exclude:['/node_modules/'],
})
},
}
执行npm run inspect
后,在output.js中会发现,如下图所示,就是上面配置生成的。
添加规则Rule的loader
module.exports = {
chainWebpack: config =>{
config.module
.rule('myRule')
//先创建一个具名的use,后面修改有用到这个名称
.use('styleloader')
//往这个具名的use中添加一个loader
.loader('style-loader')
//添加多个loader时要先.end()回到主链路
.end()
.use('cssloader')
.loader('css-loader')
.end()
.use('lessloader')
.loader('less-loader')
},
}
执行npm run inspect
后,在output.js中会发现,如下图所示,就是上面配置生成的。注意书写顺序,最后写的先执行。
添加规则Rule的loader的参数
例如要给less-loader添加参数。
module.exports = {
chainWebpack: config =>{
config.module
.rule('myRule')
.use('lessloader')
.loader('less-loader')
.options({
// 这里配置全局变量
globalVars: {
'primary': '#fff'
}
})
},
}
.options()
的参数是个对象,在对象里面配置loader的参数。
执行npm run inspect
后,在output.js中会发现,如下图所示,就是上面配置生成的。
修改规则Rule的loader的参数
module.exports = {
chainWebpack: config =>{
config.module
.rule('myRule')
.use('lessloader')
.tap(options =>{
options.globalVars.primary= 'red';
return options
})
},
}
用.tap()
来实现,其参数是个函数,函数的参数是原loader的参数对象集合options,通过修改参数options,再返回options达到修改规则Rule的loader的参数的目的。
修改前
执行npm run inspect
后,在output.js中会发现,如下图所示,就是上面修改后生成的。
修改规则Rule的loader
修改前
有两种做法
- 修改其中一个loader
module.exports = {
chainWebpack: config =>{
config.module
.rule('myRule')
.use('lessloader')
.loader('sass-loader')
},
}
修改后
- 将这个Rule的loader全部清除重新添加
module.exports = {
chainWebpack: config =>{
config.module
.rule('myRule')
.uses.clear()
.end()
.use('styleloader')
.loader('style-loader')
}
}
修改后
创建Rule.oneOf规则组
module.exports = {
chainWebpack: config =>{
config.module
.rule('myRule')
.test(/\.less$/)
.oneOf('vue-modules')
.resourceQuery('/module/')
.use('css-loader')
.loader('css-loader')
.end()
.use('less-loader')
.loader('less-loader')
.end()
.end()
.oneOf('src')
.resourceQuery('/src/')
.use('style-loader')
.loader('style-loader')
.end()
.use('css-loader')
.loader('css-loader')
.end()
.use('less-loader')
.loader('less-loader')
}
}
执行npm run inspect
后,在output.js中会发现,如下图所示,就是上面配置生成的。
修改Rule.oneOf规则组
之前创建Rule.oneOf规则组,我们给每个Rule.oneOf都起了名称,可以利用.oneOf(name)
找这个Rule.oneOf修改,修改和创建的语法一样。
module.exports = {
chainWebpack: config =>{
config.module
.rule('myRule')
.oneOf('vue-modules')
.resourceQuery('/module11/')
.use('css-loader')
.loader('sass-loader')
}
}
执行npm run inspect
后,在output.js中会发现,修改后的结果如下图所示。
控制loader的执行顺序
module.exports = {
chainWebpack: config =>{
config.module
.rule('myRule1')
.test(/\.less$/)
.use('lessloader')
.loader('less-loader')
config.module
.rule('myRule2')
.test(/\.less$/)
.use('styleloader')
.loader('style-loader')
config.module
.rule('myRule3')
.test(/\.less$/)
.use('cssloader')
.loader('css-loader')
}
}
执行npm run inspect
后,在output.js中会发现,如下图所示,就是上面配置生成的。
因为在同一个规则Rule的条件下,其规则Rule中的loader都是后写的先执行。
所有在同一规则Rule的条件test(/\.less$/)
下,先执行css-loader、再执行style-loader、最后执行less-loader,这样的执行顺序肯定是不对的。应该先执行less-laoder,再执行css-loader,最后执行style-loader。
这是可以利用.pre()
、.post()
、.enforce('pre'/'post')
来控制loader的执行顺序。
module.exports = {
chainWebpack: config =>{
config.module
.rule('myRule1')
.test(/\.less$/)
.use('lessloader')
.loader('less-loader')
.end()
.pre()
config.module
.rule('myRule2')
.test(/\.less$/)
.use('styleloader')
.loader('style-loader')
.end()
.post()
config.module
.rule('myRule3')
.test(/\.less$/)
.use('cssloader')
.loader('css-loader')
}
}
执行npm run inspect
后,在output.js中会发现,如下图所示,就是上面配置生成的。
此时loader的执行顺序就是先执行less-loader,再执行css-loader,最后执行style-loader。
或者这样也可以实现
module.exports = {
chainWebpack: config =>{
config.module
.rule('myRule1')
.test(/\.less$/)
.use('lessloader')
.loader('less-loader')
.end()
.enforce('pre')
config.module
.rule('myRule2')
.test(/\.less$/)
.use('styleloader')
.loader('style-loader')
.end()
.enforce('post')
config.module
.rule('myRule3')
.test(/\.less$/)
.use('cssloader')
.loader('css-loader')
}
}