概述
脚手架本质作用是用于创建项目基础结构,提供项目规范和约定。
在后端项目开发中,比如像Java 使用的IDE,IDE在创建项目的过程就是脚手架创建项目的过程。
而前端项目因为技术选型比较多样,也没有统一的标准,所以前端方向的脚手架一般不会集成在某一个IDE中。它们大多是作为独立工具存在。
- 在 react 中 我们使用的是 create-react-app
- 在 Vue 中 使用 @vue/cli
- 在 angular 中 使用的是 angular-cli
以上它们都是根据信息创建对应的项目基础结构,一般只适用于自身服务的框架项目。
- 另一类是以 Yeoman 为代表的通用性脚手架工具,它们可以根据模板生成对应的结构,这种类型的脚手架一般都很灵活,容易扩展.
- 还有一类,比如 Plop 用于去创建某一种特定类型的文件。它的特点是可以根据一个模板文件批量的生成文本或者代码
Yeoman
Yeoman 是一款用于创造现代化web应用的脚手架工具。
不同于 vue-cli 这样的工具,Yeoman 更像是一个脚手架的运行平台,我们可以用过Yeoman搭配各种 generator ( 生成器 ) 去创建任何类型的项目。
也就是说,我们可以通过创建自己的generator,去定制创建属于我们自己的前端脚手架。
基本使用
先使用Yeoman之前要保证电脑上已经安装了node环境。
从安装到运行:
- 全局安装Yeoman
npm install yo --global
# 或者 使用 yarn 安装
yarn global add yo
- 安装对应的 generator 这里我们拿node项目做演示
npm i generator-node -g
# or yarn
yarn global add generator-node
- 创建项目文件 my_project 运行 generator 来创建对应项目
# 在项目目录 my_project 运行终端执行
yo node
总结起来就是:
- 找到合适的Generator
- 全局范围安装找到Generator
- 通过yo运行对应的Generator
- 通过命令行交互填写选项
- 生成项目结构
自定义Generator
我们可以通过自定义generator,去搭建具有特色的项目结构,更加契合我们的开发需求。
要开发yeoman的生成器,自定义的generator必须遵守一个共同的命名规则:必须是 generator-name 这种格式
- 首先用我自己昵称创建一个
generator-hfk
目录 - yarn init
- 安装 yeoman-generator 模块,这是generator的基类,其中提供了我们需要的一些工具函数
yarn add yeoman-generator
- 自定义的generator 的目录结构
generator-hfk
├─ generators
│ └─ app
│ └─ index.js
├─ package.json
└─ yarn.lock
- 在根目录创建 generators / app / index.js 文件
// 此文件作为 Generator 的核心入口
// 导入依赖 yeoman-generator
// 需要导出一个继承自 Yeoman Generator 的类型
// Yeoman Generator 在工作时会自动调用我们在此类型中定义的一些生命周期方法
// 我们在这些方法中可以通过调用父类提供的一些工具方法实现一些功能,例如文件写入
const Generator = require('yeoman-generator')
module.exports = class extends Generator {
// yeoman 在生成文件时 自动调用此方法
// 尝试向目录中写入文件
writing(){
this.fs.write(
//destinationPath 目标路径
this.destinationPath('demo.txt'),
"hello my generator"
)
}
}
- 将模块包引入到全局
yarn link
- 创建一个新的 测试目录
demo_project
并定位终端到该目录 - 运行
yo hfk
, 此时可能会因为 yeoman-generator 报一个错误
解决办法:
- 可以把yeoman-generator包的版本降低
npm i yeoman-generator@4
即可 - 或者 或者全局安装yeoman-environment,并且使用yo run “名字”
通过模板生成文件
因为在writing方法中, 如果一行行的写文件内容,过于麻烦。我们可以使用 template 来创建文件。
- 首先在 generators / app 中创建 templates 目录
- 在目录中创建模板文件,文件中的内容 遵循ejs模板的规范
- 使用copyTpl 调用模板创建文件
// 此文件作为 Generator 的核心入口
// 导入依赖 yeoman-generator
// 需要导出一个继承自 Yeoman Generator 的类型
// Yeoman Generator 在工作时会自动调用我们在此类型中定义的一些生命周期方法
// 我们在这些方法中可以通过调用父类提供的一些工具方法实现一些功能,例如文件写入
const Generator = require('yeoman-generator')
module.exports = class extends Generator {
// yeoman 在生成文件时 自动调用此方法
// 尝试向目录中写入文件
writing() {
// this.fs.write(
// //destinationPath 目标路径
// this.destinationPath('demo.txt'),
// "hello my generator"
// )
// 模板文件路径
const tmpl = this.templatePath('index.html')
// 目标文件路径
const output = this.destinationPath('index.html')
// 模板数据上下文
const context = { title: 'hello,tmpl', success: false }
this.fs.copyTpl(tmpl, output, context)
}
}
接收用户输入数据
- yeoman在询问用户环节会自动调用 prompting 函数,
- 我们需要在该函数内部 return this.prompt() 它执行结果是一个promise 实例.
- 对用户提出的问题,写在prompt的参数中,参数是一个数组,一个问题对应一个数组成员,
- 用户输入的答案会被记录在answers中
const Generator = require('yeoman-generator')
module.exports = class extends Generator {
// yeoman 会在询问用户环节自动调用此方法
prompting() {
return this.prompt([
{
type: 'input',
name: 'name',
message: 'hi guys,what do your call?',
default: this.appname // 项目目录的名字
},
{
type: 'input',
name: 'sex',
message: '你是男的还是女的?',
default: '你猜' // 项目目录的名字
}
]).then(answers => {
// answers => { name: 'user input value' }
this.answers = answers
})
}
// yeoman 在生成文件时 自动调用此方法
// 尝试向目录中写入文件
writing() {
// 模板文件路径
const tmpl = this.templatePath('index.html')
// 目标文件路径
const output = this.destinationPath('index.html')
// 模板数据上下文
const context = this.answers
this.fs.copyTpl(tmpl, output, context)
}
}
案例
有了前面的铺垫,我们就可以准备来搞一个构建Vue项目的Generator,当然肯定是简易版的。
准备工作:
- 先准备好一个用作模板的Vue项目,包含自己需要的各种文件
- 准备一个文件路径数组
- 在模板中的需要替换内容的文件用 <%= 值 %> 留下空
模板结构 :
templates
├─ .browserslistrc
├─ .editorconfig
├─ .env.development
├─ .env.production
├─ .eslintrc.js
├─ .gitignore
├─ babel.config.js
├─ package.json
├─ postcss.config.js
├─ public
│ ├─ favicon.ico
│ └─ index.html
├─ README.md
└─ src
├─ App.vue
├─ assets
│ └─ logo.png
├─ components
│ └─ HelloWorld.vue
├─ main.js
├─ router.js
├─ store
│ ├─ actions.js
│ ├─ getters.js
│ ├─ index.js
│ ├─ mutations.js
│ └─ state.js
├─ utils
│ └─ request.js
└─ views
├─ About.vue
└─ Home.vue
模板文件路径数组 :
const templates = [
'.browserslistrc',
'.editorconfig',
'.env.development',
'.env.production',
'.eslintrc.js',
'.gitignore',
'babel.config.js',
'package.json',
'postcss.config.js',
'README.md',
'public/favicon.ico',
'public/index.html',
'src/App.vue',
'src/main.js',
'src/router.js',
'src/assets/logo.png',
'src/components/HelloWorld.vue',
'src/store/actions.js',
'src/store/getters.js',
'src/store/index.js',
'src/store/mutations.js',
'src/store/state.js',
'src/utils/request.js',
'src/views/About.vue',
'src/views/Home.vue'
]
模板中需要替换的内容 :
// public / index.html
<link rel="icon" href="<%%= BASE_URL %>favicon.ico">
<title><%= name %></title>
// package.json 中
"name": "<%= name %>",
"version": "<%= version %>",
在 generators/app/index.js 完成代码,上代码~
// 引入 yeoman-generator
const Generator = require('yeoman-generator')
module.exports = class extends Generator {
// 用户输入 触发
prompting() {
return this.prompt([
{
type: 'input',
name: 'name',
message: 'Your project name',
default: this.appname
},
{
type: 'input',
name: 'version',
message: '靓仔, 搞个版本号哈',
default: '1.0'
},
{
type: 'input',
name: 'balabala',
message: '靓仔, 我创建了哈,你爱输啥输啥',
default: 'Y'
}
]).then(answer => {
this.answers = answer
})
}
// 生成文件 触发
writing() {
const templates = [
'.browserslistrc',
'.editorconfig',
'.env.development',
'.env.production',
'.eslintrc.js',
'.gitignore',
'babel.config.js',
'package.json',
'postcss.config.js',
'README.md',
'public/favicon.ico',
'public/index.html',
'src/App.vue',
'src/main.js',
'src/router.js',
'src/assets/logo.png',
'src/components/HelloWorld.vue',
'src/store/actions.js',
'src/store/getters.js',
'src/store/index.js',
'src/store/mutations.js',
'src/store/state.js',
'src/utils/request.js',
'src/views/About.vue',
'src/views/Home.vue'
]
for (const path of templates) {
this.fs.copyTpl(
this.templatePath(path),
this.destinationPath(path),
this.answers
)
}
}
}
写完之后需要yarn link
测试是否能够正常工作
发布到npm
自己写完的generator也可以发布到npm上使用,因为这只是测试代码,我这里就不发布了。
基本流程是
- 创建本地仓库
- 提交代码到本地 关联到远程仓库
- 如果使用的npm是淘宝的镜像,在publish之前要改回原地址,因为淘宝的镜像源是只读的,而你的包是要发布到npm上的
- 改完镜像地址后就可以运行 npm publish