《Webpack5 核心原理与应用实践》学习笔记-> webpack的loader运行与调试

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 《Webpack5 核心原理与应用实践》学习笔记-> webpack的loader运行与调试


loaderwebpack提供解析计算机文件类型能力,但是它是依赖于webpack运行的,那么它该如何调试?webpack可以同时加载多个loader,它加载的流程又是什么样的?接下来就一起来看看吧。

为 Loader 编写单元测试


任何功能,编写单元测试都是很有必要的,对于不同的应用程序,运行的环境不一样,编写的单元测试代码也不一样,例如webpack的单元测试就肯定是需要webpack的运行环境的,webpack的运行环境很简单,npx webpack不就启动了一个webpack环境,但是单元测试一般都是代码直接运行跑程序,那么如何不通过命令行启动一个webpack环境呢?


1. 在node环境中运行webpack


我们可以直接引入webpack,然后通过node运行webpack,传入对应的webpack配置就ok了:


// 引入webpack
const webpack = require('webpack');
const MemoryFS = require('memory-fs');
module.exports = function () {
    // 使用项目中的配置
    const webpackConfig = require('../../webpack.config.js');
    // 测试环境配置
    const config = {
        mode: 'development',
        devtool: 'sourcemap',
        entry: `../main.js`,
        output: false
    }
    // 合并覆盖
    Object.assign(webpackConfig, config)
    // 获得webpack编译实例
    const compiler = webpack(config)
    // 将输出内容输出到内存中
    compiler.outputFileSystem = new MemoryFS()
    // 返回一个promise,最终结果在成功的回调中
    return new Promise((resolve, reject) => compiler.run((err, stats) => {
        if (err) reject(err)
        resolve(stats)
    }))
}

使用jest进行测试


// 指向上面的文件
const compiler = require('./compiler.js');
describe('Loader', () => {
    test('Defaults', () => {
        return compiler()
            .then((stats) => {
                const [module] = stats.toJson().modules
                expect(module.source).toMatchSnapshot()
            })
            .catch((err) => err)
    })
})

2. 使用mock模拟webpack环境


上面的方法和直接使用命令行运行webpack区别不是很大,效率也比较低下,mock环境还是比较推荐的。


// 手写一个构造函数,用来模拟 webpack 上下文,里面只需要模拟自己使用到的属性即可
function WebpackLoaderMock (options) {
    this.context = options.context || '';
    this.query = options.query;
    this.options = options.options || {};
    this.resource = options.resource;
    this._asyncCallback = options.async;
}
WebpackLoaderMock.prototype.async = function () {
    return this._asyncCallback;
};
function testTemplate(loader, testFn) {
    loader.call(new WebpackLoaderMock({
        async: function (err, source) {
            testFn(source);
        }
    }), '这里是输入loader解析的数据');
}
describe('macro', function () {
    it('should be parsed', function (done) {
        // 自己的 loader
        const loader = require('./loader.js');
        testTemplate(loader, function (output) {
            // 解析完成会进入这个回调函数,来判断结果是否ok
            assert.equal(output, '这里是正确解析结果');
            done();
        });
    });
});

链式调用模型详解


链式调用的意思就是一个文件类型使用到了多个loader处理,那么这些loader是如何协同处理这一个文件,例如.less文件,我们一般需要配置style-loadercss-loader, less-loaderwebpack启动后会以一种所谓“链式调用”的方式按 use 数组顺序从后到前调用loader


module.exports = {
    module: {
        rules: [
            {
                test: /.less$/i,
                use: ["style-loader", "css-loader", "less-loader"],
            },
        ],
    },
};

  • 首先调用 less-loader 将 Less 代码转译为 CSS 代码;
  • less-loader 结果传入 css-loader,进一步将 CSS 内容包装成类似 module.exports = "${css}" 的 JavaScript 代码片段;
  • css-loader 结果传入 style-loader,在运行时调用 injectStyle 等函数,将内容注入到页面的 <style> 标签。


链式调用分工明确,每一个loader处理完自己的职责之后,将结果丢给下一个loader进行处理。


