.36-浅析webpack源码之Parser类

  眼看webpack4都出了,我还在撸3的源码,真的是捉急啊……

  不过现在只是beta版本,等出稳定版本后跑跑4的源码去。

  之前漏了一个东西没有讲,如下:

asyncLib.parallel([/**/], (err, results) => {
    if (err) return callback(err);
    loaders = results[0].concat(loaders, results[1], results[2]);
    process.nextTick(() => {
        callback(null, {
            context: context,
            request: loaders.map(loaderToIdent).concat([resource]).join("!"),
            dependencies: data.dependencies,
            userRequest,
            rawRequest: request,
            loaders,
            resource,
            resourceResolveData,
            // 这个东西
            parser: this.getParser(settings.parser)
        });
    });
});

  在解析完入口文件路径与JS文件对应的babel-loader路径后,返回的包装对象多了一个parser属性,看似简单,实则麻烦的要死。

  这里的参数settings.parser为undefined,来源如下:

const settings = {};
const useLoadersPost = [];
const useLoaders = [];
const useLoadersPre = [];
result.forEach(r => {
    if (r.type === "use") {
        // ...
    } else {
        // parser参数
        settings[r.type] = r.value;
    }
});

  这里的参数来源于modules.rules的parser参数,一般情况下不会传吧,反正vue-cli中没有……

  因此这里的parserOptions参数为undefined,直接看原型方法getParser:

getParser(parserOptions) {
    // 默认键
    let ident = "null";
    // 检测参数
    if (parserOptions) {
        if (parserOptions.ident)
            ident = parserOptions.ident;
        else
            ident = JSON.stringify(parserOptions);
    }
    // 尝试获取缓存
    const parser = this.parserCache[ident];
    if (parser)
        return parser;
    // 创建新的parser并设置缓存
    return this.parserCache[ident] = this.createParser(parserOptions);
}

  老套的尝试获取对应键的缓存值,如果不存在就新建一个,所有参数为undefined的默认键为null字符串。

createParser(parserOptions) {
    // new一个Parser对象
    const parser = new Parser();
    // 触发事件流
    this.applyPlugins2("parser", parser, parserOptions || {});
    // 返回parser
    return parser;
}

  这个方法简单易懂,就是生成一个新的parser,但是每一行代码都是分量足足,让我联想到了vue源码中的parse方法……

Parser类

  这东西就跟compiler、compilation对象一样,内容非常繁杂,干讲是没办法的,所以简单过一下构造,调用的时候跑内部方法。

class Parser extends Tapable {
    constructor(options) {
        super();
        this.options = options;
        this.scope = undefined;
        this.state = undefined;
        this.comments = undefined;
        this.initializeEvaluating();
    }

    initializeEvaluating() {
        this.plugin("evaluate Literal", expr => {
            switch (typeof expr.value) {
                case "number":
                    return new BasicEvaluatedExpression().setNumber(expr.value).setRange(expr.range);
                case "string":
                    return new BasicEvaluatedExpression().setString(expr.value).setRange(expr.range);
                case "boolean":
                    return new BasicEvaluatedExpression().setBoolean(expr.value).setRange(expr.range);
            }
            if (expr.value === null)
                return new BasicEvaluatedExpression().setNull().setRange(expr.range);
            if (expr.value instanceof RegExp)
                return new BasicEvaluatedExpression().setRegExp(expr.value).setRange(expr.range);
        });
        // 非常多的plugin...

        // 非常非常多的原型方法
    }
}

  这个类内容非常多,但是内容类型很简单,只有事件流的注入与一些原型方法,所以在初始化不会有任何实际动作。

  这样就返回了一个包含大量事件流的Parser对象,之后apply的时候再回头解析。

parser事件流

  这个事件流回头翻了好几节才找到,集中在第24节的大图中,插件列表如下:

compiler.apply(
    new CompatibilityPlugin(),
    new HarmonyModulesPlugin(options.module),
    new AMDPlugin(options.module, options.amd || {}),
    new CommonJsPlugin(options.module),
    new LoaderPlugin(),
    new NodeStuffPlugin(options.node),
    new RequireJsStuffPlugin(),
    new APIPlugin(),
    new ConstPlugin(),
    new UseStrictPlugin(),
    new RequireIncludePlugin(),
    new RequireEnsurePlugin(),
    new RequireContextPlugin(options.resolve.modules, options.resolve.extensions, options.resolve.mainFiles),
    new ImportPlugin(options.module),
    new SystemPlugin(options.module)
);

NodeSourcePlugin

  但是第一个事件是在NodeSourcePlugin插件中,注入地点如下:

class WebpackOptionsApply extends OptionsApply {
    constructor() {
        super();
    }

    process(options, compiler) {
        // ...
        if (typeof options.target === "string") {
            let JsonpTemplatePlugin;
            let NodeSourcePlugin;
            let NodeTargetPlugin;
            let NodeTemplatePlugin;

            switch (options.target) {
                case "web":
                    JsonpTemplatePlugin = require("./JsonpTemplatePlugin");
                    NodeSourcePlugin = require("./node/NodeSourcePlugin");
                    compiler.apply(
                        new JsonpTemplatePlugin(options.output),
                        new FunctionModulePlugin(options.output),
                        // 这里
                        new NodeSourcePlugin(options.node),
                        new LoaderTargetPlugin(options.target)
                    );
                    break;
                    // ...
            }
            // ...
        }
        // ...
    }
}

  感觉一下回到了2017年的感觉……

  很遗憾,这个插件很快就挂了,源码简要如下:

