阅读 363

超简单的脚手架开发教程,按需拉取模版,自动安装依赖

平时在工作中我们都会用一些脚手架工具来快速构建项目的原型,比如Vue的vue-cli,React的create-react-app等;但是因为这些脚手架是通用型的,少不了还要对其进行二次封装,比如配置CSS、配置网络请求axios、配置UI库等等;既然这样的话那就有必要根据自己的需求来定制属于自己的脚手架。

项目效果

Untitled.gif

1584715957953.jpg

1584716138618.jpg

项目核心依赖

  • commander 命令行工具
  • download-git-repo git仓库代码下载
  • chalk 命令行输出样式美化
  • Inquirer.js 命令行交互
  • ora 命令行加载中效果
  • cross-spawn 执行依赖安装命令

相关依赖的用法,请到对应的github仓库查看,我们只会用到最基本的用法,稍微看一下就知道怎么用了。

项目结构

├── bin
│   └── www             //可执行文件
├── dist
    ├── ...             //生成文件
└── src
    ├── index.ts        //主流程入口文件
    ├── init.ts         //脚手架初始化
    └── utils
        ├── constants.ts //定义常量
        ├── install.ts       //安装依赖
        ├── downloadGitRepo.ts   //拉取远程模版
        └── downloadGitRepo.d.ts       // 类型声明
├── .babelrc         //babel配置文件
├── tsconfig.json    //typescript配置文件
└── package.json     //包管理

复制代码

初始化项目

创建一个空项目(gong-cli)(名字随意,如果准备发布到npm,就不能和别人的重名),使用 npm init -y进行初始化。

gong-cli 命令

node.js 内置了对命令行操作的支持,package.json 中的 bin 字段可以定义命令名和关联的执行文件。在 package.json 中添加 bin 字段

package.json

{
    "name": "gong-cli",
    "version": "1.0.0",
    "description": "a simple cli",
    "main": "dist/index.js",
    "files": [   // 上传到npm的文件
      "bin",
      "dist/**/*.js"
    ],
    "bin": {
      "gong-cli": "./bin/www"
    },
    "scripts": {
      "compile": "babel src -d dist --extensions '.ts' ",
      "watch": "npm run compile -- --watch"
    },
}

复制代码

www

#!/usr/bin/env node
require('../dist/index.js'); // 通过babel把ts编译成js再运行
复制代码

在当前 gong-cli 的根目录下执行 npm link,将 gong-cli 命令链接到全局环境;这样在开发环境中,就可以运行gong-cli相关命令。

入口文件

./src/index.ts

import commander from 'commander'
import { VERSION } from './utils/constants'
const program = new commander.Command()

program
  .command('init <projectName>')
  .description('gong-cli init')
  .action(() => {
    require('./init')(...process.argv.slice(3))
  })

program.version(VERSION, '-v --version')

program.parse(process.argv)
复制代码

在入口文件中我们通过command定义了一个init <projectName>命令,其中projectName由用户自己输入,不能为空;比如:

gong-cli init demo
复制代码

当用户输入该命令后,即开始加载初始化函数init,传入的参数为字符串demo

模版初始化

./src/init.ts

import { download } from './utils/downloadGitRepo'
import { install } from './utils/install'
import { promisify } from 'util'
import ora from 'ora'
import inquirer from 'inquirer'
import fs from 'fs'
import chalk from 'chalk'
import path from 'path'

const exist = promisify(fs.stat)

const init = async (projectName: string) => {
  const projectExist = await exist(projectName).catch(err => {
    // 处理除文件已存在之外的其他错误
    if (err.code !== 'ENOENT') {
      console.log(chalk.redBright.bold(err))
    }
  })
  // 文件已存在
  if (projectExist) {
    console.log(chalk.redBright.bold('The file has exited!'))
    return
  }
  // 接收用户命令
  inquirer
    .prompt([
      {
        name: 'description',
        message: 'Please enter the project description',
      },
      {
        name: 'author',
        message: 'Please enter the project author',
      },
      {
        type: 'list',
        name: 'language',
        message: 'select the develop language',
        choices: ['javaScript', 'typeScript'],
      },
      {
        type: 'list',
        name: 'package',
        message: 'select the package management',
        choices: ['npm', 'yarn'],
      },
    ])
    .then(async answer => {
      // 下载模板 配置相关信息
      let loading = ora('downloading template...')
      loading.start()
      loading.color = 'yellow'
      download(projectName, answer.language).then(
        async () => {
          loading.succeed()
          const fileName = `${projectName}/package.json`
          if (await exist(fileName)) {
            const data = fs.readFileSync(fileName).toString()
            let json = JSON.parse(data)
            json.name = projectName
            json.author = answer.author
            json.description = answer.description
            fs.writeFileSync(
              fileName,
              JSON.stringify(json, null, '\t'),
              'utf-8',
            )
            console.log(chalk.green('Project initialization finished!'))
            console.log()
            console.log(chalk.yellowBright('start install dependencies...'))
            // 安装依赖
            await install({
              cwd: path.join(process.cwd(), projectName),
              package: answer.package,
            }).then(() => {
              console.log()
              console.log('We suggest that you begin by typing:')
              console.log()
              console.log(chalk.cyan('  cd'), projectName)
              console.log(`  ${chalk.cyan(`${answer.package} start`)}`)
            })
          }
        },
        () => {
          loading.fail()
        },
      )
    })
}

module.exports = init

复制代码

进入init初始化函数后,首先判断是否存在和demo同名的文件夹,如果有抛出错误让用户重新输入;然后通过inquirer获取用户在终端输入的信息,这里我们只先获取用户定义的项目描述、项目作者、项目开发语言、项目包管理四个字段。然后通过download去下载对应的模版,下载完模版之后再通过install函数安装依赖。整个过程到这就结束了,还是比较简单的。

拉取远程模版

./src/utils/downloadGitRepo.ts

import downloadGit from 'download-git-repo'

export const download = async (projectName: string, language: string) => {
  // 这里先用了巨硬家的react模版
  let api = 'microsoft/'
  language === 'javaScript'
    ? (api = api + 'vscode-react-sample')
    : (api = api + 'TypeScript-React-Starter')
  return new Promise((resolve, reject) => {
    downloadGit(api, projectName, (err: any) => {
      if (err) {
        reject(err)
      }
      resolve()
    })
  })
}

复制代码

安装依赖

./src/utils/install.ts

const spawn = require('cross-spawn')
interface installProps {
  cwd: string  // 项目路径
  package: string //包管理器 yarn 或者 npm
}
export const install = async (options: installProps) => {
  const cwd = options.cwd
  return new Promise((resolve, reject) => {
    const command = options.package
    const args = ['install', '--save', '--save-exact', '--loglevel', 'error']
    const child = spawn(command, args, {
      cwd,
      stdio: ['pipe', process.stdout, process.stderr],
    })

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

复制代码

启动项目

yarn watch

打包发布到npm

运行打包命令yarn compile,生成的文件在dist文件夹下;然后运行npm adduser登录自己的npm账户,最后运行npm publishbindist两个文件夹的内容发布到npm上。其它用户通过 npm install gong-cli -g 全局安装后, 即可使用 gong-cli 命令安装脚手架。

完整源码地址