前端腳手架初探究

先看下效果,如今腳手架還不完善只是完成了初始化功能,具體功能還得等以後慢慢完善 javascript

思想

本前端腳手架的思想,其實就是前端

  1. 使用inquirer.js 和用戶進行交互,經過用戶的輸入進行定製化配置
  2. 從遠端git倉庫下載咱們提早定義好的模版
  3. 將用戶的輸入做爲插值插入到咱們的模版中。實現定製化

好處

前端腳手架的好處主要是java

  1. 規範統一,由於複製過程當中可能會出現本身錯誤,若是是新建能夠參考我以前文章開發的vscode插件,自動填充模版
  2. 後期能夠自定義須要,好比項目中須要element能夠經過插值進行插入,不須要也能夠選擇不引入,節省開發時間
  3. 啓動新項目能夠加入以前定義好的業務組件,好比loading toast組件等

具體看下代碼node

初始化 bin/init.jsgit

#!/usr/bin/env node

const program = require('commander')
const path = require('path')
const fs = require('fs')
const glob = require('glob') // npm i glob -D
const download = require('../lib/download') //下載配置
const inquirer = require('inquirer') // 按需引入
const logSymbols = require("log-symbols");
const chalk = require('chalk')
const remove = require('../lib/remove') // 刪除文件js
const generator = require('../lib/generator')// 模版插入
const CFonts = require('cfonts');

program.usage('<project-name>')
            .parse(process.argv) // 加入這個能獲取到項目名稱

// 根據輸入,獲取項目名稱
// console.log(program)
let projectName = program.rawArgs[2] // 獲取項目名稱

if (!projectName) {  // project-name 必填  若是沒有輸入名稱執行helphelp
  // 至關於執行命令的--help選項,顯示help信息,這是commander內置的一個命令選項
  program.help()
  return
}

// 當前目錄爲空,若是當前目錄的名稱和project-name同樣,則直接在當前目錄下建立工程,不然,在當前目錄下建立以project-name做爲名稱的目錄做爲工程的根目錄
// 當前目錄不爲空,若是目錄中不存在與project-name同名的目錄,則建立以project-name做爲名稱的目錄做爲工程的根目錄,不然提示項目已經存在,結束命令執行。
const list = glob.sync('*')  // 遍歷當前目錄
let next = undefined;

let rootName = path.basename(process.cwd());
if (list.length) {  // 若是當前目錄不爲空
  if (list.some(n => {
    const fileName = path.resolve(process.cwd(), n);
    const isDir = fs.statSync(fileName).isDirectory();
    return projectName === n && isDir
  })) {
    console.log(`項目${projectName}已經存在`);
    remove(path.resolve(process.cwd(), projectName)) // 刪除重複名字文件,而且重複建立,(邏輯修改)--> 詢問是否刪除重名文件而後,用戶回答是,而後刪除文件,並從新覆蓋
    // return;
  }
  rootName = projectName;
  next = Promise.resolve(projectName);
} else if (rootName === projectName) {
  rootName = '.';
  next = inquirer.prompt([
    {
      name: 'buildInCurrent',
      message: '當前目錄爲空,且目錄名稱和項目名稱相同,是否直接在當前目錄下建立新項目?',
      type: 'confirm',
      default: true
    }
  ]).then(answer => {
    console.log(answer.buildInCurrent)
    return Promise.resolve(answer.buildInCurrent ? '.' : projectName)
  })
} else {
  rootName = projectName;
  next = Promise.resolve(projectName)
}

next && go()

function go () {
  // 預留,處理子命令
  // console.log(path.resolve(process.cwd(), path.join('.', rootName))) // 打印當前項目目錄
  // download(rootName)
  //   .then(target => console.log(target))
  //   .catch(err => console.log(err))
  next.then(projectRoot => { //
    if (projectRoot !== '.') {
      fs.mkdirSync(projectRoot)
    }
    CFonts.say('amazing', {
      font: 'block',              // define the font face
      align: 'left',              // define text alignment
      colors: ['#f80'],         // define all colors
      background: 'transparent',  // define the background color, you can also use `backgroundColor` here as key
      letterSpacing: 1,           // define letter spacing
      lineHeight: 1,              // define the line height
      space: true,                // define if the output text should have empty lines on top and on the bottom
      maxLength: '0',             // define how many character can be on one line
  });
    return download(projectRoot).then(target => {
      return {
        projectRoot,
        downloadTemp: target
      }
    })
  }).then(context => {
    // console.log(context)
    return inquirer.prompt([
      {
        name: 'projectName',
    	  message: '項目的名稱',
        default: context.name
      }, {
        name: 'projectVersion',
        message: '項目的版本號',
        default: '1.0.0'
      }, {
        name: 'projectDescription',
        message: '項目的簡介',
        default: `A project named ${context.projectRoot}`
      },{
        name: 'supportMacawAdmin',
        message: '是否使用element',
        default: "No",
      }
    ]).then(answers => { // 可選選項回調函數
      // return latestVersion('macaw-ui').then(version => {
      //   answers.supportUiVersion = version
      //   return {
      //     ...context,
      //     metadata: {
      //       ...answers
      //     }
      //   }
      // }).catch(err => {
      //   return Promise.reject(err)
      // })
      let v = answers.supportMacawAdmin.toUpperCase();
      answers.supportMacawAdmin = v === "YES" || v === "Y";
      return {
        ...context,
        metadata: {
          ...answers
        }
      }
    })
  }).then(context => {
    console.log("生成文件")
    console.log(context)
     //刪除臨時文件夾,將文件移動到目標目錄下
     return generator(context);
  }).then(context => {
    // 成功用綠色顯示,給出積極的反饋
    console.log(logSymbols.success, chalk.green('建立成功:)'))
    console.log(chalk.green('cd ' + context.projectRoot + '\nnpm install\nnpm run dev'))
  }).catch(err => {
    console.error(err)
     // 失敗了用紅色,加強提示
     console.log(err);
     console.error(logSymbols.error, chalk.red(`建立失敗:${err.message}`))
  })
}

