Skip to main content

Optimize Workflow with gulp in 5 Minutes

Free2016-08-20#Tool#gulp教程#BrowserSync#多浏览器同步#前端工作流

Optimize workflow, save time and improve efficiency

Preface

5 minutes is yesterday's meme, don't hit me, haha

I. Why gulp?

Speaking of build tools (bundling tools), think of the sad grunt, and that note from May last year: [Grunt Tutorial](/articles/grunt 教程/)

There are many build tools to choose from:

  • grunt that crazily writes configuration files

  • gulp that pipes here and there

  • webpack with a great logo

  • npm scripts that doesn't like beating around the bush

  • rollup that claims to be epoch-making

  • ...

But why gulp?

First, grunt is outdated, last May grunt could barely hold on (plugin count was much more than gulp, that's why there was that note), now there's no advantage anymore, plugin count is no longer gulp's shortcoming

As npm scripts says, pipe is not gulp's original creation, operating system already supports it, so npm scripts' philosophy is face shell commands directly, remove unnecessary abstraction layers (grunt, gulp...), for example gulp-jshint, just execute jshint's provided cli directly, what do you need gulp for?

As for webpack/rollup, should be similar to grunt/gulp (all belong to unnecessary abstraction layers in npm scripts' eyes), no need to change build tools every day. So, choosing gulp is merely because of cost (team was already using it)

II. npm scripts

From a design philosophy perspective, npm scripts seems more advanced, several reasons:

  • Remove intermediate abstraction layer, directly use cli provided by package, can use package's new features without waiting for plugin updates

  • Simpler structure, reduces debugging complexity (package problem? configuration problem?plugin version/plugin itself problem?project code problem?)

  • Can execute any script that operating system can execute (python, bash scripts, etc., gulp + shelljs barely keeps up, grunt fell behind)

  • No plugins, but "plugin" count is the most (why wait for others to write plugins? All scripts that can be executed through command line are "plugins")

Of course, there are also some disadvantages:

  • package.json cannot add comments. Write in readme? Still a bit uncomfortable

  • Need to know a little about command line. Although npm encapsulates over 1000 basic commands, the process from "delete folder" to rm -rf to rimraf is still too tortuous, understanding common commands is necessary

  • Doesn't support variables. Supports environment variables and any scripts, enough ("build": "node bin/build.js")

Example:

// 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"
    }
}

More complex commands can write a bash shell script, or node + shelljs, nothing extra

III. Create a node Command Line Tool

