图解Webpack——实现Loader

loader承担的是翻译官的职责,利用其弥补了让webpack只能理解JavaScript和JSON文件的问题,从而可以处理其它类型的文件,所以loader对webpack的重要性不言而喻,所以学习构建一个loader是学习webpack的必经之路。在学习编写一个loader之前,要明确一下loader的职责: 其职责是单一的,只需要完成一种转换。下面将逐步阐述选择loader开发中的几个关键点并实现一个loader。

一、Loader分类

loader是一个CommonJs风格的函数,接收输入的source后可通过同步或异步的方式进行处理,然后将内容进行输出。

1.1 同步Loader

同步loader指的是同步的返回转换后的内容。,由于是在Node.js这样的单线程环境,所以转换过程会阻塞整个构建,构建缓慢,不适用于耗时较长的环境中。对于同步loader,主要有两种方法返回转换后的内容:return和this.callback.
  1. return<br/>

利用return可直接返回转换后结果。

module.exports = function(source, map, meta){
    // ...
    // output为处理后结果
    return output;
}
  1. this.callback<br/>

该方法相比于return更加灵活,其参数主要有四个:

this.callback(
  err: Error | null,
  content: string | Buffer,
  sourceMap?: SourceMap,
  meta?: any
);

(1)第一个参数为无法转换原内容是,Webpack会返回一个Error。<br/>
(2)第二个参数即为经过转换后的内容(为输出的内容)。<br/>
(3)指与编译后代码所映射的源代码,便于调试。为了在此loader中获取该sourceMap,则需要在创建的webpack做一下配置(以js为例,babel-loader会将基础ES6语法进行转换为ES5,通过devtool可以开启source-map):

// webpack.config.js
module.exports = {
    // ...
    module: {
        rules: [
            {
                test: /\.js$/,
                use: [
                    'test-loader',// 该loader即为自己构建的loader
                    {
                        loader: 'babel-loader',
                        options: {
                            presets: [
                                '@babel/preset-env'
                            ]
                        }
                    }
                ]
            }
        ]
    },
    devtool: 'eval-source-map',
}

(4)可以是任何东西,输出该参数,即可在下一个loader中获取并使用,例如通过各loader之间共享通用的AST,加速编译时间。

利用this.callback可返回传递多参数的结果。
module.exports = function(source, map, meta) {
    // 处理后获得的结果output
    const output = dealOperation(source);
    this.callback(null, output, map, meta);
}

1.2 异步Loader

同步loader只适合于计算量小,速度快的场景,但是对于计量算大、耗时比较长的场景(例如网络请求),使用同步loader会阻塞整个构建过程,导致构建速度变慢,采用异步loader即可避免该问题。对于异步loader,使用this.async()可以获取到callback函数,该函数参数和同步loader中this.callback参数一致。
module.exports = function(content, map, meta) {
    // 获取callback函数
    const callback = this.async();
    // 用setTimeout模拟该异步过程
    setTimeout(() => {
        // 处理后获得的结果output
    const output = dealOperation(source);
        callback(null, output, map, meta);
    }, 100)
}

二、文件转化后类型

默认情况下,资源文件经转化后都是UTF-8格式编码的字符串,但是对于图片这样的文件经过转化后是二进制格式的内容,为了让loader支持接收二进制资源,需要设置raw(以图片资源为例进行展示)
// webpack.config.js
module.exports = {
    // ...
    module: {
        rules: [
            {
                test: /\.(png|jpg|gif)$/,
                use: [
                    'url-loader',
                    'raw-test-loader',// 自己的loader
                ]
            }
        ]
    }
}
// raw-test-loader.js
module.exports = function(source, map, meta) {
    // 处理输入的资源
    const output = dealOperation(source);
    return output;
}
// 通过该属性告诉webpack该loader是否需要二进制数据
module.exports.raw = true;

三、options选项

对于webpack配置中,loader往往有一些options参数,对于自己编写的loader中为了获取options参数,官方推荐使用loader-utils包,利用该包即可获取options中参数,然后在loader中进行处理。
const loaderUtils = require('loader-utils');

module.exports = function (source, map, meta){
    // 获取options
    const options = loaderUtils.getOptions(this);
    const output = dealOperation(source);
    
    return output;
}

四、是否缓存

对于转换操作需要大量的计算,非常耗时,每次重新构建会让构建过程变的非常缓慢。webpack会默认缓存所有loader的处理结果,即要处理文件和其相关依赖没发生变化就会利用其缓存(注意loader除了this.addDependency里指定的依赖外,不应该有任何外部依赖)。通过this.cacheable可控制其是否进行缓存。
module.exports = function(source, map, meta) {
    // 关闭缓存
    this.cacheable(false);
    return source;
}

五、实现一个loader

本节是loader实战,编写了一个用于字母大小写转换的loader,利用该loader能够实现将txt文件中字母的大小写转换,其loader内容及webpack.config.js相关配置如下所示(详细代码见 github上代码)
// format-letters-loader.js
const loaderUtils = require('loader-utils');

const Lowercase2Uppercase = 'L2U';
const Uppercase2Lowercase = 'U2L';

module.exports = function (source, map, meta) {
    let output = '';
    // 获取options
    const options = loaderUtils.getOptions(this);
    const { formatType } = options;
    switch(formatType) {
        case Lowercase2Uppercase: {
            output = source.toUpperCase();
            break;
        }
        case Uppercase2Lowercase: {
            output = source.toLowerCase();
            break;
        }
        default: {
            output = source;
        }
    }

    this.callback(null, output, map, meta);
};
// webpack.config.js
module.exports = {
    // ...
    module: {
        rules: [
            {
                exclude: /\.(css|js|html|png|jpg|gif)$/,
                use: [
                    {
                        loader: 'file-loader',
                        options: {
                            name: '[name].[ext]',
                            outputPath: 'asset',
                        }
                    },
                    {
                        loader: 'format-letters-loader',
                        options: {
                            formatType: 'U2L'
                        }
                    },
                ]
            }
        ]
    },
    // 解析loader包是设置模块如何被解析
    resolveLoader: {
        modules: ['./node_modules', './loader'],// 告诉 webpack 解析loader时应该搜索的目录。
    },
}

注:本文只是起到抛砖引玉的作用,希望各位大佬多多指点。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值