不过,这只是链式调用的一部分,这里面有两个问题:


  • Loader 链条一旦启动之后,需要所有 Loader 都执行完毕才会结束,没有中断的机会 —— 除非显式抛出异常;
  • 某些场景下并不需要关心资源的具体内容,但 Loader 需要在 source 内容被读取出来之后才会执行。


为了解决上面这两个问题,webpack引出了pitch函数,在loader上挂载的pitch函数,要比loader提前运行,下面的官网原话:


loader 总是 从右到左被调用。有些情况下,loader 只关心 request 后面的 元数据(metadata) ,并且忽略前一个 loader 的结果。在实际(从右到左)执行 loader 之前,会先 从左到右 调用 loader 上的 pitch 方法。


官网中有很完整的说明,就是写几个空的loader,挂载几个pitch函数就ok了:pitching-loader


Pitch 函数的完整签名:


/**
 * @param remainingRequest 当前 loader 之后的资源请求字符串
 * @param previousRequest 在执行当前 loader 之前经历过的 loader 列表
 * @param data 与 Loader 函数的 `data` 相同,用于传递需要在 Loader 传播的信息。
 */
function pitch(
    remainingRequest: string, previousRequest: string, data = {}
): void {
}

示例代码:

// webpack.config.js
const path = require('path');
module.exports = {
    mode: "development",
    entry: "./src/main.js",
    output: {
        path: path.resolve(__dirname, "./dist"),
        filename: "[name].js",
        clean: true
    },
    module: {
        rules: [{
            test: /.js$/,
            use: ['./src/loader/a-loader.js', './src/loader/b-loader.js', './src/loader/c-loader.js'], // 这里先后挂载了3个loader
        }]
    }
}


  • a-loader
function loader(source, sourceMap, data) {
    console.log('==========> 这里是a-loader 的 loader,我是第六个执行的 <==========');
    console.log();
    this.callback(
        null,
        source,
        sourceMap,
        data
    );
}
loader.pitch = function (remainingRequest, previousRequest, data ) {
    console.log('==========> 这里是 a-loader 的 pitch,我是第一个执行的 <==========');
    console.log('remainingRequest', remainingRequest);
    console.log('previousRequest', previousRequest);
    console.log('data', data);
    console.log();
}
module.exports = loader

  • b-loader


function loader(source, sourceMap, data) {
    console.log('==========> 这里是b-loader 的 loader,我是第五个执行的 <==========');
    console.log();
    this.callback(
        null,
        source,
        sourceMap,
        data
    );
}
loader.pitch = function (remainingRequest, previousRequest, data ) {
    console.log('==========> 这里是b-loader 的 pitch,我是第二个执行的 <==========');
    console.log('remainingRequest', remainingRequest);
    console.log('previousRequest', previousRequest);
    console.log('data', data);
    console.log();
}
module.exports = loader

  • c-loader


function loader(source, sourceMap, data) {
    console.log('==========> 这里是c-loader 的 loader,我是第四个执行的 <==========');
    console.log();
    this.callback(
        null,
        source,
        sourceMap,
        data
    );
}
loader.pitch = function (remainingRequest, previousRequest, data ) {
    console.log('==========> 这里是c-loader 的 pitch,我是第三个执行的 <==========');
    console.log('remainingRequest', remainingRequest);
    console.log('previousRequest', previousRequest);
    console.log('data', data);
    console.log();
}
module.exports = loader

  • 运行结果

image.png

写了这么多,也知道有一个pitch函数会在loader之前运行,那么这个pitch函数到底有什么作用呢?


picth真说有什么用我不清楚,但是它可以阻塞后续loader的内容解析,只要return出内容就可以了,这个就留作感兴趣的同学自行去尝试吧。


总结


loader作为webpack的文件解析器,自身有着一整套生命周期和解析流程,就算不开发loader,这套思想也可以为我们的日常开发提供一定的参考思路,loader总体还是很复杂的,这一篇我记录的并不是很详细,因为还是有点一知半解,官网也没找到比较好的参考资料,后续会考虑补一个全面对loader流程的解析。


