上手 yeoman generator

  • 2019 年 12 月 5 日
  • 筆記

本文作者:IMWeb 刘志龙 原文出处:IMWeb社区 未经同意,禁止转载

最近折腾脚手架相关的一些事情。说到脚手架,不得不谈的就是yeoman了。

是什么

yeoman是一个脚手架生成工具。

yeoman generator则是yeoman的精髓所在。

从我的理解来看。yeoman就是一个工具外壳,它定制了如何调用generator,给generator提供了运行环境。yeoman generator则是解耦出来的核心部分,负责完成一个脚手架应该做的事。

线上已经有很多generator,可以满足我们一大波需求。不过要做到真正灵活,完全符合自己的需求、业务中的需要就要自己自定义generator了。

怎么做

yeoman的强大之处在于它提供了一套非常强大的编写自定义generatorAPI,而且上手非常容易。只要按照特定的约束,很快就可以定制一套自己的generator。话不多说,马上一起来看看怎么做。

目录结构

|- app      |- index.js      |- template          |- 模板文件  |- package.json (主入口为app/index.js)

初始化一个npm包,定制目录结构如上,这样就简单完成generator的目录结构啦。当然你可以用generator-generator生成符合规范的generator,这样更加快捷。

index.js结构示例

package.json不多说,注意主入口写好就行。index.js 也是按照具体的约束,一个简单的示例:

var generators = require('yeoman-generator')    module.exports = generators.Base.extend({    constructor: function () {      generators.Base.apply(this, arguments)    }      // 方法A      // 方法B  })

一个 Yeoman Generator 被创建后,会依次调用它原型上的方法,调用的顺序如下:

initializing - 初始化一些状态之类的,通常是和用户输入的 options 或者 arguments 打交道,这个后面说。  prompting - 和用户交互的时候(命令行问答之类的)调用。  configuring - 保存配置文件(如 .babelrc 等)。  default - 其他方法都会在这里按顺序统一调用。  writing - 在这里写一些模板文件。  conflicts - 处理文件冲突,比如当前目录下已经有了同名文件。  install - 开始安装依赖。

也可以自定义方法,比如demo里方法A会先于方法B执行。下面具体介绍下每个方法的一些作用。

prompting

用于做命令行的交互,这个应该是最常用的一个功能。用于在命令行和用户交互,用户提一些问题,我们的generator收集问题的结果。一个简单的例子:

prompting: function () {          let models = [{              name: 'eslint',              checked: true          }, {              name: 'sass-lint',              checked: true          }];            const prompts = [{              type: 'checkbox',              name: 'enable',              message: '开启哪些功能?',              choices: models          }, {              type: 'confirm',              name: 'installDependencies',              message: '安装相关依赖?',              when: (props) => {                  return props.enable.length;              }          }, {              type: 'list',              name: 'tool',              message: '使用npm/tnpm?',              choices: [                  CFG.CHOICE.TOOL.NPM,                  CFG.CHOICE.TOOL.TNPM              ],              when: (props) => {                  return props.installDependencies;              }          }];            return this.prompt(prompts).then((options) => {              this.userOptions = options;          });      },

这里不对代码细解释,只需要知道这里可以做用户命令行交互,具体每个参数有什么意义,github上搜索一下Inquirer.js就很清晰了。

writing

这里用于文件拷贝,读文件,写文件。一个简单的例子:

writing: function () {          const options = this.userOptions;          const imlintrcPath = this.cfg.imPath;          const imlintrcJson = {              config: options          };          const enableModules = options.enable || [];          const MODULES = CFG.MODULES;          const pkgPath = this.cfg.pkgPath;          let pkgJson;            /** 1. 复制基础样板文件 */          this.cfg.files.forEach((item) => {              this.fs.copy(                  this.templatePath(item),                  this.destinationPath(item)              );          });            /** 2. 创建imlintrc文件 */          this.fs.writeJSON(imlintrcPath, imlintrcJson);            /** 3. 修改package.json scripts配置 */          try {              pkgJson = this.fs.readJSON(pkgPath);          } catch (ex) {              console.log('imlint: package.json不合法');              process.exit();          }            pkgJson.devDependencies = pkgJson.devDependencies || {};          pkgJson.scripts = pkgJson.scripts || {};          Object.assign(pkgJson.scripts, this.cfg.scripts || {});            /** 4. 部署对应模块,包括:1. 迁移文件 2. 修改package.json devDependencies配置 */          enableModules.forEach((item) => {              const cur = MODULES[item];                if (!cur) {                  return;              }                Object.assign(pkgJson.devDependencies, cur.pkgs);                if (cur.files) {                  cur.files.forEach((file) => {                      this.fs.copy(                          this.templatePath(file),                          this.destinationPath(file)                      );                  });              }          });            /** 5. 写package.json文件 */          this.fs.writeJSON(pkgPath, pkgJson);      },

具体文件API的意义可参见mem-fs-editor 这个库

install

用于安装依赖,比如npm install一个lodash

install() {      this.npmInstall(['lodash'], { 'save-dev': true });  }

发布

index.js写完,一个简单的generator就ok了。上面这个DEMO具体的详细例子,可以看 generator-imlint-init

将上面这个npm包发布后,就可以按如下方法安装使用了~~

npm install -g yo  npm install -g generator-imlint-init    yo imlint-init

小结

例子比较简单,方法也只说了下generator最常用的三个方法。更多的功能参见yeoman官方文档~~