如何快速开发⼀个⾃⼰的项⽬脚⼿架?
1. 脚⼿架怎么⼯作?
功能丰富程度不同的脚⼿架,复杂程度⾃然也不太⼀样。但是总体来说,脚⼿架的⼯作⼤体都会包含⼏个步骤:
初始化,⼀般在这个时候会进⾏环境的初始化,做⼀些前置的检查
⽤户输⼊,例如⽤ vue-cli 的时候,它会“问”你很多配置选项
⽣成配置⽂件
⽣成项⽬结构,这是候可能会使⽤⼀个项⽬模版
安装依赖
清理、校验等收尾⼯作
此外,你还需要处理命令⾏⾏为等。往往我们只是想轻量级、快速得创建⼀个特定场景的脚⼿架(不⽤想vue-cli那么完备)。⽽对于想要快速创建⼀个脚⼿架,其实我们不⽤完全从零开始。Yeoman 就是⼀个可
以帮我们快速创建脚⼿架的⼯具。
可能很多同学都不太了解,那么先简单介绍⼀下 Yeoman 是什么,⼜是如何帮我们来简化脚⼿架搭建的。
⾸先,Yeoman 可以简单理解为是⼀个脚⼿架的运⾏框架,它定义了⼀个脚⼿架在运⾏过程中所要经历的各个阶段(例如我们上⾯说的,可能会先读取⽤户输⼊,然后⽣成项⽬⽂件,最后安装依赖),我们所需要的就是在⽣命周期的对应阶段,填充对应的操作代码即可。⽽我们填充代码的地⽅,在 Yeoman 中叫做 generator,物如其名,Yeoman 通过调⽤某个 generator 即可⽣成(generate)对应的项⽬。
如果你还不是特别清楚它们之间的关系,那么可以举个⼩例⼦:
将脚⼿架开发类⽐为前端组件开发,Yeoman 的⾓⾊就像是 react,是⼀个框架,尤其是定义了组件的⽣命周期函数;⽽ generator 类似于你写的⼀个 React 业务组件,根据 React 的规则在各个⽣命周期中填代码即可。
Yeoman 内置的“⽣命周期”⽅法执⾏顺序如下:
1. initializing
2. prompting
3. default
4. writing
5. conflicts
6. install
7. end
其中 default 阶段会执⾏你⾃定义地各种⽅法。
同时,Yeoman 还集成了脚⼿架开发中常⽤的各类⼯具,像是⽂件操作、模版填充、终端上的⽤户交互功能,命令⾏等,并且封装成了简单易⽤的⽅法。
通过这两点,Yeoman 可以帮我们⼤⼤规范与简化脚⼿架的开发。
2. 开发⼀个⾃⼰的脚⼿架
了解了⼀些脚⼿架的⼯作⽅式与 Yeoman 的基本概念,咱们就可以来创建⼀个属于⾃⼰的脚⼿架。作为例⼦,这个脚⼿架的功能很简单,它会为我们创建⼀个最简版的基于 webpack 的前端项⽬。
2.1. 准备⼀个项⽬模版
脚⼿架是帮助我们快速⽣成⼀套既定的项⽬架构、⽂件、配置,⽽最常见的做法的就是先写好⼀套项⽬框架模版,等到脚⼿架要⽣成项⽬时,则将这套模版拷贝到⽬标⽬录下。这⾥其实会有两个⼩点需要关注。
第⼀个是模版内变量的填充。
在模版中的某些⽂件内容可能会需要⽣成时动态替换,例如根据⽤户在终端中输⼊的内容,动态填充package.json中的name值。⽽Yeoman 内置了 ejs 作为模版引擎,可以直接使⽤。
第⼆个就是模版的放置位置。
⼀种是直接放在本地,也就是直接放到 generator 中,跟随 generator ⼀起下载,每次安装都是本地拷贝,速度很快,但是项⽬模版⾃⾝的更新升级⽐较困难,需要提⽰⽤户升级 generator。
另⼀种则是将模版⽂件放到某个服务器上,每次使⽤脚⼿架初始化时通过某个地址动态下载,想要更新升级模版会很⽅便,通常会选择托管在 github 上。
关于第⼆个模版放置究竟是选择在本地好,还是远端好,其实还是依据你个⼈的业务场景⽽定,在不同
的场景的限制的需求不同,我之前既写过模版放在本地的脚⼿架(即和脚⼿架⼀起通过 npm 安装),也写过托管在 git 仓库上的这种⽅式。
回到我们「创建⼀个最简版的基于 Webpack 的前端项⽬」的⽬标,我准备了⼀个项⽬模版,之后就会⽤它来作为脚⼿架⽣成的项⽬内容。
2.2. 创建 generator(yeoman-generator)
创建 Yeoman 的 generator 需要遵循它的规则。
⾸先是 generator 命名规则。需要以generator打头,横线连接。例如你想创建⼀个名为 webpack-kickoff 的 generator,包名需要取
成 generator-webpack-kickoff。
这样,当你通过
npm i -g yo
安装完 Yeoman 的 CLI 后,就可以通过yo命令来使⽤ generator 来启动脚⼿架:
yo webpack-kickoff
这⾥的 webpack-kickoff 就是包名⾥generator-后⾯的内容,Yeoman 会按这个规则去全局相匹配的包。
其次,依据 Yeoman 的规范,默认情况下你需要在项⽬(即 generator)的generators/app/⽬录下创建index.js,在其中写⼊你的脚⼿架⼯作流程。当然,也可以通过修改配置来扩展或改变这个规则。
此外,你创建的 generator 类需要继承 yeoman-generator。所以我们会在generators/app/index.js中写如下代码:
const Generator = require('yeoman-generator');
class WebpackKickoffGenerator extends Generator {
constructor(params, opts) {
super(params, opts);
}
}
还记得之前提到的“⽣命周期”⽅法么?包括 initializing、prompting、default、writing、conflicts、install 和 end。除了default,其他都代表了Generator 中的⼀个同名⽅法,你需要的就是在⼦类中重写后所需的对应⽅法。default阶段则会执⾏⽤户定义的类⽅法。
例如,你想在初始化时打印下版本信息,可以这么做:
const Generator = require('yeoman-generator');
class WebpackKickoffGenerator extends Generator {
constructor(params, opts) {怎么看项目是什么框架
super(params, opts);
}
initializing() {
const version = require('../../package.json').version;
this.log(version);
}
}
可见,剩下的⼯作就是在 WebpackKickoffGenerator 类中填充各种⽅法的实现细节了。
2.3. 处理⽤户交互
脚⼿架⼯作中⼀般都会有⼀些⽤户⾃定义的内容,例如创建的项⽬⽬录名,或者是否启⽤某个配置等。这些交互⼀般都是通过交互式的终端来实现的,例如下⾯这个功能。
可以使⽤ Inquirer.js 来实现。⽽ Yeoman 已经帮我们集成好了,直接在 generator ⾥调⽤ this.prompt 即可。
在⽤户交互部分的需求也⽐较简单,只需要询问⽤户所需创建的项⽬⽬录名即可,随后也会作为项⽬名。
按照 Yeoman 的流程规范,我们将该部分代码写在 prompting ⽅法中:
class WebpackKickoffGenerator extends Generator {
// ……
prompting() {
const done = this.async();
const opts = [{
type: 'input',
name: 'dirName',
message: 'Please enter the directory name for your project:',
default: 'webpack-app',
validate: dirName => {
if (dirName.length < 1) {
return '⚠ directory name must not be null!';
}
return true;
}
}];
return this.prompt(opts).then(({dirName}) => {
this.dirName = dirName;
done();
});
}
/
/ ……
}
注意,由于⽤户交互是⼀个“异步”的⾏为,为了让后续⽣命周期⽅法在“异步”完成后再继续执⾏,需要调⽤this.async()⽅法来通知⽅法为异步⽅法,避免顺序执⾏完同步代码后直接调⽤下⼀阶段的⽣命周期⽅法。调⽤后会返回⼀个函数,执⾏函数表明该阶段完成。
2.4. 下载模版
正如2.1.中所述,我们选择将模版托管在 github 上,因此在⽣成具体项⽬代码前,需要将相应的⽂件下载下来。可以使⽤ download-git-repo 来快速实现。
class WebpackKickoffGenerator extends Generator {
// ……
_downloadTemplate() {
return new Promise((resolve, reject) => {
const dirPath = this.destinationPath(this.dirName, '.tmp');
download('alienzhou/webpack-kickoff-template', dirPath, err => {
if (err) {
reject(err);
return;
}
resolve();
});
});
}
// ……
}
这⾥我们使⽤了this.destinationPath()⽅法,该⽅法主要⽤于获取路径。不传参时返回当前命令⾏运⾏的⽬录;如果收到多个参数,则会进⾏路径的拼接。
此外,如果你细⼼的话,会发现_downloadTemplate()⽅法带了⼀个下划线前缀。这是 Yeoman 中的⼀个约定:Yeoman 执⾏顺序中有个default阶段,该阶段包含了所有⽤户⾃定义的类⽅法。但是,如果某些⽅法你不希望被 Yeoman 的脚⼿架流程直接调⽤,⽽是作为⼯具⽅法提供给其他类⽅法,则可以添加⼀个下划线前缀。对于这种命名的⽅法,则会在default阶段被忽略。
2.5. 模版⽂件拷贝
项⽬模版下载完毕后,下⾯就可以将相关的⽬录、⽂件拷贝到⽬标⽂件夹中。这些都可以在writing阶段操作。此时需要遍历模版中的所有⽬录,将所有⽂件进⾏模版填充与拷贝。遍历⽅式如下:
class WebpackKickoffGenerator extends Generator {
// ……
_walk(filePath, templateRoot) {
if (fs.statSync(filePath).isDirectory()) {
this._solve(filePath, name), templateRoot);
});
return;
}
const relativePath = lative(templateRoot, filePath);
const destination = this.destinationPath(this.dirName, relativePath);
pyTpl(filePath, destination, {
dirName: this.dirName
});
}
// ……
}
这⾥使⽤了pyTpl()⽅法,它⽀持⽂件拷贝,同时还可以指定相应的模版参数,此外,如果出现重名覆盖情况会在控制台⾃动输出相应信息。
最后,把下载与拷贝整合起来即可完成writing阶段。
class WebpackKickoffGenerator extends Generator {
// ……
writing() {
const done = this.async();
this._downloadTemplate()
.then(() => {
const templateRoot = this.destinationPath(this.dirName, '.tmp');
this._walk(templateRoot, templateRoot);
done();
})
.catch(err => {
});
}
// ……
}
2.6. 依赖安装
到⽬前,脚⼿架已经可以帮我们把项⽬开发所需的配置、⽬录结构、依赖清单都准备好了。这时候可以进⼀步帮开发⼈员将依赖安装完毕,这样脚⼿架创建项⽬完成后,开发⼈员就可以直接开发了。
Yeoman 也提供了this.npmInstall()来⽅法来实现 npm 包的安装:
class WebpackKickoffGenerator extends Generator {
// ……
install() {
this.npmInstall('', {}, {
cwd: this.destinationPath(this.dirName)
});
}
// ……
}
到这⾥,脚⼿架的核⼼功能就完成了。已经可以使⽤咱们的这个 generator 来快速创建项⽬了。很简单吧~
完整的代码可以参考 generator-webpack-kickoff。
3. 使⽤脚⼿架
使⽤该脚⼿架会同时需要 Yeoman 与上述咱们刚创建的 yeoman-generator。当然,有⼀个前提,Yeoman 与这个 generator 都需要全局安装。全局安装 Yeoman 没啥有问题(npm install -g yo),处理 generator-webpack-kickoff 的话可能有⼏种⽅式:
1. 直接发布到 npm,然后正常全局安装
2. 直接⼿动拷贝到全局 node_modules
3. 使⽤npm link将某个⽬录链接到全局
依据2.2.节的内容,咱们的 generator 名称为 generator-webpack-kickoff。由于我的包已经发到 npm 上了,所以要使⽤该脚⼿架可以运⾏如下指令:
# 安装⼀次即可
npm i -g yo
npm i -g generator-webpack-kickoff
# 启动脚⼿架
yo webpack-kickoff
4. 优化
从上⽂这个例⼦可以看出,实现⼀个脚⼿架⾮常简单。例⼦虽⼩,但也包含了脚⼿架开发的主要部分。当然,这篇⽂章为了简化,省略了⼀些“优化”功能。例如
项⽬⽬录的重名检测,⽣成项⽬时,检查是否⽬录已存在,并提⽰警告
项⽬模版的缓存。虽然我们使⽤ github 托管⽅式,但也可以考虑不必每次都重新下载,可以放⼀份本地缓存,然后每天或每周更新;
CLI 的优化。完整版⾥还会包含⼀些更丰富的 CLI 使⽤,例如我们在动图中看到的 loading 效果、头尾
显⽰的信息⾯板等。这些⼯具包括
ora,⽤于创建 spinner,也就是上⾯所说的 loading 效果
chalk,⽤于打印彩⾊的信息
update-notifier,⽤于检查包的线上版本与本地版本
beeper,可以“哔”⼀下你,例如出错的时候
boxen,创建头尾的那个⼩“⾯板”
版本检查。上⾯提到可以⽤ update-notifier 来检查版本。所以可以在 initializing 阶段进⾏版本检查,提⽰⽤户更新脚⼿架。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论