Home >Web Front-end >HTML Tutorial >gulp进阶-自定义gulp插件_html/css_WEB-ITnose

gulp进阶-自定义gulp插件_html/css_WEB-ITnose

WBOY
WBOYOriginal
2016-06-21 08:58:401377browse

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

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

需求描述

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

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

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

<html><head>    <!--InlineResource:\.css$--></head><body>    <!--InlineResource:\.js$--></body></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(/<!--InlineResource:(.*?)-->/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':                     '<script>' + dep + '</script>'            break            case 'css':                return '<link rel="stylesheet" href="' + dep + '">'            break        }        return ''    }).join('')}

最终,我们将标签列表拼接为一个字符串来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中,例如:

      <!--InjectResources:*\.js$??inline-->

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

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

  • . . .

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn