从零搭建一个简单的 Vue 脚手架

脚手架预览图

前言

编程中的脚手架,我理解是通过机器自动生成开发前的基础代码,像建筑工程开始时搭建的架子,然后工人不断砌砖,直到建筑完成。脚手架有助于我们专注于 code,使我们更快、更好的完成项目开发。

虽然市面上有 vue-cli 这样优秀的脚手架,但是它所提供的基础代码,还不能满足我日常开发需求,因此在此基础上自定义项目模板。

为了方便拉取项目模板,自己学习了如何搭建前端脚手架,所以这篇文章也是记录自己的学习过程。

项目地址:Github在新窗口打开

开始

在开始之前,我们先看下使用到的 npm 依赖包有哪些?

新建项目

  1. 在空文件夹下面初始化:
$ npm init -y

文件目录大概如下:

project
│  .npmignore
│  package.json
│  README.md
│
├─bin
│     cli.js
└─util
      checkVersion.js
      checkVersion.txt
      clearConsole.js
  1. package.json 文件中,加入 bin 字段:
{
    // ...
    "bin": {
        "httn-vue": "bin/cli.js"
    },
    // ...
}

bin 的作用是指定各个内部命令对应的可执行文件的位置。关于 bin 的更多解释戳这里在新窗口打开

  1. 新建对应的脚本命令文件 cli.js
#!/usr/bin/env node

console.log('hello world')

文件开头必须是 #!/usr/bin/env nodeusr/bin/env 表示可以去 PATH 目录中查找脚本解释器,同时指定使用 node 去执行该文件。

在当前文件夹下执行如下命令:

$ npm link
$ httn-vue

npm link 的作用是将 npm 模块链接到对应的运行项目中去,方便对模块进行调试和测试。更多解释戳这里在新窗口打开

终端输出结果:hello world。

核心文件

cli.js 引入相关依赖和文件

#!/usr/bin/env node

const path = require('path')
const pkg = require('../package.json')
const fs = require('fs-extra') // 操作系统文件
const { program } = require('commander') // 命令行
const inquirer = require('inquirer') // 询问交互
const semver = require('semver') // 判断 node 版本是否在某个范围
const chalk = require('chalk') // 彩色日志
const ora = require('ora') // 加载动画
const download = require('download-git-repo') // 拉取模板
const validateProjectName = require('validate-npm-package-name') // 验证包名合法性
const { checkVersion } = require('../util/checkVersion') // 检查脚手架版本
const { clearConsole } = require('../util/clearConsole') // 清空日志

const requiredVersion = pkg.engines.node
const log = console.log
const loading = ora({
  color: 'green',
  text: '加载中'
})
// ...

检查 node 版本

// ...
const checkNodeVersion = (wanted, id) => {
  if (!semver.satisfies(process.version, wanted, { includePrerelease: true })) {
    log(chalk.red(
      'You are using Node ' + process.version + ', but this version of ' + id +
      ' requires Node ' + wanted + '.\nPlease upgrade your Node version.'
    ))
    process.exit(1)
  }
}

checkNodeVersion(requiredVersion, 'httn-cli')
// ...

semver.satisfies(version, range, option):判断 version 是否在 range 这个范围,结果返回 Boolean 值。 { includePrerelease: true }:禁止从范围中排除预发布标记版本的默认行为,除非它们明确选择加入。

a命令行操作

// ...
program
  .name('httn-cli')
  .version(`httn-cli ${pkg.version}`, '-v --version')
  .usage('<command> [options]')
// ...

.usage.name:通过这两个选项可以修改帮助信息的首行提示,name 属性也可以从参数中推导出来。

$ httn-vue
Usage: httn-cli <command> [options]

.version() 其默认选项为 -V--version,设置了版本后,命令行会输出当前的版本号。

program.version(`httn-cli ${pkg.version}`)
$ httn-vue --version
1.0.0

