写在前面
5分钟是昨天的梗,不要打我,哈哈
一.为什么是gulp?
说起构建工具(打包工具),就想起忧伤的grunt,还有去年5月的那篇笔记:Grunt教程
可选的构建工具非常多:
疯狂编写配置文件的grunt
pipe来pipe去的gulp
logo很棒的webpack
不喜欢拐弯抹角的npm scripts
自称跨时代的rollup
…
但为什么是gulp?
首先,grunt过气了,去年5月grunt还勉强撑得住(插件数量比gulp多很多,所以才有了那篇笔记),现在已经没什么优势了,插件数量不再是gulp的短板
如npm scripts所说,pipe不是gulp原创,操作系统本来就支持,所以npm scripts的理念是直面shell命令,去掉不需要的抽象层(grunt, gulp…),比如gulp-jshint,直接执行jshint提供的cli不就完了,要gulp干啥?
至于webpack/rollup,应该和grunt/gulp差不多(都属于npm scripts眼中不必要的抽象层),没有必要天天换构建工具。所以,选择gulp仅仅是因为成本(团队本来就在用)
二.npm scripts
从设计理念上来看,npm scripts似乎更超前些,几点原因:
去掉中间抽象层,直接使用package提供的cli,不用等插件更新就能用上package新特性
结构更简单,降低了调试复杂度(package的问题?
配置的问题?插件版本/插件本身的问题?项目代码的问题?)可以执行操作系统所能执行的任何脚本(python、bash脚本等等,gulp + shelljs勉强跟上,grunt掉队了)
没有插件,但“插件”数量却最多(等人写插件干啥?能搞到的所有能通过命令行执行的脚本全都是“插件”)
当然,也存在一些缺点:
package.json无法添加注释。写在readme里?还是有点难受
需要对命令行多少了解一点。虽然npm封装了1000多个基础命令,但从“删除文件夹”到
rm -rf
到rimraf的过程还是太曲折,了解常用命令是必须的不支持变量。支持环境变量和任何脚本,足够了(
"build": "node bin/build.js"
)
示例:
// package.json
{
...
"scripts": {
// basic
"watch-test": "mocha --watch --reporter spec test",
"test": "mocha \"src/**/*.test.js\" --require test/setup.js --compilers js:babel-register",
// pipe
"build-js": "browserify -t reactify app/js/main.js | uglifyjs -mc > static/bundle.js",
// and(&&)、or(||)
"build": "npm run build-js && npm run build-less",
// concurrent
"watch": "npm run watch-js & npm run watch-less & npm run watch-server"
}
}
更复杂的命令可以写个bash shell脚本,或者node + shelljs,没有多余的东西
三.捏一个node命令行工具
想自定义一个全局命令,在任何目录都能执行?其实很容易的
编写nodejs代码,添上Shebang(在文件首行添上
#!/usr/bin/env node
,表示用node来执行该文件),保存xxx.js
npm init
或者直接手写个package.json
修改package.json,添加
"bin": {"hoho": "xxx.js"}
npm link
把本地package link到全局(类似于windows环境变量path)
关键步骤是这样,但存在几个问题:
命令如何跨平台?(windows、unix)
如何获取命令行参数?
如何使用
gulp
?
shelljs
模块提供了跨平台命令;yargs
模块能够获取命令行参数;使用gulp
需要npm install gulp --save-dev
本地安装gulp并添加依赖;此外,想要在命令行输出五颜六色的东西,还可以用colors
模块
注意:本地安装gulp
是必须的,因为gulp
的设计理念是不希望所有package依赖一个全局gulp
,所以要require('gulp')
就必须本地安装一个,多很多文件感觉不太舒服,但更灵活
命令行工具只是第0步,重要但不关键,具体步骤可以查看这个:Nodejs 制作命令行工具
四.用gulp优化工作流
这里的“工作流”包括但不限于基本的打包步骤(编译、代码检查、资源压缩等等),比如开发过程中需要的工具,以及发布所需的工具,都属于工作流。优化工作流的过程就是简化这些步骤,把枯燥繁琐的事情交给工具来办,我们就能解脱出来做更多的事情,从而提高效率
为了让工具容易扩展和维护,需要考虑结构,比如:
.
|____bin
| |____wf.js # 命令行入口(接收命令行指令,分发给子命令)
|____config.json # 配置参数(比如需要同步的路径glob)
|____gulpfile.js # 定义gulp task
|____index.js # package入口(package.main)
|____lib # 子命令
| |____wf-ftp.js # 自测、提测
| |____wf-sync.js # 多浏览器同步
| |____wf-test.js
|____node_modules
| |____...
|____package.json
|____README.md
其中wf-sync
是对Browser Sync的包装,支持多浏览器同步(刷新同步、滚动同步、样式变化同步等等),开发过程中非常省心
wf-ftp
提供命令行ftp支持,可配置待上传路径glob及远程路径,远程环境相对稳定,用于在开发过程中与PM、UI确认效果,开发完成后wf ftp -r
打包提测
上面结构中关键是index
与wf
,前者负责初始化,后者类似于路由
顶层index.js
负责初始化全局设置,并约定子命令,提供子命令执行方法
var argv = require('yargs').argv;
var colors = require("colors");
var path = require("path");
var config = require("./config.json");
// 命令行输出五颜六色的东西
colors.setTheme({
warn: 'yellow',
debug: 'blue',
...
});
var wf = {
log: function(str) {
console.log(colors.log(str));
},
...
};
Object.defineProperty(global, 'wf', {
enumerable: true,
writable: false,
Configurable: false,
value: wf
});
wf.config = config;
// 执行子命令(约定子命令的调用方式)
wf.run = function() {
//当前命令
var cmd = process.argv[2];
//执行路径
var cwd = argv.path || argv.p || process.cwd();
wf.cmd = cmd;
wf.cwd = cwd;
var cmdFile = require("./lib/wf-" + cmd);
// 要求lib中的每个模块都必须export一个run()方法作为入口
cmdFile.run && cmdFile.run();
};
module.exports = wf;
中层bin/wf.js
负责路由控制,如下:
#!/usr/bin/env node
var wf = require("../index.js");
var _ = require("underscore");
var yargs = require('yargs');
var shell = require("shelljs");
var cmd = process.argv[2];
var cmdList = {
"test": "test",
"ftp": "ftp",
"sync": "sync"
};
if (cmdList[cmd]) {
var cmdMod = require("../lib/wf-" + cmd);
if (cmdMod && cmdMod.name && cmdMod.desc) {
// 设置子命令
var argv = yargs.command(cmdMod.name, cmdMod.desc, function(yargs) {
cmdMod.usage && yargs.usage(cmdMod.usage);
cmdMod.param && _.each(cmdMod.param, function(v, i) {
yargs.option(v.short, {
alias: v.full,
describe: v.describe,
// 是否必须
demand: v.demand
});
});
yargs.help("h").alias("h", "help");
}).argv;
if (argv.h || argv.help) {
yargs.help('h').argv;
return;
}
// 执行子命令
wf.run();
}
} else {
if (yargs.argv.h || yargs.argv.help) {
yargs.usage('Usage: wf [options]')
.example('wf test', '测试安装是否成功')
.example('wf ftp', 'ftp上传')
.example('wf sync', '多浏览器同步')
.help('h')
.alias('h', 'help').argv;
} else {
shell.exec("wf -h");
}
}
下层lib/wf-xxx.js
负责执行gulpfile.js
中定义的task,例如wf-sync.js
:
/**
* 多浏览器同步
*/
var shell = require('shelljs');
var argv = require('yargs').argv;
module.exports = {
run: function() {
var cwd = wf.cwd;
var cmd = wf.cmd;
shell.cd(__dirname + '/../');
shell.exec('gulp sync --cwd=' + cwd);
},
name: 'sync',
desc: "多浏览器同步",
usage: "Usage: wf sync"
};
底层gulp task负责干活,例如gulpfile.js
中的sync
:
var browserSync = require('browser-sync').create(),
/**
* BrowserSync task.
*
*
* Usage: `gulp sync`
*/
gulp.task('sync', function() {
// browser sync
browserSync.init({
port: 3333,
server: cwd
});
gulp.watch(cwd + '**/*.*').on('change', browserSync.reload);
});
再下面我们就不关心了,回头看看各模块的作用:
shelljs 负责执行命令,类似于`require('child_process').spawn(CMD)`,但提供了跨平台包装
yargs 负责接收并传递命令行参数,命令行->node->gulp task。此外还负责生成文档(命令行帮助)
colors 锦上添花的东西,不重要
gulp及n个插件 负责干活
package.json
声明依赖、入口与可执行文件位置,如下:
{
"name": "wf",
"version": "1.0.0",
"description": "for better workflow",
"main": "index.js",
"dependencies": {},
"devDependencies": {
"browser-sync": "^2.14.0",
"colors": "^1.1.2",
"gulp": "^3.9.1",
"gulp-rename": "^1.2.2",
"gulp-util": "^3.0.7",
"shelljs": "^0.7.3",
"underscore": "^1.8.3",
"vinyl-ftp": "^0.5.0",
"yargs": "^4.8.1"
},
"bin": {
"wf": "bin/wf.js"
}
}
npm link
到全局之后,就可以愉快地:
wf go # 创建项目目录
wf sync # 开启Browser Sync,进入疯狂开发模式
wf ftp # 上传到ftp个人目录,与PM、UI确认效果
wf ftp -a # 监听文件变化,自动ftp,效果频繁变更时开启
wf ftp -r # 开发完毕,提测,打包并上传到ftp公共目录
五.总结
优化工作流,节省时间提高效率
用grunt?gulp?webpack?fis?rollup?npm scripts?都不重要,用顺手的工具简化日常工作才是目的
投资2小时回报很多年的事情,越早做越好,而且,node让一切变得熟悉而简单了,纯FEer也能分分钟搞出一个好用的命令行工具