zoukankan      html  css  js  c++  java
  • .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的一个对象参数,具体原型方法的调用后面再看。

  • 相关阅读:
    极简代码搞定视频剪辑
    python 遍历本地文件
    安装Anaconda需要知道的pc信息
    ng4 路由多参数传参以及接收
    Js之设置日期时间 判断日期是否在范围内
    VsCode显示左边折叠代码+-按钮
    H5+.Net Webapi集成微信分享前后端代码 微信JS-SDK wx.onMenuShareTimeline wx.onMenuShareAppMessage
    压测工具之JMeter之环境配置及运行
    C# 交集、差集、并集、去重
    Nginx初学者指南
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/8462873.html
Copyright © 2011-2022 走看看