>[success] # 使用Yeoman ~~~ 1.安装'yo' 后,他就想npm一样,可以管理与生成器相关的所有内容,使用其他开发通过Yeoman封装 上传的node脚手架模板,直接生成了一个node项目开发的基本配置,如果通过Yeoman配置一个自己的 脚手架 ~~~ [官网案例说明](https://yeoman.io/authoring/index.html) >[info] ## 通过Yeoman 创建一个自己的脚手架 ~~~ 1.创建自己的'Yeoman generator',需要Yeoman的Generator的模块名称必须是'generator - < name >'的格式 如果不遵守,Yeoman后续工作时就无法找到你所提供的生成器模块 2.创建一个自己的'Yeoman' 2.1.创建一个规定格式的文件夹名 例如'generator-sample' 2.2.创建'package.json' -- 'npm init -y' 2.3.安装'yeoman-generator' -- 'npm install yeoman-generator' 这个某块提供了生成器的基类 ,基类中提供 了一些工具函数,这些函数可以让自己的'Yeoman generator',配置简单地复制一堆样板文件, 或者可以更高级地询问用户的喜好来搭建定制项目的脚手架 2.4.Yeoman的功能取决如何构造目录树,每个子生成器都包含在其自己的文件夹中,其中'默认生成器' 必须在'app' 目录中,也就是说当通过'yo <生成器名称例如generator-sample>' 时候实际相当执行是 在app目录配置的生成器设置,也可以调用其他生成器(官网叫子集生成器)通过 'yo name:<子集生成器的名>' ~~~ [项目目录规则](https://yeoman.io/authoring/index.html#folder-tree) * 项目目录的例子 在 generators/ 下注册生成器 ~~~ 1.生成器描述文件 package.json,其中限定了 name、file、keywords 等对应字段的规范赋值。 2.作为主体的 generators/app 目录,包含生成器的核心文件。该目录是执行 yo 命令时的默认查找目录, Yeoman 支持多目录的方式集成多个子生成器 3.app/index.js 是生成器的核心控制模块,其内容是导出一个继承自 yeoman-generator 的类,并由后者 提供运行时上下文、用户交互、生成器组合等功能。 app/templates/ 目录是我们需要复制到新项目中的脚手架模板目录。 ~~~ ~~~ ├───package.json └───generators ------ '生成器的目录' ├───app ------'默认生成器目录' │ ├── templates ----- '用于存放项目模板文件' │ │ └─── temp.txt │ └───index.js ------'默认生成器实现' └───router -----'其他生成器目录当然这个名字可以随便起' └───index.js ------'其他生成器实现' ~~~ * Yeoman 也允许直接在./ 目录下注册的生成器 ~~~ ├───package.json ├───app/ │ └───index.js └───router/ └───index.js // 这种配置需保将files属性指向package.json所有生成器文件夹 { "files": [ "app", "router" ] } ~~~ >[danger] ##### 编写index.js 中文件 ~~~ 1.现在我们想做一个脚手架,执行命令后帮我们自动在当前目录创建一个'temp.txt'文件,文件内容 是随机数字 ~~~ * 代码实现 ~~~ // 此文件作为 Generator 的核心入口 // 需要导出一个继承自 Yeoman Generator 的类型 // Yeoman Generator 在工作时会自动调用我们在此类型中定义的一些生命周期方法 // 我们在这些方法中可以通过调用父类提供的一些工具方法实现一些功能,例如文件写入 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() ) } } ~~~ * 如何使用创建好的Generator ~~~ 1.因为是在本地开发生成器,因此不能作为全局npm模块使用。可以使用npm创建一个全局模块 并将其符号链接到本地​​模块,在命令行上,从生成器项目的根目录输入指令 -- 'npm link' 2.在创建自己的'Generator '时候文件规范'generator - < name >',此时就可以配合'yo' 来执行自己创建的'Generator ' 例如案例中起的名字'generator-sample' 在我们想生成项目的文件下就可以输入指令 -- 'yo sample' ~~~ >[danger] ##### 关于'yeoman-generator' ~~~ 1.可以回想在使用其他脚手架工具时候一般步骤 1.1.初始化,一般在这个时候会进行环境的初始化,做一些前置的检查 1.2.用户输入,例如用 vue-cli 的时候,它会“问”你很多配置选项 1.3.生成配置文件 1.4.生成项目结构,这是候可能会使用一个项目模版 1.5.安装依赖 1.6.清理、校验等收尾工作 2.这些步骤过程都可以通过继承'yeoman-generator' 来实现,'yeoman-generator'为整体提供了 类似的生命周期的方法: 2.1.'initializing' - 初始化方法,用于获取项目状态、配置等 2.2.'prompting' - 调用'inquire'方法接收用户输入阶段,来处理终端的交互 2.3.'configuring' - 保存配置信息和文件创建 .editorconfig 等文件 2.4.'default' - 这里指的是自己自定义的方法,而不是名为default的方法,自己定义的方法如果不想被调用 那么需要确保方法名以_为开头 2.5.'writing' - 执行文件写操作,即项目文件写入文件系统中 2.6.'conflicts':统一处理冲突,如要生成的文件已经存在是否覆盖等处理 2.7.'install' -安装项目的依赖,比如npm install 或者 bower install,需调用 this.installDependencies 方法 2.8.'end' - 生成器结束阶段,可清除临时文件 3.因此上面案例中想做到的创建文件并且写入内容,这个操作放到了'writing' 方法的原因就是因为这个方法 可以 执行文件写操作,即项目文件写入文件系统中 4.除了上面案例yo 提供的额外的文件写入这种api外,还提供了下面这些使用的api 4.1.'this.fs.read' - 读取文件 4.2.'this.fs.readJSON' - 以JSON方式读取文件 4.3.'this.fs.write' - 写文件 4.4.'this.fs.writeJson' - 以JSON 方式写文件 4.5.'this.fs.append' - 将内容已追加方式写入文件 4.6.'this.fs.extendJSON' - 扩展JSON文件内容 4.7.'this.fs.delete' - 删除文件 还有一系列路劲及模板接口: 4.8.'this.fs.copyTpl' - 复制模板文件,并按参数解析模板内容,写入目标文件中 4.9.'this.templatePath' - 返回模板文件路径,即上述 generator/app/templates 中的文件路径 4.10.'this.destinationPath' - 返回目标文件路径,即执行 yo 生成模板文件的路径 4.11.'this.registerTransformStream' - 生命钩子接口,用于转化文件内容,兼容gulp插件 这些api 是由yo 的 mem-fs-editor 提供[https://github.com/sboudrias/mem-fs-editor] ~~~ [官网对于生命周期说明](https://yeoman.io/authoring/running-context.html#the-run-loop) >[danger] ##### yo 的Ejs模板功能 ~~~ 1.上面的示例是通过'this.fs.write' 去写文件,最大的我问题就是通过代码控制一些相对的文件内容,当文件的 内容相对复杂的时候这种写文件的成本也相对提高,yo 内部可以使用 EJS,这部分的文件需要放到templates 文件夹下的 ~~~ ~~~ // template/.txt 例如 <%= title %> 其他的 EJS 语法也支持 <% if (success) { %> sgh <% }%> ~~~ ~~~ // index.js const Generator = require('yeoman-generator') module.exports = class extends Generator { writing () { // 通过模板方式写入文件到目标目录 // 模板文件路径 const tmpl = this.templatePath('temp.txt') // 输出目录路径 const output = this.destinationPath('temp.txt') //('src/temp.txt') 也可以将输出文件指定到指定文件夹下 // 模板数据上下文 const context = { title:'Hello sgh~', success:false } // 参数 模板文件的路径 输出文件的路径 模板数据的上下文 this.fs.copyTpl(tmpl, output, context) } } ~~~ >[danger] ##### 安装packjson 内容 ~~~ 1.有时候希望我们的模板生成后还可以安装指定的 npm 包,这个时候除了使用上面介绍的,用esj这种生成 pack.json,也可以通'this.fs.writeJson' 来动态写json文件的配置 2.后续配合'npmInstall' api 来安装,当然也可以使用'yarnInstall' ~~~ [官网这部分介绍链接](https://yeoman.io/authoring/dependencies.html) ~~~ // index.js const Generator = require('yeoman-generator') module.exports = class extends Generator { writing() { const pkgJson = { devDependencies: { eslint: '^3.15.0' }, dependencies: { react: '^16.2.0' } }; // 创建package.json 文件目录 this.fs.extendJSON(this.destinationPath('package.json'), pkgJson); } // 调用安装 install(){ // 调用yarn 来安装指定库 this.yarnInstall() } } ~~~ >[danger] ##### 接收用户询问 -- prompting ~~~ 1.在常见的脚手架,其实可以看见,为了更加针对给用户自由度体验,会产生询问用户接下来的安装 内容的选择,这一部分的内容在'prompting'完成的 ~~~ [官网参考链接](https://yeoman.io/authoring/user-interactions.html) ~~~ // index.js const Generator = require('yeoman-generator') module.exports = class extends Generator { async prompting() { // 这里的变量名随便起的也就是说可以不叫 answers this.answers = await this.prompt([ { type: "input", name: "name", message: "Your project name", default: this.appname // Default to current folder name }, { type: "confirm", name: "cool", message: "Would you like to enable the Cool feature?" } ]); // this.log("app name", answers.name); // this.log("cool feature", answers.cool); } writing() { // 配合prompting 询问来生成动态文件 const tmpl = this.templatePath('temp.html') // 输出目标路径 const output = this.destinationPath('temp.html') // 模板数据上下文 const context = this.answers // 询问的内容生成的对象 配合esj 模板生成想要的内容 this.fs.copyTpl(tmpl, output, context) } }; ~~~ >[info] ## 定义一个 vue 脚手架 ~~~ 1.思路就是可以先在'templates' 定义好项目的结构目录,利用'yeoman-generator' 来写我们的脚手架 生成器 ~~~ ![](https://img.kancloud.cn/4f/2b/4f2b51cb728b6c81000bafd4f424d7ae_276x480.png) * 在 index.js 完成的代码,这里可以定义一些用户的输出配合esj模板更加动态定制 ~~~ 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 => { this.answers = answers }) } 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' ] // 生成文件 templates.forEach(item => { // item => 每个文件路径 this.fs.copyTpl( this.templatePath(item), this.destinationPath(item), this.answers ) }) } } ~~~ >[danger] ##### 发布 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 的关键词,官方会发现到此项目。 ~~~ >[danger] ##### 快速创建yo 的generator 目录 ~~~ 1.当然也可以直接使用已经为我们创建好的'generator' 项目使用,会帮我创建好一个自定义的'generator' 项目目录格式的脚手架 npm i -g generator-generator yo generator ~~~ >[danger] ##### 参考文章 [前端自动化工具 - yeoman](https://www.yuque.com/kongdepeng/rgpm60/qrluup) [手把手入门 Yeoman 模板开发](https://juejin.im/post/6844903743012487181) [如何快速开发一个自己的项目脚手架?](https://zhuanlan.zhihu.com/p/66190308) [使用 Yeoman 定制自己的项目脚手架 ](https://juejin.im/post/6844904191912050695)