目录
相关文章
|
9天前
|
监控 前端开发 JavaScript
Webpack 中 HMR 插件的工作原理
【10月更文挑战第23天】可以进一步深入探讨 HMR 工作原理的具体细节、不同场景下的应用案例,以及与其他相关技术的结合应用等方面的内容。通过全面、系统地了解 HMR 插件的工作原理,能够更好地利用这一功能,为项目的成功开发提供有力保障。同时,要不断关注技术的发展动态,以便及时掌握最新的 HMR 技术和最佳实践。
|
9天前
|
缓存 前端开发 JavaScript
Webpack 动态加载的原理
【10月更文挑战第23天】Webpack 动态加载通过巧妙的机制和策略,实现了模块的按需加载和高效运行,提升了应用程序的性能和用户体验。同时,它也为前端开发提供了更大的灵活性和可扩展性,适应了不断变化的业务需求和技术发展。
|
9天前
|
缓存 前端开发 JavaScript
Webpack 4 和 Webpack 5 区别?
【10月更文挑战第23天】随着时间的推移,Webpack 可能会继续发展和演进,未来的版本可能会带来更多的新特性和改进。保持对技术发展的关注和学习,将有助于我们更好地应对不断变化的前端开发环境。
|
9天前
|
缓存 前端开发 JavaScript
webpack 原理
【10月更文挑战第23天】Webpack 原理是一个复杂但又非常重要的体系。它通过模块解析、依赖管理、加载器和插件的协作,实现了对各种模块的高效打包和处理,为现代前端项目的开发和部署提供了强大的支持。同时,通过代码分割、按需加载、热模块替换等功能,提升了应用程序的性能和用户体验。随着前端技术的不断发展,Webpack 也在不断演进和完善,以适应不断变化的需求和挑战。
|
28天前
|
缓存 前端开发 JavaScript
Webpack 打包的基本原理
【10月更文挑战第5天】
|
21天前
|
前端开发 UED
Webpack 中处理 CSS 和图片资源的多 Loader 配置
【10月更文挑战第12天】 处理 CSS 和图片资源是 Webpack 配置中的重要部分。通过合理选择和配置多个 Loader,可以实现对这些资源的精细处理和优化,提升项目的性能和用户体验。在实际应用中,需要不断探索和实践,根据项目的具体情况进行灵活调整和优化,以达到最佳的处理效果。通过对 Webpack 中多 Loader 处理 CSS 和图片资源的深入了解和掌握,你将能够更好地应对各种复杂的资源处理需求,为项目的成功构建和运行提供坚实的基础。
46 1
|
21天前
|
前端开发 JavaScript
Webpack 中多个 Loader 的配置
【10月更文挑战第12天】使用多个 Loader 进行配置是 Webpack 中常见的操作,可以实现对各种资源的精细处理和优化。在配置时,需要根据具体需求合理选择和排列 Loader,并注意它们之间的顺序和交互关系。同时,不断了解和掌握新的 Loader 以及它们的特性,有助于更好地发挥 Webpack 的强大功能,提升项目的开发效率和质量。通过深入理解和熟练运用多个 Loader 的配置方法,你将能够更加灵活地处理各种资源,满足项目的多样化需求。
38 2
|
21天前
|
前端开发 JavaScript
Webpack 常用 Loader 和 Plugin
【10月更文挑战第12天】Webpack 是一个强大的模块打包工具,能够将各种资源模块进行打包和处理。Loader 用于转换模块的源代码,如 `babel-loader` 将 ES6+ 代码转换为 ES5,`css-loader` 处理 CSS 文件等。Plugin 扩展 Webpack 功能,如 `HtmlWebpackPlugin` 自动生成 HTML 文件,`UglifyJsPlugin` 压缩 JavaScript 代码。通过合理配置和使用 Loader 和 Plugin,可以构建高效、优化的项目。
19 2
|
2月前
|
设计模式 前端开发 JavaScript
webpack实战之手写一个loader和plugin
该文章详细讲解了如何从零开始编写一个自定义的Webpack Loader和Plugin,包括它们的工作原理、开发步骤以及如何将自定义的Loader和Plugin集成到Webpack配置中。
webpack实战之手写一个loader和plugin
|
2月前
|
JavaScript 前端开发
手写一个简易bundler打包工具带你了解Webpack原理
该文章通过手写一个简易的打包工具bundler,帮助读者理解Webpack的工作原理,包括模块解析、依赖关系构建、转换源代码以及生成最终输出文件的整个流程。