转载

封装你的Gulp代码

近年来, 前端工程化 深入人心,而工程化中必不可少的环节就是构建。所谓 构建 就是基于既定的流程对项目中的文件进行处理,从而得到最终用于发布的文件。而 Gulp 正是目前最流行的前端构建工具之一。

Gulp的使用

以下是使用Gulp进行构建的例子,也是本文的示例项目。文件结构为:

/Users/me/project/project-a

- src/

- dist/

- package.json

- gulpfile.js

其中「src」为源代码目录,「dist」为发布代码目录。「gulpfile.js」的代码为:

var gulp = require('gulp'),  gulpCleanCSS = require('gulp-clean-css'),  gulpUglify = require('gulp-uglify'),  gulpMD5 = require('gulp-md5-plus');  // 把.html文件直接copy到发布目录 gulp.task('copy-html', function() {  return gulp.src('./src/*.html')   .pipe( gulp.dest('./dist') ); });  gulp.task('compress-css', ['copy-html'], function() {  return gulp.src('./src/*.css')   // 压缩CSS代码   .pipe( gulpCleanCSS() )    // 文件名增加MD5,并替换.html文件中的引用地址   .pipe( gulpMD5(10, './dist/*.html') )    // 构建到发布目录   .pipe( gulp.dest('./dist') ); });  gulp.task('compress-js', ['copy-html'], function() {  return gulp.src('./src/*.js')   // 压缩JS代码   .pipe( gulpUglify() )   // 文件名增加MD5,并替换.html文件中的引用地址   .pipe( gulpMD5(10, './dist/*.html') )   // 构建到发布目录   .pipe( gulp.dest('./dist') ); });  gulp.task('default', ['compress-css', 'compress-js']);

要正常执行这段代码,还要进行部署。第一步,全局安装Gulp:

npm install gulp -g

第二步,初始化「package.json」的内容(放置一个空对象即可):

{ }

最后一步,在项目目录下安装Gulp以及用到的插件:

cd /Users/me/project/project-a npm install gulp --save npm install gulp-clean-css --save npm install gulp-uglify --save npm install gulp-md5-plus —save

部署完成后,项目目录下会多了一个「node_modules」文件夹,「package.json」也会记录了所需的依赖。此时在命令行中执行「gulp」就可以构建了。

然而,如果有多个项目,要怎么办呢?

/Users/me/project

- project-a/

- project-b/

- project-c/

即使它们的构建流程是一样的,也要在每个项目下 重复部署 构建环境。一旦构建流程的代码有所改动,又得在每个项目中重新部署。为了解决这个问题,我们把「gulpfile.js」、「node_modules」以及「package.json」提取出来:

/Users/me/project

- project-a/

- project-b/

- project-c/

- gulpfile.js

- node_modules/

- package.json

这样,其他项目在构建的时候也可以用「gulpfile.js」。例如要构建「project-b」时可以执行这样的命令:

cd /Users/me/project gulp --project ./project-b

也就是增加「--project」参数指定要构建的项目。当然,「gulpfile.js」也要做一些改动去拿到这个参数的值。首先,安装「minimist」包:

cd /Users/me/project npm install minimist --save

在「gulpfile.js」中通过这个包解析命令行参数:

var argv = require('minimist')( process.argv.slice(2) ); argv.project; // './project-b'

然后修改「gulpfile.js」中所有匹配路径的代码即可:

var path = require('path');  // gulp.src('./src/*.html') gulp.src( path.join(argv.project, './src/*.html') )  // gulp.dest('./dist') gulp.dest( path.join(argv.project, './dist') )

然而,因为Gulp默认情况下是调用当前目录的「gulpfile.js」,所以,如果当前目录不是该文件所在的目录,就要增加「--gulpfile」参数去指定它的路径:

cd /Users/me/project gulp --project ./project-a  cd /Users/me/project-a gulp --gulpfile ../gulpfile.js --project ./project-a  cd /Users/me gulp --gulpfile ./project/gulpfile.js --project ./project-a

这显然还不够方便。下面我们进一步把构建代码封装成命令行工具「fe-build」,使其可以这样调用:

cd /Users/me fe-build /Users/me/project/project-a  cd /Users/me/project/project-a fe-build .

封装为npm包

Gulp的调用入口

要基于Gulp封装另一个npm包,得先理解Gulp的调用方式。查看Gulp的「package.json」可以发现这样一段:

"bin": {     "gulp": "./bin/gulp.js" }

也就是说,在命令行中执行「gulp」时,实际上运行的是Gulp包内bin目录下的「gulp.js」。

下面做个小实验,在「project」目录下新建「run.js」,其内容为:

require('gulp/bin/gulp');

然后在命令行运行:

cd /Users/me/project node run.js --project ./project-a

构建结果跟直接运行「gulp」是一致的。这意味着我们可以通过代码调用Gulp,而且在调用之前还可以做一些「手脚」。

封装你的Gulp代码

准备

建立另一个项目去开发这个工具:

/Users/me/tool/fe-build

- node_modules/

- bin/fe-build.js

- gulpfile.js

- package.json

其中「gulpfile.js」、「node_modules」和「package.json」都可以从「project」目录剪切过来。开发的时候,要达到的效果是,通过以下调试命令可以对project-a进行构建:

cd /Users/me/tool/fe-build node bin/fe-build.js /Users/me/project/project-a

编码

第一步,在「fe-build.js」中获取路径参数。该参数直接跟在运行的js文件后面,获取更方便了(不需要再借助「minimist」):

var pjPath = process.argv[2];  // 可能是相对路径,要resolve var path = require('path'); pjPath = path.resolve(pjPath);

