美文网首页我爱编程
前端工程化系列[07] Yeoman-generator的创建

前端工程化系列[07] Yeoman-generator的创建

作者: 文顶顶 | 来源:发表于2018-06-15 18:11 被阅读67次

    Yeoman脚手架使用入门Yeoman脚手架核心机制这两篇文章中已经对Yeoman脚手架工具的基本使用以及去核心运转机制进行了深入的介绍,这篇文章将以实例的方式来教会如何从零开始创建属于我们自己的generator。

    点击获取本文示例的generator

    generator创建准备

    这里我们一切从零开始,在创建自己的generator之前需要做一些准备工作,比如准备好yo命令行工具,比如对生成器生成的项目结构和目录文件有清晰的规划等。

    yo命令行工具

    在安装了NodeJS和npm的前提下,可以通过下面的命令来安装yo命令行工具,并检查安装是否成功。

    $ npm install -g yo
    $ yo --version

    generator-generator的安装

    创建generator可以完全从零开始,也可以使用Yeoman官方提供的generator引导,这里我们选择使用Yeoman官方推荐的方式来处理。
    $ mkdir YeomanTest && cd YeomanTest/ 创建新的目录并进入
    $ npm install -g generator-generator 安装Yeoman引导generator

    列出具体的执行情况

    wendingding$ mkdir YeomanTest
    wendingding$ cd YeomanTest/
    wendingding$ pwd
    /Users/文顶顶/Desktop/Yeoman/YeomanTest
    wendingding$ npm install -g generator-generator + generator-generator@4.0.2
    
    updated 1 package in 117.639s
    
       ╭─────────────────────────────────────╮
       │                                     │
       │   Update available 5.5.1 → 6.1.0    │
       │     Run npm i -g npm to update      │
       │                                     │
       ╰─────────────────────────────────────╯
    

    执行Yeoman官方的引导generator,并处理交互式配置部分,下面列出具体的执行情况。

    wendingding$ yo generator
    ? Your generator name generator-wendingding
    Your generator must be inside a folder named generator-wendingding
    I'll automatically create this folder.
    ? Description 博客文章测试创建生成器
    ? Project homepage url http://www.wendingding.com
    ? Author's Name 文顶顶
    ? Author's Email 18681537032@163.com
    ? Author's Homepage http://www.wendingding.com
    ? Package keywords (comma to split) wendingding
    ? Send coverage reports to coveralls Yes
    ? GitHub username or organization flowerField
    ? Which license do you want to use? Apache 2.0
       create package.json
       create README.md
       create .editorconfig
       create .gitattributes
       create .gitignore
       create generators/app/index.js
       create generators/app/templates/dummyfile.txt
       create __tests__/app.js
       create .travis.yml
       create .eslintignore
       create LICENSE
    I'm all done. Running npm install for you to install the required dependencies.
    If this fails, try running the command yourself.
    

    在执行generator-generator这个生成器的过程中,会询问项目名称、作者、使用协议、主页地址等等信息,依次选择填空即可。

    注意:按照约定,Yeoman generator的名字必须以“generator-”的前缀开头,这是因为所有的generator其实都是全局安装的node模块,所以Yeoman其实是完全依靠文件系统来对这些生成器进行查找操作的。

    当上面的命令执行完毕后,会发现在当前的路径下面生成了generator-wendingding目录,进入到generator-wendingding目录,使用tree命令查看当前目录结构,显示如下:

    .
    ├── LICENSE
    ├── README.md
    ├── __tests__
    ├── generators
    ├── app
    │    ├── index.js
    │    └── templates
    │        └── dummyfile.txt
    ├── node_modules
    ├── package-lock.json
    └── package.json
    

    上面目录结构中虽然有很多文件,但我们真正需要关注的应该是generators路径下面的app/index.js文件以及templates目录,其中index文件对应是generators的组装指令部分,templates路径用于存放项目所有的模板文件。

    项目模板文件准备

    上面这些准备工作完成之后,接下来我们开始着手分析目标项目的文件结构,即我们使用自己创建的这个脚手架来搭建项目,其结构目录应该是怎样的?需要包含哪些文件等等。任何时候,明确知道你的目标,知道自己正在做什么至关重要。

    下面试着给出目标项目的文件结构。

    .
    ├── Gruntfile.js
    ├── bower.json
    ├── build
    ├── dist
    ├── package.json
    └── src
        ├── css
        │   └── style.css
        ├── index.html
        ├── js
        │   └── index.js
        ├── libs
        │   └── jquery
        └── template
    

    我们可以看到该项目应该包含bulidsrc以及dist三个目录,其中src目录中需要创建名为cssjslibstemplate的文件夹,分别用来保存样式文件、脚本文件、依赖的框架以及模板文件等。

    除了这些必要的文件外,假设目标项目需要使用bower来进行依赖管理,使用Grunt来进行自动化构建,所以自然还应该拥有Gruntfile.js、bower.json以及package.json文件。

    假设目标项目中一定会使用到jQuery框架,可能会使用到bootstrap框架。

    现在我们可以开始分析生成器中应该包含项目模板文件了,也就是在generators/templates路径中应该包含哪些文件。

    固定文件

    index.jsstyle.css创建空文件即可。
    Gruntfile.js文件因为内容固定不变,所以选择直接从旧项目中拷贝。
    .jshintrc文件用于js文件语法检查,内容也是固定不变的。
    .bowerrc文件用于重置Bower下载包的安装路径,内容为{"directory": "src/libs/"}

    灵活文件

    package.json文件中项目名称、作者以及开源协议等需要用户配置
    bower.json文件的项目名称、作者、开源协议以及依赖框架等需要用户配置

    可选文件

    bootstrap框架相关的部分为可选文件,需要根据用户配置进行处理。

    依赖文件

    jQuery框架相关的部分为依赖文件,在组装指令部分通过在代码中调用方法来下载和安装。

    根据上面的分析,我们在generators/templates准备了多个模板文件,下面列出文件结构以及主要文件的具体内容:

    .
    └── app
        ├── index.js
        └── templates
            ├── Gruntfile.js
            ├── bower.json
            ├── css
            │   └── style.css
            ├── index.html
            ├── js
            │   └── index.js
            └── package.json
    

    package.json文件内容

    {
      "name": "<%= appName %>",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "author": "<%= appAuthor %>",
      "license": "<%= appLicense %>",
      "devDependencies": {
        "grunt": "^1.0.2",
        "grunt-contrib-concat": "^1.0.1",
        "grunt-contrib-cssmin": "^2.2.1",
        "grunt-contrib-jshint": "^1.1.0",
        "grunt-contrib-uglify": "^3.3.0",
        "grunt-contrib-watch": "^1.0.0"
      }
    }
    

    bower.json文件内容

    {
      "name": "<%= appName %>",
      "description": "\"测试使用\"",
      "main": "js/index.js",
      "authors": [
        "<%= appAuthor %>"
      ],
      "license": "<%= appLicense %>",
      "keywords": [
        "generator-wendingding",
        "yeoman-generator"
      ],
      "homepage": "https://github.com/flowerField/generator-wen",
      "ignore": [
        "**/.*",
        "node_modules",
        "bower_components",
        "test",
        "tests"
      ],
      "dependencies": {
        "jquery": "^3.3.1"<% if(isIncludeBootstrap) { %>,
        "bootstrap": "^4.1.1" <% } %>
      }
    }
    

    index.html文件内容

    <!DOCTYPE html>
    <html lang="en" dir="ltr">
      <head>
        <meta charset="utf-8">
        <title><%= appName %></title>
        <link rel="stylesheet" href="css/style.css">
        <script type="text/javascript" src="js/index.js"></script>
      </head>
      <body>
    
      </body>
    </html>
    

    Gruntfile.js文件内容

    //包装函数
    module.exports = function (grunt) {
        // 项目配置信息
        grunt.config.init({
            pkg:grunt.file.readJSON("package.json"),
            //代码合并
            concat:{
                options:{
                    stripBanners:true,
                 banner:'/*项目名称:<%=pkg.name%> 项目版本:<%=pkg.version%> 项目的作者:<%=pkg.author%> 更新时间:<%=grunt.template.today("yyyy-mm-dd")%>*/\n'
                },
                target:{
                    src:["src/js/*.js"],
                    dest:'build/js/index.js'
                }
            },
            //js代码压缩
            uglify:{
                target:{
                    src:"build/js/index.js",
                    dest:"build/js/index.min.js"
                }
            },
            //css代码压缩
            cssmin:{
                target:{
                    src:"src/css/style.css",
                    dest:"build/css/style.min.css"
                }
            },
            //js语法检查
            jshint:{
                target:['Gruntfile.js',"dist/js/index.js"],
            },
            //监听 自动构建
            watch:{
                target:{
                    files:["src/js/*.js","src/css/*.css"],
                    //只要指定路径的文件(js和css)发生了变化,就自动执行tasks中列出的任务
                    tasks:["concat","jshint","uglify","cssmin"]
                }
            }
        });
        //通过命令行安装插件(省略...)
        //从node_modules路径加载插件
        grunt.loadNpmTasks("grunt-contrib-concat");
        grunt.loadNpmTasks("grunt-contrib-uglify");
        grunt.loadNpmTasks("grunt-contrib-cssmin");
        grunt.loadNpmTasks("grunt-contrib-jshint");
        grunt.loadNpmTasks("grunt-contrib-watch");
        //注册任务:在执行$ grunt命令的时候依次执行代码的合并|检查|压缩等任务并开启监听
        grunt.registerTask("default",["concat","jshint","uglify","cssmin","watch"]);
    };
    

    注意:上面部分文件中很多地方使用模板语法来传递参数,Yeoman所用的模板语言是EJS,具体用法请参考EJS官网

    组装指令

    处理完上面这些工作之后,接下来就是最最核心的部分了,我们需要在app/index.js文件中编写组装指令,这部分代码控制着这个生成器应该怎么执行,包括交互式配置的具体内容、如何复制文件以及框架依赖和Node模块下载等内容。

    下面列出该示例中的index.js文件内容

    'use strict';
    const Generator = require('yeoman-generator');
    const chalk = require('chalk');
    const yosay = require('yosay');
    const mkdirp = require('mkdirp');
    
    module.exports = class extends Generator {
      prompting() {
        this.log(
          // yosay(`Welcome to the transcendent ${chalk.red('generator-wen')} generator!`)
          yosay(`欢迎使用\n${chalk.red('generator-wen')} !\n Author:文顶顶`)
        );
    
        const prompts = [
          {
            type    : 'input',
            name    : 'appName',
            message : '请输入项目名称:',
            default : this.appname        //appname是内置对象,代表工程名,这里就是ys
         },
         {
           type    : 'input',
           name    : 'appAuthor',
           message : '请输入作者姓名:',
           default : '文顶顶'
        },
        {
            type: 'list',
            name: 'appLicense',
            message: '请选择使用的license:',
            choices: ['MIT', 'ISC', 'Apache-2.0', 'AGPL-3.0']
          },
          {
            type    : 'confirm',
            name    : 'isIncludeBootstrap',
            message : '是否需要使用bootStrap框架?',
            default : false
         },
    
        ];
    
        return this.prompt(prompts).then(props => {
          // To access props later use this.props.someAnswer;
          this.props = props;
        });
      }
    
      writing() {
        mkdirp("build");
        mkdirp("dist");
        mkdirp("src/template");
    
        this.fs.copyTpl(
          this.templatePath('index.html'),
          this.destinationPath('src/index.html'),
          {appName: this.props.appName}
        );
    
        this.fs.copy(
          this.templatePath('css/style.css'),
          this.destinationPath('src/css/style.css')
        );
    
        this.fs.copy(
          this.templatePath('js/index.js'),
          this.destinationPath('src/js/index.js')
        );
    
        this.fs.copy(
          this.templatePath('.bowerrc'),
          this.destinationPath('.bowerrc')
        );
    
        this.fs.copy(
          this.templatePath('Gruntfile.js'),
          this.destinationPath('Gruntfile.js')
        );
    
        this.fs.copy(
          this.templatePath('.jshintrc'),
          this.destinationPath('.jshintrc')
        );
    
        this.fs.copyTpl(
          this.templatePath('package.json'),
          this.destinationPath('package.json'),
           {appName: this.props.appName,appAuthor:this.props.appAuthor,appLicense:this.props.appLicense}
        );
    
        this.fs.copyTpl(
          this.templatePath('bower.json'),
          this.destinationPath('bower.json'),
           {appName: this.props.appName,appAuthor:this.props.appAuthor,appLicense:this.props.appLicense,isIncludeBootstrap:this.props.isIncludeBootstrap}
        );
      }
    
      install() {
        //this.installDependencies();
        this.bowerInstall();
      }
    };
    
    

    上面的代码大概由三部分组成,第一部分为prompting函数用来处理安装提示,第二部分为writing函数用来设置模板文件的复制操作,第三部分为install函数用来处理框架依赖和node包的安装。

    generator的发布和测试

    项目模板文件和组装指令都准备好了后,我们就可以发布自己的generator了,可以先通过$ npm link命令以软连接的方式生成一个全局的npm包,测试使用。
    具体的执行细节如下

    wendingding:generator-wendingding wendingding$ npm link
    up to date in 3.897s
    /usr/local/lib/node_modules/generator-wendingding -> /Users/文顶顶/Desktop/Yeoman/YeomanTest/generator-wendingding
    wendingding:generator-wendingding wendingding$
    

    测试·使用自己创建的generator来生成初始化项目

    随便找个目录新建文件夹,使用$ yo wendingding命令即可完成项目的初始化工作。

    wendingding:YeomanTest wendingding$ mkdir Demo
    wendingding:YeomanTest wendingding$ cd Demo/
    wendingding:Demo wendingding$ yo wendingding
    
         _-----_     ╭──────────────────────────╮
        |       |    │         欢迎使用           │
        |--(o)--|    │      generator-wen !      │
       `---------´   │      Author:文顶顶        │
        ( _´U`_ )    ╰──────────────────────────╯
        /___A___\   /
         |  ~  |     
       __'.___.'__   
     ´   `  |° ´ Y `
    
    ? 请输入项目名称: Demo
    ? 请输入作者姓名: 文顶顶
    ? 请选择使用的license: Apache-2.0
    ? 是否需要使用bootStrap框架? Yes
       create bower.json
       create package.json
       create src/index.html
       create src/css/style.css
       create src/js/index.js
       create .bowerrc
       create Gruntfile.js
       create .jshintrc
    bower invalid-meta  for:/Users/文顶顶/Desktop/Yeoman/YeomanTest/Demo/bower.json
    bower invalid-meta  The "name" is recommended to be lowercase, can contain digits, dots, dashes
    bower cached        https://github.com/jquery/jquery-dist.git#3.3.1
    bower validate      3.3.1 against https://github.com/jquery/jquery-dist.git#^3.3.1
    bower cached        https://github.com/twbs/bootstrap.git#4.1.1
    bower validate      4.1.1 against https://github.com/twbs/bootstrap.git#^4.1.1
    bower install       jquery#3.3.1
    bower install       bootstrap#4.1.1
    
    jquery#3.3.1 src/libs/jquery
    
    bootstrap#4.1.1 src/libs/bootstrap
    wendingding:Demo wendingding$ tree -L 3
    .
    ├── Gruntfile.js
    ├── bower.json
    ├── build
    ├── dist
    ├── package.json
    └── src
        ├── css
        │   └── style.css
        ├── index.html
        ├── js
        │   └── index.js
        ├── libs
        │   ├── bootstrap
        │   └── jquery
        └── template
    
    9 directories, 6 files
    

    如果需要把这个生成器发布到社区,可以先到npm官网注册一个自己的npm账号,然后在该生成器的目录下执行$ npm publish命令即可。


    相关文章

      网友评论

        本文标题:前端工程化系列[07] Yeoman-generator的创建

        本文链接:https://www.haomeiwen.com/subject/tvpmeftx.html