用gulp花5分钟优化工作流

写在前面

5分钟是昨天的梗,不要打我,哈哈

一.为什么是gulp?

说起构建工具(打包工具),就想起忧伤的grunt,还有去年5月的那篇笔记:Grunt教程

可选的构建工具非常多:

但为什么是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 -rfrimraf的过程还是太曲折,了解常用命令是必须的

  • 不支持变量。支持环境变量和任何脚本,足够了("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命令行工具

想自定义一个全局命令,在任何目录都能执行?其实很容易的

  1. 编写nodejs代码,添上Shebang(在文件首行添上#!/usr/bin/env node,表示用node来执行该文件),保存xxx.js

  2. npm init或者直接手写个package.json

  3. 修改package.json,添加"bin": {"hoho": "xxx.js"}

  4. 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打包提测

上面结构中关键是indexwf,前者负责初始化,后者类似于路由

顶层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也能分分钟搞出一个好用的命令行工具

参考资料

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code