如何寫一個本身的腳手架 - 一鍵初始化項目

介紹

腳手架的做用:爲減小重複性工做而作的重複性工做javascript

即爲了開發中的:編譯 es6,js 模塊化,壓縮代碼,熱更新等功能,咱們使用webpack等打包工具,可是又帶來了新的問題:初始化工程的麻煩,複雜的webpack配置,以及各類配置文件,因此就有了一鍵生成項目,0 配置開發的腳手架html

本文項目代碼地址前端

本系列分 3 篇,詳細介紹如何實現一個腳手架:vue

  • 一鍵初始化項目
  • 0 配置開發環境與打包
  • 一鍵上傳服務器

首先說一下我的的開發習慣java

在寫功能前我會先把調用方式寫出了,而後一步一步的從使用者的角度寫,現將基礎功能寫好後,慢慢完善node

例如一鍵初始化項目功能react

我指望的就是 在命令行執行輸入 my-cli create text-project,回車後直接建立項目並生成模板,還會把依賴都下載好webpack

咱們下面就從命令行開始入手git

建立項目 my-cli,執行 npm init -y快速初始化es6

bin

my-cli

package.json 中加入:

{
  "bin": {
    "my-cli": "bin.js"
  }
}

bin.js

#!/usr/bin/env node

console.log(process.argv);

#!/usr/bin/env node這一行是必須加的,就是讓系統動態的去PATH目錄中查找node來執行你的腳本文件。

命令行執行 npm link ,建立軟連接至全局,這樣咱們就能夠全局使用my-cli命令了,在開發 npm 包的前期都會使用link方式在其餘項目中測試來開發,後期再發布到npm

命令行執行 my-cli 1 2 3

輸出:[ '/usr/local/bin/node', '/usr/local/bin/my-cli', '1', '2', '3' ]

這樣咱們就能夠獲取到用戶的輸入參數

例如my-cli create test-project

咱們就能夠經過數組第 [2] 位判斷命令類型create,經過第 [3] 位拿到項目名稱test-project

commander

node的命令行解析最經常使用的就是commander庫,來簡化複雜cli參數操做

