也來盤盤前端腳手架的那些事兒

字 數:3621, 閱讀時間:14分鐘, 閱讀原文前端

原本早就想寫這篇文章的,因爲有其餘事情耽擱了(可能仍是由於太懶),就拖到了如今,若是再不記下來,估計會拋到九霄雲外了。vue

NodeJs的出現,讓前端工程化的理念不斷深刻,正在向正規軍靠近。先是帶來了Gulp、Webpack等強大的構建工具,隨後又出現了vue-cli和create-react-app等完善的腳手架,提供了完整的項目架構,讓咱們能夠更多的關注業務,而沒必要在項目基礎設施上花費大量時間。node

可是,這些現成的腳手架未必就能知足咱們的業務需求,也未必是最佳實踐,這時咱們就能夠本身來開發一個腳手架。固然,這其實很簡單,利用npm上現成的輪子就能夠搞定,這裏作個記錄,僅當備忘,以拋磚引玉。react

緣起

在上半年的一個項目中須要自定義一個腳手架,來幫助小夥伴們提升開發效率,統一代碼輸出質量,並解決一些使用上的問題,固然也是爲了裝裝逼。
在使用腳手架方式構建以前,咱們遇到了這幾個問題:git

  • 每一個項目建立的時候,須要去Git倉庫拉取項目模板或者拷貝以前的項目,這樣作有兩個問題程序員

    • 從Git拉取項目後,因爲部分人員是有推送權限的,若是他誤操做將私有項目中的修改推送到了模板倉庫,可能會破壞Git上的項目模板
    • 從以前項目拷貝就沒法獲取最新的項目模板,這就致使有些問題明明在最新的模板中已經修復,卻在新項目中依然存在
  • 項目模板須要填寫一些配置信息,開發人員很容易忘記填寫

因此咱們來解決這些問題,思路以下:github

  • 從Git上拉取最新模板,最後幹掉Git倉庫信息,切斷和遠程倉庫的關聯
  • 在初始化時,經過問答方式強制讓使用者輸入配置信息,再根據配置信息生成配置文件,有點相似VueCli初始化項目那樣。

固然,除了上述需求,咱們還能夠再作些額外工做:vue-cli

  • 拉取完成後自動安裝項目依賴,並打開編輯器
  • 提供幫助信息及經常使用命令查看
  • 發佈到NPM,全部人員均可以直接全局安裝使用
  • ...

急急如律令

實現可執行模塊

首先,咱們須要建立一個項目,這裏就叫yncms-template-cli, 項目結構以下:shell

- commands  // 此文件夾用於放置自定義命令
- utils
- index.js  // 項目入口
- readme.md

爲了測試,咱們先在index.js放點內容:npm

#!/usr/bin/env node
// 必須在文件頭添加如上內容指定運行環境爲node
console.log('hello cli');

對於通常的nodejs項目,咱們直接使用node index.js就能夠了,可是這裏是腳手架,確定不能這樣。咱們須要把項目發佈到npm,用戶進行全局安裝,而後就能夠直接使用咱們自定義的命令,相似yncms-template這樣。

因此,咱們須要將咱們的項目作下改動,首先在packge.json中添加以下內容:

"bin": {
    "yncms-template": "index.js"
  },

這樣就能夠將yncms-template定義爲一個命令了,但此時僅僅只能在項目中使用,還不能做爲全局命令使用,這裏咱們須要使用npm link將其連接到全局命令,執行成功後在你的全局node_modules目錄下能夠找到相應文件。而後輸入命令測試一下,若是出現以下內容說明第一步已經成功一大半了:

PS E:\WorkSpace\yncms-template-cli> yncms-template
hello cli

可是,目前這個命令只有咱們本身電腦能夠用,要想其餘人也能安裝使用,須要將它發佈到npm,大體流程以下:

  1. 註冊一個npm帳戶,已有帳戶的能夠跳過這一步
  2. 使用npm login登陸,須要輸入usernamepasswordemail
  3. 使用npm public發佈

這一步比較簡單,很少說,可是請注意以下幾點:

  • 使用了nrm的須要先將源切換到npm官方源
  • package.json中有幾個字段須要完善:

    • name爲發佈的包名,不能和npm已有的包重複
    • version爲版本信息,每次發佈都必需要比線上的版本高
    • homepagebugsrepository也能夠添加上,對應以下頁面

  • 在readme.md加入腳手架介紹及使用方法,方便他人使用。若是須要在文檔中加入徽標,展現腳手架的下載次數之類的,能夠在這裏生成。

發佈成功後,須要等待一下子才能夠在npm倉庫搜索到。

建立命令

既然是腳手架,確定不能只讓它輸出一段文字吧,咱們還須要定義一些命令,用戶在命令行輸入這些命令和參數,腳手架會作出對應的操做。這裏不須要咱們本身去解析這些輸入的命令和參數,有現成的輪子(commander)可使用,徹底能夠知足咱們的須要。

