用yeoman开发一个自己的项目脚手架


上面是一个使用脚手架来初始化项目的典型例子。
随着前端工程化的理念不断深入,越来越多的人选择使用脚手架来从零到一搭建自己的项目。其中大家最熟悉的就是create-react-app和vue-cli,它们可以帮助我们初始化配置、生成项目结构、自动安装依赖,最后我们一行指令即可运行项目开始开发,或者进行项目构建(build)。
这些脚手架提供的都是普遍意义上的最佳实践,但是我在开发中发现,随着业务的不断发展,必然会出现需要针对业务开发的实际情况来进行调整。例如:

  1. 项目架构的配置
    a. webpack/gulp的配置
    ⅰ. style/less/sass的选择和处理配置。
    ⅱ. Typescript和JavaScript的loader和babel的配置,比如babel-loader和tsc。
    ⅲ. 图片的配置,比如需要file-loader或者url-loader。
    ⅳ. 静态资源的配置。
    ⅴ. 企业内部,真对于某些特定功能的webpack-loader/webpack-plugin,比如打包完图片上传。
    b. 是否内置一些第三方框架供上手使用,比如Element-UI,Antd等。
    c. 项目的路由的设计和配置。
    d. 项目目录结构的设计和配置。
    e. 项目通用功能的有关代码,比如登陆和欢迎页等。
    f.通过调整插件与配置实现 Webpack 打包性能优化
    g.针对生产环境做的单独处理

  2. 项目开发中的配置
    a. eslint。
    b. style-lint。
    c. prettier。
    d. 单元测试jest或者其他单元测试框架。
    e. 项目文档的自动生成,比如docz。
    f. git相关的,比如git提交的校验,生成changelog
    g. npm相关,除非是开发npm包,否则npm相关的不是那么重要
    h. 与开发有关的工具,比如打包构建结果的包分析、比如相似代码自动生成

  3. 项目上线脚本,这个应该是可选项?更优的方法应该是使用内部的例如发布平台之类的工具完成,或者通过jekins或者gitlab-ci进行自动化发布,回滚等操作。上线发布回滚的动作可以完全交给非开发人员完成。
    a. 比如由nodejs编写的上线脚本,或者是用于上线的shell脚本。
    b. gitlab-ci的配置文件

……
总而言之,随着业务发展,我们往往会沉淀出一套更“个性化”的业务方案。这时候我们最直接的做法就是开发出一个该方案的脚手架来,以便今后能复用这些最佳实践与方案。
那么就需要使用一个工具来帮我们做这些处理,我们使用的就是yoman。官网地址
我们可以使用yeoman和yeoman的generator来更加便捷实现我们的脚手架,可以说基本是零成本。
先说说Yeoman是什么,它想做什么?
Yeamon帮助你快速的开展一个项目工程,提供最佳实践和工具,来让你保持高效率编码。

他们自己提供了一个构建生态系统,主要通过‘yo’这个命令来构建一个完整的项目或者项目的一部分。

通过官方的生成器,他们建立了一个Yeoman的工作流,这个流是由一个强大的,固定的客户端组建,包含工具和框架帮助开发者快速建立牛逼的web应用。他们尽量提供了开发者所需的东西。比如先下载对应模版项目,然后下载模版项目中的package.json中的包

作为良好文档和深入思考构建过程的思想者,Yeoman包含了检测(静态检测)、测试以及压缩等等一系列工具,让开发者能够更加专注于思考解决方案。

如何使用:

1、npm install -g yo //权限不够,请加上 sudo,一般来说mac都需要。
安装完成之后,你就拥有了1个命令 -- yo 可以选择自定义的,也可以搜索你想要的现成的,这里我们选择自己的。 官网提供的地址:http://yeoman.io/generators/
那我们开始编写一个自己的generators。
2、开发自己的generator
创建项目
全局安装generator-generator,然后使用创建项目,根据提示输出我们的generator项目的名字,yeoman规定项目名字必须以generator开头,所以我们可以选择generator-xxx作为我们的项目名
npm i -g generator-generator
yo generator
创建好项目之后,我们可以发现已经自动为我们配置好了eslint,jest,husky

我们主要需要修改的代码位于generators/app内
● index.js定义了我们使用此generator时的一些操作配置项
● templates内存放的是要生成的项目的项目模板,项目模板也可以放在git上,然后通过download-git-repo下载到本地
验证我们创建的项目

  1. 在当前项目执行npm link使其被链接到全局
  2. 然后找一个目录,终端执行yo,此时便可以发现我们的generator已经出现在了选择列表中。然后选择我们的generator运行即可。等待完成,会发现成功的创建了一个叫做dummyfile.txt的文件夹。
    generators/app/index.js
    初始化的文件如下。
"use strict";
const Generator = require("yeoman-generator");
const chalk = require("chalk");
const yosay = require("yosay");

