为了账号安全,请及时绑定邮箱和手机立即绑定

使用Yeoman制作前端脚手架

标签:
前端工具

最近因为工作原因要做一个前端项目的开发模板,发布给公司各个业务研发部门按照前端模板开发各自的业务app,一开始使用git管理前端模板,发现有有几个弊端:

  1. 业务部门从git上拉下模板代码后,还要根据具体的业务需要进行配置,修改代码对于前端框架有一定的学习的成本,比较麻烦,对于业务来讲也没有必要学习这些框架知识

  2. 修改配置代码的工作基本上是在项目开始就要完成的,是一次性的,后续不用关心有什么变化

  3. 对于页面生成,路由生成这些操作,基本上是固定的操作,每次新建js文件,并在旧的文件中添加固定格式的代码,既繁琐又有规律可循,所以考虑是不是可以一个命令就能够添加一个页面文件,并生成路由呢

基于上面几点,考虑做一个前端脚手架项目,通过脚手架来复用项目结构,并把对于项目结构的配置工作和复杂枯燥的操作都打包到这个脚手架的功能上,通过命令行就可以完成以前好几步才能完成的步骤,方便业务开发并提升研发效率。

好了,说干就干,在网上搜索了一下,发现Yeoman就是干这个事情的,而且是谷歌开发的脚手架工具,尝试了一下非常好,下面就记录一下yeoman的使用过程

工具

  • npm

  • yo

  • generator-generator

npm的安装不多说了,可以直接去nodejs的官网https://nodejs.org/en/下载Node最新的版本,而最新的Node版本默认都自带了npm包管理工具

用npm安装yo,其中yo是Yeoman的命令行工具包,需要全局安装:
npm install -g yo

全局安装 generator-generator,顾名思义,generator-generator就是generator的generator,我们要做一个脚手架,那我们就用脚手架的脚手架生成一个脚手架...(我不是在说绕口令)
npm install -g generator-generator

步骤

1. 创建一个脚手架的基础框架

运行generator-generator来创建我们的脚手架基础框架,运行如下命令:
yo generator

接下来会有一系列的询问问题,其中generator name需要设置为必须以generator-为前缀,因为generator都是普通全局安装的Node.js模块,所以Yeoman完全依赖于文件系统找到它们。

基础框架安装完成之后的目录结构如下图:


webp

folders

我们对于脚手架的定制开发工作主要在下面两个文件夹

  • generators/app/templates 目录
    放置脚手架代码模板的文件夹

  • generators/app/index.js 文件
    配置用户输入信息,模板迁移和替换规则,安装项目依赖模块;该文件是Generator的子类,重点完成三个方法的定制,分别是prompting,writing,install,下面会重点讲解。

2. 配置用户输入项

脚手架的工作方式是:先询问用户的配置需求,比如你的项目名字是什么?要使用哪些工具类?然后根据用户的输入,完成项目文件的初始化工作。

我们先看下用户输入配置的代码,地址:generators/app/index.js

prompting() {  // Have Yeoman greet the user.
  this.log(
    yosay(`Welcome to the cool ${chalk.red('generator-xxx-porject')} generator!`)
  );  const prompts = [
    {      type: 'confirm',      name: 'someAnswer',      message: 'Would you like to enable this option?',      default: true
    },{      name: 'appName',      message: 'your appName name?'
    }
  ];  return this.prompt(prompts).then(props => {    // To access props later use this.props.someAnswer;
    this.props = props;
  });
}

prompting方法主要是来完成和用户交互的,交互的用户输入信息都放在prompts数组中:

  • name 用户输入项的标识,在获取用户输入值的时候会用到

  • message 是给用户的提示信息

  • type 非必填,默认是text,即让用户输入文本;confirm是选择输入“Yes/No"

  • default 非必填,用户输入的默认值

用户输入详细解读请参考inquirer类库,此处不再展开https://www.npmjs.com/package/inquirer

3. 模板文件

上面已经提到generators/app/templates目录存储的是文件的模板文件,是生成新的项目结构的原材料;

一个新的项目的文件和结构,是由三部分组成

  • 固定文件 直接从template目录copy到项目目录即可

  • 加工文件 根据上一步用户的输入,对templates目录下的模板进行二次加工,再copy到用户指定的目录中,以完成项目的初始化

  • 可选文件 根据用户的选择,如果需要则copy,不需要则不copy

对于加工文件,在templates文件中可以使用EJS模板语法,把模板和用户的输入信息结合起来,就生成了一个新的项目文件,EJS的模板语法如下:

  • 赋值 <%= appName%>

  • 表达式 <% if(someAnswer){ xxx } %>

4. 加工模板

项目文件的模板拷贝和用户输入替换工作都在writing方法中实现,示例代码如下:

writing() {    let target = [      // 需要加工的文件使用数组
      ['src/_route.js', 'src/route.js'],
      ['_index.ejs', 'index.ejs'],      // 不需要加工的文件
      'README.md',      'index.html',      'package.json',      'proxy.json',      'build/build.js',      'build/check-versions.js',
      ......
      ...
    ];    // 添加隐藏文件 .文件名称在linux下会有问题,所以.xxx在template里改为_xxx
    target = target.concat([
        ['_eslintrc.js', '.eslintrc.js'],
        ['_eslintignore', '.eslintignore'],
        ['_babelrc', '.babelrc'],
        ['_editorconfig', '.editorconfig'],
        ['_gitignore', '.gitignore'],
        ['_postcssrc.js', '.postcssrc.js']
      ]);
    _.forEach(target, (file) => {      let toFile;      let fromFile;      if (_.isArray(file)) {        // eslint-disable-next-line
        fromFile = file[0];        // eslint-disable-next-line
        toFile = file[1];        this.fs.copyTpl(        this.templatePath(fromFile),        this.destinationPath(toFile),        this.props
        );
      } else {
        fromFile = file;
        toFile = file;        this.fs.copy(        this.templatePath(fromFile),        this.destinationPath(toFile),        this.props
      );
      }
       
    });
  }

