预处理器-loader

127 阅读4分钟

实际上,使用webpack只能打包js文件,但是我们经常会在工程中会在其他资源,如HTML、图片、CSS字体等等,我们需要将其转化为js才能进行打包,正是通过loader来实现的。

1.loader的概述

  1. loader本质上都是函数。loader的本质可以用公式表示:output = loader(input)
  2. loader可以是链式的,对一种资源设置多个loader之后,第一个loader的输入就是文件源码,之后所有的loader的输入都为上一个loader的输出,最后一个loader则直接被送到webpack进行处理

2.loader的配置

2.1 loader的引入

如果我们在js文件中引入css,由于webpack不认识css语言,因此打包的时候会报错。因此我们需要通过npm安装loader(loader都是一些第三方npm模块)

module.exports = {
    //...
    module:{
        rules: [{
            test: /\.css$/,
            use: ['css-loader']
        }]
    }
}

与loader相关的配置都在module对象中,其中module.rules代表了模块的处理规则。规则内部有很多配置项,这里只使用了最重要的两项--test和use

  • test可以接收一个正则表达式或者一个元素为正则表达式的数组,只有正则匹配上的模块才会使用这条规则
  • use可以接收一个数组,数组包含该规则所使用的loader。在只有一个loader的情况下可以简化为字符串‘css-loader’

但是css-loader仅仅是处理了CSS的各种加载语法(@import和url()函数等等),要让样式起作用需要style-loader来把样式插入页面,这两个loader也是常常一起使用的

module.exports = {
    //...
    module:{
        rules: [{
            test: /\.css$/,
            use: ['style-loader','css-loader']
        }]
    }
}

我们把style-loader加到css-loader前面,因为webpack打包是按照数组从后往前的顺序将资源交个loader的,因此要将最后生效的放在前面

2.2 loader options

loader作为预处理器,通常会给开发者提供一些配置项,在引入loader的时候可以通过options将他们传入

module.exports = {
    //...
    module:{
        rules: [{
            test: /\.css$/,
            use: ['style-loader',
                {
                    loader:'css-loader,
                    options:{
                        //css-loader配置项
                    }
                }
           ]
        }]
    }
}

有些loader可能会使用query来代替options,从功能上来说他们没有太大区别,具体要参考loader本身的文档。

2.3 exclude与include

exclude和include也接收正则表达式或者字符串(文件绝对路径),以及由他们组成的数组。

exclude的含义是,所有被正则匹配到的模块都被排除在该规则之外,exclude: /node_modules/表示node_module中的模块不会执行这条规则。该配置项通常是必加的,否则可能拖慢整体打包速度。

include代表该规则只对正则匹配的模块生效。

exclude和include同时存在时,exclude的优先级更高

module.exports = {
    //...
    module:{
        rules: [{
            test: /\.css$/,
            use: ['style-loader','css-loader'],
            exclude:/src\/lib/,
            include:/src/
        }]
    }
}

上面的代码表示对src文件夹下面的除了lib文件夹下面的模块使用css-loader和style-loader

2.4 resource和issuer

resource和issuer可以更精确地确定规则的作用范围。在webpack中,我们认为被加载的模块是resource,而加载者是issuer

前面提到的test、exclude、include本质上属于对resource也就是被加载者的配置,如果想要对issuer加载者也增加条件限制,则需要额外写一些配置

module.exports = {
    //...
    module:{
        rules: [{
            test: /\.css$/,
            use: ['style-loader','css-loader'],
            exclude:/node_module/,
            issuer:{
                test: /\.js$/,
                include: /src/pages/
            }
        }]
    }
}

可以看到,我们添加了issuer配置对象,只有src/pages目录下面的JS文件引用CSS文件时,这条规则才会生效。

上面的代码虽然实现了我们的要求,但是text、exclude、include这些配置项有不同层级,可读性较差。

module.exports = {
    //...
    module:{
        rules: [{
            use: ['style-loader','css-loader'],
            resource:{
                test: /\.css$/,
                exclude:/node_module/,
            },
            issuer:{
                test: /\.js$/,
                include: /src/pages/
            }
        }]
    }
}

通过添加resource将外层的配置包起来,区分了resource和issuer中的规则,这样就一目了然了。

2.5 enforce

enforce用来指定一个loader的种类,只接收‘pre’和‘post’两种字符串类型。webpack中loader的执行顺讯分别pre、inline、normal、post四种类型。我们直接定义的loader都属于normal类型,inline形式官方已经不推荐使用,而pre和post则需要enforce来指定。

module.exports = {
    //...
    module:{
        rules: [{
            test: /\.js$/,
            use: ['eslint-loader'],
            enforce:'pre'
        }]
    }
}

可以看到,在配置中添加eslint-loader来对源码进行质量检测,其enforce的值为pre,代表它将在所有正常loader之前执行,这样可以保证其检测的代码不是被其他loader更改过。类似地,如果一个loader在所有loader之后执行,可以指定enforce值为post。

