美文网首页
browserify源码解析3 —— pipeline流程

browserify源码解析3 —— pipeline流程

作者: yozosann | 来源:发表于2018-08-16 13:19 被阅读0次

    这一节主要解析数据进入pipeline之后到底经历了什么,最后才能得到打包文件。
    从上节内容可以知道我们一共向这个pipe写入过两次内容:

    self.pipeline.write(row);
    
    self.pipeline.write({
      transform: globalTr,
      global: true,
      options: {}
    });
    

    然后这些内容就进入到了pipeline中:

    var pipeline = splicer.obj([
      // 记录输入管道的数据,重建管道时直接将记录的数据写入。
      // 用于像watch时需要多次打包的情况
      'record', [ this._recorder() ],
      // 依赖解析,预处理
      'deps', [ this._mdeps ],
      // 处理JSON文件
      'json', [ this._json() ],
      // 删除文件前面的BOM
      'unbom', [ this._unbom() ],
      // 删除文件前面的`#!`行
      'unshebang', [ this._unshebang() ],
      // 语法检查
      'syntax', [ this._syntax() ],
      // 排序,以确保打包结果的稳定性
      'sort', [ depsSort(dopts) ],
      // 对拥有同样内容的模块去重
      'dedupe', [ this._dedupe() ],
      // 将id从文件路径转换成数字,避免暴露系统路径信息
      'label', [ this._label(opts) ],
      // 为每个模块触发一次dep事件
      'emit-deps', [ this._emitDeps() ],
      'debug', [ this._debug(opts) ],
      // 将模块打包
      'pack', [ this._bpack ],
      'wrap', []
    ]);
    

    record操作:

    Browserify.prototype._recorder = function (opts) {
        var self = this;
        var ended = false;
        this._recorded = [];
        
        if (!this._ticked) {
            process.nextTick(function () {
                self._ticked = true;
                self._recorded.forEach(function (row) {
                    stream.push(row);
                });
                if (ended) stream.push(null);
            });
        }
        
        var stream = through.obj(write, end);
        return stream;
        
        function write (row, enc, next) {
            debugger
            self._recorded.push(row);
            if (self._ticked) this.push(row);
            next();
        }
        function end () {
            ended = true;
            console.warn('_ticked', self._recorded);
            if (self._ticked) this.push(null);
        }
    };
    

    这一步的操作主要是记录,将deps操作前进入的流都记录下来,然后进行利于进行多次打包。

    if (!this._ticked) {
            process.nextTick(function () {
                self._ticked = true;
                self._recorded.forEach(function (row) {
                    stream.push(row);
                });
                if (ended) stream.push(null);
            });
        }
    

    这一步就是Transform流中的流传递到下一步流操作中。

    deps 操作

    这一步是核心操作,需要入口和 require() 的文件或对象作为输入, 调用 module-deps 来生成一个json数据流, 这个流包含了所有依赖关系里的文件(下一节详细)。
    我们可以引入这个包,然后测试一下第一节里的main.js看下能输出什么:

    var mdeps = require('module-deps');
    var JSONStream = require('JSONStream');
    
    var md = mdeps();
    md.pipe(JSONStream.stringify()).pipe(process.stdout);
    md.write({ file: __dirname + '/main.js' });
    md.end();
    

    得到结果:

    [
      {"id":"/Users/cyl/workplace/browserify-test/lib/bar.js","source":"module.exports = function (n) { return 111 }","deps":{},"file":"/Users/cyl/workplace/browserify-test/lib/bar.js"}
      ,
      {"id":"/Users/cyl/workplace/browserify-test/w.js","source":"module.exports = {\n  c() {console.log(1)}\n} ","deps":{},"file":"/Users/cyl/workplace/browserify-test/w.js"}
      ,
      {"id":"/Users/cyl/workplace/browserify-test/foo.js","source":"var w = require('./w');\nw.c();\n\nmodule.exports = function (n) { return n * 111 }","deps":{"./w":"/Users/cyl/workplace/browserify-test/w.js"},"file":"/Users/cyl/workplace/browserify-test/foo.js"}
      ,
      {"id":"/Users/cyl/workplace/browserify-test/node_modules/gamma/index.js","source":"// transliterated from the python snippet here:\n// http://en.wikipedia.org/wiki/Lanczos_approximation\n\nvar g = 7;\nvar p = [\n    0.99999999999980993,\n    676.5203681218851,\n    -1259.1392167224028,\n    771.32342877765313,\n    -176.61502916214059,\n    12.507343278686905,\n    -0.13857109526572012,\n    9.9843695780195716e-6,\n    1.5056327351493116e-7\n];\n\nvar g_ln = 607/128;\nvar p_ln = [\n    0.99999999999999709182,\n    57.156235665862923517,\n    -59.597960355475491248,\n    14.136097974741747174,\n    -0.49191381609762019978,\n    0.33994649984811888699e-4,\n    0.46523628927048575665e-4,\n    -0.98374475304879564677e-4,\n    0.15808870322491248884e-3,\n    -0.21026444172410488319e-3,\n    0.21743961811521264320e-3,\n    -0.16431810653676389022e-3,\n    0.84418223983852743293e-4,\n    -0.26190838401581408670e-4,\n    0.36899182659531622704e-5\n];\n\n// Spouge approximation (suitable for large arguments)\nfunction lngamma(z) {\n\n    if(z < 0) return Number('0/0');\n    var x = p_ln[0];\n    for(var i = p_ln.length - 1; i > 0;--i) x += p_ln[i] / (z + i);\n    var t = z + g_ln + 0.5;\n    return .5*Math.log(2*Math.PI)+(z+.5)*Math.log(t)-t+Math.log(x)-Math.log(z);\n}\n\nmodule.exports = function gamma (z) {\n    if (z < 0.5) {\n        return Math.PI / (Math.sin(Math.PI * z) * gamma(1 - z));\n    }\n    else if(z > 100) return Math.exp(lngamma(z));\n    else {\n        z -= 1;\n        var x = p[0];\n        for (var i = 1; i < g + 2; i++) {\n            x += p[i] / (z + i);\n        }\n        var t = z + g + 0.5;\n\n        return Math.sqrt(2 * Math.PI)\n            * Math.pow(t, z + 0.5)\n            * Math.exp(-t)\n            * x\n        ;\n    }\n};\n\nmodule.exports.log = lngamma;\n","deps":{},"file":"/Users/cyl/workplace/browserify-test/node_modules/gamma/index.js"}
      ,
      {"file":"/Users/cyl/workplace/browserify-test/main.js","id":"/Users/cyl/workplace/browserify-test/main.js","source":"var foo = require('./foo.js');\nvar bar = require('./lib/bar.js');\nvar gamma = require('gamma');\nvar w = require('./w');\n\nvar elem = document.getElementById('result');\nvar x = foo(100) + bar('baz');\nw.c();\nelem.textContent = gamma(x);","deps":{"./lib/bar.js":"/Users/cyl/workplace/browserify-test/lib/bar.js","./w":"/Users/cyl/workplace/browserify-test/w.js","./foo.js":"/Users/cyl/workplace/browserify-test/foo.js","gamma":"/Users/cyl/workplace/browserify-test/node_modules/gamma/index.js"},"entry":true}
    ]
    

    每个数组元素结构:

    {
        "id": "/Users/cyl/workplace/browserify-test/foo.js",
        "source": "var w = require('./w');\nw.c();\n\nmodule.exports = function (n) { return n * 111 }",
        "deps": {
          "./w": "/Users/cyl/workplace/browserify-test/w.js"
        },
        "file": "/Users/cyl/workplace/browserify-test/foo.js"
      },
    

    这样其实就分析出来了入口文件的所有依赖关系,每个依赖就一个元素。

    • id:该依赖的id。
    • source:该依赖的源码
    • deps:该依赖所依赖的其他文件所对应的id
    • file:该依赖的文件位置
      主入口也是一个元素,不同在于他还包含一个属性:entry:true

    json操作

    这个操作就比较简单了,引用的文件可能是json文件,我们为了让他正确执行,其实在source加上module.exports=即可。

    through.obj(function (row, enc, next) {
      if (/\.json$/.test(row.file)) {
          row.source = 'module.exports=' + sanitize(row.source);
      }
      this.push(row);
      next();
    });
    

    unbom操作:

    删除文件源码前面的BOM(不然会影响执行)

    return through.obj(function (row, enc, next) {
            if (/^\ufeff/.test(row.source)) {
                row.source = row.source.replace(/^\ufeff/, '');
            }
            this.push(row);
            next();
        });
    

    unshebang操作:删除文件源码前面的#!

    syntax操作:

    这个转换会通过调用 syntax-error 检查语法错误,给出错误信息以及错误所在行和列。

    Browserify.prototype._syntax = function () {
        var self = this;
        return through.obj(function (row, enc, next) {
            var h = shasum(row.source);
            if (typeof self._syntaxCache[h] === 'undefined') {
                var err = syntaxError(row.source, row.file || row.id);
                if (err) return this.emit('error', err);
                self._syntaxCache[h] = true;
            }
            this.push(row);
            next();
        });
    };
    

    除此之外,还缓存了文件的hash值,有利于多次打包,如果文件没有改动不需要再次检测。

    sort 操作:

    这个阶段使用 deps-sort 对写入的行进行排序以确定最后生成的打包文件。(没有去看源码,不太清楚基于什么做的sort,但是能够知道的是他能分析出重复的信息,并且将row添加了一个属性indexDeps,之前deps是按照id的形式映射,现在变成了下标的形式)

    dedupe操作:基于sort给出的重复信息,对拥有同样内容的依赖去重。

    label操作:

    这阶段会把每个可能暴露系统路径的文件的ID进行转换,把原来很大的文件包用整数ID来代表。
    核心其实就一句:if (row.index) row.id = row.index;
    label 阶段还会把基于 opts.basedir 和 process.cwd() 的文件路径进行标准化,以防止暴露系统文件路径信息。
    并且记录了入口文件id:

    if (row.entry || row.expose) {
      self._bpack.standaloneModule = row.id;
    }
    

    emit-deps操作

    这个阶段会在 label 阶段结束后,给每一行触发一个 'dep' 事件。

    return through.obj(function (row, enc, next) {
            self.emit('dep', row);
            this.push(row);
            next();
        })
    

    那么这个时候外部b实例就能监听到dep事件,做一些相应操作。

    debug操作

    如果在实例化构造函数 browserify() 的时候传入了 opts.debug 参数, 那在这个转换阶段,它会使用 pack 阶段的 browser-pack 来给输入流添加 sourceRoot 和 sourceFile 属性。

    pack操作:

    调用browser-pack包将所有到模块信息打包成一个可在浏览器执行的文件,也就是我们第一节输出的那个文件。感兴趣的可以去看看源码。

    下一节主要会解析下依赖分析的过程。

    参考文献:

    browserify使用手册 -- 原版
    browserify使用手册 -- 中文
    deps-sort - github
    browser-pack - github

    相关文章

      网友评论

          本文标题:browserify源码解析3 —— pipeline流程

          本文链接:https://www.haomeiwen.com/subject/hjpnbftx.html