发布于 2016-01-25 10:04:02 | 197 次阅读 | 评论: 0 | 来源: 分享
GulpJS 流构建系统
从头编写HTML\CSS\Javascript是上个世纪的事情了,如今的JavaScript都是通过CoffeeScript这样的支持句法缩写的编辑器写成的。如果你希望写完JavaScript能够一个工具完成代码清理优化工作,Gulp 就是你的不二之选,GulpJS类似Ant或Maven之于Java。
gulp已经成为很多项目的标配了,gulp的插件生态也十分繁荣,截至2015.1.5,npm上已经有10190款gulp插件供我们使用。我们完全可以傻瓜式地搭起一套构建。
然而,我们经常会遇到一种情况,我们好不容易按照文档传入对应的参数调用了插件,却发现结果不如预期,这时候我们就要一点点去排错,这就要求我们对gulp插件的工作原理有一定的了解。本文以实现一个gulp插件为例,讲解一下gulp插件是如何工作的。
通常,我们的构建资源为js/css/html以及其它的一些资源文件,在开发或发布阶段,js/css会经过合并,压缩,重命名等处理步骤。
有些场景下,我们不能确定经过构建后生成js/css的名称或者数量,如此就不能在HTML文件中写死资源的引用地址,那么该如何实现一个Gulp的插件用以将最终生成的资源文件/地址注入到HTML中呢?
假设我们需要实现的插件是这样使用方式:
1
2
3
4
5
6
7
8
|
<html>
<head>
<!–InlineResource:.css$–>
</head>
<body>
<!–InlineResource:.js$–>
</body>
</html>
|
我们通过一个HTML注释用以声明需要依赖的资源,InlineResource 是匹配的关键词,”:”做为分割,/*.css$/,/*.js$/ 是声明要依赖的文件的正则匹配。
在gulpfile.js我们需要这边配置:
1
2
3
4
5
6
7
8
|
gulp.task(‘dist’,function(){
returngulp.src(‘index.html’)
.pipe(InjectResources(
gulp.src(['*.js','*.css'])
.pipe(hash(/*添加MD5作为文件名*/))
))
.pipe(gulp.dest(‘dist’))
})
|
这里简单介绍下其中的一些方法与步骤:
我们要关心的是第2点:如何接所有的资源文件并完成注入?
我们可以将该逻辑分成4个步骤
在开编之前,我们需要依赖一个重要的第三方库:map-stream
map-stream 用于获取当前流中的每一个文件数据,并且修改数据内容。
1
2
3
|
module.exports=function(resourcesStream){
// step 1: TODO => 这里要获取所有的js/css资源
}
|
资源流会作为参数的形式传给InjectResources方法,在此通过一个异步的实例方法获取所有的文件对象,放到一个资源列表:
1
2
3
4
5
6
7
8
9
10
11
12
|
varresources=[]
functiongetResources(done){
if(resources)returndone(resources)
// 由于下面的操作是异步的,此处要有锁…
resourcesStream.pipe(mapStream(function(data,cb){
resources.push(data)
cb(null,data)
}))
.on(‘end’,function(){
done(resources)
})
}
|
Note: mapStream的处理方法中的cb方法,第二个参数可以用于替换当前处理的文件对象
到此,我们就完成了第一步的封装啦!
1
2
3
4
5
6
|
module.exports=function(resourcesStream){
// step 1:
functiongetResources(){
...
}
}
|
1
2
3
4
5
6
7
8
|
module.exports=function(resourcesStream){
// step 1: ✔︎
// step 2: TODO => 获取当前流中的所有目标HTML文件
returnmapStream(function(data,cb){
})
}
|
InjectResources插件方法会返回一个Writable Stream实例,用于接收并处理流到InjectResources的HTML文件,mapStream的返回值就是一个writable stream。
此时,mapStream的处理方法拿到的data就是一个HTML文件对象,接下来进行内容处理。
1
2
3
4
5
6
7
8
9
10
|
module.exports=function(resourcesStream){
// step 1: ✔︎
// step 2: ✔
returnmapStream(function(data,cb){
varhtml=data.contents.toString()
// step 3: TODO => 获取HTML中的资源依赖声明
})
}
|
我们拿到的data是一个vinyl对象,contents属性是文件的内容,类型可能是Buffer也可能是String, 通过toStraing()后可以获取到字符串内容。
所有的依赖声明都有InlineResource关键词,简单点的做法,可以通过正则来定位并替换HTML中的资源依赖:
1
2
3
|
html.replace(//g,function(expr,fileRegexpStr){
// fileRegexp是用以匹配依赖资源的正则字符串
})
|
到此,我们完成了资源依赖的定位,下一步将是获取所依赖的资源用以替换。
我们将通过步骤1定义的 getResources 方法获取所需的资源文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
module.exports=function(resourcesStream){
// step 1: ✔︎
// step 2: ✔
returnmapStream(function(data,cb){
// step 3: ✔
getResources(function(list){
html.replace(depRegexp,function(expr,fileRegexpStr){
varfileRegexp=newRegExp(fileRegexpStr)
// step 4: TODO => 获取匹配的依赖
})
})
})
}
|
由于 getResources 是异步方法,因此需要把替换处理逻辑包裹在 getResources 的回调方法中
根据依赖声明中的正则表达式,对资源列表一一匹配:
1
2
3
4
5
6
7
8
9
10
|
functionmatchingDependences(list,regexp){
vardeps=[]
list.forEach(function(file){
varfpath=file.path
if(fileRegexp.test(fpath)){
deps.push(fpath)
}
})
returndeps
}
|
到此只差最后一步,将资源转换为HTML标签并注入到HTML中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
module.exports=function(resourcesStream){
// step 1: ✔︎
// step 2: ✔
returnmapStream(function(data,cb){
// step 3: ✔
// step 4: ✔
// …
html.replace(depRegexp,function(expr,fileRegexpStr){
vardeps=matchingDependences(list,fileRegexpStr)
// step 5: 文件对象转换为HTML标签
})
})
}
|
接下来的定义一个transform方法,用于将路径列表转换为HTML的资源标签列表,其中引入了 path 模块用于解析获取文件路径的一些信息,该模块是node内置模块。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
varpath=require(‘path’)
functiontransform(deps){
returndeps.map(function(dep){
varext=path.extname(dep)
switch(ext){
case‘js’:
‘‘ + dep + ‘‘
break
case‘css’:
return”
break
}
return”
}).join(”)
}
|
最终,我们将标签列表拼接为一个字符串来HTML中的依赖声明(注入):
1
2
3
4
5
6
7
8
9
|
html=html.replace(depRegexp,function(expr,fileRegexpStr){
vardeps=matchingDependences(list,fileRegexpStr)
// step 5: 文件对象转换为HTML标签
returntransform(deps)
})
// html文件对象
data.contents=newBuffer(html)
// 把修改后的文件对象放回HTML流中
cb(null,data)
|
到此也就完整地实现了一个拥有基本注入功能的插件~~~~~~
通过上面实现的示例步骤,可以清楚了解到gulp插件的工作原理。 但要做一个易用/可定制性高的插件,我们还要继续完善一下,例如:
1
|
<!–InjectResources:*.js$??inline–>
|
1
2
3
4
5
6
7
8
|
gulp.src(‘index.html’)
.pipe(
InjectResources(gulp.src(‘asserts/*.js’),{name:‘asserts’})
)
.pipe(
InjectResources(gulp.src(‘components/*.js’),{name:‘components’})
)
...
|