(咱們如今的參數簡單能夠不使用commander,直接用process.argv[3]獲取名稱,可是爲了以後會複雜的命令行,這裏也先使用commander

#!/usr/bin/env node

const program = require("commander");
const version = require("./package.json").version;

program.version(version, "-v, --version");

program
  .command("create <app-name>")
  .description("使用 my-cli 建立一個新的項目")
  .option("-d --dir <dir>", "建立目錄")
  .action((name, command) => {
    const create = require("./create/index");
    create(name, command);
  });

program.parse(process.argv);

commander 解析完成後會觸發action回調方法

命令行執行:my-cli -v

輸出:1.0.0

命令行執行: my-cli create test-project

輸出:test-project

建立項目

拿到了用戶傳入的名稱,就能夠用這麼名字建立項目
咱們的代碼儘可能保持bin.js整潔,不將接下來的代碼寫在bin.js裏,建立create文件夾,建立index.js文件

create/index.js中:

const path = require("path");
const mkdirp = require("mkdirp");

module.exports = function(name) {
  mkdirp(path.join(process.cwd(), name), function(err) {
    if (err) console.error("建立失敗");
    else console.log("建立成功");
  });
};

process.cwd()獲取工做區目錄,和用戶傳入項目名稱拼接起來

(建立文件夾咱們使用mkdirp包,能夠避免咱們一級一級的建立目錄)

修改bin.jsaction方法:

// bin.js
.action(name => {
    const create = require("./create")
    create(name)
  });

命令行執行: my-cli create test-project

輸出:建立成功

並在命令行所在目錄建立了一個test-project文件夾

模板

首先須要先列出咱們的模板包含哪些文件

一個最基礎版的vue項目模板:

|- src
  |- main.js
  |- App.vue
  |- components
    |- HelloWorld.vue
|- index.html
|- package.json

這些文件就不一一介紹了

咱們須要的就是生成這些文件,並寫入到目錄中去

模板的寫法後不少種,下面是個人寫法:

模板目錄:

|- generator
  |- index-html.js
  |- package-json.js
  |- main.js
  |- App-vue.js
  |- HelloWorld-vue.js

generator/index-html.js 模板示例:

module.exports = function(name) {
  const template = `
{
  "name": "${name}",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {},
  "devDependencies": {
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "vue": "^2.6.10"
  }
}
  `;
  return { template, dir: "", name: "package.json" };
};

dir就是目錄,例如main.jsdir就是src

create/index.jsmkdirp中新增:

const path = require("path");
const mkdirp = require("mkdirp");
const fs = require("fs");

module.exports = function(name) {
  const projectDir = path.join(process.cwd(), name);
  mkdirp(projectDir, function(err) {
    if (err) console.error("建立失敗");
    else {
      console.log(`建立${name}文件夾成功`);
      const { template, dir, name: fileName } = require("../generator/package")(name);
      fs.writeFile(path.join(projectDir, dir, fileName), template.trim(), function(err) {
        if (err) console.error(`建立${fileName}文件失敗`);
        else {
          console.log(`建立${fileName}文件成功`);
        }
      });
    }
  });
};

這裏只寫了一個模板的建立,咱們能夠用readdir來獲取目錄下全部文件來遍歷執行

下載依賴

咱們日常下載npm包都是使用命令行 npm install / yarn install
這時就須要用到 nodechild_process.spawn api 來調用系統命令

由於考慮到跨平臺兼容處理,因此使用 cross-spawn 庫,來幫咱們兼容的操做命令

咱們建立utils文件夾,建立install.js

utils/install.js

const spawn = require("cross-spawn");

module.exports = function install(options) {
  const cwd = options.cwd || process.cwd();
  return new Promise((resolve, reject) => {
    const command = options.isYarn ? "yarn" : "npm";
    const args = ["install", "--save", "--save-exact", "--loglevel", "error"];
    const child = spawn(command, args, { cwd, stdio: ["pipe", process.stdout, process.stderr] });

    child.once("close", code => {
      if (code !== 0) {
        reject({
          command: `${command} ${args.join(" ")}`
        });
        return;
      }
      resolve();
    });
    child.once("error", reject);
  });
};

而後咱們就能夠在建立完模板後調用install方法下載依賴

install({ cwd: projectDir });

要知道工做區爲咱們項目的目錄

至此,解析 cli,建立目錄,建立模板,下載依賴一套流程已經完成

基本功能都跑通以後下面就是要填充剩餘代碼和優化

優化

當代碼寫的多了以後,咱們看上面create方法內的回調嵌套回調會很是難受

node 7已經支持async,await,因此咱們將上面代碼改爲Promise

utils目錄下建立,promisify.js

module.exports = function promisify(fn) {
  return function(...args) {
    return new Promise(function(resolve, reject) {
      fn(...args, function(err, ...res) {
        if (err) return reject(err);
        if (res.length === 1) return resolve(res[0]);
        resolve(res);
      });
    });
  };
};

這個方法幫咱們把回調形式的Function改爲Promise

utils目錄下建立,fs.js

const fs = require(fs);
const promisify = require("./promisify");
const mkdirp = require("mkdirp");

exports.writeFile = promisify(fs.writeFile);
exports.readdir = promisify(fs.readdir);
exports.mkdirp = promisify(mkdirp);

fsmkdirp方法改形成promise

改造後的create.js

const path = require("path");
const fs = require("../utils/fs-promise");
const install = require("../utils/install");

module.exports = async function(name) {
  const projectDir = path.join(process.cwd(), name);
  await fs.mkdirp(projectDir);
  console.log(`建立${name}文件夾成功`);
  const { template, dir, name: fileName } = require("../generator/package")(name);
  await fs.writeFile(path.join(projectDir, dir, fileName), template.trim());
  console.log(`建立${fileName}文件成功`);
  install({ cwd: projectDir });
};

結語

關於進一步優化:

  • 更多功能與健壯 例如指定目錄建立項目,目錄不存在等狀況
  • chalkora優化log,給用戶更好的反饋
  • 經過inquirer問詢用戶獲得更多的選擇:模板vue-routervuex等更多初始化模板功能,eslint

更多的功能:

  • 內置 webpack 配置
  • 一鍵發佈服務器

其實要學會善用第三方庫,你會發現咱們上面的每一個模塊都有第三方庫的身影,咱們只是將這些功能組裝起來,再結合咱們的想法進一步封裝

雖然有vue-clicreate-react-app這些已有的腳手架,可是咱們仍是可能在某些狀況下須要本身實現腳手架部分功能,根據公司的業務來封裝,減小重複性工做,或者瞭解一下內部原理

【青團社】招聘前端方面: 高級/資深/技術專家,歡迎投遞 lishixuan@qtshe.com

相關文章
相關標籤/搜索