版本选项也支持自定义设置选项名称,可以在 .version() 方法里再传递一些参数(长选项名称、描述信息)

program.version(`httn-cli ${pkg.version}`, '-v --version')
$ httn-vue -v
1.0.0

b命令行操作

program
  .command('create <app-name>')
  .description('创建项目')
  .action(projectName => {
      console.log(`你要创建的项目名称:${projectName}`)
  })
$ httnn-vue create my-project
你要创建的项目名称:my-project

.command() 的第一个参数为命令名称。命令参数可以跟在名称后面,也可以用 .argument() 单独指定。参数可为必选的(尖括号表示)、可选的(方括号表示)或变长参数(点号表示,如果使用,只能是最后一个参数)。

执行构建项目

$ httnn-vue create my-project
  1. 检查脚手架版本是否需要更新。
// ...
const latest = checkVersion() // 获取线上的脚手架版本
if (latest && semver.gt(latest, pkg.version)) {
    log(chalk.red(`当前版本过低,请及时更新版本至${latest}`))
    process.exit(1)
}
// ...

semver.gt(v1, v2):v1 > v2 成立的话返回 true。 chalk.red() 终端输出彩色的日志。

  1. 获取项目路径和检验文件名
// ...
const inCurrent = projectName === '.'
const cwd = process.cwd() // 当前目录
const newName = inCurrent ? path.relative('../', cwd) : projectName // 项目文件夹名称
const targetDir = path.resolve(cwd, projectName) // 获取项目文件夹绝对路径
const validateResult = validateProjectName(newName) // 检验文件名

if (!validateResult.validForNewPackages) {
    console.error(chalk.red(`Invalid project name: "${projectName}"`))
    validateResult.errors && validateResult.errors.forEach(err => {
        console.error(chalk.red.dim(`Error: ${err}`))
    })
    validateResult.warnings && validateResult.warnings.forEach(warn => {
        console.error(chalk.red.dim(`Warning: ${warn}`))
    })
    process.exit(1)
}
// ...
  1. 交互命令
// ...
clearConsole() // 清空日志
const answer = await inquirer.prompt([
    {
        type: 'input',
        name: 'name',
        message: '请输入项目名称',
    },
    {
        type: 'input',
        name: 'author',
        message: '请输入项目作者名',
    },
    {
        type: 'input',
        name: 'description',
        message: '请输入项目简介',
    },
    {
        type: 'list',
        message: '使用哪种模板开发',
        name: 'tmp',
        choices: ['VueBase', 'VueMobile'],
    }
])
// ...
  1. 拉取模板
// ...
download(downloadAdress(answer.tmp), targetDir, downloadCallBack.bind(null, answer, targetDir))

loading.color = 'green'
loading.text = '正在拉取模板'
loading.start()
// ...
  1. 修改 package.json 文件
// ...
const filename = path.join(targetDir, 'package.json') // 修改的文件路径
if (fs.existsSync(filename)) {
    let newPkgJson = fs.readFileSync(filename).toString()

    newPkgJson = JSON.parse(newPkgJson)

    newPkgJson.name = answer.name
    newPkgJson.author = answer.author
    newPkgJson.description = answer.description

    newPkgJson = JSON.stringify(newPkgJson, null, '\t')

    fs.writeFileSync(filename, newPkgJson)

    log('拉取模板成功!')
    log(`\n`)
    log(chalk.green(`第一步:$ cd ${answer.name}`))
    log(chalk.green(`第二步:$ yarn install or npm install`))
    log(chalk.green(`第三步:$ npm run serve`))
}
// ...

发布脚手架

参考之前写的文章

结语

脚手架搭建到这里就结束了。其实整体看下来还是比较基础的,之后脚手架有完善也会在这里更新,希望本篇文章可以帮助到你。npm 地址在新窗口打开

参考文献

如何搭建一个属于自己的脚手架在新窗口打开