脚手架初入门

138 阅读5分钟

脚手架的本质就是帮助开发者减少重复性工作,提升开发效率

许多时候,我们在日常开发中都有现成的cli可以使用,像vue的vue create hello-world,react的npx create-react-app my-app,大部分公司也都设计了符合自己业务的脚手架。 这些脚手架的最终目的都是帮助开发者减少低级重复劳动,专注业务提高开发效率。

在开始开发一个脚手架前,我们需要先来了解下几个常用的库

  • chalk 可以在终端显示颜色
  • commander 提供了命令行输入和参数解析,简化命令行开发
  • inquirer 交互式命令行工具,用来收集用户填入表单
  • ora 终端加载动画效果,增加趣味性
  • shelljs 通过在代码中编写shell命令实现功能
  • puppeteer 主要用来启动无头浏览器生成网站缩略图
  • download-git-repo 用来下载远程模板

目标

预计通过这个脚手架可以做到以下几点:

// 初始化一个项目
llscw create project-name
// 初始化配置信息
llscw init

开始搭建

1. 初始化 cli

接下来我们开始从头来搭建一个脚手架

mkdir llscw-cli && cd llscw-cli
npm init -y

我们想要的情况是在我们运行llscw命令的时候,执行对应的 node 文件,因此我们可以在package.json文件中指明bin来配置入口:


{
  "name": "-llscw-react-cli",
  "version": "1.0.1",
  "description": "llscw cli",
  "private": false,
  "main": "index.js",
  "bin": {
    "llscw": "./bin/index.js"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/lqt0327/-llscw-react-cli.git"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

对应的./bin/index.js文件中,需要写入以下声明:

#!/usr/bin/env node

上面这段声明的意思是,指定这个脚本文件采用node执行,而/usr/bin/env的作用是告诉系统可以在PATH目录中查找。配置#!/usr/bin/env node的目的就是为了解决不同用户node路径不同的问题,让系统动态的去查找node来执行你的脚本文件。

接下来我们来测试一下我们的脚手架,在当前目录执行npm link来增加一个软连接。

再在我们的package.json文件中增加一行命令:

"scripts": {
    "llscw": "llscw"
}

这样,一个简单的cli就完成了。

2. create 模版、组件

我们接下来先实现一个创建模版的功能。这里就需要用到上面提到的commander库来进行命令行输入参数的解析:

program
  .command('create [template]')
  .description('生成 llscw 模板')
  .action(function(template){
    generate(template);
  });

通过上面这段代码,我们就可以实现通过命令llscw create 模版名称来从远程git仓库里拉取我们事先编辑好的项目的代码。 不过在那之前,还需要将generate函数进行完善。

const path = require('path');
const fs = require('fs');
const chalk = require('chalk');
const download = require('download-git-repo');
const ora = require('ora');

async function generate(name) {
  const config = await getTemplateName();
  const targetDir = path.join(targetRootPath, name);
  //创建新模块文件夹
  fs.mkdirSync(targetDir);
  readAndCopyFile()
  console.log(chalk.green(`生成模板 "${name}" 完成!`));
}
async function readAndCopyFile() {
  const spinner = ora('开始下载模版...').start();
  await downLoadTemplate(`github:lqt0327/react-template#main`, name, true, spinner);
  spinner.succeed('模版下载完成');
  console.log();
  console.info('初始化文件配置信息...');
  console.log();
  console.log(chalk.green(`你的项目 ${name} 已创建成功!`));
  console.log();
}
async function downLoadTemplate(repository, projectName, clone, spinner) {
  await new Promise((resolve, reject) => {
    download(
      repository,
      projectName,
      {
        clone
      },
      (err) => {
        if (err) {
          spinner.fail()
          return reject(err)
        }
        resolve();
      }
    );
  });
}

在上面的代码中,我们通过download-git-repo这个npm包来拉取远程仓库的代码,并通过orachalk来处理交互信息。 这样一来,我们就可以尝试通过llscw create project-name命令来初始化一个项目了。

3. 初始化配置文件

我们接下来先实现一个初始化配置文件的功能。初始化配置工作的目的是对一些不是用llscw create创建的项目,进行配置文件的初始化工作,这里同样需要用到上面提到的commander库来进行命令行输入参数的解析:

program
  .version(pkg.version,'-v, --version')
  .command('init')
  .description('初始化 llscw config 配置文件')
  .action(initial);

然后来完善initial函数,通过检测当前目录下是否已经存在llscw.config.js来给用户进行是否覆盖的提示:

const path = require('path');
const fs = require('fs');
const chalk = require('chalk');
const figlet = require('figlet');
const inquirer = require('inquirer');

function copyLlscwConfigJS(){
  figlet('llscw cli', function(err, data) {
    if(err){
      console.log(chalk.red('Some thing about figlet is wrong!'));
    }
    console.log(chalk.yellow(data));
    let targetFilePath = path.resolve('llscw.config.js');
    let templatePath = path.join(__dirname,'../tpl/llscw.config.js');
    let contents = fs.readFileSync(templatePath,'utf8');
    fs.writeFileSync(targetFilePath,contents,'utf8');
    console.log(chalk.green('初始化配置成功 \n'));
    process.exit(0);
  });
}

module.exports = function(){
  // 配置文件如果存在则提示是否覆盖
  if(fs.existsSync(path.resolve('llscw.config.js'))){
    // 连续提问
    inquirer.prompt([
      {
        name:'init-confirm',
        type:'confirm',
        message:`llscw.config.js 已经存在,是否覆盖?`,
        validate: function(input){
          if(input.lowerCase !== 'y' && input.lowerCase !== 'n' ){
            return 'Please input y/n !'
          }
          else{
            return true;
          }
        }
      }
    ])
      .then(answers=>{
        if(answers['init-confirm']){
          copyLlscwConfigJS();
        }
        else{
          process.exit(0);
        }
      })
      .catch(err=>{
        console.log(chalk.red(err));
      })
  }
  else{
    copyLlscwConfigJS();
  }
};

通过figletnpm包来在终端中展示自定义的签名,这段代码核心就是通过node向本地写入脚手架对应的配置文件。

到此,一个简易的脚手架完成了,可以看到,脚手架本质其实就是把你编写好的项目模版,在需要使用的时候方便的copy下来,避免每次重新搭建或手动复制粘贴啥的

项目git地址:github.com/lqt0327/-ll…

上面这些代码是我早期入门写的,在草稿箱里搁了半年多了2333,比较简单粗糙,不过想了想,也算是自我成长的记录,而且简单点才适合初学者看嘛,就不重写了

后期完善项目,git地址:github.com/lqt0327/-ll…
集成了以下脚手架(持续迭代完善中……):

  • vue2 预渲染
  • react17 同构
  • react17 + router v6

感兴趣的可以先看看,后期也会单独沉淀相关文章