手把手入门 Yeoman 模板开发

对大多数一个前端团队来说,Yeoman(简称yo)是一个非常值得学习的工具,它为前端项目提供了一种行之有效的方法,开发、分发、使用项目手脚架,提高项目启动速度,复用项目结构。

本文以generator-iview-admin 为例,简单说明手脚架,即generator的开发过程。

准备

开发 Yeoman 模板,需预先安装yo:

npm i -g yo

yo 细心的为我们准备了手脚架项目的手脚架generator-generator

npm i -g generator-generator

安装后,进入开发目录,运行:

yo generator

项目结构

执行上述命令后,主要生成如下文件:

├── .yo-rc.json
├── package.json
├── generators
│   ├── app
│       ├── templates
│           ├── dummyfile.txt
│       ├── index.js

其中

  • .yo-rc.json 用于存储项目配置,一般不会用到,无需关注
  • package.json npm 项目信息文件,主要关注 author、version 域即可
  • generators 目录即项目模板代码
  • generators/templates 用于存放项目模板文件
  • generators/app/index.js 定义项目手脚架的代码

开发

每个generator都会继承yeoman-generator类,即上述的generators/app/index.js文件。该类有几个重要的生命周期节点:

  1. initializing - 初始化方法,用于获取项目状态、配置等
  2. prompting - 调用inquire方法获取用户输入
  3. configuring - 保存配置,创建 .editorconfig 等文件
  4. writing - 执行文件写操作,即项目文件写入文件系统中
  5. install - 执行安装操作,需调用 this.installDependencies 方法
  6. end - 最后执行,可清楚临时文件等

上述方法均支持返回 Promise 方式实现异步操作,仅当返回的Promise 实例 resolve 时才会执行下一步操作。

Step 1. 获取用户配置

首先,我们需要在 prompting 中询问用户配置(完整实例在此处):

prompting() {
  // Have Yeoman greet the user.
  this.log(yosay('Welcome to the divine ' + chalk.red('generator-iview-admin') + ' generator!'));

  const prompts = [{
    type: 'input',
    name: 'name',
    message: 'Your project name',
    default: this.appname
  }, {
    type: 'confirm',
    name: 'lint',
    message: 'Use ESLint to lint your code?'
  }, {
    name: 'lintStyle',
    type: 'list',
    message: 'Pick an ESLint preset',
    when(answers) {
      return answers.lint;
    },
    choices: [{
      name: 'Airbnb (https://github.com/airbnb/javascript)',
      value: 'airbnb',
      short: 'Airbnb'
    }, {
      name: 'Standard (https://github.com/feross/standard)',
      value: 'standard',
      short: 'Standard'
    }]
  }];

  return this.prompt(prompts).then((props) => {
    // To access props later use this.props.someAnswer;
    this.props = props;
  });
}

这里,将用户输入的结果配置到 this.props 对象中,方便后续访问。

Step 2. 定义模板

yo 的核心,本质上是按需修改模板文件,一般包含三种方法:

  1. 直接写入文件
    对于简单对象,如JSON,可以使用 initPackage 方式。即:读入模板、按配置修改对象、写入文件
  2. 使用模板方式,将结果写入文件
    对于复杂文件,可使用 yo 提供的模板(ejs)引擎进行编写。
  3. 通过AST树修改文件
    通过解析语法树,深入理解模板内容,一般用于修改已存在的文件内容。建议使用 esprimacheerio 解析AST。

以ejs方式为例,编写模板(完整实例在此处):

<% if(vueFile==='standalone'){ %>
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
<% } %>

...

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,<% if(vueFile==='runtime'){ %>
  render: h => h(App)<% } else if(vueFile==='standalone'){ %>
  template: '<App/>',
  components: { App }<% } %>
});

Step 3. 按需写入

获取配置后,可以正式开始将模板写入硬盘中(完整实例在此处):

writing() {
  this.initPackage();
  this.renderTplFile();
}

initPackage() {
  let pkg = this.fs.readJSON(this.templatePath('package.json'), {});
  const {
    props
  } = this;

  pkg = _.merge(pkg, {
    name: props.name,
    description: props.description
  });
  
  ... 
  
  this.fs.writeJSON(this.destinationPath('package.json'), pkg);
}