「pjPath」肯定要在「gulpfile.js」中使用,但是「gulpfile.js」无法访问「fe-build.js」中的局部变量。所以在调用Gulp之前,还要把「pjPath」记录到全局变量:

process.env.PJ_PATH = pjPath; require('gulp/bin/gulp');

第二步,修改「gulpfile.js」中所有匹配路径的代码:

var pjPath = process.env.PJ_PATH; gulp.src( path.join(pjPath, 'src', '*.html') ) gulp.dest( path.join(pjPath, 'dist') )

尝试执行调试命令,但出现错误:

Task '/Users/me/project/project-a' is not in your gulpfile

要理解这个错误的起因,首先要知道,无论是「fe-build.js」还是通过它调用的Gulp,都是从全局变量「 process.argv 」获取命令行参数的。而查找Gulp的官方文档可以发现,「gulp」有这样一种执行方式:

gulp <task> <othertask>

所以Gulp就把路径参数识别为任务id了。所幸的是,「process.argv」这个数组是可以修改的。既然如此,只要在调用Gulp之前,把项目路径参数从数组里面移掉就可以了。

var path = require('path');  var pjPath = path.resolve(process.argv[2]); process.env.PJ_PATH = pjPath; process.argv.splice(2, 1);  require('gulp/bin/gulp');

现在再执行调试命令,已经可以正常运行了。但是稍微改一下:

cd /Users/me node /Users/me/tool/fe-build/bin/fe-build.js /Users/me/project/project-a

还是出现错误:

No gulpfile found

导致这个问题的原因是,「gulpfile.js」不在当前目录(「/Users/me」)下。所以「--gulpfile」参数还是必须的,不过并不是加到命令上,而是操作「process.argv」:

process.argv.push(     '--gulpfile',     path.resolve(__dirname, '../gulpfile.js') );

其中「 __dirname 」是全局变量,表示 当前脚本文件所在的目录 。因为「fe-build.js」跟「gulpfile.js」的路径关系是确定的,所以通过前者的路径就可以很方便地计算出后者的路径。

「fe-build.js」的全部代码为:

var path = require('path');  // 可能是相对路径,要resolve var pjPath = path.resolve(process.argv[2]); // 移除参数,以免gulp把它当成任务id process.argv.splice(2, 1); // 记录起来,以便在gulpfile.js中使用 process.env.PJ_PATH = pjPath;  // 增加--gulpfile参数 process.argv.push(  '--gulpfile',  // __dirname是全局变量,表示当前文件所在目录  path.resolve(__dirname, '../gulpfile.js') );  require('gulp/bin/gulp');

「gulpfile.js」的全部代码为:

var path = require('path'),  gulp = require('gulp'),  gulpCleanCSS = require('gulp-clean-css'),  gulpUglify = require('gulp-uglify'),  gulpMD5 = require('gulp-md5-plus');  // 获取具体项目路径 var pjPath = process.env.PJ_PATH;  gulp.task('copy-html', function() {  return gulp.src( path.join(pjPath, 'src', '*.html') )   .pipe( gulp.dest( path.join(pjPath, 'dist') ) ); });  gulp.task('compress-css', ['copy-html'], function() {  return gulp.src( path.join(pjPath, 'src', '*.css') )   .pipe( gulpCleanCSS() )   .pipe( gulpMD5( 10, path.join(pjPath, 'dist', '*.html') ) )   .pipe( gulp.dest( path.join(pjPath, 'dist') ) ); });  gulp.task('compress-js', ['copy-html'], function() {  return gulp.src( path.join(pjPath, 'src', '*.js') )   .pipe( gulpUglify() )   .pipe( gulpMD5( 10, path.join(pjPath, 'dist', '*.html') ) )   .pipe( gulp.dest( path.join(pjPath, 'dist') ) ); });  gulp.task('default', ['compress-css', 'compress-js']);

安装

「npm」命令对全局安装的包是有要求的,必须在「package.json」中指定name、version和bin。修改「package.json」增加这三项(「minimist」的依赖可以删掉了):

{   "name": "fe-build",   "version": "1.0.0",   "bin": {     "fe-build": "bin/fe-build.js"   },   "dependencies": {     "gulp": "^3.9.1",     "gulp-clean-css": "^2.0.4",     "gulp-md5-plus": "^0.2.0",     "gulp-uglify": "^1.5.3"   } }

然后就可以进行安装:

cd /Users/me/tool/fe-build npm install -g

构建目标项目:

fe-build /Users/me/project/project-a

意外情况出现了:

syntax error

相信很多新手用Node.js开发命令行工具时也会遇到相同的问题。原因在于,操作系统不知道用什么环境去运行「fe-build」(开发的时候指定了「node fe-build.js」,所以不会有问题)。解决方法也很简单,在「fe-build.js」的开头声明运行环境即可(留意第一行):

#!/usr/bin/env node  var path = require('path');  // 可能是相对路径,要resolve var pjPath = path.resolve(process.argv[2]); // 移除参数,以免gulp把它当成任务id process.argv.splice(2, 1); // 记录起来,以便在gulpfile.js中使用 process.env.PJ_PATH = pjPath;  // 增加--gulpfile参数 process.argv.push(  '--gulpfile',  // __dirname是全局变量,表示当前文件所在目录  path.resolve(__dirname, '../gulpfile.js') );  require('gulp/bin/gulp');

改好之后重新安装一次,就可以正常运行了。

就这样,一个专属的构建工具就完成了。而且,不知道大家有没有发现这样做的另一个好处:「gulpfile.js」以及部署产生的「node_modules」、「package.json」不用再放在具体项目的目录下,可以减少开发时的干扰。

原文  http://www.heeroluo.net/article/detail/131/make-your-gulp-code-as-a-package
正文到此结束
Loading...