小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
PS:此文实现的generator-vue-3(创建vue3+ts+eslint项目):github地址、npm地址
前言
说到脚手架,大家应该都不陌生。我们最熟悉的,创建react技术栈项目的create-react-app,创建vue技术栈项目的vue-cli,都是帮助我们从0到1快速搭建项目的脚手架。
那既然已经有了成熟的脚手架,我们为什么还要搭建自己项目的脚手架呢?
通常,一个团队的项目,会有一些定制化配置:
- 项目发布的ci脚本
- eslint等规范配置
- env不同环境的配置
- 登录代理配置 ......
如果每次新建项目都要重新配置,或把原来的项目复制一份再改改。那既不能保证大家项目技术栈的一致性,也影响了大家的开发效率。那就思考,我们能不能自己写个模板,像vue-cli脚手架一样自动创建定制化项目呢?
今天我就和大家分享一下如何定制一套自己的项目脚手架,提升开发效率。
什么是yeoman
简介
yeoman是现代化前端项目的脚手架工具,用于生成包含指定框架结构的工程化目录结构。它是整个前端自动化工厂的第一站。
可以理解为,yeoman是一个脚手架的运行框架,它定义了一个脚手架在运行过程中所要经历的各个阶段(先读取用户输入,然后生成项目文件,最后安装依赖),我们所需要的就是在生命周期的对应阶段,填充对应的操作代码即可。
生命周期
yeoman提供了各个阶段的生命周期,按运行顺序优先级如下:
initializing- 初始化阶段 (检查当前项目状态, 获取配置, 等等)prompting- 用户输入阶段 (where you’d callthis.prompt())configuring- 保存配置并配置项目 (创建.editorconfig文件和其他元数据文件)default- 默认执行阶段writing- 依据模板进行新项目结构的写操作 (路由, 控制台, 等等)conflicts- 处理冲突阶段 (内部使用)install- 安装依赖阶段 (npm, bower)end- 结尾阶段(清理多余文件等等)
遵循这些优先级准则,您的生成器将与其他友好共存。
了解了yeoman的生命周期,接下来我们看一下yeoman如何生成脚手架。
什么是generator
generator-xxx生成器
yeoman相当于是一个脚手架的运行框架,想要生成某个脚手架模板的项目,则需要找到对应命名为generator-xxx的生成器。yeoman 通过yo命令调用generator-xxx 即可生成(generate)脚手架项目。
也就是说需要通过yeoman调用generator来生成项目的脚手架,都需要命名为generator-xxx(xxx为脚手架名称,通过yo xxx进行调用)
generator-generator
yeoman 提供了 generator-generator, 可以帮助我们快速生成一个脚手架模板。
如何生成脚手架模板
安装yeoman和generator
全局安装yo(yeoman)和generator-generator(生成脚手架模板)
npm install yo generator-generator -g
生成脚手架模板
yo generator
执行上述命令时,如下图,需要输入你的generator-name,发布后,可以通过调用yo name命令生成此脚手架的项目。
执行完后,就可生成如以下目录的脚手架模板项目:
.
├── generators/ // generator主要代码目录
│ └── app/
│ ├── index.js // generator配置文件,yeoman生命周期
│ └── templates/ // 模板文件,可以是自己配置后的项目
│ └── dummyfile.txt
├── .editorconfig
├── .eslintignore
├── .gitattributes
├── .gitignore
├── .travis.yml
├── .yo-rc.json
├── LICENSE
├── README.md
├── package.json
└── __tests__/ // 测试用例
└── app.js
如何定制脚手架
template模板
template模板目录:/generators/app/templates
此文件夹下可放置模板文件或模板项目,通过app/index.js中的生命周期阶段,对模板文件或模板项目中的字段进行重写或者替换,从而生成新的项目。
下图中的templates中为定制后的vue模板项目
package.json中红框是等待用户输入后,填充的内容
配置文件
配置文件目录:/generators/app/index.js
提供了yeoman-generator基础库,可在此基础上进行扩展和填充。
index.js
"use strict";
const Generator = require("yeoman-generator");
const chalk = require("chalk");
const yosay = require("yosay");
const path = require("path");
const mkdirp = require("mkdirp");
// 5.0.0版本需要动态引入install
const _ = require("lodash");
_.extend(Generator.prototype, require("yeoman-generator/lib/actions/install"));
module.exports = class extends Generator {
// 向用户展示交互式问题收集关键参数
prompting() {
// Have Yeoman greet the user.
this.log(
yosay(
`Welcome to the stunning ${chalk.red("generator-vue-3")} generator!`
)
);
const prompts = [
{
type: "input",
name: "namespace",
message: "Please input your project namespace,such as @baidu:",
default: ""
},
{
type: "input",
name: "name",
message: "Please input project name:",
default: "vue"
},
{
type: "input",
name: "description",
message: "Please input project description:",
default: "a vue project"
},
{
type: "input",
name: "author",
message: "Author's Name",
default: ""
},
{
type: "input",
name: "email",
message: "Author's Email",
default: ""
},
{
type: "input",
name: "license",
message: "License",
default: ""
}
];
return this.prompt(prompts).then(props => {
// To access props later use this.props.someAnswer;
this.props = props;
if (this.props.namespace) {
this.props.fullName = this.props.namespace + "/" + this.props.name;
} else {
this.props.fullName = this.props.name;
}
});
}
// 未匹配任何生命周期方法的非私有方法均在此环节*自动*执行
default() {
if (path.basename(this.destinationPath()) !== this.props.name) {
this.log(`\nYour generator must be inside a folder named
${this.props.name}\n
I will automatically create this folder.\n`);
mkdirp(this.props.name);
this.destinationRoot(this.destinationPath(this.props.name));
}
}
// 依据模板进行新项目结构的写操作
writing() {
this.log("\nWriting...\n");
this.__writingCopy(["package.json"], {
name: this.props.name,
fullName: this.props.fullName,
description: this.props.description,
author: this.props.author,
email: this.props.email,
license: this.props.license
});
this.__writingCopy(["README.md"], {
name: this.props.name,
fullName: this.props.fullName,
description: this.props.description,
author: this.props.author,
year: new Date().getFullYear()
});
this.__writingCopy([
"build",
"public",
"src",
".browserslistrc",
".editorconfig",
".env.development",
".env.production",
".env.test",
".eslintrc.js",
".eslintrc.json",
".gitignore",
"babel.config.js",
"ci.yml",
"stylelint.config.js",
"tsconfig.json",
"vue.config.js"
]);
}
__writingCopy(filePath, params) {
this.log(this, "haha");
filePath.forEach(item => {
this.fs.copyTpl(
this.templatePath(item),
this.destinationPath(item),
params
);
});
}
// 安装依赖阶段
install() {
this.log("install...");
// 5.0.0以上版本废弃,默认关闭
this.npmInstall();
}
};
5.0.0版本后不自动install依赖
yeoman-generator5.0.0版本后不会自动install依赖,需要手动触发,触发方式有以下几种:
配置完模板文件和配置文件,脚手架已经完成,接下来我们进行脚手架测试。
如何测试脚手架
npm link模块链接到全局
通过npm link将模块链接到全局node_modules中,可在全局中使用该模块。
调用yo xxx生成脚手架项目
本地开发的generator-XXX未经过发布,需要在package.json所在目录开启命令行,输入npm link将其安装到本地的全局环境,然后通过yo XXX或yo XXX:YYY的方式来调用,结果如下图:
按提示输入项目的scope、名称、描述等信息,会自动生成项目。进入生成的项目后,如没有install依赖,需要手动install依赖后再运行。
可通过npm link,yo xxx的方式来测试是否可以顺利生成脚手架项目。
如出现# npm ERR! notarget No matching version found for @babel/helper-validator-identifier@7.9.5的错误,可通过npm cache clean --force尝试解决。
如何发布脚手架
将本地开发的generator-xxx脚手架做为npm包发布。
发布步骤为:
- 登录npm:
npm login - 发布npm:
npm publish
发布成功,如下图:
npm上查看刚才发布的包generator-vue-3:
如何使用脚手架
全局安装脚手架,比如generator-vue-3
npm install -g generator-vue-3
使用yo调用脚手架生成项目
yo vue-3
生成成功后,进入目录,启动项目。