寫在前面
5 分鐘是昨天的梗,不要打我,哈哈
一。為什麼是 gulp?
說起構建工具(打包工具),就想起���傷的 grunt,還有去年 5 月的那篇筆記:[Grunt 教程](/articles/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 也能分分鐘搞出一個好用的命令行工具
參考資料
-
我為何放棄 Gulp 與 Grunt,轉投 npm scripts(中):這 3 篇都不錯,客觀接受
暫無評論,快來發表你的看法吧