前端项目工程化 -- 脚手架 Yeoman 的使用
Yeoman是什么?
Yeoman是一款最老牌、最强大、最通用的、用于创建现代化web应用的脚手架工具,与vue-cli不同,Yeoman更像是一个脚手架的运行平台,可以搭配不同的generator创建任何类型的项目,即我们可以通过创建generator来定制自己的前端脚手架,功能可以说是十分的强大。
Yeoman基础使用
- 安装
yarn global add yo
前面我们介绍到,Yeoman是通过搭配不同的generator来创建项目,所有我们还要找到对应项目类型的generator,比如我们想要生成一个node-module的项目,可以使用generator-node这样一个generator模块,使用方法还是通过全局安装的方式安装到本地
yarn global add generator-node
这样两个模块安装完成过后就可以用运行generator-node生成器帮我们创建一个从全新的node-module项目
yo node
执行上述命令会进行交互,按照提示输入Module Name、Description、homepage url等信息
最终生成项目文件并安装依赖,目录结构如下:
Yeoman之Sub Generator -- 在已有项目上创建一些特定类型的文件
有的时候我们并不想创建一个完整的项目,我们可能只是想在原有的项目中添加一个reame.md文件,也可能是想添加一些配置文件,比如bable、eslint的配置文件,通常这些文件都会有一些基础内容,通过Sub Generator来自动生成可以提高一定效率
- 运行Sub Generator的方式:在Generator后面加":" , 并跟上Sub Generator的名称
以上面的node-module项目为例,我们可以使用yo node:cli
来为此项目添加一些cli相关的文件
使用新的cli支持的时候会提示重写package.json,这里输入Y回车,会自动在package.json中添加bin和dependencies选择:
还会在根目录中添加lib文件夹,其中包括了一个cli.js,提供了cli应用的基础结构
有了这些之后,就可以把我们的模块作为全局的命令行模块来使用,只需要使用yarn link
来link到全局范围,然后我们就可以通过我们的模块的名字来运行加进来的模块了,记住需要安装一下依赖
my-module --help
运行Sub Generator之前需要确认是存在这个Sub Generator的,比如generator-node下面就有cli、git、readme等Sub Generator
自定义Generator
前面讲到,Yeoman可以根据我们自己创建的generator来定制自己的前端脚手架,现在我们就来尝试创建一个Generator
创建Generator本质上就是创建一个npm模块,但是Generator有特定的目录结构
Generator的模块名称必须是generator-<name>
的形式,不然Yeoman将无法找到对应的Generator模块
现在我们开始创建Generator
- 创建一个空文件夹,并初始化package.json
mkdir generator-sample
cd generator-sample
yarn init -y
-
安装yeoman-generator模块,这个模块提供了生成器的基类,这个基类中提供了一些工具函数可以让我们创建生成器的时候更便捷
-
创建项目目录
- index.js文件内容,很简单的内容
// 此文件作为Generator的核心入口
// 需要导出一个继承自Yeoman Generator的类型
// Yeoman Generator在工作时会自动调用我们在此类型中定义的一些生命周期方法
// 在这些 方法中可以通过父类提供的一些工具方法实现一些功能,例如文件写入
const Generator= require('yeoman-generator')
module.exports=class extends Generator{
writing(){
// Yeoman自动在文件生成阶段调用此方法
// 我们这里尝试往项目目录中写入文件
this.fs.write(
this.destinationPath('temp.txt'),
Math.random().toString()
)
}
}
- 执行
yarn link
,把我们的sample模块link到全局,使之成为全局模块包
- link成功后,我们再创建一个空目录比如my-proj,并在此目录中执行
yo sample
,就可以创建出一个文件temp.txt
根据模板创建文件
通过上面的generator例子,我们知道了怎么去创建一个generator并且使用这个generator生成文件,但是我们实际中可能并不只是单纯的拷贝文件,有的时候还需要根据模板生成文件,这个好办吗?答案是没问题的
我们可以在app目录下添加一个templates目录,并在templates目录中添加模板文件
注意这个 templates目录的名称是固定的,不能错了,不然生成文件的时候找不到templates目录会报错提示文件不存在
模板文件内容可以使用EJS模板标记输出数据,也可以使用其它的EJS语法比如
<% if (success) {%>
哈哈哈
<% }%>
具体生成文件时,我们就不用再借助fs.write来生成文件了,而是使用fs中专门使用模板引擎的方法copeTpl
具体的代码如下:
const Generator= require('yeoman-generator')
module.exports=class extends Generator{
writing(){
// Yeoman自动在文件生成阶段调用此方法
// // 我们这里尝试往项目目录中写入文件
// this.fs.write(
// this.destinationPath('temp.txt'),
// Math.random().toString()
// )
// 通过模板方式写入文件到目标目录
//模板文件路径
const tmpl=this.templatePath('foo.txt')
// 输出目标路径
const output=this.destinationPath('foo.txt')
// 模板数据上下文
const context = { title:'Helle Yeoman',success:true }
this.fs.copyTpl(tmpl,output,context)
}
}
接下来,我们再回到我们的项目my-proj目录中,再次执行yo sample
,就可以得到根据模板生成的文件foo.txt
接收用户输入
一般在创建项目的时候,比如通过vue-cli创建vue项目的时候,都会让我们为项目起一个名称,还会问需要使用什么插件呀、用不用保存配置啥的,这些就是与用户的交互,需要用户输入或选择,那么下面我们就来看看Generator中发起交互和用户输入是怎么实现的。
在Generator类中提供了prompt方法用于向用户发起命令行询问,这个方法接收一个数组作为参数,数组的每一项都是一个问题比如:
prompting(){
return this.prompt([
{
type:'input',// 让用户以输入的方式回答
name:'name',// 答案对应的键
message:'Your project name',// 提示信息
default:this.appname //appname为项目生成目录的文件夹的名称,作为这个问题的默认答案
}
]).then(answers=>{
// answer是一个对象,比如:{name:'my-proj'}
this.answers=answers
})
}
上面拿到answers后可以把这个answers作为模板数据的上下文:
// 模板数据的上下文
const context= this.answers
这样index.js代码如下 :
const Generator= require('yeoman-generator')
module.exports=class extends Generator{
prompting(){
return this.prompt([
{
type:'input',
name:'name',
message:'Your projct name',
default:this.appname
}
]).then(answers=>{
this.answers=answers
})
}
writing(){
// Yeoman自动在文件生成阶段调用此方法
// // 我们这里尝试往项目目录中写入文件
// this.fs.write(
// this.destinationPath('temp.txt'),
// Math.random().toString()
// )
// 通过模板方式写入文件到目标目录
//模板文件路径
const tmpl=this.templatePath('index.html')
// 输出目标路径
const output=this.destinationPath('index.html')
// 模板数据上下文
// const context = { title:'Helle Yeoman',success:true }
const context = this.answers
this.fs.copyTpl(tmpl,output,context)
}
}
我们在templates目录中添加一个index.html文件,并使用EJS语法接收项目名称,如下所示:
此时再去执行yo sample
,则会提示输入项目名称了
输出的html文件内容如下:
使用Yeoman生成Vue项目
通过上面的讲解,相信大家能够了解Yeoman是怎么搭配Generator进行文件生成的了,下面我们就再来通过Yeoman创建一个生成Vue项目的脚手架
首先,我们需要一个Vue项目,作为示例,我们可以通过vue-cli创建直接拿来用,可以写一些自己常用的业务或者扩展在里面
然后呢,我们需要创建一个全新的Generator,用来生成这个Vue的项目结构
mkdir generator-myvue
cd gemerator-myvue
yarn init -y
yarn add yeoman-generator
在generator-myvue目录中添加目录结构如下:
把需要生成的vue项目文件拷贝到templates目录下
把需要用户输入来替换的地方进行EJS替换,这里作为示例就只替换了package.json中的项目名称
注意一点就是原本就是EJS语法的地方,比如index.html中有的地方本身就是EJS语法需要原样输出的,需要在"%"后面多加一个"%",来让其原样输出
接下来就是generator的index.js代码了
在writing方法, 需要一个数组来存储所有文件的路径,因为需要遍历此数组来把数组中所有的文件生成到目标目录中
const Generator= require('yeoman-generator')
module.exports=class extends Generator{
prompting(){
return this.prompt([
{
type:'input',
name:'name',
message:'Your projct name',
default:this.appname
}
]).then(answers=>{
this.answers=answers
})
}
writing(){
const templates=[
'.browserslistrc',
'.editorconfig',
'.eslintrc.js',
'.gitignore',
'public/favicon.ico',
'public/index.html',
'src/main.js',
//...把所有需要生成的文件路径都写到这里
]
// 通过模板方式写入文件到目标目录
templates.forEach(item=>{
this.fs.copyTpl(
this.templatePath(item),
this.destinationPath(item),
this.answers
)
})
}
}
这样我们的my-vue脚手架就做好了,需要把这个generator脚手架link到全局
尝试生成一个项目,并给项目起一个名称:
发布Generator
因为Generator实际上就是一个npm模块,所以我们去发布一个Generator模块,实际上就是发布一个npm模块
所以我们只需要通过npm publish这样一个命令将Generator模块发布成一个公开的模块就可以了
具体怎么操作呢 ?不难,首先把我们的Generator推送到GitHub仓库,然后在项目根目录下执行yarn publish
注意镜像不要使用淘宝镜像,yarn可以使用yarn的
镜像