Gulp探究折腾之路(I)2

文/晚晴幽草(简书作者)
原文链接:http://www.jianshu.com/p/9768a4dc7cf7
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

前言: gulp是前端开发过程中对代码进行构建的工具,是自动化项目的构建利器;她不仅能对网站资源进行优化,而且在开发过程中很多重复的任务能够使用正确的工具自动完成;使用她,我们不仅可以很愉快的编写代码,而且大大提高我们的工作效率。相比于grunt的频繁 IO 操作,gulp的流操作,能更快地更便捷地完成构建工作。此处仅记录初步折腾中所遇点滴以及待解决的点。

文章首链-晚晴幽草轩

Gulp折腾之初探

折腾之战略上的藐视

回过头看Gulp的折腾历程,使用还是非常简易的。所以战略上一定要藐视"她";当然战术上要给予足够的重视。毕竟要依赖她以及其他各种插件and编辑器等实现前端工程化,组件化,模块化,便捷化是一个蛮复杂的过程。总之,折腾伊始需要自信就好,折腾过程带着耐心就好。譬如,想借助gulp压缩美化下js代码,写如下代码于gulpfile.js即可:

var gulp = require('gulp'),
uglify = require('gulp-uglify');
var gNeedDealJsFile = './js/*.js'; //javascrip代码存放路径
var outPut = "./build/"; //指定输出文件存放目录
gulp.task('scripts', function() {
return gulp.src(gNeedDealJsFile)
.pipe(uglify())
.pipe(rename({
suffix: '.min'
}))
.pipe(gulp.dest(outPut));
});

在gulpfile.js同级目录运行gulp scripts即可;PS:当然前提是您已经安装了nodejs,并且使用npm安装了代码中需要的插件gulpgulp-uglify与本地。

摒弃了gulp.run()

gulp的API很简单,常用的也就几个:watch, task, dest, src;目前已经摒弃了run方法。

//创建Default Task:注册缺省任务
gulp.task('default', function() {
gulp.run('jshint', 'scripts'); gulp.watch(workSpace, function() {
gulp.run('jshint', 'scripts');
})
});

gulp.run() has been deprecated. Use task dependencies or gulp.watch task triggering instead.

可以根据gulp.task(name[, deps], fn)特性写法替代之。deps:(Array)一个包含任务列表的数组,这些任务会在你当前任务运行之前完成。

注意: 你的任务是否在这些前置依赖的任务完成之前运行了?请一定要确保你所依赖的任务列表中的任务都使用了正确的异步执行方式:使用一个 callback,或者返回一个 promise 或 stream。

//创建Default Task:注册缺省任务
gulp.task('default', ['jshint' , 'scripts', 'watch']);

折腾gulp.src()

Gulp使用node-glob来从你指定的glob里面获取文件,这里列举下面的例子来阐述,方便大家理解:

js/app.js 精确匹配文件
js/.js 仅匹配js目录下的所有后缀为.js的文件
js/
/.js 匹配js目录及其子目录下所有后缀为.js的文件
!js/app.js 从匹配结果中排除js/app.js,这种方法在你想要匹配除了特殊文件之外的所有文件时非常管用
*.+(js|css) 匹配根目录下所有后缀为.js或者.css的文件
此外,Gulp也有很多其他的特征,但并不常用。如果你想了解更多的特征,请查看Minimatch文档。

js目录下包含了压缩和未压缩的JavaScript文件,现在我们想要创建一个任务来压缩还没有被压缩的文件,我们需要先匹配目录下所有的JavaScript文件,然后排除后缀为.min.js的文件:

gulp.src(['js/**/*.js', '!js/**/*.min.js'])

使用gulp-jshint()

好吧,不同帮派,不同侠士,对于代码的输出是不一致的。而这JS又没像Py那般天生带有美丽的基因。即便出了jshint这样的好利器,然,也得考虑下兼顾团队已有的Style。所以,这里配置得纠结下,微说Gulp之gulp-jshint

gulp.task('lint', function(){
return gulp.src(workSpace)
.pipe(jshint())
.pipe(jshint.reporter('YOUR_REPOTER_HERE'));
});

这reporter使用"default"的话,就会采用默认蛮严格的检查手段。为了能够规范而不失灵活的撸起JS,在使用JShint之时,过滤掉哪些不合时宜的写法(如下写法就可以不留情的规避之),就得斟酌下咯。列举些常见不推荐的写法,运行时提醒如下:

Missing semicolon. (W033) //丢失分号
['lack'] is better written in dot notation //推荐xx.lack写法,而不是xx['lack']
You might be leaking a variable (disX) here. (W120) //不推荐连等写法:比如posX = disX = 0;
'status' is defined but never used. (W098) //不推荐:定义之而不用之
Use '!==' to compare with ''. (W041) //不推荐 "!="或者"=="做变量是否相等判断。

只对发生更改的 js 文件进行语法检测

更现实的开发场景是, 项目代码已存在很久,代码中有大量的不符合 jshint 规范的代码。 而根据当前的 gulp 配置,每次发生修改,都会全量检测一遍所有的文件的语法问题,实际上已存在的问题我并不想在本次提交中修复(同时也是其他同事写的,例如不加分号问题,改动量太大)。

结果就是,一启动 gulp,哗哗的语法错误提示,根本找不到自己想看的文件检测结果。这时修改一个文件,又对所有文件做了一次全量检测,又是哗哗的满屏错误提示。而我只关心当前修改的文件检测结果。

gulp 项目首页推荐了一个gulp-cached插件,正好解决了这个问题。

npm install gulp-cached --save-dev

配置如下

var gulp = require('gulp'),
cache = require('gulp-cached'),
jshint = require('gulp-jshint'); gulp.task('jshint', function () {
gulp.src('./src/**/*.js')
.pipe(cache('jshint'))
.pipe(jshint())
.pipe(jshint.reporter('default'));
}); gulp.task('watch', function () {
gulp.watch('./src/**/*.js', ['jshint']);
}); gulp.task('default', ['jshint', 'watch']);

这样就能清晰的看到刚才发生修改文件的语法检测结果了,欧耶。

压缩-合并-重命名-输出~JS代码

压缩gulp-uglify,挺好;
Ps: 有一款gulp-minify(Desc: Minify JavaScript with UglifyJS2),默认压缩完毕之后会生成一个带-min的文件,略烦。

合并gulp-concat;指定合并生成名字即可:

.pipe(concat('all.js'))

重命名: gulp-rename;可以仅仅为名字指定后缀(不改变文件后缀)Like This:

.pipe(rename({suffix: '.min'}))

输出: gulp自带方法gulp.dest

//outPutPathName: String
.pipe(gulp.dest(outPutPathName));

压缩~CSS代码(合并-重命名-输出同上)

压缩gulp-minify-css;嗯,这个直接将原文件压缩了,默认没有改其名字。如果直接输出到该目录下,会覆盖原文件;若要输出同目录之下,可改改名字再输出,Like this:

.pipe(minifycss())
.pipe(rename({suffix: '.min'}))
.pipe(gulp.dest(outPut));

PS:这个插件(默认)也会帮着干掉注释,过滤掉空类(没有内容的描述)等等。

gulp-load-plugins模块

一般情况下,gulpfile.js中的模块需要一个个加载。

var gulp = require('gulp'),
jshint = require('gulp-jshint'),
uglify = require('gulp-uglify'),
concat = require('gulp-concat'); gulp.task('js', function () {
return gulp.src('js/*.js')
.pipe(jshint())
.pipe(jshint.reporter('default'))
.pipe(uglify())
.pipe(concat('app.js'))
.pipe(gulp.dest('build'));
});

上面代码中,除了gulp模块以外,还加载另外三个模块。

这种一一加载的写法,比较麻烦。使用gulp-load-plugins模块,可以加载package.json文件中所有的gulp模块。上面的代码用gulp-load-plugins模块改写,就是下面这样。

var gulp = require('gulp'),
gulpLoadPlugins = require('gulp-load-plugins'),
plugins = gulpLoadPlugins(); gulp.task('js', function () {
return gulp.src('js/*.js')
.pipe(plugins.jshint())
.pipe(plugins.jshint.reporter('default'))
.pipe(plugins.uglify())
.pipe(plugins.concat('app.js'))
.pipe(gulp.dest('build'));
});

上面代码假设package.json文件包含以下内容。

{
"devDependencies": {
"gulp-concat": "~2.2.0",
"gulp-uglify": "~0.2.1",
"gulp-jshint": "~1.5.1",
"gulp": "~3.5.6"
}
}

gulp插件的快速安装

谈及了基本应用之后,不得不谈下如何快速安装插件(毕竟,合作开发,彼此都能便捷使用,才是王道)。对于所使用的插件可以一个个安装,同时也可以批量安装Like This:

npm install gulp-concat gulp-uglify gulp-jshint [...] gulp-rename --save-dev

--save:将保存配置信息至package.json(package.json是nodejs项目配置文件);
-dev:保存至package.json的devDependencies节点,不指定-dev将保存至dependencies节点;

为什么要保存至package.json?因为node插件包相对来说非常庞大,所以不加入版本管理,将配置信息写入package.json并将其加入版本管理,其他开发者对应下载即可(命令提示符执行npm install,则会根据package.json下载所有需要的包)。

对于这块细节可以参看gulp详细入门教程;大概来讲就是可以借助npm init命令,按照其一步步提示,输入项目相关信息。完毕之后,加入需要依赖的插件以及版本信息即可(注意:这是一个普通json文件,一定得符合json格式;同时需要安装的插件版本也得是 <= 线上最新版本)。PS:前文涉及到的插件下载,即可如此配置予以完成:在生成的package.json中加入如下json(2015-10-26日情形):

 "devDependencies": {
"gulp-concat": "~2.6.0",
"gulp-uglify": "~1.4.2",
"gulp-jshint": "~1.11.2",
"gulp-rename": "~1.2.2",
"gulp-minify-css": "~1.2.1",
"gulp-cached": "~1.1.0",
"gulp": "~3.9.0"
}

如此,其他开发者只需更新gulpfile.js以及这个package.json,运行npm install即可下载gulp所依赖插件于本地,(@ο@) 哇~So Cool。

gulp多项目管理(npm link)

要谈下多项目管理了;比如这种场景:对于项目非常多而小,而且彼此间相互独立;伊始,采用在项目根目录之下gulpfile.js和package.json(毕竟每个单独小项目都来一发单独配置,岂不是要疯掉了),会将需要的插件down于根目录下;而在gulpfile.js中控制所要操作的单个小项目路径。这样每次切换项目,都要更改下gulpfile.js,即便将这个分离出来写一个config.js,也得手动去更改,略略有点蛋疼。幸好,gulp有npm link,哇哦,体贴如你,夫复何求?

对于npm link的介绍,可以参见@阮一峰npm模块管理器一文;我们可以将所需的插件,全局环境下载,在单独项目中link全局环境下插件。如此,不仅可以不用每次都down,而且,依赖的插件需要update,只需一地更新,所link之处都会受益。

具体做法,首先将需要的插件全局环境下Down下来:

npm install gulp gulp-concat gulp-rename gulp-uglify  gulp-minify-css gulp-jshint gulp-cached -g

接下来,(如果我们要使用gulp-concat这个模块)我们进入单独小项目(eg: gulpTest),使用npm link gulp-concat命令会去【mac/linux】/usr/local/lib/node_modules/, 【window】D:\UserProfiles\username\AppData\Roaming\npm\node_modules,目录下查找名叫gulp-concat的模块,找到这个模块后把该的目录链接到 ~/work/**/gulpTest/node_modules/gulp-concat 这个目录上来。如此,在此项目小项目中的gulpfile.js中也可以加以使用了。当然,npm link也支持多个参数:

npm link gulp gulp-concat gulp-rename gulp-uglify  gulp-minify-css gulp-jshint gulp-cached

PS:这样使用时,需要注意忽略掉node_modules目录下的js/css代码,gulp.src参数数组中可以加入'!node_modules/**/*.+(js|css)'以过滤之。


注:即便使用npm link感觉也不是一个特别简洁的方案。并且在使用的时候还遇到了些许问题: 有提问在@segmentFaultgulp如何管理多项目? 以及@V2EX gulp如何管理多项目;热心码友也提出了一些可行的建议,比如:配置多个task,或者干脆使用npm run(毕竟项目太小),或者采用 fbi Node.js库nodejs-fbi,或者采用nodejs 中的 NODE_PATH@nodejs 中的 NODE_PATH等等;此处有待进一步学习&折腾&择决; (如有简洁方案,欢求指点,拜谢)。


gulp-util幸之助

幸亏有gulp-util之协助:在折腾的过程中,难免不会出现奇奇怪怪的问题;然而gulp本身的报错提示机制真心让新手的我蛋蛋的忧伤:比如在折腾的过程中压缩JS代码就出现Uglify throws Parse error;可是提示却...幸好@*Uglify throws Parse error一个问答中,大牛给予了明灯般的指导:引入gulp-util;原话如下:

