搭一个脚手架,考验了你的 nodejs 水平、工程化能力、以及工具服务的设计能力,是前端进阶不可或缺的过程前端
笔者在开发 cli 的过程当中,调研流行的 cli 并造成最佳实践,本文旨在用最短的篇幅实现主要功能,揭露核心原理,同时提供 demo 仓库与你们学习探讨。vue
通篇阅读大约须要 10 分钟,基于本教程本身撸一个 cli 大约须要花费 15 分钟node
其实脚手架的初衷,就是提供一个最佳实践的基础模板,所以模板拷贝是其核心功能webpack
几年前我曾写过一个极简的脚手架,大该干了这么一件事儿git
一个命令,就能够把我预设的完整的工程目录建立好,特别方便效率。github
我想,这应该算是一个雏形脚手架吧web
上面雏形脚手架能够很好的服务于我的需求,可是毕竟过于干瘪和简陋,要想成为被你们普遍接受的工具,还须要完善。chrome
你们熟知的 vue-cli
create-react-app
@tarojs/cli
umi
最基本功能:首先提出一些列问题选项,而后为你的新建项目提供一份模板并安装依赖,再提供调试构建命令vue-cli
没错,最核心的部分就是这个思路;但若是要作成一个可伸缩的、用户友好的,还需考虑这些需求:
看起来信息量有点大,但其实都并不晦涩,咱们一一说明一下意图
好比用户使用 v1.0.0 的模板建立了项目,半年后,已经迭代升级到了 v2.0.0。咱们须要依旧可以找到 v1.0.0 版本,由于老用户不想或者不方便升级。
像我以前的雏形脚手架,将模板打一个压缩包放在云服务器上是不可行的,一旦更新就全量替换了
npm 仓库自然支持版本管理,所以将模板发布到 npm 上天然解决了这个问题 (非开源项目,可考虑自建仓库或者私有的仓库)
好比咱们一开始咱们的脚手架支持 H5 的模板。
半年后,随着业务发展,需支持微信小程序的模板。
此时,咱们无需额外再开发一个 cli,而是让 cli 一开始设计的就支持扩展,这符合了开放封闭的设计原则
npm 提供了一些命令来检测包的版本,好比你 npm view react version
返回 16.9.0
,告知你最新版本
借此,能够判断用户目前安装的是否最新版本,并提示用户更新
模板虽然说是为了统一,但也要在统一中支持差别,可经过问询用户,来提供差别化支持,好比:
这些问询的结果,将影响咱们最终的模板,好比咱们根据是否 TypeScript 会在两套预设的模板中选一个套,将用户输入的「项目介绍」插入 package.json 的 description 字段等等
合适的格式、颜色、字体、进图条等,给与用户良好的信息反馈
下文会介绍一些经常使用的库,来提供这些功能
咱们一般使用 webpack 来构建/调试,对于不一样的模板,构建流程存在较大差别,咱们须要支持为不一样的模板配置不一样的构建
所以构建能力也被抽离成单独的 npm 包,模板中可指定其构建包
由于存在多版本,咱们须要约束,让全部项目的贡献者的产出是一致的
其核心原则就是:针对那些可能致使差别的因素,咱们都收录到工程中,让 git 仓库记录,从而实现一样,所以,如今流行的脚手架,如 umi
taro
,都将 构建能力 local 化到本地工程中,后续会作详细阐明
一个被实践检验,可以符合上述需求的脚手架架构,其实很是简单,首先咱们拆分红三类 npm 包:
包 | 功能 | 安装位置 | 备注 |
---|---|---|---|
全局命令包 | 就像一个大脑,负责响应全局命令,并进行调度 | 全局包路径 | global 安装,提供全局命令 |
模板插件包 | 初始化工程所拷贝的模板 | 某个约定路径,如 ~/.maoda |
模板可随业务扩展 |
构建插件包 | 提供构建(webpack)能力 | 工程内 (目前主流脚手架都改用此方案) | 不一样模板可以使用同一构建包,也可不一样 |
注:构建插件包,早期不少脚手架都把它放在工程外,好比放在全局,优点是多工程可复用一套 webpack 能力,但弊端也暴露出来,即在多人协同开发的项目中,因为构建插件包不在工程里没能被 git 仓库收录,致使一些不可预期的差别结果。
其调度关系以下:
前面说了一通理论,下面开始正式搭建
全局命令包的功能:负责接收全局命令,并调度。
好比我作的 cli 的模板 demo cli-tpl
npm i cli-tpl -g # 或 yarn global add cli-tpl 复制代码
全局安装后,暴露出一个 dcli
命令 (本身随便取的名字),该命令有如下典型功能:
暴露全局命令经过 package.json 中 bin 来指定,可参考个人 demo
命令 | 效果 |
---|---|
dcli install [pkgName] |
安装一个「模板插件包」到 ~/.maoda 路径,若是已经安装再执行,则询问更新到最新版,如安装 dcli install gen-tpl |
dcli init |
以某个模板初始化一个新工程,执行后会让你从已装模板里选择 |
dcli build |
在工程根目录执行 (或写进工程的 scripts 里),尝试读取工程依赖的「构建插件包」并执行构建 |
dcli dev |
与 dcli build 相似,只不过是执行调试 |
重要性 | 包名称 | 功能 |
---|---|---|
必要 | minimist | 解析用户命令,将 process.argv 解析成对象 |
必要 | fs-extra | 对 fs 库的扩展,支持 promise |
必要 | chalk | 让你 console.log 出来的字带颜色,好比成功时的绿色字 |
必要 | import-from | 相似 require,但支持指定目录,让你能够跨工程目录进行 require,好比全局包想引用工程路径下的内容 |
必要 | resolve-from | 同上,只不过是 require.resolve |
必要 | inquirer | 询问用户并记录反馈结果,界面互动的神器 |
必要 | yeoman-environment | 【核心】用于执行一个「模板插件包」,后文详细描述 |
锦上添花 | easy-table | 相似 console.table,输出漂亮的表格 |
锦上添花 | ora | 提供 loading 菊花 |
锦上添花 | semver | 提供版本比较 |
锦上添花 | figlet | console.log出一个漂亮的大logo |
锦上添花 | cross-spawn | 跨平台的child_process (跨 Windows/Mac) |
锦上添花 | osenv | 跨平台的系统信息 |
锦上添花 | open | 跨平台打开 app,好比调试的时候开打 chrome |
命令的解析与分发,是「全局命令包」的核心功能,其过程比较简单。你们也能够直接看仓库 cli-tpl (所有功能压缩到大约300行代码)
npm view cli-tpl version
命令查询当前 npm 库最新版本dcli install
可拿到 install
(正式版推荐使用 minimist 解析参数)install.js
文件来处理该逻辑require('./scripts/' + command)
这样,若是 command 是 install
则映射执行 script/install.js
文件接下来咱们看下 4 个核心命令,主要是:
命令 | 效果 |
---|---|
install | 帮用户安装/升级一个「模板插件包」 |
init | 帮用户初始化一个工程,并拷贝模板 |
build | 调用工程中的「构建插件包」,帮用户webpack构建 |
dev | 帮用户启动 devServer 进行调试 |
下面逐一阐述每一个命令的实现过程以及效果:
install 意思就是把这个模板插件包下载到硬盘;此处我作了一个最小功能的 demo 包 gen-tpl (后文详细分解) 来辅助讲解
dcli install gen-tpl
复制代码
核心处理流程以下:
~/.maoda
下是否已经有安装过 gen-tpl
包
~/.maoda
目录下执行 npm install)execSync('npm i gen-tpl@latest -S', { cwd: '~/.maoda' })
咱们能够为「模板插件包」的名称作一个约定,即具有固定的前缀,诸如
gen-xxx
这是一个脚手架高频而核心的功能
dcli init
复制代码
此时会分发去执行 script/init.js
文件,咱们看看其逻辑
~/.maoda
下的 package.json
文件,读取其中 dependacies
字段,拿到已安装的「模板插件包」
inquery
库发起对话,罗列出已装模板,让用户选择,好比上图的 gen-pc
gen-h5
gen-tpl
gen-tpl
这个模板,则用 yeoman-environment
这个库去执行缓存目录里的这个包 ~/.maoda/gen-tpl/index.js
import-from
这个库这里直接用包名称作选项,为了演示更直观,实际一般用包的 description 作选项,更友好一些,好比
gen-pc
包可能描述为生成PC模板
dcli build
复制代码
maoda.js
(采用约定式的配置,相似 webpack.config.js
.babelrc
.prettierrc
)maoda.js
中 builder
配置项 (即指定的构建插件包),好比本 demo 中指定为 build-tpl
build-tpl
npm install
(或 yarn add,此处有个小技巧,可根据用户工程中 lock 文件的类型,判断用户使用的 npm 仍是 yarn)build-tpl
一般,咱们用配置文件指明「构建插件包」,也能够直接在命令里指明,好比 dcli build --builder=build-h5;后者每每适用于一套代码打包出多种结果,如京东的 Taro cli
平时你们用惯了 npm run build
yarn build
,只需在咱们的模板中的 package.json
添加一行:
{ "script": { ++ "build": "dcli build" } } 复制代码
相似 build 只不过 webpack 配置不一样,此处略
核心功能:提供模板文件夹 + 文件夹的拷贝。这里一样提供了一个样例工程 gen-tpl (仅 50 行代码)
处理流程以下:
name: <%= packageName %>
填充成 name: 个人工程
【重点来了】看似流程蛮多,其实只用一个现成的轮子便可搞定,即 yeoman-generator,它帮咱们把这些过程都封装好了,咱们只需继承基类,并写几个预设的生命周期函数便可,无脑到使人发指 (细节处理,可参考模板仓库)
module.exports = class extends Generator { // 【问询环节】 prompting() { return this.prompt([ { type: 'input', name: 'appName', message: '请输入项目名称:', }, { type: 'list', choices: ['Javascript', 'TypeScript'], name: 'language', message: '请选择项目语言', default: 'TypeScript', }, ]).then(answers => { this.answers = answers }) } // 【模板拷贝】 writing() { // 从模板路径拷贝到工程路径 this.fs.copy(this.templatePath(), this.destinationPath()) } // 【安装依赖】 install() { this.installDependencies() } end() { this.log('happy coding!') } } 复制代码
很明显,「模板插件包」导出的是一个 class,咱们须要经过上文提到的「全局命令包」里的 yeoman-environment
来启动:
// 【节选自 全局命令包 init 命令,略修改以增长可读性】 yoemanEnv.register(resolveFrom('./maoda', 'gen-tpl'), 'gen-tpl') yoemanEnv.run('gen-tpl', (e, d) => { d && this.console('happy coding', 'green') }) 复制代码
这里一样用到前文提到的 resolve-from
包,进行跨目录的引用解析
yeoman 是一个比较完善的生态,模板插件包可用 yeoman 提供的全局命令 yo 来建立,但并不是必要,此处就不展开说了
一样咱们提供了一个构建插件包的模板 build-tpl (20行代码,启动 webpack),webpack 配置都是空的,你们在开发过程当中可自行定制
构建插件包其实核心就是 webpack 能力,webpack 能力这里就不展开说了,这里只描述一下调用关系
以 dcli build
为例,「全局命令包」在收到 build 命令后,启动「构建插件包」
importFrom(process.cwd(), 'build-tpl') 复制代码
没错,就是这么简单,import-from 库能跨文件目录,指定使用特定目录的文件;使得全局包能够直接去执行工程目录的包 效果与同工程下 require('build-tpl')
同样
此处也可使用 import-cwd 库
而 build-tpl 这个构建插件包,负责将内置的 webpack.config.js 与用户工程下自定义的 webpackCustom 进行 merge,而后执行 webpack 流程
固然,构建工具不必定非要使用 webpack,好比能够选择 rollup 或者像 Taro 在构建小程序代码时候,本身建立一套工具
笔者认为,只有够精简,才能下降入门门槛,才能强化记忆;所以,本文的案例,在成熟的脚手架上进行不断删减,剔除掉哪些徒增记忆负担的部分,只保留精髓和核心,旨在快速在脑海里建模出一个企业级脚手架
同时提供了脚手架 3 个组成部分的 仓库/npm 包,以增长可操做性
如需引用与实际开发中,咱们须要继续丰满其血肉,包括但不限于:
文章博客地址:github.com/imaoda/js-f… 欢迎批评指正