前端工程化实战 - 脚手架工具
2.1脚手架工具概要
脚手架工具的本质作用:创建项目基础结构、提供项目规范和约定
通常我们再去开发相同类型的项目时都会有一些相同的约定,其中包括:
- 相同的组织结构
- 相同的开发范式
- 相同的模块依赖
- 相同的工具配置
- 相同的基础代码
这样一来,就会出现,当我们去搭建新项目时有大量的重复工作要做,脚手架工具就是去用来解决这一问题的。我们可以通过脚手架工具去快速搭建特定类型的项目骨架,基于这个骨架完成后续的开发工作。
2.2常用的脚手架工具
目前市面上有许多成熟的前端脚手架工具,但大都是为了特定项目类型服务,根据信息创建对应的项目基础结构,例如:
React.js项目 --> creat-react-app
Vue.js项目 --> vue-cli
Angular.js项目 --> angular-cli
还有一类以 Yeoman 这样的工具为代表的通用型项目脚手架工具。它们可以根据一套模板生成一个对应的项目结构,这种类型的脚手架一般都很灵活,很容易扩展。
除了上述所说的创建项目时才会用到的脚手架工具,还有一类以 Plop 为代表的脚手架,它们用于在项目开发过程中创建一些特定类型的文件,例如创建组件/模板所需要的文件。
2.3Yeoman 脚手架工具
- 一款用于创造现代化Web应用的脚手架工具(The web’s scaffolding tool for medern webapps)
Yeoman更像是一个脚手架的运行平台,我们可以通过Yeoman搭配不同的generator创建任何类型的项目。
Yeoman 基础使用
因为Yeoman是一款基于node.js开发的工具模块,我们首先确定node环境的基础下:
- 在全局范围安装 yo
$ npm install yo --global # or yarn global add yo
- 安装对应的 generator
$ npm install generator-node --global # or yarn global add generator-node
- 通过 yo 运行 generator
$ cd path/to/project-dir
$ mkdir my-module
$ yo node
- 填写项目相关信息
Sub Generator
有时候我们并不需要去创建完整的项目结构,可能只是需要在已有的项目之上去创建一些特定类型的文件。我们可以使用Yeoman提供的Sub Generator这样一个特性来实现。
在项目目录下运行特定的Sub Generator命令生成对应的文件,例如在我的项目中使用generator-node中的子集的cli生成器,来帮我们生成一个cli应用所需要的文件。
$ yo node:cli
这里会提示我们是否要重写package.json这样一个文件,原因是在去添加cli支持的时候,会添加一些新的模块和配置,我们选择yes
? Overwrite package.json? overwrite
force package.json
create lib\cli.js
lib目录下的cli.js提供了一些cli应用基础的代码结构,有了这些我们就可以将这个模块作为一个全局的命令行模块去使用。本地的模块我们通过
yarn link
到全局范围,之后我们就可以通过模块的名字运行加进来的模块了,注意:如果成功但是通过名字无法运行可以通过
yarn global bin
为此路径配置环境变量以便全局执行
yarn
my-module --help
值得注意的是,并不是没一个generatordd都提供自己生成器,所以我们在使用之前需要通过你所使用的generator的官方文档明确这个generator下面有没有子集生成器。
Yeoman 使用步骤总结
- 明确你的需求
- 找到合适的 Generator
- 全局范围安装找到的 Generator
- 通过 Yo 运行对应的 Generator
- 通过命令行交互填写选项
- 生成你所需要的项目结构
2.4基于 Yeoman 搭建自己的脚手架
即便是市面上已经有了很多的Generator,我们还是有创造自己的Generator的必要,因为市面上的Generator都是通用的,而我们在实际开发过程中会出现一部分基础代码甚至业务代码在相同类型项目时还是重复的,这时我们就可以把公共部分都放到脚手架工具当中生成,让脚手架工具发挥更大的价值。
创建一个Generator本质上就是一个NPM模块,我们来看一下Generator基本结构:
如果你需要提供多个Sub Generator,可以在app下添加一个新生成器的目录作为其子生成器。
注意:Yeoman的Generator的模块名称必须是 generator - < name >的格式,如果不遵守,Yeoman后续工作时就无法找到你所提供的生成器模块。
1.创建工作目录
mkdir generator-sample
cd .\generator-sample\
2.创建package.json
yarn init
3.安装yeoman-generator的模块,它提供了生成器的一个基类,这个基类中提供了一些工具函数,让我们可以在创建生成器的时候更加便捷
yarn add yeoman-generator
4.安装完依赖过后,打开这个目录,在这个目录下按照项目结构要求创建一个generators目录,在其之下创建一个app目录,在下面创建index.js文件
- 此文件会作为Generators的核心入口
- 需要导出自一个继承 Yeoman Generator 的类型
- Yeoman Generator 在工作时会自动调用我们在此类型中定义的一些生命周期方法
- 我们在这些方法中可以通过调用父类提供的一些工具方法实现一些功能,例如文件写入
// index.js 样例
const Generator = require('yeoman-generator')
module.exports = class extends Generator {
writing () {
// Yeoman 自动在生成文件阶段调用此方法
// 尝试往项目目录写入文件
// 这里的fs模块与Node中的fs不一样,这是一个高度封装的模块,功能更加强大
this.fs.write ( // 参数 绝对路径 内容
this.destinationPath('temp.txt'),
Math.random().toString()
)
}
}
5.将此模块链接到全局范围,使之成为一个全局模块包
yarn link
6.创建一个新的工作目录
cd ..
mkdir my-proj
cd .\my-proj\
7.在新的工作目录下输入命令 yo sample 自动创建了名叫 temp.txt 的文件,里面内容为一个随机数
yo sample
create temp.txt
根据模板创建文件
很多时候我们需要去自动创建的文件有很多,而且文件的内容也相对复杂,在这种情况下,我们就可以使用模板去创建文件,因为这样可以更加便捷一些。
在生成器目录下添加一个目录templates,将要生成的文件都放入目录作为模板。内部可以使用 EJS 模板标记输出数据。
// foo.txt
例如 <%= title %>
其他的 EJS 语法也支持
<% if (success) { %>
sgh
<% }%>
// index.js
const Generator = require('yeoman-generator')
module.exports = class extends Generator {
writing () {
// 通过模板方式写入文件到目标目录
// 参数 模板文件的路径 输出文件的路径 模板数据的上下文
// 模板文件路径
const tmpl = this.templatePath('foo.txt')
// 输出目录路径
const output = this.destinationPath('foo.txt')
// 模板数据上下文
const context = { title:'Hello sgh~', success:false }
this.fs.copyTpl(tmpl, output, context)
}
}
- 相对于手动创建每一个文件,模板的方式大大提高了效率
接收用户输入数据
对于模板中的一些动态数据,例如项目的标题,项目的名称,这样的数据,我们一般通过命令行交互的方式去询问使用者从而得到。
我们可以通过实现prompting方法发起命令行交互的询问。
// bar.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%= name %></title>
</head>
<body>
<h1><%= name %></h1>
</body>
</html>
// index.js
const Generator = require('yeoman-generator')
module.exports = class extends Generator {
prompting () {
// Yeoman 在询问用户环节会自动调用此方法
// 在此方法中可以调用父类的 prompt() 方法发出对用户的命令行询问
return this.prompt([
{
type: 'input',
name: 'name',
message: 'Your project name',
default: this.appname // appname 为项目生成目录名称
}
])
.then(answers => {
// answers => { name: 'user input value' }
this.answers = answers
})
}
writing () {
// 模板文件路径
const tmpl = this.templatePath('bar.html')
// 输出目标路径
const output = this.destinationPath('bar.html')
// 模板数据上下文
const context = this.answers
this.fs.copyTpl(tmpl, output, context)
}
}
发布 Generator
Generator就是一个npm模块,所以我们发布Generator实际就是去发布一个npm模块。
1.创建 .gitignore 去忽略项目中的 node_modules
echo node_modules > .gitignore
2.初始化本地空仓库
git init
3.查看本地仓库状态并提交
git status
git add .
git commit -m "feat: initial commit"
4.在github创建一个新的仓库并推送
git remote add origin [仓库地址]
git push -u origin master
5.发布模块(使用官方镜像)
yarn publish [--registry=https://registry.yarnpkg.com]
6.如果需要你的Generator在官方的仓库列表出现,可以为项目添加一个 yeoman-generator 的关键词,官方会发现到此项目。
2.5Plop 脚手架工具
除了像Yeoman这样大型的脚手架工具,还有一些小型的脚手架工具也非常出色。
在我们开发中经常需要创建相同类型的文件,整个过程非常繁琐,而且很难统一项目中基础的代码。
Plop是一款主要去为了创建项目中特定类型文件的小工具,有点类似 Yeoman 中的 Sub Generator,不过它不会独立去使用,一般我们会把 Plop 集成到项目当中用来去自动化的创建同类型的项目文件。
Plop 的具体使用
1.将 Plop 作为一个npm的模块安装到开发依赖当中
yarn add plop --dev
2.在项目的根目录下创建 plopfile.js 文件
// plopfile.js
// Plop 入口文件,需要导出一个函数
// 此函数接收一个 plop 对象,用于创建生成器任务
module.exports = plop => {
// 参数 生成器的名字 生成器的配置选项
plop.setGenerator('component', {
description: 'create a component',
prompts: [ // 命令行交互问题
{
type: 'input',
name: 'name',
message: 'component name',
default: 'MyComponent'
}
],
actions: [ // 完成命令行交互过后的动作
{
type: 'add', // 代表添加文件
path: 'src/components/{{name}}/{{name}}.js', // 使用插值表达式插入数据
templateFile: 'plop-templates/component.hbs' // 模板文件
},
{
type: 'add', // 代表添加文件
path: 'src/components/{{name}}/{{name}}.css',
templateFile: 'plop-templates/component.css.hbs'
},
{
type: 'add', // 代表添加文件
path: 'src/components/{{name}}/{{name}}.test.js',
templateFile: 'plop-templates/component.test.hbs'
}
]
})
}
3.编写用于生成特定类型文件的模板
4.Plop提供了CLI程序,我们可以启动这个Plop程序运行脚手架任务
yarn plop [生成器的名字:component]