阅读 1616

Webpack之loader配置详解

一、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-loadercss-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选项后,testincludeexclude选项不能使用。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-laoder,再执行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')
    }
}
复制代码