前端脚手架开发原理

最近有在折腾本身的脚手架,没搞过这玩意,查阅了大量文章,有所收获,因而有了这篇记录。但愿对一些童鞋有所帮助。前端

了解 vue-cli 基本原理

要了解前端脚手架的原理,能够拿咱们最熟悉的 vue-cli 这个脚手架来讲说,使用过的童鞋都知道使用脚手架建立 vue 项目时,须要使用命令:vue

vue create 项目名
复制代码

你是否想过这句命令的原理呢,当这句命令执行时,它在背后到底进行了怎样的操做呢?node

当咱们在电脑上安装完 vue-cli 后,就可使用一个 vue 的命令。在使用命令前,咱们能够先查看一下帮助信息,在终端中输入 vue --help 命令就能够看到一些帮助信息,以下:linux

$ vue --help
Usage: vue <command> [options]

Options:
  -V, --version                              output the version number
  -h, --help                                 output usage information

Commands:
  create [options] <app-name>                create a new project powered by vue-cli-service
  add [options] <plugin> [pluginOptions]     install a plugin and invoke its generator in an already created project
  invoke [options] <plugin> [pluginOptions]  invoke the generator of a plugin in an already created project
  inspect [options] [paths...]               inspect the webpack config in a project with vue-cli-service
  serve [options] [entry]                    serve a .js or .vue file in development mode with zero config
  build [options] [entry]                    build a .js or .vue file in production mode with zero config
  ui [options]                               start and open the vue-cli ui
  init [options] <template> <app-name>       generate a project from a remote template (legacy API, requires @vue/cli-init)
  config [options] [value]                   inspect and modify the config
  outdated [options]                         (experimental) check for outdated vue cli service / plugins
  upgrade [options] [plugin-name]            (experimental) upgrade vue cli service / plugins
  migrate [options] [plugin-name]            (experimental) run migrator for an already-installed cli plugin
  info                                       print debugging information about your environment

  Run vue <command> --help for detailed usage of given command.
复制代码

从输出信息上能够看到 vue 是主命令,当咱们须要使用脚手架的一些操做命令时,须要以vue <command> [options] 这样的方式去调用, 全部的操做都是基于这个主命令拓展的,好比 --help,它属于 Options,能够输出帮助文档, 又好比 create 命令,他属于 Commands,它也有本身的使用格式,可使用命令 vue create --help 查看,以下:webpack

$ vue create --help
Usage: create [options] <app-name>

create a new project powered by vue-cli-service

Options:
  -p, --preset <presetName>       Skip prompts and use saved or remote preset
  -d, --default                   Skip prompts and use default preset
  -i, --inlinePreset <json>       Skip prompts and use inline JSON string as preset
  -m, --packageManager <command>  Use specified npm client when installing dependencies
  -r, --registry <url>            Use specified npm registry when installing dependencies (only for npm)
  -g, --git [message]             Force git initialization with initial commit message
  -n, --no-git                    Skip git initialization
  -f, --force                     Overwrite target directory if it exists
  --merge                         Merge target directory if it exists
  -c, --clone                     Use git clone when fetching remote preset
  -x, --proxy <proxyUrl>          Use specified proxy when creating project
  -b, --bare                      Scaffold project without beginner instructions
  --skipGetStarted                Skip displaying "Get started" instructions
  -h, --help                      output usage information
复制代码

能够看到,即便是一个子命令,它的使用参数也是不少的,由此也说明了 vue-cli 脚手架在背后作了不少工做,可是咱们也没必要惊慌于脚手架的复杂,任何复杂的东西都不是一步到位的,咱们能够先看看最基础的 vue 命令是怎么实现的。git

vue 命令的真身

在终端能够直接执行 vue 命令,说明它是一个全局命令,在个人 mac 电脑上,可使用 which vue 命令来查找它的位置:github

$ which vue
/usr/local/bin/vue
复制代码

/usr/local/bin/目录存放了不少全局命令变量,包括 vue,咱们能够在这个目录下使用 ll vue 来看下 vue 命令的详情:web

$ ll vue
lrwxr-xr-x  1 wangjian  admin    39B  6  2 14:17 vue -> ../lib/node_modules/@vue/cli/bin/vue.js
复制代码

能够看到,vue 实际上是一个软连接,它指向的地址是../lib/node_modules/@vue/cli/bin/vue.js,这个地址实际上是 npm 全局安装的包的存放地址。还记得咱们安装脚手架时的包名就是 @vue/cli 吗,它在全局注册的 vue 命令其实就是指向这个包文件下的 bin/vue.js 文件。如今咱们明白了使用命令 vue 时,执行的程序是 vue.js。其实就是:vue-cli

vue -V

// 等同于下面

/usr/local/lib/node_modules/@vue/cli/bin/vue.js -V

复制代码

/usr/local/lib/node_modules/ 是我电脑上 npm 全局安装包的存放位置shell

那么问题来了,vue.js 是怎么运行起来的?js 文件是不能直接在终端中执行的。

.js 文件怎么直接运行在终端

js 文件主要有两种执行方式,一个是在 web 页面中执行,另外一个就是使用 node 执行。很显然上述 vue.js 文件的执行应该是属于第二种,即运行在 node 环境中。那么是在哪里执行的呢,咱们并无找到相似于 node. vue.js 这样的执行入口。

