gulp-prefix插件源码阅读

为什么会阅读这个插件的源码呢,因为在维护老项目的时候看到这个插件并没有给所有需要加前缀的url加上前缀,并因为ios需要支持https,而安卓客户端却不需要,为了能够自适应不同的客户端系统,则需要给url添加以双斜杠(//)开头的url,但是该插件却将双斜杠替换成了单斜杠,所以打算阅读下源代码看这个插件内部到底做了什么。

先列出gulp-prefix的源码如下:

'use strict';
var through = require('through2'),
    url = require("url"),
    urljoin = require("url-join"),
    trumpet = require("trumpet"),
    concat  = require("concat-stream"),
    _prefixer;

_prefixer = function(prefix, attr, invalid) {
  return function(node) {
    node.getAttribute(attr, function(uri) {

      uri = url.parse(uri, false, true);

      if(uri.host || !uri.path)
        return;

      if (!/^[!#$&-;=?-\[\]_a-z~\.\/\{\}]+$/.test(uri.path)) {
        return;
      }

      if (invalid && new RegExp(invalid).test(uri.path)){
        return;
      }

      var file_prefix = (typeof prefix === 'function') ? prefix(uri) : prefix;

      node.setAttribute(attr, urljoin(file_prefix, uri.path));
    });
  };
};

module.exports = function(prefix, selectors, ignore) {

  return through.obj(function(file, enc, cb) {

    if (!selectors) {
      selectors = [
      { match: "script[src]", attr: "src" },
      { match: "link[href]", attr: "href"},
      { match: "img[src]", attr: "src"},
      { match: "input[src]", attr: "src"},
      { match: "img[data-ng-src]", attr: "data-ng-src"}
      ];
    }

    if(!prefix)
      cb(null, file);

    else {
      var tr = trumpet();

      for (var a in selectors)
        tr.selectAll(selectors[a].match, _prefixer(prefix, selectors[a].attr, ignore))

      tr.pipe(concat(function (data) {
        if (Array.isArray(data) && data.length === 0) data = null;
        file.contents = data;
        cb(null, file);
      }));

      file.pipe(tr);
    } 
  });
};

代码的开头是依赖模块的引入和一些变量的定义,这个插件主要依赖了url,though2,url-join,trumpet,concat-stream等模块。

  • url 模块

    url模块是node内置模块,用来对url进行解析处理并生成由url各个部分组成的解析结果对象。

  • though2

    用来生成transform stream。 具体stream的一些概念参看Node Stream初窥,though2的具体使用参看文档。

  • url-join

    作用将url的各个部分链接起来,形成合法的url。

  • trumpet

    使用css 选择器解析处理stream 形式的html。

    //使用方法
    var trumpet = require('trumpet');
    //Create a new trumpet stream. This stream is readable and writable. Pipe an html stream into tr and get back a transformed html stream.
    var tr = trumpet(); 
    
    //将html 可读流和tr链接
    var fs = require('fs');
    fs.createReadStream(__dirname + '/html/read_all.html').pipe(tr);
    //将产生的transformed html stream,选择其中满足css selector,创建一个可读流链接到输出流
    //这里如果span.createReadStream() 不加任何参数的话,默认取得是标签内的内容,不带标签,如果要获取到完整的html片段,则需要调用时增加选项参数{outer:true}
    tr.selectAll('.b span', function (span) {
          span.createReadStream().pipe(process.stdout);
    });
    
  • concat-stream

    从名字可以猜这个模块应该是用来连接什么的,文档介绍说如果你有将stream中的所有buffer连接成一个buffer,然后再进行处理的需求的话,那么这个模块就是你想要的,由于stream的特性,导致每次得到的数据只是一个chunk buffer,所以有时你需要得到全部的数据你才能进行某些操作,那么就可以通过这个模块到达想要的结果。

    //使用例子
    var fs = require('fs')
    var concat = require('concat-stream')
    
    function gotPicture(imageBuffer) {
      // imageBuffer is all of `cat.png` as a node.js Buffer
    }
    
    function handleError(err) {
      // handle your error appropriately here, e.g.:
      console.error(err) // print the error to STDERR
      process.exit(1) // exit program with non-zero exit code
    }
    
    var readStream = fs.createReadStream('cat.png')
    var concatStream = concat(gotPicture)
    
    readStream.on('error', handleError)
    readStream.pipe(concatStream)
    

在了解了上面gulp-prefix所依赖的插件后,我们就可以开始看这个插件到底做了什么事情了。首先看module.exports,导出一个函数这个函数可以接受三个参数:

  • prefix

    字符串或者用于生成url前缀的函数。

  • selectors

    对象数组,需要被替换的html标签,以及标签属性。具体格式如下:

    [
      { match: "script[src]", attr: "src" },
      { match: "link[href]", attr: "href"},
      { match: "img[src]", attr: "src"},
      { match: "input[src]", attr: "src"},
      { match: "img[data-ng-src]", attr: "data-ng-src"}
    ]
    
  • ignore

    要被过滤的路径,new Regex(ignore).test(path) 为true的话,则不进行添加前缀操作。

再来看这段代码:

if(!prefix)
  cb(null, file);

else {
  var tr = trumpet();

  for (var a in selectors)
    tr.selectAll(selectors[a].match, _prefixer(prefix, selectors[a].attr, ignore))

  tr.pipe(concat(function (data) {
    if (Array.isArray(data) && data.length === 0) data = null;
    file.contents = data;
    cb(null, file);
  }));

  file.pipe(tr);
}

对prefix进行判断,如果为空,则将file交给下一个插件进行处理,如果不为空,则进行如下处理,创建tr,用于和file链接,遍历selectors,然后选择匹配选择器的元素交由_prefixer的方法进行处理,这个方法会返回一个回调函数,这个回调函数的参数node就是匹配选择器的元素。然后在将tr处理后的file流向concat创建的流,当整个文件处理完,调用传入concat的回调,然后设置file内容为处理后的内容,流向下个插件。

其中_prefixer,是真正修改url的地方,我们看下代码内容:

function(prefix, attr, invalid) {
  return function(node) {
    node.getAttribute(attr, function(uri) {

      uri = url.parse(uri, false, true);

      if(uri.host || !uri.path)
        return;

      if (!/^[!#$&-;=?-\[\]_a-z~\.\/\{\}]+$/.test(uri.path)) {
        return;
      }

      if (invalid && new RegExp(invalid).test(uri.path)){
        return;
      }

      var file_prefix = (typeof prefix === 'function') ? prefix(uri) : prefix;

      node.setAttribute(attr, urljoin(file_prefix, uri.path));
    });
  };
};

从代码中看出返回的回调函数,从node中取出attr属性值(url路径),然后通过url模块进行解析,url.parse的参数含义参考文档url.parse,最后一个参数设置为true,考虑了路径为双斜杠开头的情况,那么后面// 到 /之间的值就是host了。如果存在host或者path不存在,则认为这种情况不需要添加前缀了。如果是合法path则接下去进行处理,如果path匹配ignore(invalid),则不处理,最后进行前缀添加,如果prefix是函数,执行函数并返回file_prefix文件前缀。然后通过url-join模块合并前缀和path,重新设置node(节点)的属性值。

当读到这里的时候,发现img标签中的data-lazyload-src没有被加前缀的原因是默认的selectors中没有对应的selector,添加后修复了这个问题。但是双斜杠的问题还是存在,从整个代码来看,最可能出问题的地方就是urljoin这里了,后来看了url-join模块的源码,才发现gulp-prefix用的是很早的版本,从package.json中可以看出:

 "dependencies": {
    "through2": "~1.0.0",
    "url": "~0.10.1",
    "trumpet": "~1.7.0",
    "concat-stream": "~1.4.5",
    "url-join": "0.0.1" //这里版本为0.0.1,现在已经是v1.1.0
}

0.0.1版本的url-join源代码如下:

function normalize (str) {
  return str
          .replace(/[\/]+/g, '/')
          .replace(/\/\?/g, '?')
          .replace(/\/\#/g, '#')
          .replace(/\:\//g, '://');
}

module.exports = function () {
  var joined = [].slice.call(arguments, 0).join('/');
  return normalize(joined);
};

很明显这个地方将双斜杠给替换成了单斜杠,给作者提了pull request,作者并没有鸟,尝试自己升级了url-join最后解决了双斜杠被删成单斜杠的问题。

gulp-prefix 可以改进的地方

  • selectors 参数格式,数组的话作者并没有增量合并,而是需要自己枚举所有情况。
  • url-join 在package.json中写死了版本,导致双斜杠变单斜杠的问题。

查了一波MIT证书的限制,对于MIT证书来说,只要用户在项目副本中包含了版权声明和许可声明,你可以拿这个代码做任何事情。所以决定对代码略作修改使其更好的用在自己的平常构建任务中。改造后重命名为gulp-prefix-url,并写了测试用例进行测试。插件的GitHub地址为:https://github.com/xdimh/gulp-prefix-url。

参考

  1. 各种 License
  2. choosealicense
© xdimh all right reserved,powered by Gitbook该文件修订时间: 2017-03-20 15:15:16

results matching ""

    No results matching ""

    results matching ""

      No results matching ""