webpack源码分析之三:loader

前言

在webpack特性里面,它可以支持将非javaScript文件打包,但前面写到webpack的模块化打包只能应用于含有特定规范的JavaScript文件。本次介绍的loader则是用来解决这类问题的。本文章loader的实现基于code-splitting

功能分析

举个例子:

webpack.config.js中的配置loader

module: {
            rules: [
                {
                    test: /\.js$/,
                    loader: "test-loader!test-loader2"
                }
            ]
        }

业务代码中的内联loader

require('d!c');

分析:

我们需要将这些loader解析成可运行的函数,并在parse模块解析语法树之前运行掉这些loader函数

所以我们需要:

  1. resolve模块:分析出module对应的loader字符串,并解析出各个loader的绝对路径
  2. buildDeps模块:通过文件路径获取需要运行的loader函数,将其压入队列,之后再依次按序递归出loader函数运行,如果是异步loader,则要通过回调函数来递归下一个loader函数。

实现

resolve 模块

实现思路:

  1. 将配置内的loaders,shell命令的loaders,require/import的内联loader从前至后组成一个数组。配置内的loaders需要正则匹配test属性,来获取配置内的loader字符串。所有loader字符串内部又可以截取'!',获取完整的loader。
  2. 分析并解析该数组内的字符串,获取各个loader的绝对路径。并返回解析好的字符串。这块的实现和文件打包类似。

最终require内的字符串如下

/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/node_modules/d.js!
/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/node_modules/test-loader/index.js!
/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/node_modules/test-loader2/index.js!
/Users/zhujian/Documents/workspace/webpack/simple-webpack/example/node_modules/c.js

buildDeps模块

实现思路:

  1. 解析文件路径,并获取需要运行的loader函数,存入数组
  2. 数组在通过pop函数一个个递归,考虑到存在异步loader函数的情况,需要为运行函数提供async,以及callback的上下文。具体的上下文可参考Loader API

loader递归逻辑如下:

nextLoader.apply(null, content);
    function nextLoader() {
        const args = Array.prototype.slice.apply(arguments);
        if (loaderFunctions.length > 0) {
            const loaderFunction = loaderFunctions.pop();
            let async = false;
            const context = {
                fileName,
                options,
                debug: options.debug,
                async: function () {
                    async = true;
                    return nextLoader;
                },
                callback: function () {
                    async = true;
                    nextLoader.apply(null, arguments)
                }
            };
            const resVal = loaderFunction.apply(context, args);
            if (!async) {
                nextLoader(resVal);
            }
        } else {
            callback(null, args[0])
        }
    }

测试

将以上3个loader,test-loader,test-loader2,异步loader d.js,打包c.js

test-loader

module.exports = function(content) {
    return content+"\nexports.answer = 42;\n"
}

test-loader2

module.exports = function(content) {
    return content+"\nexports.test2 = test2;\n"
}

异步loader d.js

module.exports = function (content) {
    const d = 'd';
    this.async();
    const b = content + "\nexports.d = 2000;\n";
    setTimeout(this.callback.bind(this, b), 2000);
}

c.js

const c = 'c';

module.exports = c;

最终打包出来的c.js的代码如下

....
/* 1 */
/***/(function(module, exports,__webpack_require__) {
const c = 'c';

module.exports = c;

exports.test2 = test2;

exports.answer = 42;

exports.d = 2000;

/***/}
....

代码实现

本人的简易版webpack实现simple-webpack

(完)

相关推荐