视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
gulp进阶-自定义gulp插件_html/css_WEB-ITnose
2020-11-27 16:36:50 责编:小采
文档

gulp已经成为很多项目的标配了,gulp的插件生态也十分繁荣,截至2015.1.5,npm上已经有10190款gulp插件供我们使用。我们完全可以傻瓜式地搭起一套构建。

然而,我们经常会遇到一种情况,我们好不容易按照文档传入对应的参数调用了插件,却发现结果不如预期,这时候我们就要一点点去排错,这就要求我们对gulp插件的工作原理有一定的了解。本文以实现一个gulp插件为例,讲解一下gulp插件是如何工作的。

需求描述

通常,我们的构建资源为js/css/html以及其它的一些资源文件,在开发或发布阶段,js/css会经过合并,压缩,重命名等处理步骤。

有些场景下,我们不能确定经过构建后生成js/css的名称或者数量,如此就不能在HTML文件中写死资源的引用地址,那么该如何实现一个Gulp的插件用以将最终生成的资源文件/地址注入到HTML中呢?

假设我们需要实现的插件是这样使用方式:

  

我们通过一个HTML注释用以声明需要依赖的资源,InlineResource 是匹配的关键词,":"做为分割,/*.css$/,/*.js$/ 是声明要依赖的文件的正则匹配。

在gulpfile.js我们需要这边配置:

gulp.task('dist', function () { return gulp.src('index.html') .pipe(InjectResources( gulp.src(['*.js', '*.css']) .pipe(hash(/*添加MD5作为文件名*/)) )) .pipe(gulp.dest('dist'))})

