最近有在折腾自己的脚手架,没搞过这玩意,查阅了大量文章,有所收获,于是有了这篇记录。希望对一些童鞋有所帮助。
了解 vue-cli 基本原理
要了解前端脚手架的原理,可以拿我们最熟悉的 vue-cli 这个脚手架来说说,使用过的童鞋都知道使用脚手架创建 vue 项目时,需要使用命令:
vue create 项目名
你是否想过这句命令的原理呢,当这句命令执行时,它在背后到底进行了怎样的操作呢?
当我们在电脑上安装完 vue-cli 后,就可以使用一个 vue 的命令。在使用命令前,我们可以先查看一下帮助信息,在终端中输入 vue --help 命令就可以看到一些帮助信息,如下:
$ 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 查看,如下:
$ 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 命令是怎么实现的。
vue 命令的真身
在终端可以直接执行 vue 命令,说明它是一个全局命令,在我的 mac 电脑上,可以使用 which vue 命令来查找它的位置:
$ which vue
/usr/local/bin/vue
/usr/local/bin/目录存放了很多全局命令变量,包括 vue,我们可以在这个目录下使用 ll vue 来看下 vue 命令的详情:
$ 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 -V
// 等同于下面
/usr/local/lib/node_modules/@vue/cli/bin/vue.js -V
/usr/local/lib/node_modules/ 是我电脑上 npm 全局安装包的存放位置 那么问题来了,
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,然后就可以执行了,如下:
能够在终端直接调用 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,如下:
mac 电脑需要加上 sudo 执行。此时我们查看下全局环境下是否有 hi 命令:
有了,再使用 ll /usr/local/bin/hi 查看下详细信息:
图中可以看到 my-cli 项目已经出现在 npm 全局安装路径中,hi 命令指向的正是项目中的 index.js 文件。来验证下 hi 命令:
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:
可以看到 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:
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('正在执行删除操作');
}
此时在终端输入对应命令的效果:
是不是像那么回事了。
结束
以上就是一个前端脚手架开发的基本原理,了解了这些基础,就可以慢慢开发一个自己的脚手架了。当然开发一个能用于生产的脚手架不是像写 demo 那样简单的,需要学习的还有很多。之后有时间我会继续记录开发脚手架的相关文章,共同学习!