幫助(--help)

安裝好commander後,咱們將index.js中內容改成以下:

#!/usr/bin/env node
const commander = require('commander');
// 利用commander解析命令行輸入,必須寫在全部內容最後面
commander.parse(process.argv);

這時,雖然咱們沒有定義任何命令,可是commander內部給咱們定義了一個幫助命令--help(簡寫-h):

PS E:\WorkSpace\yncms-template-cli> yncms-template -h
Usage: index [options]

Options:
  -h, --help  output usage information

版本(--version)

接下來,咱們再建立一個查詢版本的命令參數,在index.js增長以下內容:

// 查看版本號
commander.version(require('./package.json').version);

這樣,咱們在命令行就能夠查看版本號了:

PS E:\WorkSpace\yncms-template-cli> yncms-template -V
1.0.10
PS E:\WorkSpace\yncms-template-cli> yncms-template --version
1.0.10

默認參數是大寫V,若是須要改爲小寫,將上面內容作以下改動便可:

// 查看版本號
commander
    .version(require('./package.json').version)
    .option('-v,--version', '查看版本號');
PS E:\WorkSpace\yncms-template-cli> yncms-template -h
Usage: index [options]
Options:
  -V, --version  output the version number
  -h, --help     output usage information

init子命令

接下來,咱們來定義一個init命令,如yncms-template init test
index.js中增長以下內容:

commander
    .command('init <name>') // 定義init子命令,<name>爲必需參數可在action的function中接收,如需設置非必需參數,可以使用中括號
    .option('-d, --dev', '獲取開發版') // 配置參數,簡寫和全寫中使用,分割
    .description('建立項目') // 命令描述說明
    .action(function (name, option) { // 命令執行操做,參數對應上面的設置的參數
        // 咱們須要執行的全部操做,都在這裏完成
        console.log(name);
        console.log(option.dev);
    });

如今測試一下:

PS E:\WorkSpace\yncms-template-cli> yncms-template init test -d
test
true

commander具體的用法,請自行查看官方文檔

如此,一個自定義命令雛形就算完成了,然還有幾件事情要作:

  • 實現init命令具體執行的操做,下面會有單獨部分來講。
  • 爲了方便維護,將命令action拆分到commands文件夾中

拉取項目

上面,咱們定義了init命令,可是並無達到初始化項目的目的,接下來咱們就實現一下。

通常來講,項目模板有兩種處理方式:

  • 將項目模板和本腳手架放在一塊兒,好處是用戶安裝腳手架後,模板在本地,初始化會比較快;缺點是項目模板更新比較麻煩,由於和腳手架耦合在一塊兒了
  • 將項目放置到單獨的GIT倉庫,好處是模板更新比較簡單,由於是相互獨立的,只須要維護模板本身的倉庫便可,另外能夠控制拉取權限,由於若是是私有項目,那麼沒有權限的人員是沒法拉取成功的;缺點就是每次初始化都要去GIT拉取,可能會慢點,不過影響不大,因此建議選擇此種方式

首先,咱們利用download-git-repo封裝一個clone方法,用於從git拉取項目。

// utils/clone.js
const download = require('download-git-repo');
const symbols = require('log-symbols');  // 用於輸出圖標
const ora = require('ora'); // 用於輸出loading
const chalk = require('chalk'); // 用於改變文字顏色
module.exports = function (remote, name, option) {
    const downSpinner = ora('正在下載模板...').start();
    return new Promise((resolve, reject) => {
        download(remote, name, option, err => {
            if (err) {
                downSpinner.fail();
                console.log(symbols.error, chalk.red(err));
                reject(err);
                return;
            };
            downSpinner.succeed(chalk.green('模板下載成功!'));
            resolve();
        });
    });
  };
// commands/init.js
const shell = require('shelljs');
const symbols = require('log-symbols');
const clone = require('../utils/clone.js');
const remote = 'https://gitee.com/letwrong/cli-demo.git';
let branch = 'master';

const initAction = async (name, option) => {
    // 0. 檢查控制檯是否能夠運行`git `,
    if (!shell.which('git')) {
        console.log(symbols.error, '對不起,git命令不可用!');
        shell.exit(1);
    }
    // 1. 驗證輸入name是否合法
    if (fs.existsSync(name)) {
        console.log(symbols.warning,`已存在項目文件夾${name}!`);
        return;
    }
    if (name.match(/[^A-Za-z0-9\u4e00-\u9fa5_-]/g)) {
        console.log(symbols.error, '項目名稱存在非法字符!');
        return;
    }
    // 2. 獲取option,肯定模板類型(分支)
    if (option.dev) branch = 'develop';
    // 4. 下載模板
    await clone(`direct:${remote}#${branch}`, name, { clone: true });
};

module.exports = initAction;

測試一下,不出意外就能夠成功拉取項目了。

