自定義腳手架與項目統一規範

前言

最近在公司兼顧了三個項目的開發,跟不一樣的人合做發現不少不統一的問題:javascript

  • 項目目錄結構不統一
  • UI框架不一
  • 二次封裝的axios攔截器不一
  • vuex的處理方式不一
  • 代碼格式不一

等等這些問題,在從開發者的角度來講,先要花時間去熟悉其餘人搭的項目,清楚代碼之間的依賴才能才能真正的去完成開發任務,或許這裏面花的時間不會很長,可是每一個人都花了這樣的時間,從公司角度來講那應該是一個很大的成本了,咱們應該作的是去統一規範、統一架構。另外公司目前前端框架的選型統一是使用vue,當咱們每次用vue-cli去初始化項目的時候,都要從新寫一些公用的代碼或者去舊項目copy,這時有一個專屬於公司的一個模版會不會更好?從這裏出發,我就查看了一些解決方案和腳手架的實現方式。前端

腳手架

說到腳手架應該就會想到vue-cli 、 create-react-app 等等,實際上腳手架的做用是什麼呢?簡單來講就是在前端工做流中負責項目起始階段建立初始文件。那這樣是否能夠把前言所述的問題,歸類總結作成一個綜合解決這些問題的項目模版,經過腳手架來初始化每一個項目的初始文件呢?事實是能夠的。那下面就明確需求,寫一個自定義腳手架初始化項目文件。如下謹記錄簡單實現過程,重點是解決工做中存在的問題。vue

第三方庫(npm包)

  • commander:命令行工具
  • axios:請求
  • ora:加載loading
  • download-git-repo:在git中下載模板
  • inquirer:交互命令行工具
  • chalk:粉筆(不一樣顏色字體)
  • metalsmith:讀取全部文件,實現模板渲染
  • consolidate:模板引擎
  • ncp:複製文件

初始化項目後運行安裝java

npm install commander axios ora download-git-repo inquirer chalk metalsmith consolidate ncp
複製代碼

初始化項目

新建文件夾「xxx-cli」,進入到該目錄node

npm init -y   # 初始化package.json
複製代碼

文件結構react

├── bin
│   └── www  // 全局命令執行的根文件
├── src
│   ├── main.js // 入口文件
│   └── create.js   // create
│   └── constants.js   // 存放常量
│── package.json
複製代碼

www文件中使用main做爲入口文件,而且以node環境執行此文件ios

#! /usr/bin/env node
require('../src/main.js');
複製代碼

設置在命令下執行xxx-cli時調用bin目錄下的www文件,package.json中加入; ('xxx-cli'這個名字能夠任意起,但若是須要發包須要到官網上驗證是否已被用)git

"bin": {
    "xxx-cli": "./bin/www"
}
複製代碼

連接包到全局下使用github

npm link
複製代碼

咱們已經能夠成功的在命令行中使用xxx-cli命令,而且能夠執行main.js文件!vuex

瞭解commander 命令行工具

constants.js

const { version } = require('../package.json');
/ 存儲模板的位置
const downloadDirectory = `${process.env[process.platform === 'darwin' ? 'HOME' : 'USERPROFILE']}/.template`;
module.exports = {
  version,
  downloadDirectory,
};
複製代碼

main.js就是咱們的入口文件

const program = require('commander');
const { version } = require('./constants');

program
    .command('create')
    .alias('c')
    .description('to create a new project')
    .action(() => {
      console.log('create');
    });

program.version(version)
  .parse(process.argv); // process.argv就是用戶在命令行中傳入的參數
複製代碼

執行 xxx-cli --versionxxx-cli create 是否是已經有一提示了!以上就是commander做用的表現。

create命令

下面就重點來實現create的action的邏輯。爲了後續的拓展,改造一下main.js,把create的邏輯寫在create.js裏

// main.js
const program = require('commander');
const path = require('path');
const { version } = require('./constants');

const mapAction = { // 須要生成的指令數據
  create: {
    alias: 'c',
    description: 'create a project',
    examples: [
      'xxx-cli create <project-name>',
    ],
  },
  '*': {
    alias: '',
    description: 'command not found',
    examples: [],
  },
};
Reflect.ownKeys(mapAction).forEach((action) => {
  program
    .command(action) // 命令名
    .alias(mapAction[action].alias) // 命令別名
    .description(mapAction[action].description) // 命令描述
    .action(() => { // 命令執行的操做
      if (action === '*') { // 命令不存在
        console.log(mapAction[action].description);
      } else {
        require(path.resolve(__dirname, action))(...process.argv.slice(3)); // 引入命令對應操做模塊
      }
    });
});

program.on('--help', () => { // help命令打印幫助信息
  console.log('\nExample');
  Reflect.ownKeys(mapAction).forEach((action) => {
    mapAction[action].examples.forEach((item) => {
      console.log(item);
    });
  });
});

program
  .version(version)
  .parse(process.argv);
  
複製代碼

create功能需求:在命令行輸入「xxx-cli create project-name」創建一個「project-name」的項目,項目裏的文件是咱們提早建好的template,能夠選擇不一樣的template。下面來實先create的操做:

  • 獲取組織下的全部模版;
  • 獲取當前選擇項目的對應版本號;
  • 拉取模版存到一個目錄下備用;
  • 拷貝生成新項目;

如今須要在git上拉取項目模版,這裏用axios去獲取

npm insatll axios
複製代碼

create.js

// create.js
const fs = require('fs'); 
const path = require('path');

const axios = require('axios');
const ora = require('ora');
const Inquirer = require('inquirer');
const { promisify } = require('util');
const chalk = require('chalk');
const MetalSmith = require('metalsmith');
let { render } = require('consolidate').els;
let downloadGitRepo = require('download-git-repo');
let ncp = require('ncp');

render = promisify(render);
downloadGitRepo = promisify(downloadGitRepo);
const { downloadDirectory } = require('./constants');

ncp = promisify(ncp);

// 獲取倉庫列表
const fetchRepoList = async () => {
  // 獲取當前組織中的全部倉庫信息,這個倉庫中存放的都是項目模板
  const { data } = await axios.get('https://api.github.com/orgs/xxx/repos');// xxx表明某個倉庫
  return data;
};
// 獲取選中模版的tags列表
const fechTagList = async (repo) => {
  const { data } = await axios.get(`https://api.github.com/repos/xxx/${repo}/tags`);
  return data;
};

// 封裝loading
const waitFnloading = (fn, message) => async (...args) => { // 高階函數
  const spinner = ora(message);
  spinner.start();
  const result = await fn(...args);
  spinner.succeed();
  return result;
};

// 下載模版
const download = async (repo, tag) => {
  let api = `xxx/${repo}`;
  if (tag) {
    api += `#${tag}`;
  }
  // /user/xxxx/.template/repo
  const dest = `${downloadDirectory}/${repo}`;
  await downloadGitReop(api, dest); // 下載模版存放到指定路徑
  return dest; // 下載的最終目錄路徑
};

// 邏輯主體部分
module.exports = async (projectName = 'my-project') => {
  if (fs.existsSync(projectName)) { // 判斷 projectName 文件夾是否存在?
    console.log(chalk.red('Folder already exists.'));
  } else {
  // 1.獲取組織下的全部模版;
    let repos = await waitLoading(fetchRepoList, 'fetching template...')();
    repos = repos.map((item) => item.name);
    const { repo } = await Inquirer.prompt({ // 選擇模版
      name: 'repo',
      type: 'list',
      message: 'please choise a template to create project',
      choices: repos,
    });

    // 2.獲取當前選擇項目的對應版本號
    let tags = await waitLoading(fetchTagList, 'fetching tags...')(repo);
    let result;
    if (tags.length > 0) {
      tags = tags.map((item) => item.name);
      const { tag } = await Inquirer.prompt({ // 選擇模版的版本號
        name: 'tag',
        type: 'list',
        message: 'please choise tags to create project',
        choices: tags,
      });
      result = await waitLoading(download, 'download template...')(repo, tag); // 下載模版,拿到緩存模版的路徑
    } else {
      result = await waitLoading(download, 'download template...')(repo); // 下載模版,拿到緩存模版的路徑
    }

    if (!fs.existsSync(path.join(result, 'ask.js'))) { // 是否須要輸入信息
      try {
        await ncp(result, path.resolve(projectName)); // 把模版複製到projectName
        console.log('\r\n', chalk.green(`cd ${projectName}\r\n`), chalk.yellow('npm install\r\n')); // 信息提示
      } catch (error) {
        console.log(error);
      }
    } else {
      await new Promise((resolve, reject) => { // 須要用戶輸入信息
        MetalSmith(__dirname)
          .source(result)
          .destination(path.resolve(projectName)) 
          .use(async (files, metal, done) => {
            const args = require(path.join(result, 'ask.js')); // 獲取填寫選項
            const select = await Inquirer.prompt(args);
            const meta = metal.metadata(); // 用戶填寫的結果
            Object.assign(meta, select);
            delete files['ask.js'];
            done();
          })
          .use((files, metal, done) => { // 根據用戶輸入編寫模版
            const obj = metal.metadata();
            Reflect.ownKeys(files).forEach(async (file) => {
              if (file.includes('js') || file.includes('json')) {
                let content = files[file].contents.toString();
                if (content.includes('<%')) {
                  content = await render(content, obj);
                  files[file].contents = Buffer.from(content);
                }
              }
            });
            done();
          })
          .build((err) => {
            if (err) {
              reject();
            } else {
              console.log('\r\n', chalk.green(`cd ${projectName}\r\n`), chalk.yellow('npm install\r\n'));
              resolve();
            }
          });
      });
    }
  }
};

複製代碼

以上建立項目的代碼基本完成。

發佈

nrm use npm
npm publish
複製代碼

發佈後,安裝

npm install xxx-cli -g // 安裝腳手架

xxx-cli create projext-xxx // 建立項目
複製代碼

一個簡單的腳手架就這樣實現了,雖然簡單,可是在公司推行起來做用和效果仍是蠻大的。

以上僅從我的遇到的問題的角度出發去實現,若有錯誤或不妥的地方請指出,感謝閱讀。

相關文章
相關標籤/搜索