複製代碼

刪除文件npm

lib/remove.jsjson

// 刪除文件系統
const fs =require("fs");
const path=require("path");

function removeDir(dir) {
  let files = fs.readdirSync(dir)
  for(var i=0;i<files.length;i++){
    let newPath = path.join(dir,files[i]);
    let stat = fs.statSync(newPath)
    if(stat.isDirectory()){
      //若是是文件夾就遞歸下去
      removeDir(newPath);
    }else {
     //刪除文件
      fs.unlinkSync(newPath);
    }
  }
  fs.rmdirSync(dir)//若是文件夾是空的,就將本身刪除掉
}

module.exports=removeDir;
複製代碼

下載模版函數

lib/down.js測試

const download = require('download-git-repo')
const path = require("path")
const ora = require('ora')

module.exports = function (target) {
  target = path.join(target || '.', '.download-temp');
  return new Promise(function (res, rej) {
    // 這裏能夠根據具體的模板地址設置下載的url,注意,若是是git,url後面的branch不能忽略
    // 格式是名字/地址 後面不加 .git 可是帶着 #分支
    let url = '名字/模版#分支'
    const spinner = ora(`正在下載項目模板,源地址:${url}`)
    spinner.start();

    download(url, target, { clone: false }, function (err) { // clone false 設置成false 具體設置看官網設置
      if (err) {
        spinner.fail()
        rej(err)
      }
      else {
        // 下載的模板存放在一個臨時路徑中,下載完成後,能夠向下通知這個臨時路徑,以便後續處理
        spinner.succeed()
        res(target)
      }
    })
  })
}
複製代碼

注意:其中的寫模版名稱的時候,格式必須是 名字/模版#分支ui

插值文件

lib/generator

const rm = require('rimraf').sync //以包的形式包裝rm -rf命令,用來刪除文件和文件夾的,無論文件夾是否爲空,均可刪除
const Metalsmith = require('metalsmith') // 插值
const Handlebars = require('handlebars') // 模版
const remove = require("../lib/remove") // 刪除
const fs = require("fs")
const path = require("path")
/** * 生成文件 * @param 文件的名稱 */
module.exports = function (context) {

  let metadata = context.metadata; // 用戶自定義信息
  let src = context.downloadTemp; // 暫時存放文件目錄
  let dest = './' + context.projectRoot; //項目的根目錄

  if (!src) {
    return Promise.reject(new Error(`無效的source:${src}`))
  }
  return new Promise((resolve, reject) => {
    const metalsmith = Metalsmith(process.cwd())
      .metadata(metadata) // 將用戶輸入信息放入
      .clean(false)
      .source(src)
      .destination(dest);
   
    metalsmith.use((files, metalsmith, done) => {
      const meta = metalsmith.metadata()
      Object.keys(files).forEach(fileName => {
        const t = files[fileName].contents.toString()
        console.log("打印差值")
        // console.log(t)
        files[fileName].contents = new Buffer.from(Handlebars.compile(t)(meta),'UTF-8')
      })
      done()
    }).build(err => {
      remove(src);
      err ? reject(err) : resolve(context);
    })
  })
}

複製代碼

packjson配置

{
  "name": "cli",
  "version": "1.0.0",
  "description": "amz腳手架1.0",
  "bin": {
    "amaz": "./bin/init.js"
  },
  "main": "./bin/macaw-hellow.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "cli"
  ],
  "author": "amaizngli",
  "license": "ISC",
  "dependencies": {
    "cfonts": "^2.4.5",
    "commander": "^4.0.0",
    "download-git-repo": "^3.0.2",
    "glob": "^7.1.5",
    "handlebars": "^4.5.1",
    "inquirer": "^7.0.0",
    "metalsmith": "^2.3.0",
    "ora": "^4.0.2"
  }
}

複製代碼

本地測試可使用npm link連接到全局進行開發測試

其中

特效文字使用的是cFonts插件,具體使用能夠去npm查看

其中插值使用Metalsmith模版,膠水式代碼,進行插值插入,模版使用的Handlebars模版

開發定製能夠採用git 的.gitignore文件的思路進行,可是我尚未應用。以後完善後會一併將代碼放出
複製代碼

歡迎一塊兒踩坑

相關文章
相關標籤/搜索