Want to customize a global command, can execute in any directory? Actually very easy

  1. Write nodejs code, add Shebang (add #!/usr/bin/env node on first line of file, indicates using node to execute this file), save as xxx.js

  2. npm init or directly handwrite a package.json

  3. Modify package.json, add "bin": {"hoho": "xxx.js"}

  4. npm link link local package to global (similar to windows environment variable path)

Key steps are like this, but exist several problems:

  • How to make command cross-platform? (windows, unix)

  • How to get command line arguments?

  • How to use gulp?

shelljs module provides cross-platform commands; yargs module can get command line arguments; using gulp needs npm install gulp --save-dev locally install gulp and add dependency; additionally, want to output colorful things in command line, can also use colors module

Note: Locally installing gulp is necessary, because gulp's design philosophy is not wanting all packages to depend on one global gulp, so to require('gulp') must locally install one, feels a bit uncomfortable with many more files, but more flexible

Command line tool is just step 0, important but not critical, specific steps can see this: Nodejs Making Command Line Tools

IV. Optimize Workflow with gulp

"Workflow" here includes but not limited to basic bundling steps (compilation, code checking, resource minification, etc.), for example tools needed during development, and tools needed for release, all belong to workflow. The process of optimizing workflow is simplifying these steps, handing boring and tedious things to tools to handle, we can free ourselves to do more things, thereby improving efficiency

To make tools easy to extend and maintain, need to consider structure, for example:

.
|____bin
| |____wf.js        # Command line entry (receives command line instructions, distributes to sub-commands)
|____config.json    # Configuration parameters (such as paths glob that need to be synced)
|____gulpfile.js    # Define gulp task
|____index.js       # Package entry (package.main)
|____lib            # Sub-commands
| |____wf-ftp.js    # Self-test, submit for testing
| |____wf-sync.js   # Multi-browser sync
| |____wf-test.js
|____node_modules
| |____...
|____package.json
|____README.md

Among them wf-sync is a wrapper for Browser Sync, supports multi-browser sync (refresh sync, scroll sync, style change sync, etc.), very worry-free during development

wf-ftp provides command line ftp support, can configure paths glob to upload and remote paths, remote environment is relatively stable, used to confirm effects with PM, UI during development, after development completes wf ftp -r bundle and submit for testing

Key in above structure are index and wf, former responsible for initialization, latter similar to routing

Top-level index.js responsible for initializing global settings, and agreeing on sub-commands, providing sub-command execution methods

var argv = require('yargs').argv;
var colors = require("colors");
var path = require("path");
var config = require("./config.json");

// Command line output colorful things
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;

// Execute sub-commands (agree on sub-command calling method)
wf.run = function() {
    //Current command
    var cmd = process.argv[2];
    //Execution path
    var cwd = argv.path || argv.p || process.cwd();
    wf.cmd = cmd;
    wf.cwd = cwd;
    var cmdFile = require("./lib/wf-" + cmd);
// Require each module in lib must export a run() method as entry
    cmdFile.run && cmdFile.run();
};

module.exports = wf;

Middle-level bin/wf.js responsible for routing control, as follows:

#!/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) {
        // Set sub-commands
        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,
                    // Whether required
                    demand: v.demand
                });
            });
            yargs.help("h").alias("h", "help");
        }).argv;
        if (argv.h || argv.help) {
            yargs.help('h').argv;
            return;
        }
        
        // Execute sub-commands
        wf.run();
    }
} else {
    if (yargs.argv.h || yargs.argv.help) {
        yargs.usage('Usage: wf [options]')
            .example('wf test', 'Test if installation is successful')
            .example('wf ftp', 'ftp upload')
            .example('wf sync', 'Multi-browser sync')
            .help('h')
            .alias('h', 'help').argv;
    } else {
        shell.exec("wf -h");
    }
}

Lower-level lib/wf-xxx.js responsible for executing tasks defined in gulpfile.js, for example wf-sync.js:

/**
 * Multi-browser sync
 */
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: "Multi-browser sync",
    usage: "Usage: wf sync"
};

Bottom-level gulp task responsible for doing work, for example sync in gulpfile.js:

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);
});

Below that we don't care anymore, look back at each module's role:

shelljs Responsible for executing commands, similar to `require('child_process').spawn(CMD)`, but provides cross-platform wrapper
yargs   Responsible for receiving and passing command line arguments, command line->node->gulp task. Additionally responsible for generating documentation (command line help)

colors  Icing on the cake, not important

gulp and n plugins   Responsible for doing work

package.json declares dependencies, entry and executable file location, as follows:

{
  "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"
  }
}

After npm link to global, can happily:

wf go       # Create project directory

wf sync     # Start Browser Sync, enter crazy development mode

wf ftp      # Upload to ftp personal directory, confirm effects with PM, UI
wf ftp -a   # Listen for file changes, automatically ftp, enable when effects change frequently

wf ftp -r   # Development complete, submit for testing, bundle and upload to ftp public directory

V. Summary

Optimize workflow, save time and improve efficiency

Use grunt? gulp? webpack? fis? rollup? npm scripts? None important, using handy tools to simplify daily work is the purpose

Invest 2 hours return many years thing, the earlier do the better, and, node makes everything familiar and simple, pure FEer can also quickly create a useful command line tool

References

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment