上面是一个使用脚手架来初始化项目的典型例子。
随着前端工程化的理念不断深入,越来越多的人选择使用脚手架来从零到一搭建自己的项目。其中大家最熟悉的就是create-react-app和vue-cli,它们可以帮助我们初始化配置、生成项目结构、自动安装依赖,最后我们一行指令即可运行项目开始开发,或者进行项目构建(build)。
这些脚手架提供的都是普遍意义上的最佳实践,但是我在开发中发现,随着业务的不断发展,必然会出现需要针对业务开发的实际情况来进行调整。例如:
-
项目架构的配置
a. webpack/gulp的配置
ⅰ. style/less/sass的选择和处理配置。
ⅱ. Typescript和JavaScript的loader和babel的配置,比如babel-loader和tsc。
ⅲ. 图片的配置,比如需要file-loader或者url-loader。
ⅳ. 静态资源的配置。
ⅴ. 企业内部,真对于某些特定功能的webpack-loader/webpack-plugin,比如打包完图片上传。
b. 是否内置一些第三方框架供上手使用,比如Element-UI,Antd等。
c. 项目的路由的设计和配置。
d. 项目目录结构的设计和配置。
e. 项目通用功能的有关代码,比如登陆和欢迎页等。
f.通过调整插件与配置实现 Webpack 打包性能优化
g.针对生产环境做的单独处理 -
项目开发中的配置
a. eslint。
b. style-lint。
c. prettier。
d. 单元测试jest或者其他单元测试框架。
e. 项目文档的自动生成,比如docz。
f. git相关的,比如git提交的校验,生成changelog
g. npm相关,除非是开发npm包,否则npm相关的不是那么重要
h. 与开发有关的工具,比如打包构建结果的包分析、比如相似代码自动生成 -
项目上线脚本,这个应该是可选项?更优的方法应该是使用内部的例如发布平台之类的工具完成,或者通过jekins或者gitlab-ci进行自动化发布,回滚等操作。上线发布回滚的动作可以完全交给非开发人员完成。
a. 比如由nodejs编写的上线脚本,或者是用于上线的shell脚本。
b. gitlab-ci的配置文件
……
总而言之,随着业务发展,我们往往会沉淀出一套更“个性化”的业务方案。这时候我们最直接的做法就是开发出一个该方案的脚手架来,以便今后能复用这些最佳实践与方案。
那么就需要使用一个工具来帮我们做这些处理,我们使用的就是yoman。官网地址
我们可以使用yeoman和yeoman的generator来更加便捷实现我们的脚手架,可以说基本是零成本。
先说说Yeoman是什么,它想做什么?
Yeamon帮助你快速的开展一个项目工程,提供最佳实践和工具,来让你保持高效率编码。
他们自己提供了一个构建生态系统,主要通过‘yo’这个命令来构建一个完整的项目或者项目的一部分。
通过官方的生成器,他们建立了一个Yeoman的工作流,这个流是由一个强大的,固定的客户端组建,包含工具和框架帮助开发者快速建立牛逼的web应用。他们尽量提供了开发者所需的东西。比如先下载对应模版项目,然后下载模版项目中的package.json中的包
作为良好文档和深入思考构建过程的思想者,Yeoman包含了检测(静态检测)、测试以及压缩等等一系列工具,让开发者能够更加专注于思考解决方案。
如何使用:
1、npm install -g yo //权限不够,请加上 sudo,一般来说mac都需要。
安装完成之后,你就拥有了1个命令 -- yo 可以选择自定义的,也可以搜索你想要的现成的,这里我们选择自己的。 官网提供的地址:http://yeoman.io/generators/
那我们开始编写一个自己的generators。
2、开发自己的generator
创建项目
全局安装generator-generator,然后使用创建项目,根据提示输出我们的generator项目的名字,yeoman规定项目名字必须以generator开头,所以我们可以选择generator-xxx作为我们的项目名
npm i -g generator-generator
yo generator
创建好项目之后,我们可以发现已经自动为我们配置好了eslint,jest,husky
我们主要需要修改的代码位于generators/app内
● index.js定义了我们使用此generator时的一些操作配置项
● templates内存放的是要生成的项目的项目模板,项目模板也可以放在git上,然后通过download-git-repo下载到本地
验证我们创建的项目
- 在当前项目执行npm link使其被链接到全局
- 然后找一个目录,终端执行yo,此时便可以发现我们的generator已经出现在了选择列表中。然后选择我们的generator运行即可。等待完成,会发现成功的创建了一个叫做dummyfile.txt的文件夹。
generators/app/index.js
初始化的文件如下。
"use strict";
const Generator = require("yeoman-generator");
const chalk = require("chalk");
const yosay = require("yosay");
module.exports = class extends Generator {
prompting() {
// Have Yeoman greet the user.
this.log(
yosay(
`Welcome to the fantastic ${chalk.red("generator-rc-op")} generator!`
)
);
const prompts = [
{
type: "confirm",
name: "someAnswer",
message: "Would you like to enable this option?",
default: true
}
];
return this.prompt(prompts).then(props => {
// To access props later use this.props.someAnswer;
this.props = props;
});
}
writing() {
this.fs.copy(
this.templatePath("dummyfile.txt"),
this.destinationPath("dummyfile.txt")
);
}
install() {
this.installDependencies();
}
};
从文件的内容可以看出主要操作是暴露出一个继承自Generator的类,在类的内部定义了部分生命周期方法,供yoeman在适当的时候调用。我们只需要完善这些方法就可以了。
yeoman提供的生命周期如下:
- initializing:初始化必要的依赖,或者比如检测新版本
- prompting:用来处理终端的交互
- default:这里指的是自己自定义的方法,而不是名为default的方法,自己定义的方法如果不想被调用,那么需要确保方法名以_为开头。
- writing:将经过ejs模板渲染后的内容写入文件系统。
- conflicts:勇于解决将文件内容写入文件系统时可能造成的冲突
- install:安装项目的依赖,比如npm install 或者 bower install
- end:做一些收尾的工作。
这些生命周期方法均支持返回Promise来进行异步操作。
内置常用工具
终端交互:内置了Inquirer提供终端交互
模板填充:内置了ejs模板
文件操作
● this.fs.copyTpl:用于拷贝并且根据数据使用ejs模板渲染文件。此函数接受三个参数,源文件路径,目标文件路径,传给ejs用于渲染目标问价的数据。
● this.fs.copy:将文件丛源路径复制到目标路径。
● this.destinationPath() :此函数返回最终生成项目的路径
其他
● this.npmInstall:实现npm isntall
// 部分代码,完整代码见最下方链接
writing() {
const done = this.async();
this.log("⚙ Finish basic configuration.", chalk.green("✔"));
this.log("📂 Generate the project template and configuration...");
let spinner = ora({
text: `Download the template from ${GIT_BASE}${TPL_REPOSITORY}...`,
spinner: ORA_SPINNER
}).start();
this._downloadTemplate(TPL_REPOSITORY)
.then(() => {
spinner.stopAndPersist({
symbol: chalk.green(" ✔"),
text: `Finish downloading the template from ${GIT_BASE}${TPL_REPOSITORY}`
});
spinner = ora({
text: `Copy files into the project folder...`,
spinner: ORA_SPINNER
}).start();
const templateRoot = this.destinationPath(this.dirName, ".tmp");
this.log(`\n${templateRoot}`, "templateRoot");
const resoPath = path.resolve(templateRoot, "../");
this.log(resoPath, "resoPath");
spinner.stopAndPersist({
symbol: chalk.green(" ✔"),
text: `Finish copying files into the project folder`
});
execSync(`cp -R ${resoPath}/.tmp/* ${resoPath}`, { stdio: "inherit" });
spinner = ora({
text: `Clean tmp files and folders...`,
spinner: ORA_SPINNER
}).start();
fs.removeSync(templateRoot);
spinner.stopAndPersist({
symbol: chalk.green(" ✔"),
text: `Finish cleaning tmp files and folders`
});
// ExecSync(`rm -R ${resoPath}/.tmp`, { stdio: "inherit" });
// 将新生成的项目的title换成自定义的项目名称
ejs.renderFile(
`${resoPath}/public/index.html`,
{ dirName: this.dirName },
function(err, str) {
// Str => 输出渲染后的 HTML 字符串
fs.writeFile(`${resoPath}/public/index.html`, str);
}
);
// 将新生成的项目的package.json的name换成自定义的项目名称
ejs.renderFile(
`${resoPath}/package.json`,
{ dirName: this.dirName },
function(err, str) {
// Str => 输出渲染后的 HTML 字符串
fs.writeFile(`${resoPath}/package.json`, str);
}
);
done();
})
.catch(err => this.env.error(err));
}
其他可能用到的工具
● ora:用于创建Spinner,即比如下载模板文件时需要展示的laoding效果。
● chalk:用于打印彩色的信息
● update-notifier
● beeper
● boxen
调试
由于yeoman是通过npm包的形式进行使用的,所以我们可以在我们开发的generator项目的跟目录下,执行npm link 将我们开发的generator链接到全局,这样我们直接使用yo命令查看可以使用的generator,就可以看到我们所开发的generator了,并且是实时生效的