最近折腾脚手架相关的一些事情。说到脚手架,不得不谈的就是 yeoman
了。
yeoman
是一个脚手架生成工具。
yeoman generator
则是 yeoman
的精髓所在。
从我的理解来看。 yeoman
就是一个工具外壳,它定制了如何调用 generator
,给 generator
提供了运行环境。 yeoman generator
则是解耦出来的核心部分,负责完成一个脚手架应该做的事。
线上已经有很多 generator
,可以满足我们一大波需求。不过要做到真正灵活,完全符合自己的需求、业务中的需要就要自己自定义 generator
了。
yeoman
的强大之处在于它提供了一套非常强大的编写自定义 generator
的 API
,而且上手非常容易。只要按照特定的约束,很快就可以定制一套自己的 generator
。话不多说,马上一起来看看怎么做。
|- app |- index.js |- template |- 模板文件 |- package.json (主入口为app/index.js)
初始化一个npm包,定制目录结构如上,这样就简单完成 generator
的目录结构啦。当然你可以用 generator-generator
生成符合规范的 generator
,这样更加快捷。
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执行。下面具体介绍下每个方法的一些作用。
用于做命令行的交互,这个应该是最常用的一个功能。用于在命令行和用户交互,用户提一些问题,我们的 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: 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
这个库
用于安装依赖,比如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
官方文档~~