// NodeSourcePlugin.js
params.normalModuleFactory.plugin("parser", function(parser, parserOptions) {

    if (parserOptions.node === false)
        return;

    // ...more
});

  由于传进来的parserOptions为空对象,所以直接返回。

  剩下的均来源于后面的批量plugin。

CompatibilityPlugin、HarmonyModulesPlugin...

// CompatibilityPlugin.js
params.normalModuleFactory.plugin("parser", (parser, parserOptions) => {
    if (typeof parserOptions.browserify !== "undefined" && !parserOptions.browserify)
        return;

    // ...
});

  阵亡。

  后面的HarmonyModulesPlugin、AMDPlugin、CommonJsPlugin、RequireJsStuffPlugin分别尝试获取parserOptions的对应属性,失败直接返回。

APIPlugin

  这个没有阵亡,做了点事。

const REPLACEMENTS = {
    __webpack_require__: "__webpack_require__", // eslint-disable-line camelcase
    __webpack_public_path__: "__webpack_require__.p", // eslint-disable-line camelcase
    __webpack_modules__: "__webpack_require__.m", // eslint-disable-line camelcase
    __webpack_chunk_load__: "__webpack_require__.e", // eslint-disable-line camelcase
    __non_webpack_require__: "require", // eslint-disable-line camelcase
    __webpack_nonce__: "__webpack_require__.nc", // eslint-disable-line camelcase
    "require.onError": "__webpack_require__.oe" // eslint-disable-line camelcase
};
const REPLACEMENT_TYPES = {
    __webpack_public_path__: "string", // eslint-disable-line camelcase
    __webpack_require__: "function", // eslint-disable-line camelcase
    __webpack_modules__: "object", // eslint-disable-line camelcase
    __webpack_chunk_load__: "function", // eslint-disable-line camelcase
    __webpack_nonce__: "string" // eslint-disable-line camelcase
};

class APIPlugin {
    apply(compiler) {
        compiler.plugin("compilation", (compilation, params) => {
            // set...

            params.normalModuleFactory.plugin("parser", parser => {
                // 简单明了注入事件流
                Object.keys(REPLACEMENTS).forEach(key => {
                    // 'expression __webpack_require__'等等
                    parser.plugin(`expression ${key}`, ParserHelpers.toConstantDependency(REPLACEMENTS[key]));
                    // 'evaluate typeof __webpack_require__'等等
                    parser.plugin(`evaluate typeof ${key}`, ParserHelpers.evaluateToString(REPLACEMENT_TYPES[key]));
                });
            });
        });
    }
}

module.exports = APIPlugin;

  十分的简单,根据两个内置的配置对象批量注入事件,通过ParserHelpers类来辅助生成事件函数。

  该辅助类的两个方法源码如下:

ParserHelpers.toConstantDependency = function(value) {
    // value来源于APIPlugin插件
    // expr为触发事件流时给的参数 暂时不知道是什么
    return function constDependency(expr) {
        var dep = new ConstDependency(value, expr.range);
        dep.loc = expr.loc;
        this.state.current.addDependency(dep);
        return true;
    };
};
ParserHelpers.evaluateToString = function(value) {
    // 一样的
    return function stringExpression(expr) {
        return new BasicEvaluatedExpression().setString(value).setRange(expr.range);
    };
};

  这里又引入了两个辅助类来生成新对象。

  第一个方法有一个方法调用:this.state.current.addDependency,我是根本找不到定义的地点,不知道这个方法是如何执行的,尝试在控制台输出,结果发现整个打包过程中,根本就没有触发这两批事件流。

  这就完全无法得知其作用了,所以这个插件暂时跳过,可以当做没有。

ConstPlugin

  这个插件注入了三个事件流,简单看一下:

params.normalModuleFactory.plugin("parser", parser => {
    parser.plugin("statement if", function(statement) {
        // ...
    });
    parser.plugin("expression ?:", function(expression) {
        // ...
    });
    parser.plugin("evaluate Identifier __resourceQuery", function(expr) {
        // ...
    });
    parser.plugin("expression __resourceQuery", function() {
        // ...
    });
});

UseStrictPlugin

// UseStrictPlugin.js
params.normalModuleFactory.plugin("parser", (parser) => {
    const parserInstance = parser;
    parser.plugin("program", (ast) => {
        // ...
    });
});

  注入program事件流。

  后面的RequireIncludePlugin、RequireEnsurePlugin、RequireContextPlugin、ImportPlugin、SystemPlugin插件全部跳过,内容我就不贴进来了。

  

  总的来说,由于传进来的options为空对象,这个parser事件流的触发毫无意义,只是单纯注入了几个事件流,最后返回了这个parser对象,作为了request的一个对象参数,具体原型方法的调用后面再看。