我们也可以不使用enforce,只要保证写loader的时候顺序正确即可。但是实际工程中规模越来越大,难以保证loader按照预想方式工作,使用enforce可以强制指定loader作用顺序

3. 常见loader介绍

3.1 babel-loader

babel-loader用来处理ES6+并将其编译为ES5,它使得我们可以使用最新的语言特性。

使用babel=loader需要我们通过npm安装babel-loader,@babel/core,@babel/preset-env

  • babel-loader:他是使babel和webpack协同工作的模块
  • @babel/core:他是babel编译器的核心模块
  • @babel/preset-env:他是babel官方推荐的预置器,可根据用户设置的目标环境自动添加所需的插件和补丁来编译ES6+代码
rules:[
    test: /\.js$/,
    exclude:/node_module/,
    use:{
        loader: 'babel-loader',
        option: {
            cacheDirectory: true,
            presets: [{
                'env',
                {
                    modules:false
                }
            }]
        }
    }
]
  1. babel-loader是用来处理JS文件的,需要在exclude中排除node_module,否则会编译其中所有模块,大大拖慢打包速度,并且可能改变第三方原有模块
  2. 对于babel-loader我们添加cacheDirectory配置项,他会启用缓存机制,在重复打包为改变过的模块是防止二次编译,同样也会加快打包速度。cacheDirectory可以接收一个字符串类型的路径也作为缓存路径,这个值也可以是true,此时缓存目录会指向node_module/.cache/babel-loader
  3. 由于@babel/preset-env会将ES6 Module转化为CommonJs的形式,这会导致Webpack中的tree-shaking特性消失、将@babel/preset-env的modules配置项设置为false会禁用模块语句的转化,而将ES6 Module(导入和导出语法)交给webpack本身处理
  4. babel-loader支持从.babelrc文件读取Babel配置,因此可以将preset和plugins从webpack配置文件中提取出来,也能达到相同的效果

3.2 ts-loader

ts-loader的作用是将ts转化为js

module.exports = {
    //...
    module:{
        rules: [{
            test: /\.ts$/,
            use: ['ts-loader'],
        }]
    }
}

需要注意的是,ts-loader本身的配置不在ts-loader中,而是工程目录下的tsconfig.json中

{
    "compilerOptions": {
        "target": "es5",
        "sourceMap": true
    }
}

3.3 html-loader

html-loader用于将HTML文件转化为字符串并进行格式化,这使得我们可以将一个HTML片段通过JS加载进来

 rules: [{
            test: /\.html$/,
            use: ['html-loader'],
        }]

使用实例:

image.png

3.4 file-loader

file-loader用于打包文件类型的资源,并将其存储在publicPath中

module.exports = {
    entry:'./app.js',
    output:{
        path:path.join(__dirname,'dist'),
        filename:'bundle.js'
    },
    module:{
        rules: [{
            test: /\.(png|jpg|gif)$/,
            use: ['file-loader'],
        }]
    }
}

现在对png、jpg和gif这类图片资源使用file-loader,然后就可以在JS中加载图片了

image.png

webpack入门之二--资源输入和输出 - 掘金 (juejin.cn)之前介绍过,output.path是资源打包的输出路径,output.publicPath是资源引用路径。像上面这样使用webpack打包之后,dist目录会生成名为c6f482ac9a1905e1d7d22caa903971fc.jpg图片文件。由于没有加上output.publicPath,这里打印出来的图片路径只是文件名,默认为文件的hash值加上文件后缀。

添加上publicPath

module.exports = {
    entry:'./app.js',
    output:{
        path:path.join(__dirname,'dist'),
        filename:'bundle.js',
        publicPath:'./assets/'
    },
    module:{
        rules: [{
            test: /\.(png|jpg|gif)$/,
            use: ['file-loader'],
        }]
    }
}

此时图片路径就变成了./assets/c6f482ac9a1905e1d7d22caa903971fc.jpg

image.png

file-loader也支持配置文件文件名以及publicPath(这里的publicPath会覆盖原有的output.publicPath,通过loader的options传入)

module:{
        rules: [{
            test: /\.(png|jpg|gif)$/,
            use: {
                loader:'file-loader',
                options:{
                    name:'[name].[ext]',
                    publicPath: './another-path/'
                }
            }
        }]
}

image.png

3.5 url-loader

url-loader和file-loader作用类似,唯一不同之处在于用户可以设置一个文件大小的阈值,当大于该阈值时与file-loader一样将打包的文件返回publicPath,而小于该阈值时则返回文件的base64编码格式,这样请求图片时就不需要再发送网络请求了

rules: [{
            test: /\.(png|jpg|gif)$/,
            use: {
                loader:'url-loader',
                options:{
                    limit:10240,
                    name:'[name].[ext]',
                    publicPath: './assets-path/'
                }
            }
        }]

3.6 vue-loader

vue-loader用来处理vue组件,还可以将组件的模板、JS以及样式进行拆分。在安装时,除了必要的vue和vueloader以外,还要安装vue-template-compiler来编译Vue模板以及css-loader来处理样式

 rules: [{
            test: /\.vue$/,
            use: ['vue-loader'],
        }]