Skip to main content

执行协议

exec: 协议在获取时使用预配置的运行时环境在临时目录中执行 Node.js 脚本。然后,此脚本应填充环境中定义的特殊目录,并在生成完成后退出。

¥The exec: protocol executes a Node.js script inside a temporary directory at fetch-time with a preconfigured runtime environment. This script is then expected to populate a special directory defined in the environment, and exit once the generation has finished.

yarn add my-pkg@exec:./package-builder.js

你为什么要这样做

¥Why would you want that

典型的 Yarn 获取器从互联网下载包 - 如果你要使用的项目事先已打包,但一旦你需要自己打包,就会失败,则此方法可正常工作。Yarn 的内置机制允许你在兼容的 git 存储库上运行 prepare 脚本并将结果用作最终包,但即使这样也还不够 - 你可能需要克隆特定分支、进入特定目录、运行特定构建脚本...所有这些都使我们很难支持每个用例。

¥Typical Yarn fetchers download packages from the internet - this works fine if the project you want to use got packaged beforehand, but fails short as soon as you need to bundle it yourself. Yarn's builtin mechanism allows you to run the prepare script on compatible git repositories and use the result as final package, but even that isn't always enough - you may need to clone a specific branch, go into a specific directory, run a specific build script ... all things that makes it hard for us to support every single use case.

exec: 协议代表一种定义如何获取指定包的方法。从某种意义上说,它可以看作是 Yarn 提供的 Fetcher API 的更高级版本。

¥The exec: protocol represents a way to define yourself how the specified package should be fetched. In a sense, it can be seen as a more high-level version of the Fetcher API that Yarn provides.

生成器脚本和 require

¥Generator scripts & require

由于生成器将在非常特殊的上下文中调用(在任何包安装到磁盘之前),因此它将无法调用 require 函数(即使使用相对路径也不行)。如果你需要非常复杂的生成器,只需使用 Webpack 或 Rollup 等工具将它们预先打包在单个脚本中即可。

¥Because the generator will be called in a very special context (before any package has been installed on the disk), it won't be able to call the require function (not even with relative paths). Should you need very complex generators, just bundle them up beforehand in a single script using tools such as Webpack or Rollup.

由于这个限制,并且因为生成器几乎总是需要使用 Node 内置模块,所以这些模块在全局范围内可用 - 与 Node REPL 已经执行的操作非常相似。因此,无需手动要求 fs 模块:它可通过全局 fs 变量获得!

¥Because of this restriction, and because generators will pretty much always need to use the Node builtin modules, those are made available in the global scope - in a very similar way to what the Node REPL already does. As a result, no need to manually require the fs module: it's available through the global fs variable!

运行时环境

¥Runtime environment

为了让脚本了解生成过程中涉及的各种预定义文件夹,Yarn 将向脚本注入一个特殊的 execEnv 全局变量。此对象的 interface 定义如下:

¥In order to let the script knows about the various predefined folders involved in the generation process, Yarn will inject a special execEnv global variable available to the script. This object's interface is defined as such:

属性类型描述
tempDirstring脚本可以自由使用的空临时目录的绝对路径。在调用脚本之前自动创建。
buildDirstring脚本预期生成包文件的空目录的绝对路径。在调用脚本之前自动创建。
locatorstring字符串化的 locator 标识生成器包。

你可以在 execEnv.tempDir 中自由地做任何你想做的事情,但是在执行结束时,Yarn 将期望 execEnv.buildDir 包含可以压缩到存档中并存储在缓存中的文件。

¥You're free to do whatever you want inside execEnv.tempDir but, at the end of the execution, Yarn will expect execEnv.buildDir to contain the files that can be compressed into an archive and stored within the cache.

示例

¥Examples

生成 hello world 包:

¥Generate an hello world package:

fs.writeFileSync(path.join(execEnv.buildDir, 'package.json'), JSON.stringify({
name: 'hello-world',
version: '1.0.0',
}));

fs.writeFileSync(path.join(execEnv.buildDir, 'index.js'), `
module.exports = 'hello world!';
`);

克隆 monorepo 并构建特定包:

¥Clone a monorepo and build a specific package:

const pathToRepo = path.join(execEnv.tempDir, 'repo');
const pathToArchive = path.join(execEnv.tempDir, 'archive.tgz');
const pathToSubpackage = path.join(pathToRepo, 'packages/foobar');

// Clone the repository
child_process.execFileSync(`git`, [`clone`, `git@github.com:foo/bar`, pathToRepo]);

// Install the dependencies
child_process.execFileSync(`yarn`, [`install`], {cwd: pathToRepo});

// Pack a specific workspace
child_process.execFileSync(`yarn`, [`pack`, `--out`, pathToArchive], {cwd: pathToSubpackage});

// Send the package content into the build directory
child_process.execFileSync(`tar`, [`-x`, `-z`, `--strip-components=1`, `-f`, pathToArchive, `-C`, execEnv.buildDir]);