使用文件系统

位置上下文和路径

Yeoman 文件实用程序基于这样一个理念:您始终在磁盘上拥有两个位置上下文。这些上下文是您的生成器很可能从中读取和写入的文件夹。

目标上下文

第一个上下文是目标上下文。目标是 Yeoman 将在其中搭建新应用程序的文件夹。它是您的用户项目文件夹,您将在其中编写大部分脚手架。

目标上下文定义为当前工作目录或包含 .yo-rc.json 文件的最近的父文件夹。 .yo-rc.json 文件定义了 Yeoman 项目的根目录。此文件允许您的用户在子目录中运行命令并在项目上执行操作。这确保了最终用户的一致行为。

您可以使用 this.destinationRoot() 获取目标路径,或者通过使用 this.destinationPath('sub/path') 连接路径来获取。

// Given destination root is ~/projects
class extends Generator {
  paths() {
    this.destinationRoot();
    // returns '~/projects'

    this.destinationPath('index.js');
    // returns '~/projects/index.js'
  }
}

您也可以使用 this.destinationRoot('new/path') 手动设置它。但是为了保持一致性,您可能不应该更改默认目标。

如果您想知道用户从哪里运行 yo,那么您可以使用 this.contextRoot 获取路径。这是 yo 被调用的原始路径;在我们使用 .yo-rc.json 确定项目根目录之前。

模板上下文

模板上下文是您存储模板文件的文件夹。通常是从您读取和复制的文件夹。

默认情况下,模板上下文定义为 ./templates/。您可以使用 this.sourceRoot('new/template/path') 覆盖此默认值。

您可以使用 this.sourceRoot() 获取路径值,或者通过使用 this.templatePath('app/index.js') 连接路径来获取。

class extends Generator {
  paths() {
    this.sourceRoot();
    // returns './templates'

    this.templatePath('index.js');
    // returns './templates/index.js'
  }
};

“内存中”文件系统

在覆盖用户文件时,Yeoman 非常小心。基本上,对预先存在文件的每次写入都将经历一个冲突解决过程。此过程要求用户验证覆盖其文件内容的每个文件写入。

此行为可以防止意外情况并降低错误风险。另一方面,这意味着每个文件都是异步写入磁盘的。

由于异步 API 更难使用,因此 Yeoman 提供了一个同步文件系统 API,其中每个文件都写入 内存文件系统,并且仅在 Yeoman 运行完成后才写入磁盘。

此内存文件系统在所有 组合生成器 之间共享。

文件实用程序

生成器在 this.fs 上公开所有文件方法,这是一个 mem-fs 编辑器 的实例 - 请务必查看 模块文档 以获取所有可用方法。

值得注意的是,尽管 this.fs 公开了 commit,但您不应在生成器中调用它。Yeoman 在运行循环的冲突阶段之后内部调用此方法。

示例:复制模板文件

这是一个示例,我们希望复制和处理模板文件。

假设 ./templates/index.html 的内容为

<html>
  <head>
    <title><%= title %></title>
  </head>
</html>

然后,我们将使用 copyTpl 方法复制文件,同时将内容作为模板进行处理。 copyTpl 使用 ejs 模板语法

class extends Generator {
  writing() {
    this.fs.copyTpl(
      this.templatePath('index.html'),
      this.destinationPath('public/index.html'),
      { title: 'Templating with Yeoman' }
    );
  }
}

生成器运行完成后,public/index.html 将包含

<html>
  <head>
    <title>Templating with Yeoman</title>
  </head>
</html>

一个非常常见的场景是在 提示阶段 存储用户答案并将其用于模板化

class extends Generator {
  async prompting() {
    this.answers = await this.prompt([{
      type    : 'input',
      name    : 'title',
      message : 'Your project title',
    }]);
  }

  writing() {
    this.fs.copyTpl(
      this.templatePath('index.html'),
      this.destinationPath('public/index.html'),
      { title: this.answers.title } // user answer `title` used
    );
  }
}

通过流转换输出文件

生成器系统允许您对每个文件写入应用自定义过滤器。自动美化文件、规范化空格等都是完全可能的。

在每个 Yeoman 进程中,我们将每个修改后的文件写入磁盘。此过程通过 vinyl 对象流(就像 gulp)传递。任何生成器作者都可以注册一个 transformStream 来修改文件路径和/或内容。

通过 registerTransformStream() 方法注册新的修改器。这是一个示例

var beautify = require("gulp-beautify");
this.registerTransformStream(beautify({ indent_size: 2 }));

请注意,**任何类型的每个文件都将通过此流传递**。确保任何转换流都将通过它不支持的文件。像 gulp-ifgulp-filter 这样的工具将帮助过滤无效类型并将其传递。

您基本上可以使用任何gulp 插件与 Yeoman 转换流一起在写入阶段处理生成的文件。

提示:更新现有文件的内容

更新预先存在的文件并不总是简单的任务。最可靠的方法是解析文件 AST(抽象语法树)并对其进行编辑。此解决方案的主要问题是编辑 AST 可能很冗长且有点难以理解。

一些流行的 AST 解析器是

使用 RegEx 解析代码文件是一条危险的道路,在这样做之前,您应该阅读 这篇 CS 人类学答案 并掌握 RegEx 解析的缺陷。如果您确实选择使用 RegEx 而不是 AST 树来编辑现有文件,请务必小心并提供完整的单元测试。- 请务必不要破坏用户的代码。