前端脚手架初探究

先看下效果,如今脚手架还不完善只是完成了初始化功能,具体功能还得等以后慢慢完善 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文件的思路进行,可是我尚未应用。以后完善后会一并将代码放出
复制代码

欢迎一块儿踩坑

相关文章
相关标签/搜索