需要重点关注四个方法:

  • this.templatePath:返回template目录下文件的地址

  • this.destinationPath:指定加工完成后文件的存放地址,一般是项目目录

  • this.fs.copy:把文件从一个目录复制到另一个目录,一般是从template目录复制到你所指定的项目目录,用于固定文件可选文件(根据用户选择)

  • this.fs.copyTpl:和上面的函数作用一样,不过会事先经过模板引擎的处理,一般用来根据用户输入处理加工文件

那么怎么加工文件呢?上面我们已经介绍了EJS模板的赋值和表达式语法,比如我们上面有一个输入项是appName

in prompting function :

const prompts = [
    {
      name: 'appName',
      message: 'your appName name?'
    }
  ];

我们把这个输入项作为html的title,那么我们就可以在template文件夹里面放入下面的模板文件:

<!DOCTYPE html><html lang="en"><head>
  <meta charset="UTF-8">
  <title><%= appName %></title></head><body>
  <div id="vue"></div></body></html>
5. 安装依赖

好了,项目文件加工完成并复制到了指定的目录,接下来要运行我们的项目,还需要使用npm install 安装项目的依赖,那么可不可以把这个操作也放到脚手架的安装过程中呢?下面的 install() 方法就可以:

install() { // 安装npm依赖和bower依赖
 //this.installDependencies(); 
 // 只安装bower依赖
 //this.bowerInstall();
 // 只安装npm组件
 this.npmInstall();
}

其中有三个方法可以使用,既可以安装npm依赖包,也可以安装bower依赖包,具体请参考上面的代码注释。

6. 本地运行脚手架

综上,一个脚手架最基本的功能就完成了,首先先将这个脚手架链接到本地:
npm link // 如果提示权限问题请使用sudo npm link

此时脚手架已经可以本地使用了,在本地创建一个项目目录,进入该目录,尝试使用该脚手架,比如你的脚手架项目名称为generator-name,命令行中应该去掉脚手架项目的前缀“generator-”来运行:
yo name

根据设定的提示和输入信息,Yeoman会一步一步安装你的项目文件,最终生成你指定的项目结构。

7. 发布脚手架到npm

如果你希望自己的脚手架给更多的人提供方便,可以把它发布到npm上。

首先需要一个npm账号,如果没有可以使用npm adduser创建;
如果有则运行npm login登陆,然后到工程根目录下,运行npm publish就可以发布了。

Yeoman生命周期

按照yeoman的生命周期运行任务,基本上已经满足了我们对于脚手架的基本需求,我们来总结一下这些生命周期函数:

  • initializing - 初始化函数

  • prompting - 接收用户输入阶段

  • configuring - 保存配置信息和文件

  • default - 自定义功能函数名称,如 method1

  • writing - 生成项目目录结构阶段

  • conflicts - 统一处理冲突,如要生成的文件已经存在是否覆盖等处理

  • install - 安装依赖阶段

  • end - 生成器结束阶段

进阶 sub-generator

1. sub-generator 的需求

上面最基本的脚手架解决了我们项目开发初期生成项目文件的需求,然而在实际的开发过程中,我们每开发一个页面或组件,都要手工创建文件,是不是觉得有点麻烦呢,下面我们介绍用命令行的方式增加或者修改项目文件的方法。

先看下我们想要的效果,我们要创建一个叫xoxo的组件:

yo my-generator:component xoxo
create src/components/xoxo.vue

一个命令脚手架就在目标目录src/components/下生成了xoxo.vue,是怎么实现的呢?请往下看

2. 创建sub-generator脚手架工具

sub-generator是Yeoman generator包的一部分,它的作用是给generator提供额外的脚手架功能,比如给项目添加新的元素

创建生成 component 的 sub-generator,如下:
yo generator:subgenerator component

这样在generator文件加下创建了sub-generator的工作目录(component),该目录和app目录在同一级,我们把模板文件copy到templates目录,如图:


webp

subgenerator.png

3. 接收用户输入参数

与generator脚手架不同的地方,sub-generator只是用命令行参数的方式指定要创建的组件名称就可以了,那么命令行参数是怎样获得的呢?

index.js 代码实现如下:

module.exports = class extends Generator {
  initializing() {    this.argument('componentName', { type: String, required: true });
  }
  prompting() {}
 
  writing() {    this.fs.copyTpl(      this.templatePath('src/components/example-app.vue.EJS'),      this.destinationPath('src/components/' + this.options.componentName + '.vue'),
      { componentName: this.options.componentName }
    );
  }
  install() {}
};

initializing 方法指定了用户输入的命令行参数:

  • 参数名称为componentName,类型为stringrequired属性指定了该参数是必须的。

  • 用户输入命令完成后,该参数存储到this.options中,可以使用this.options.componentName获取参数值

以上,一个简单的sub-generator就生成了,只用了yeoman生命周期的initializingwriting两个方法

完结

好了,关于Yeoman的用法就介绍到这了,其实Yeoman生成脚手架不只限于前端,任何语言的脚手架都可以用Yeoman来实现,还有很多强大功能等待你的发现,更多更详细的说明请参考Yeoman官网:

http://yeoman.io/



作者:阳光的记忆
链接:https://www.jianshu.com/p/b69661065a8d


点击查看更多内容
3人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消