module.exports = class extends Generator {
  prompting() {
    // Have Yeoman greet the user.
    this.log(
      yosay(
        `Welcome to the fantastic ${chalk.red("generator-rc-op")} generator!`
      )
    );

    const prompts = [
      {
        type: "confirm",
        name: "someAnswer",
        message: "Would you like to enable this option?",
        default: true
      }
    ];

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

  writing() {
    this.fs.copy(
      this.templatePath("dummyfile.txt"),
      this.destinationPath("dummyfile.txt")
    );
  }

  install() {
    this.installDependencies();
  }
};

从文件的内容可以看出主要操作是暴露出一个继承自Generator的类,在类的内部定义了部分生命周期方法,供yoeman在适当的时候调用。我们只需要完善这些方法就可以了。

yeoman提供的生命周期如下:

  1. initializing:初始化必要的依赖,或者比如检测新版本
  2. prompting:用来处理终端的交互
  3. default:这里指的是自己自定义的方法,而不是名为default的方法,自己定义的方法如果不想被调用,那么需要确保方法名以_为开头。
  4. writing:将经过ejs模板渲染后的内容写入文件系统。
  5. conflicts:勇于解决将文件内容写入文件系统时可能造成的冲突
  6. install:安装项目的依赖,比如npm install 或者 bower install
  7. end:做一些收尾的工作。

这些生命周期方法均支持返回Promise来进行异步操作。

内置常用工具

终端交互:内置了Inquirer提供终端交互
模板填充:内置了ejs模板
文件操作
● this.fs.copyTpl:用于拷贝并且根据数据使用ejs模板渲染文件。此函数接受三个参数,源文件路径,目标文件路径,传给ejs用于渲染目标问价的数据。
● this.fs.copy:将文件丛源路径复制到目标路径。
● this.destinationPath() :此函数返回最终生成项目的路径
其他
● this.npmInstall:实现npm isntall

// 部分代码,完整代码见最下方链接
writing() {
    const done = this.async();
    this.log("⚙  Finish basic configuration.", chalk.green("✔"));
    this.log("📂 Generate the project template and configuration...");
    let spinner = ora({
      text: `Download the template from ${GIT_BASE}${TPL_REPOSITORY}...`,
      spinner: ORA_SPINNER
    }).start();
    this._downloadTemplate(TPL_REPOSITORY)
      .then(() => {
        spinner.stopAndPersist({
          symbol: chalk.green("   ✔"),
          text: `Finish downloading the template from ${GIT_BASE}${TPL_REPOSITORY}`
        });
        spinner = ora({
          text: `Copy files into the project folder...`,
          spinner: ORA_SPINNER
        }).start();
        const templateRoot = this.destinationPath(this.dirName, ".tmp");
        this.log(`\n${templateRoot}`, "templateRoot");
        const resoPath = path.resolve(templateRoot, "../");
        this.log(resoPath, "resoPath");
        spinner.stopAndPersist({
          symbol: chalk.green("   ✔"),
          text: `Finish copying files into the project folder`
        });
        execSync(`cp -R ${resoPath}/.tmp/* ${resoPath}`, { stdio: "inherit" });
        spinner = ora({
          text: `Clean tmp files and folders...`,
          spinner: ORA_SPINNER
        }).start();
        fs.removeSync(templateRoot);
        spinner.stopAndPersist({
          symbol: chalk.green("   ✔"),
          text: `Finish cleaning tmp files and folders`
        });
        // ExecSync(`rm -R ${resoPath}/.tmp`, { stdio: "inherit" });
        // 将新生成的项目的title换成自定义的项目名称
        ejs.renderFile(
          `${resoPath}/public/index.html`,
          { dirName: this.dirName },
          function(err, str) {
            // Str => 输出渲染后的 HTML 字符串
            fs.writeFile(`${resoPath}/public/index.html`, str);
          }
        );
        // 将新生成的项目的package.json的name换成自定义的项目名称
        ejs.renderFile(
          `${resoPath}/package.json`,
          { dirName: this.dirName },
          function(err, str) {
            // Str => 输出渲染后的 HTML 字符串
            fs.writeFile(`${resoPath}/package.json`, str);
          }
        );
        done();
      })
      .catch(err => this.env.error(err));
  }

其他可能用到的工具

● ora:用于创建Spinner,即比如下载模板文件时需要展示的laoding效果。
● chalk:用于打印彩色的信息
● update-notifier
● beeper
● boxen
调试
由于yeoman是通过npm包的形式进行使用的,所以我们可以在我们开发的generator项目的跟目录下,执行npm link 将我们开发的generator链接到全局,这样我们直接使用yo命令查看可以使用的generator,就可以看到我们所开发的generator了,并且是实时生效的

我的代码是在本地的,用的npm link,没有上传到github或npm,此处贴出其他大佬的项目代码。快动手试一下吧~

模版完整代码 注意要给ejs写入的空位
脚手架完整代码

参考文章:

前端自动化工具 - yeoman
如何快速开发一个自己的项目脚手架?