browserify源码解析3 —— pipeline流程

2018-08-16  本文已影响0人  yozosann

这一节主要解析数据进入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"
  },

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

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

上一篇下一篇

猜你喜欢

热点阅读