renderTplFile() {
  let target = [
    ...
    'src/components/Hello.vue',
  ];

  if (this.props.unitTest) {
    target = target.concat([
      ...
      'build/webpack.test.conf.js'
    ]);
  }
  ...
  _.forEach(target, (file) => {
    this.fs.copyTpl(
      this.templatePath(file),
      this.destinationPath(file),
      this.props
    );
  });
}

yo 提供mem-fs-editor实例接口,包含一系列fs工具:

  • this.fs.read - 读取文件
  • this.fs.readJSON - 以JSON方式读取文件
  • this.fs.write - 写文件
  • this.fs.writeJson - 以JSON 方式写文件
  • this.fs.append - 将内容已追加方式写入文件
  • this.fs.extendJSON - 扩展JSON文件内容
  • this.fs.delete - 删除文件

此外,还有一系列路劲及模板接口:

  1. this.fs.copyTpl - 复制模板文件,并按参数解析模板内容,写入目标文件中
  2. this.templatePath - 返回模板文件路径,即上述 generator/app/templates 中的文件路径
  3. this.destinationPath - 返回目标文件路径,即执行 yo 生成模板文件的路径
  4. this.registerTransformStream - 生命钩子接口,用于转化文件内容,兼容gulp插件

至此,我们已了解开发一个yo模板所需要的所有接口。

添加子模板

yo允许添加任意数量的子模板,只需执行:

yo generator:subgenerator [name]

yo generator:subgenerator test 为例,生成如下文件:

├── generators
│   ├── app
│   ├── test
│       ├── templates
│           ├── dummyfile.txt
│       ├── index.js

templates、 index.js 文件的作用与上述无异,可直接开发。

试运行

可以通过如下方式,将项目加入本地generator 库:

npm link

之后,就可以执行如下命令,生成手脚架:

yo [your template name]

发布

模板开发完毕后,如需发布可执行如下命令:

npm publish

注意:

  1. 如果npm尚未登录,可执行 npm adduser 操作进行登录
  2. 发布npm包必须使用 https://registry.npmjs.org/ 源,切记切换。

1.4k 声望
6.8k 粉丝
0 条评论
推荐阅读
🦀🦀🦀 一个普通人的前端职业成长之路
大家好,我是 范文杰,一个前端从业者,最近刚写完我的第一本小册《Webpack 核心原理与实践应用》,这对我是一个不小的职业突破,所以不能免俗地想做个总结,分享我这九年工作的成长经历,以及我对前端这个职业的...

范文杰3阅读 1.1k

封面图
Vue中的diff算法
diff算法是一种通过同层的树节点进行比较的高效算法,避免了对树进行逐层搜索遍历,所以时间复杂度只有 O(n)。diff算法的在很多场景下都有应用,例如在 vue 虚拟 dom 渲染成真实 dom 的新旧 VNode 节点比较更新时...

款冬27阅读 14.2k评论 7

【已结束】SegmentFault 思否写作挑战赛!
SegmentFault 思否写作挑战赛 是思否社区新上线的系列社区活动在 2 月 8 日 正式面向社区所有用户开启;挑战赛中包含多个可供作者选择的热门技术方向,根据挑战难度分为多个等级,快来参与挑战,向更好的自己前进!

SegmentFault思否20阅读 5k评论 10

封面图
vue-property-decorator使用手册
@Component 装饰器可以接收一个对象作为参数,可以在对象中声明 components ,filters,directives等未提供装饰器的选项,也可以声明computed,watch等

似曾相识17阅读 29.6k评论 7

前端开发工具
前端教程阿西河前端教程丨一个助你成为全栈开发的网站前端高手进阶JavaScript利用js实现表单的校验KeyCode 查询表现代 JavaScript 教程时间戳(Unix timestamp)转换工具HTML minifierJavaScript代码压缩-js代码压...

寒青14阅读 2.6k

万字长文~vue+express+mysql带你彻底搞懂项目中的权限控制(附所有源码)
所谓的权限,其实指的就是:用户是否能看到,以及是否允许其对数据进行增删改查的操作,因为现在开发项目的主流方式是前后端分离,所以整个项目的权限是后端权限控制搭配前端权限控制共同实现的

水冗水孚11阅读 1.5k

[译]Vue.js + Astro 比 Vue SPA 更好吗?
在本文中,我将向您展示如何使用 Astro 构建基于 Vue 的应用程序,我们将了解其独特的架构如何带来比单页应用程序 (SPA) 更好的性能。

杭州程序员张张7阅读 4.1k评论 3

1.4k 声望
6.8k 粉丝
宣传栏