美文网首页
browserify源码解析4——依赖解析

browserify源码解析4——依赖解析

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

主要解析源码解析过程中的:

'deps', [ this._mdeps ],

从上一节了解,当文件写入经过该流程后,入口文件的所有依赖都会被解析出来:

[
  {"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}
]

this._mdeps是由_createDeps()操作创建。
_createDeps()引用了module-deps包,然后传入了一些opts,返回了一个流。
我们需要了解module-deps的源码。

既然知道module-deps也是一个流 我们就从transform开始看:
之前我们write的两次流,也就流到module-deps里的transform里了。

第一次写入的是入口文件:

row:{
  "entry":true,
  "expose":false,
  "file":"/Users/cyl/workplace/browserify-test/main.js", order: 0
}

到了transform进行lookupPackage操作:

self.lookupPackage(row.file, function (err, pkg) {
        if (err && self.options.ignoreMissing) {
            self.emit('missing', row.file, self.top);
            self.pending --;
            return next();
        }
        if (err) return self.emit('error', err)
        self.pending --;
        self._input.push({ row: row, pkg: pkg });
        next();
    });

其实做的事情 就是解析目录的package文件,并把该过程的文件数据流都记录在input里了。

第二次 我们写入的是一个这样的结构:

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

到了module-deps的transform就变成了:

if (row.transform && row.global) {
        this.globalTransforms.push([ row.transform, row.options ]);
        return next();
    }

其实这里就是记录了一个全局的包转换函数,作用后面再说。

流入的两次流都执行完了,那么就执行到了flush里了。

Object.keys(files).forEach(function (key) {
  var r = files[key];
  var pkg = r.pkg || {};
  var dir = r.row.file ? path.dirname(r.row.file) : self.basedir;
  if (!pkg.__dirname) pkg.__dirname = dir;
  
  self.walk(r.row, xtend(self.top, {
      filename: path.join(dir, '_fake.js')
  }));
});

遍历input里的所有文件,依次对每个文件进行walk操作。

在walk里的操作里,又调用了resolve操作,resolve调用的是由browserify里传过来opt里的resolver。这个resolver调用的是一个browser-resolve包,他主要的作用是将文件源码路径地址替换为在浏览器可以执行的源码。我们希望我们发的一个js包既能在浏览器里运行,也能在node里运行,但是有些情况就使用一份代码是不现实的于是就会在package.json里进行区分。
详见:
browserify使用手册里package.json这一节。
也可以参考:怎样写一个能同时用于 Node 和浏览器的 JavaScript 包?

执行结束后会走到walk调用resolve之后的回调函数,this.persistentCache里:

function persistentCacheFallback(dataAsString, cb) {
  var stream = dataAsString ? toStream(dataAsString) : self.readFile(file, id, pkg).on('error', cb);
  stream
    .pipe(self.getTransforms(fakePath || file, pkg, {
      builtin: builtin,
      inNodeModules: parent.inNodeModules
    }))
    .on('error', cb)
    .pipe(concat(function (body) {
      var src = body.toString('utf8');
      try {
        var deps = getDeps(file, src);
      } catch (err) {
        cb(err);
      }
      if (deps) {
        cb(null, {
          source: src,
          package: pkg,
          deps: deps.reduce(function (deps, dep) {
            deps[dep] = true;
            return deps;
          }, {})
        });
      }
    }));
}

这里的stream就是,经过resolve后返回的file id 和pkg。
然后又创建了一个流进行了简单的一次流操作。

第一执行的是getTransforms流操作:
首先得到transforms,然后遍历transforms执行makeTransform,当遍历结束执行完transforms之后调用done方法将流回到walk中的流程中。makeTransform中执行如果为函数执行globaltr 将流文件进行insertGlobals,返回一个流进行wrapTransform,如果是一个对象执行loadTransform。 每次执行的结果都放入streams里。

return insertGlobals(file, xtend(opts, {
  debug: opts.debug,
  always: opts.insertGlobals,
  basedir: opts.commondir === false && isArray(opts.builtins)
      ? '/'
      : opts.basedir || process.cwd()
  ,
  vars: vars
}));

配置了一个全局的 insert-module-globals 转换来检测和执行 process, Buffer, global, __dirname, __filename。

前面执行的都是铺垫,进行一些内部包替换等操作,目的就是为了最终解析出来的源码能够在浏览器中正常运行, 现在就到了最重要的解析依赖的这一步。

流的第二步操作:

concat(function (body) {
  var src = body.toString('utf8');
  try { var deps = getDeps(file, src); }
  catch (err) { cb(err); }
  if (deps) {
      cb(null, {
          source: src,
          package: pkg,
          deps: deps.reduce(function (deps, dep) {
              deps[dep] = true;
              return deps;
          }, {})
      });
  }
}

它调用 getDeps 调用 self.parseDeps 最后调用的一个detective的包。

detective的核心代码非常简单:

function visit(node, st, c) {
        var hasRequire = wordRe.test(src.slice(node.start, node.end));
        if (!hasRequire) return;
        walk.base[node.type](node, st, c);
        if (node.type !== 'CallExpression') return;
        if (isRequire(node)) {
            if (node.arguments.length) {
                var arg = node.arguments[0];
                if (arg.type === 'Literal') {
                    modules.strings.push(arg.value);
                }
                else if (arg.type === 'TemplateLiteral'
                        && arg.quasis.length === 1
                        && arg.expressions.length === 0) {

                    modules.strings.push(arg.quasis[0].value.raw);
                }
                else {
                    modules.expressions.push(src.slice(arg.start, arg.end));
                }
            }
            if (opts.nodes) modules.nodes.push(node);
        }
    }

使用了解析抽象语法树的包acorn-node。walk.recursive遍历解析出来的抽象语法树,匹配函数中的/\brequire\b/字段,拿到调用的值。调用的值就是该文件所依赖的包,最后放在一个数组里返回。

返回之后回调用一个函数fromDeps:

(function resolve () {
  if (self.inputPending > 0) return setTimeout(resolve);
  deps.forEach(function (id) {
      if (opts.filter && !opts.filter(id)) {
          resolved[id] = false;
          if (--p === 0) done();
          return;
      }
      var isTopLevel = self._isTopLevel(fakePath || file);
      var current = {
          id: file,
          filename: file,
          paths: self.paths,
          package: pkg,
          inNodeModules: parent.inNodeModules || !isTopLevel
      };
      self.walk(id, current, function (err, r) {
          resolved[id] = r;
          if (--p === 0) done();
      });
  });
  if (deps.length === 0) done();
})();

它依次遍历依赖,并且调用walk递归解析依赖的依赖,每解析完一个依赖就会调用done函数:

function done () {
  if (!rec.id) rec.id = file;
  if (!rec.source) rec.source = src;
  if (!rec.deps) rec.deps = resolved;
  if (!rec.file) rec.file = file;
  
  if (self.entries.indexOf(file) >= 0) {
      rec.entry = true;
  }
  self.push(rec);
  
  if (cb) cb(null, file);
  if (-- self.pending === 0) self.push(null);
}

将最后解析后的结果传到browserify 中'deps', [ this._mdeps ],的下一个流操作里,直至所有依赖都被解析完成。

参考文献:

browserify使用手册
抽象语法树
estree

相关文章

网友评论

      本文标题:browserify源码解析4——依赖解析

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