uglify will parse the script content before minifying it. I suspect that one of the browserify source maps are being included in the stream down to uglify. Anyway to find the problem you can use gulp-util's log method to handle uglify's exceptions. Example:
```js
...
var gulpUtil = require('gulp-util');

gulp.task('scripts', function() {
...
.pipe(sourcemaps.init({loadMaps: true}))
.pipe(uglify().on('error', gulpUtil.log)) // notice the error event here
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('./web/js'));
});

###**实时刷新页面**
####**gulp-livereload模块**
`gulp-livereload`模块用于自动刷新浏览器,反映出源码的最新变化。它除了模块以外,还需要在浏览器中安装插件,用来配合源码变化。 [LiveReload](http://livereload.com/)结合了浏览器扩展(包括Chrome extension),在发现文件被修改时会实时更新网页。它可以和gulp-watch插件或者前面描述的gulp-watch()函数一起使用。下面有一个`gulp-livereload`仓库中的README文件提到的例子:
```js
var gulp = require('gulp'),
less = require('gulp-less'),
livereload = require('gulp-livereload'),
watch = require('gulp-watch'); gulp.task('less', function() {
gulp.src('less/*.less')
.pipe(watch())
.pipe(less())
.pipe(gulp.dest('css'))
.pipe(livereload());
});

这会监听到所有与less/*.less相匹配的文件的变化。一旦监测到变化,就会生成css并保存,然后重新加载网页.

BrowserSync

安装 BrowserSync
您可以选择从Node.js的包管理(NPM)库中 安装BrowserSync。打开一个终端窗口,运行以下命令:

npm install -g browser-sync

您告诉包管理器下载BrowserSync文件,并在全局下安装它们,您可以在所有项目(任何目录)中使用。

当然您也可以结合gulpjs或gruntjs构建工具来使用,在您需要构建的项目里运行下面的命令:

npm install --save-dev browser-sync

启动 BrowserSync
静态网站
如果您想要监听.css文件, 您需要使用服务器模式。 BrowserSync 将启动一个小型服务器,并提供一个URL来查看您的网站。

// --files 路径是相对于运行该命令的项目(目录)
browser-sync start --server --files "css/*.css"

如果您需要监听多个类型的文件,您只需要用逗号隔开。例如我们再加入一个.html文件

// --files 路径是相对于运行该命令的项目(目录)
browser-sync start --server --files "css/*.css, *.html"
// 如果你的文件层级比较深,您可以考虑使用 "**"(表示任意目录)匹配,任意目录下任意.css 或 .html文件。
browser-sync start --server --files "**/*.css, **/*.html"

注:在该文件下运行命令,默认需要启动网站文件:index.html。

动态网站
如果您已经有其他本地服务器环境PHP或类似的,您需要使用代理模式。 BrowserSync将通过代理URL(localhost:3000)来查看您的网站。

// 主机名可以是ip或域名
browser-sync start --proxy "主机名" "css/*.css"

在本地创建了一个PHP服务器环境,并通过绑定Browsersync.cn来访问本地服务器,使用以下命令方式,Browsersync将提供一个新的地址localhost:3000来访问Browsersync.cn,并监听其css目录下的所有css文件。

browser-sync start --proxy "Browsersync.cn" "css/*.css"

参考博文:BrowserSync,迅捷从免F5开始


:使用的时候纯路径比如"Browsersync.cn"尚好着,然而地址后面带一堆参数时候,就会遇到些问题;暂时还未搞明白以解决之,特注之,待弄懂!!


前端组件html模板化(gulp-tlp2mod)

参考博文:component-html-engine-demo有叙述有Demo,帮了大忙。

为了前端代码的模块化,必要将JS逻辑代码于布局模板代码分离开来(当然还有CSS以及静态资源也都当分离);初来乍到,遇到问题了,如何把tpl转变成js呢?,OK既然用了构建工具,那么自然也有对应的插件吧,果然 gulp-tpl2mod~模板文件转js插件;再借助require.js, 先使用gulp-tpl2mod把模板转换成js字符串,然后包装成一个模块,再main.js中引用这个模块就行了,大功告成;如此,项目工程化又前进一步。

折腾之入门所参考文章资料

gulp API 文档
Gulp入门教程
Gulp开发教程(翻译)
Gulp:任务自动管理工具
前端构建工具gulp入门教程
gulp plugins 插件介绍
gulp详细入门教程

上一篇:注册表 ReadBool类型和 ReadInteger 的关系


下一篇:Java学习笔记29(集合框架三:泛型)