这里简单介绍下其中的一些方法与步骤:

  • gulp.src('index.html')会读取文件系统中当前目录下的index.html,并生成一个可读的Stream,用于后续的步骤消费

  • InjectResources(stream)是我们将要实现的插件,它接受一个参数用以获取要注入到HTML中的JS/CSS,此参数应该是一个 Stream实例,用生成一个Stream实例,用于接收并处理上一步流进来的数据

  • hash(options)是一个第三方插件,用于往当前流中的文件名添加md5串,如: gulp-hash

  • gulp.dest('dist')用于将注入资源后的HTML文件生成到当前目录下

  • 我们要关心的是第2点:如何接所有的资源文件并完成注入?

    我们可以将该逻辑分成4个步骤

    1. 获取所有的js/css资源
    2. 获取所有的HTML文件
    3. 定位HTML中的依赖声明
    4. 匹配所依赖的资源
    5. 生成并注入依赖的资源标签

    在开编之前,我们需要依赖一个重要的第三方库: map-stream

    map-stream 用于获取当前流中的每一个文件数据,并且修改数据内容。

    步骤1 (JS/CSS资源)

    module.exports = function (resourcesStream) { // step 1: TODO => 这里要获取所有的js/css资源}

    资源流会作为参数的形式传给InjectResources方法,在此通过一个异步的实例方法获取所有的文件对象,放到一个资源列表:

    var resources = []function getResources(done) { if (resources) return done(resources) // 由于下面的操作是异步的,此处要有锁... resourcesStream.pipe(mapStream(function (data, cb) { resources.push(data) cb(null, data) })) .on('end', function () { done(resources) })}

  • mapStream的处理方法中获取到的data是由gulp.src生成的 vinyl对象,代表了一个文件
  • 每一个stream都会在接受后抛出end事件
  • Note: mapStream的处理方法中的cb方法,第二个参数可以用于替换当前处理的文件对象

    到此,我们就完成了第一步的封装啦!

    module.exports = function (resourcesStream) { // step 1: function getResources () { ... }}

    步骤2 (HTML文件)

    module.exports = function (resourcesStream) { // step 1: ?? // step 2: TODO => 获取当前流中的所有目标HTML文件 return mapStream(function (data, cb) { })}

    InjectResources插件方法会返回一个 Writable Stream实例,用于接收并处理流到InjectResources的HTML文件,mapStream的返回值就是一个writable stream。

    此时,mapStream的处理方法拿到的data就是一个HTML文件对象,接下来进行内容处理。

    步骤3 (定位依赖)

    module.exports = function (resourcesStream) { // step 1: ?? // step 2: ? return mapStream(function (data, cb) { var html = data.contents.toString() // step 3: TODO => 获取HTML中的资源依赖声明 })}

    我们拿到的data是一个 vinyl对象,contents属性是文件的内容,类型可能是Buffer也可能是String, 通过toStraing()后可以获取到字符串内容。

    所有的依赖声明都有InlineResource关键词,简单点的做法,可以通过正则来定位并替换HTML中的资源依赖:

    html.replace(//g, function (expr, fileRegexpStr){ // fileRegexp是用以匹配依赖资源的正则字符串})

    到此,我们完成了资源依赖的定位,下一步将是获取所依赖的资源用以替换。

    步骤4 (依赖匹配)

    我们将通过步骤1定义的 getResources 方法获取所需的资源文件:

    module.exports = function (resourcesStream) { // step 1: ?? // step 2: ? return mapStream(function (data, cb) { // step 3: ? getResources(function (list) { html.replace(depRegexp, function (expr, fileRegexpStr) { var fileRegexp = new RegExp(fileRegexpStr) // step 4: TODO => 获取匹配的依赖 }) }) })}

    由于 getResources 是异步方法,因此需要把替换处理逻辑包裹在 getResources 的回调方法中

    根据依赖声明中的正则表达式,对资源列表一一匹配:

    function matchingDependences(list, regexp) { var deps = [] list.forEach(function (file) { var fpath = file.path if (fileRegexp.test(fpath)) { deps.push(fpath) } }) return deps}

    到此只差最后一步,将资源转换为HTML标签并注入到HTML中

    步骤5 (资源转换/依赖注入)

    module.exports = function (resourcesStream) { // step 1: ?? // step 2: ? return mapStream(function (data, cb) { // step 3: ? // step 4: ? // ... html.replace(depRegexp, function (expr, fileRegexpStr) { var deps = matchingDependences(list, fileRegexpStr) // step 5: 文件对象转换为HTML标签 }) })}

    接下来的定义一个transform方法,用于将路径列表转换为HTML的资源标签列表,其中引入了 path模块用于解析获取文件路径的一些信息,该模块是node内置模块。

    var path = require('path') function transform(deps) { return deps.map(function (dep) { var ext = path.extname(dep) switch (ext) { case 'js': '

    最终,我们将标签列表拼接为一个字符串来HTML中的依赖声明(注入):

    html = html.replace(depRegexp, function (expr, fileRegexpStr) { var deps = matchingDependences(list, fileRegexpStr) // step 5: 文件对象转换为HTML标签 return transform(deps)})// html文件对象data.contents = new Buffer(html)// 把修改后的文件对象放回HTML流中cb(null, data)

    到此也就完整地实现了一个拥有基本注入功能的插件~~~~~~

    One More Thing

    通过上面实现的示例步骤,可以清楚了解到gulp插件的工作原理。 但要做一个易用/可定制性高的插件,我们还要继续完善一下,例如:

  • 比较资源的路径与HTML的路径,输出相对路径作为默认的标签资源路径
  • 提供 sort 选项方法用于修改资源的注入顺序
  • 提供 transform 选项方法用于定制标签中的资源路径
  • 在依赖声明中支持 inline 声明,用以将资源内容内联到HTML中,例如:

     

  • 支持命名空间,用于往同一个资源流中使用多次资源注入的区分,例如:

     gulp.src('index.html') .pipe( InjectResources(gulp.src('asserts/*.js'), { name: 'asserts'}) ) .pipe( InjectResources(gulp.src('components/*.js'), { name: 'components'}) ) ...

  • . . .

  • 下载本文
    显示全文
    专题