近年来,前端工程化深入人心,而工程化中必不可少的环节就是构建。所谓构建就是基于既定的流程对项目中的文件进行处理,从而得到最终用于发布的文件。而Gulp正是目前最流行的前端构建工具之一。
Gulp的使用
以下是使用Gulp进行构建的例子,也是本文的示例项目。文件结构为:
/Users/me/project/project-a
- src/
- dist/
- package.json
- gulpfile.js
其中「src」为源代码目录,「dist」为发布代码目录。
要正常执行Gulp的代码,还要进行部署。第一步,全局安装Gulp:
npm install gulp -g
第二步就是进入到项目目录里安装Gulp以及用到的插件
部署完成后,项目目录下会多了一个「node_modules」文件夹,「package.json」也会记录了所需的依赖。此时在命令行中执行「gulp」就可以构建了。
然而,如果有多个项目,要怎么办呢?
只能在每个项目下重复部署构建环境。一旦构建流程的代码有所改动,又得在每个项目中重新部署。
封装为npm包
我们的想法是能够将Gulp抽取出来,封装到一个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,而且在调用之前还可以做一些「手脚」。
准备
建立另一个项目去开发这个工具:
/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
所以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」的全部代码为:
#!/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');
安装
「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
就这样,一个专属的构建工具就完成了。而且,不知道大家有没有发现这样做的另一个好处:「gulpfile.js」以及部署产生的「node_modules」、「package.json」不用再放在具体项目的目录下,可以减少开发时的干扰。
参考文章: http://www.heeroluo.net/article/detail/131/make-your-gulp-code-as-a-package
————
前端·小h
纸上得来终觉浅,绝知此事要躬行