体现在项目的整个流程:
创建项目
创建项目结构
创建特定类型文件
编码
格式化校验
校验代码风格
编译 / 构建 / 打包
预览/测试
Web Server / Mock
Live Reloading / HMR
Source Map
提交
Git Hooks
Lint-staged
持续集成
部署
CI / CD
自动发布
如create-react-cli
如Yeoman
如plop
以创建node模块项目为例
在全局范围安装yo
安装对应的generator (不同的generator可生成不同的项目)
通过yo运行generator(运行方式是把包名前面的generator-去掉通过yo来运行)
如果想要在原有的项目基础上去添加一些配置文件需要使用Sub Generator
下面以向node项目中添加cli功能举例:
Sub Generator
在原有的基础上加上配置文件
基本使用,把上述项目变成一个cli命令,所以需要使用node:cli子集Generator来进行生成
yo node:cli
yarn link // 将本地的项目link到全局范围,就相当于 yarn link 自动给全局配置了一个 映射关系
因为转遍成cli项目后新增了新的依赖,所以需要安装一下新的依赖,使用yarn命令即可
my-module --help // 这样就不用使用yo node来使用了
注意,并不是所有的generator都提供子集sub生成器,需要根据Yeoman官网中给出的generator来
因为不同的Generator会生成不同的手脚架,那么我们自定义一个Generator将会生成自己所需要的项目结构
基于Yeoman搭建自己的脚手架,以Vue为例
Generator本质上就是一个npm模块
创建Generator模块
默认是app文件夹存放的是生成器对应的代码,如果有sub生成器的话就直接和app同级新建一个文件夹即可,如上图中就有了个component生成器
基本流程:
// index.js
// 此文件作为Generator的核心入口
// 需要导出一个继承自Yeoman Generator的类型
// 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 // 当前生成目录的文件夹的名字
}
])
.then(answers => {
// 这个answer是个对象,对象里面的键就是name,值就是用户输入的数据:{name:'用户输入的数据'}
this.answers = answers
})
}
writing () {
// Yeoman会在自动生成文件阶段调用此方法
this.fs.write(
this.destinationPath('temp.txt'), // 自动获取生成项目目录下的文件路径
Math.random().toString()
)
}
}
首先新建一个template模板文件夹,里面放模板文件,然后项目会根据这个模板文件来进行创建
// index.js
// 此文件作为Generator的核心入口
// 需要导出一个继承自Yeoman Generator的类型
// 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 // 当前生成目录的文件夹的名字
}
])
.then(answers => {
// 这个answer是个对象,对象里面的键就是name,值就是用户输入的数据:{name:'用户输入的数据'}
this.answers = answers
})
}
writing () {
/*this.fs.write(
this.destinationPath('temp.txt'),
Math.random().toString()
)*/ // 若使用模板方法就不用这种方式写入了
// 通过模板方法写入文件到目标目录
// 模板文件路径:
const tmpl = this.templagePath('foo.txt') // 模板文件
const output = this.destinationPath('foo.txt') // 输出路径
const context = this.answers // 模板数据上下文
this.fs.copyTpl(templ, output, context) // 将模板文件映射到输出文件中
}
}
// 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'
}
]
})
}
创建一个项目文件,并通过yarn init-y 进行初始化
在package.json中指定一个bin字段,用来指定脚手架的入口文件cli.js
// package.json 文件
{
"name": "sample-scaffolding", // 脚手架项目名称
"version": "0.1.0",
"main": "index.js",
"bin": "cli.js", // 指定脚手架的入口文件
"author": "zce (https://zce.me)" ,
"license": "MIT",
"dependencies": {
"ejs": "^2.6.2",
"inquirer": "^7.0.0"
}
}
创建cli.js文件
#!/usr/bin/env node
// Node CLI 应用入口文件必须要有这样的文件头
// console.log('demo-cli working!'); // test
// 脚手架的工作过程:
// 1. 通过命令行交互询问用户问题
// 2. 根据用户回答的结果生成文件
const fs = require('fs')
const path = require('path')
const inquirer = require('inquirer') // 发起命令行的询问
const ejs = require('ejs')
inquirer.prompt([
{
type: 'input', // 方式
name: 'name', // 键名
message: 'Project name?' // 问题(问题的答案就是键值)
}
])
.then(anwsers => {
// console.log(anwsers) // {name:'用户输入的内容'}
// 根据用户回答的结果生成文件
// 模板目录
const tmplDir = path.join(__dirname, 'templates')
// 目标目录
const destDir = process.cwd() // 命令行在哪个目录执行就输出到哪个路径,所以借助process的cwd方法
// 将模板下的文件全部转换到目标目录
fs.readdir(tmplDir, (err, files) => {