脚手架闲聊 | 七日打卡

865 阅读4分钟

楔子

前端工程化日益盛行,脚手架成为了我们日常开发中必不可少的东西,例如:vue-cli、react-create-app 等等。

使用脚手架,我们可以很方便的简化我们的开发流程,有效的提高我们的研发能效

例如我们需要从零开始搭建一个项目,首先需要去创建目录,在个目录下去创建相应初始化的文件,去配置 webpack、gulp、rollup 等模块打包器,这往往会花费我们一天甚至数天的时间。然而脚手架只需要在命令行敲入简简单单的一行命令,即可在短短的几分钟内为你创建一个项目模板,极大程度上节省了我们的时间成本。

相信大部分的同学对脚手架的认识只停留在使用上面,今儿我们就来了解一下脚手架的原理。

注:本文章是使用 Node.js 进行脚手架开发的

脚手架的作用

大家可以想一想脚手架的作用是什么?开发脚手架的核心目标是什么?

没错,就是提升研发效能!

脚手架的核心价值

  1. 自动化:项目重复、代码拷贝、git 操作、发布上线操作
  2. 标准化:项目创建、git flow、发布流程、回滚流程
  3. 数据化:研发过程系统化、数据化、使研发过程可以被量化

和自动化构建工具的区别

有的同学可能会问到,世面上已经有了 jenkins、travis 等自动化构建工具了,而且也很成熟,为什么还要开发相关的脚手架?

  1. 需求不满足:jenkins、travis 通常在 git hooks 中触发,需要在服务端执行,无法覆盖研发人员本地的功能,如:创建项目自动化、本地 git 操作自动化等
  2. 定制复杂:jenkins、travis 定制过程需要开发插件,其过程较为复杂,需要使用 java 语言,对前端开发不够友好

脚手架的本质

脚手架本质上来说,其实就是一个操作系统的客户端。

它通过命令行来执行,例如:

vue create vue-project

脚手架的执行原理

为什么我们在命令行中输入 vue create vue-project 后就能执行呢?

用户在命令行中输入了命令 vue create vue-project ,操作系统首先会在全局的环境变量里面去查找 vue 这个环境变量,找到后就会去执行它,没有找到就会报错,如下图所示:

脚手架的实现原理

通过 npm 全局安装一个脚手架例如 @vue/cli 后,会去解析 package.json 文件中的 bin 配置,然后会去在 node 的安装目录下的 bin 目录中,创建一个软连接并且连接到软件包中,软连接的名称就是 bin 配置的 key(键值对的键),连接的文件就是 bin 配置的 value(键值对的值)value 指向的文件中需要设置 #! /usr/bin/env node 来标识文件执行的方法

实现一个最简单的脚手架

第一步:我们需要创建一个文件夹 test-cli;
第二步:在命令行中,进入到 test-cli 文件夹,然后执行 npm init 初始化项目;
第三步:修改 package.json 文件,在里面的配置项里加上如下的:

{
    ...
    "bin": {
      "test-cli": "./bin/index.js"
    },
    ...
}

第四步:在 test-cli 里面新建一个 bin 文件夹,并且在里面创建一个 index.js 文件,接下来打开 index.js 文件在里面第一行写上 #! /usr/bin/env node;
第五步:可以写上一些内容了,比如 console.log('xxx') 滑稽脸;
第六步:在命令行中,进入到 test-cli 文件夹,然后执行 npm link;
第七步:在命令行中,输入 test-cli 并且回车,你就会发现在命令行中就会打印出 xxx;

这就是一个最简单的脚手架了,如果你想做的更好一点的,比如常用脚手架的一些命令式操作可以了解一下 yargs、commander 两个插件,这里给上一个基于 commander 的注册命令的代码,有兴趣的同学可以复制到刚刚的 index.js 里面执行一下 test-cli --help

#! /usr/bin/env node

const commander = require('commander')
const pkg = require('../package.json')

// 获取 commander 的单例
// const { program } = commander

// 手动实例化一个 commander 实例
const program = new commander.Command()

program
    .name(Object.keys(pkg.bin)[0])
    .usage('<command> [options]')
    .version(pkg.version)
    .option('-d, --debug', '是否开启调试模式', false)
    .option('-e, --env <envName>', '获取环境变量名称', false)

// addCommand 注册子命令
const service = new commander.Command('service')
service
    .command('start [port]')
    .description('start service at some port')
    .action((prot, cmdObj) => {
        console.log('do server start', prot)
    })
service
    .command('stop')
    .description('stop service')
    .action(() => {
        console.log('do server stop')
    })


program.addCommand(service)

program.on('option:debug', () => {
    console.log('debug')
})

program.on('command:*', (obj) => {
    console.log('未知的命令', obj)
    const availableCommands = program.commands.map(cmd => cmd.name())
    console.log('可用的命令', availableCommands)
})

program    
    .parse(process.argv)

// console.log(program.debug)
// console.log(program.env)
// console.log(program.opts())

总结

脚手架的开发博大精深,它为我打开了一个新世界的大门,同时也让我对 Node.js 也有了更深入的了解。也是突破现有技术瓶颈的一个方向,也是作为一个高级前端开发工程师必须要掌握的技能。