這裏拉取的項目是和遠程倉庫關聯的,咱們須要將其刪掉(因爲咱們項目是svn管理,因此直接把.git文件夾刪掉,若是使用git的話,能夠git init初始化便可),清理掉一些多餘文件:

// commands/init.js
// 5. 清理文件
const deleteDir = ['.git', '.gitignore', 'README.md', 'docs']; // 須要清理的文件
const pwd = shell.pwd();
deleteDir.map(item => shell.rm('-rf', pwd + `/${name}/${item}`));

來點個性化

在上述過程當中,咱們實現了一個腳手架的基本功能,大體分爲三個流程(拉取模板->建立項目->收尾清理),也解決了上面我項目中遇到的第一個問題。接下來,咱們就來看下第二個問題如何解決。

解決的思路就是在建立項目的時候,就經過命令行強制要求開發人員輸入對應的配置,而後自動寫入配置文件,這樣就能夠有效避免忘記填寫的尷尬。固然經過這種方式也能夠實現根據用戶的輸入來動態初始化項目,達到個性化的目的。

這裏咱們直接使用現成的輪子inquirer就能夠搞定,效果和VueCli建立項目同樣,支持不少類型,比較強大,也比較簡單,具體用法看官方文檔就能夠了。這裏我直接上代碼,在第4步(下載模板)前面增長以下:

// init.js
const inquirer = require('inquirer');
// 定義須要詢問的問題
const questions = [
  {
    type: 'input',
    message: '請輸入模板名稱:',
    name: 'name',
    validate(val) {
      if (!val) return '模板名稱不能爲空!';
      if (val.match(/[^A-Za-z0-9\u4e00-\u9fa5_-]/g)) return '模板名稱包含非法字符,請從新輸入';
      return true;
    }
  },
  {
    type: 'input',
    message: '請輸入模板關鍵詞(;分割):',
    name: 'keywords'
  },
  {
    type: 'input',
    message: '請輸入模板簡介:',
    name: 'description'
  },
  {
    type: 'list',
    message: '請選擇模板類型:',
    choices: ['響應式', '桌面端', '移動端'],
    name: 'type'
  },
  {
    type: 'list',
    message: '請選擇模板分類:',
    choices: ['整站', '單頁', '專題'],
    name: 'category'
  },
  {
    type: 'input',
    message: '請輸入模板風格:',
    name: 'style'
  },
  {
    type: 'input',
    message: '請輸入模板色系:',
    name: 'color'
  },
  {
    type: 'input',
    message: '請輸入您的名字:',
    name: 'author'
  }
];
// 經過inquirer獲取到用戶輸入的內容
const answers = await inquirer.prompt(questions);
// 將用戶的配置打印,確認一下是否正確
console.log('------------------------');
console.log(answers);
let confirm = await inquirer.prompt([
    {
        type: 'confirm',
        message: '確認建立?',
        default: 'Y',
        name: 'isConfirm'
    }
]);
if (!confirm.isConfirm) return false;

獲取到用戶輸入的配置之後,就能夠寫入配置文件或者作個性化的處理了,這個太簡單,我這裏就不贅述了。

錦上添花

到這裏,一個徹底知足需求的腳手架就完成了,可是做爲一個有追求的程序員,咱們能夠在界面和易用性上面再作點什麼:

  • 爲異步操做加上loding動畫,能夠直接使用ora
const installSpinner = ora('正在安裝依賴...').start();
if (shell.exec('npm install').code !== 0) {
    console.log(symbols.warning, chalk.yellow('自動安裝失敗,請手動安裝!'));
    installSpinner.fail(); // 安裝失敗
    shell.exit(1);
}
installSpinner.succeed(chalk.green('依賴安裝成功!'));
  • 在操做成功或者失敗給出圖標提示,使用log-symbols

  • 能夠給文字加點顏色,同理用現成的輪子Chalk

  • 在安裝依賴或者其餘耗時比較長的時候,用戶可能會把終端切到後臺,這時咱們的操做完成後可使用node-notifier發出系統通知給予用戶提示。
notifier.notify({
    title: 'YNCMS-template-cli',
    icon: path.join(__dirname, 'coulson.png'),
    message: ' ♪(^∀^●)ノ 恭喜,項目建立成功!'
});
  • 在建立項目的時候,咱們可能會須要執行一些shell命令,可使用shelljs來完成,例如咱們要在項目建立結束後打開vscode並退出終端
// 8. 打開編輯器
if (shell.which('code')) shell.exec('code ./');
shell.exit(1);

結語

到這裏,會發現開發一個腳手架其實很簡單,都是使用現成的輪子就能夠搞定,不曉得哪位大牛說過玩NodeJS就是玩輪子。

除了上述方法,咱們也能夠直接經過大名鼎鼎的Yeoman來建立,不過我的以爲不必,畢竟這玩意也不難。

一個好的腳手架應該是可以解決工做中遇到的問題,提升開發效率的。

相關文章
相關標籤/搜索