其实答案就在 vue.js 文件自己。当咱们打开这个文件就能够看到文件开头第一行写着:

#!/usr/bin/env node
复制代码

这句代码的做用就是会在所处电脑系统上自动查找 node 变量,而后使用 node 来执行当前脚本文件。意思就是说当 js 文件的头部加上这一句后,就可使用 ./ 的形式来执行这个文件。能够测试一下,写一个简单的 js 脚本,就叫 test.js 吧:

#!/usr/bin/env node

console.log("hello world")
复制代码

注意,若是是 mac 系统或者 linux 系统,须要给这个文件赋权 chmod 755 test.js,而后就能够执行了,以下:

image.png

可以在终端直接调用 js 文件是开发脚手架的一个关键知识点,下面咱们就尝试作一个简单的脚手架。

一个简单的脚手架雏形

咱们能够尝试搭建一个简单的脚手架雏形,它的功能没必要完备,只要咱们理解了开发流程就能够本身拓展。

随便找一个目录建立项目,而后 npm 初始化:

mkdir my-cli
cd my-cli
npm init -y
复制代码

而后在项目目录中新建一个入口文件 index.js(名字随意),在文件中随意写上一些 js 语句,记得首行要加上 #!/usr/bin/env node,像这样:

#! /usr/bin/env node

console.log("This is my cli tool")
复制代码

此时项目结构很简单:

my-cli
├── index.js
└── package.json
复制代码

咱们还须要在 package.json 文件中配置 bin 选项,它是配置各个内部命令对应的可执行文件的位置,修改以下:

{
  "name": "my-cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "bin": {
    "hi": "index.js"  // 此处添加 bin 命令
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
复制代码

bin 能够配置成一个对象,每个 key 名都会被注册成一个命令名,这里我配置成 hi 命令。说到这里引出一个问题,你知道为何咱们安装脚手架时安装的包是 @vue/cli,但最后执行的倒是 vue 命令吗?就是由于 vue-cli 脚手架在注册 bin 命令时,key 名为 vue

此时我但愿在终端输入 hi 时,就能够执行 index.js 文件,当咱们把这个项目发布到 npm 上,而后本地全局安装后能够实现这样的功能,但咱们本地调试有一个更简单的方法,就是使用 npm link,它能够将项目连接到全局,达到等同于全局安装的效果。在项目中执行 npm link,以下:

image.png

mac 电脑须要加上 sudo 执行。此时咱们查看下全局环境下是否有 hi 命令:

image.png

有了,再使用 ll /usr/local/bin/hi 查看下详细信息:

image.png

图中能够看到 my-cli 项目已经出如今 npm 全局安装路径中,hi 命令指向的正是项目中的 index.js 文件。来验证下 hi 命令:

image.png

hi 命令生效了。而且因为使用的是 npm link 方式,因此当你修改 index.js 文件时,是能够同步更新 hi 命令执行结果的。到这里,脚手架的第一步已经完成,接下来就是拓展命令操做而已,其实本质上就是在 index.js 文件中编写 nodejs 代码。

实现 hi init 命令

咱们能够尝试拓展一个 init 命令,这里只写思路,不涉及特别复杂的 init 内容。

当咱们在终端输入 hi init,但愿可以识别到 init 参数,并执行对应逻辑。在 nodejs 中,可使用 process 模块中的 argv 参数获取命令行参数,将 my-cli 项目中的 index.js 文件改成以下内容:

#! /usr/bin/env node

const process = require('process')

console.log('argv=>>',process.argv)
复制代码

而后在终端执行 hi init

image.png

能够看到 process.argv 数组的第三项开始就是咱们在 hi 命令后输入的参数,这样就好办了,咱们再改下代码:

#! /usr/bin/env node

const process = require('process')

if(process.argv[2] === 'init'){
    init()
}

function init(){
    console.log('start init')
}
复制代码

此时在终端执行 hi init

image.png

init 命令生效,实际开发中就能够写入对应逻辑,这里再也不扩展说明了。

使用 commanderjs 插件来开发

真实脚手架开发中,须要定义的操做命令可能会有不少,就如 vue-cli 那样,此时须要借助插件来解析参数,若是不用插件,就须要本身一个个来判断所输入的参数,影响效率且容易出错。这里推荐 commander 插件,具体使用方法请参考文档,我这里列举一个使用插件的小例子:

#! /usr/bin/env node

const process = require('process')
const {program} = require('commander')

program
    .version('0.0.1')
    .option('-i, --init','用来初始化项目的')
    .option('-r, --remove','用来删除的')
    .parse(process.argv)


const options = program.opts()

if(options.init){
    console.log('正在执行初始化操做');
}

if(options.remove){
    console.log('正在执行删除操做');
}
复制代码

此时在终端输入对应命令的效果:

image.png

image.png

image.png

image.png

是否是像那么回事了。

结束

以上就是一个前端脚手架开发的基本原理,了解了这些基础,就能够慢慢开发一个本身的脚手架了。固然开发一个能用于生产的脚手架不是像写 demo 那样简单的,须要学习的还有不少。以后有时间我会继续记录开发脚手架的相关文章,共同窗习!