如何用 zx within() 隔离配置变更的作用域?
【免费下载链接】zxA tool for writing better scripts项目地址: https://gitcode.com/GitHub_Trending/zx/zx
用 zx 写脚本时,$对象保存着 zx 的全部默认配置($.cwd、$.env、$.prefix、$.verbose等),所有$命令都使用这份配置。直接给$.foo = 'baz'或$.cwd = '/tmp'赋值会改变全局上下文,脚本后续所有命令都会受到影响。
很多任务只需要"临时"改配置:比如让某几条命令在/tmp下执行、让单条命令跑在另一个 Node 版本上,执行完希望配置自动恢复原状。zx 的within()就是为这个场景提供的 API:它创建一个新异步上下文,回调内部对配置的修改只在该上下文中生效,回调结束后外部配置保持原样。本文基于官方 API 文档给出写法、运行方式和验证手段。
准备环境
按安装文档的要求:
- 操作系统:Linux、macOS 或 Windows
- JavaScript 运行时:Node.js >= 12.17.0,或 Bun >= 1.0.0、Deno 1.x/2.x、GraalVM Node.js
- 某种 bash 或 PowerShell
在项目目录安装 zx:
npm install zx按快速上手文档的约定,把脚本写成.mjs文件(这样可以在顶层用await),文件开头加 shebang,然后直接用zxCLI 运行:
// script.mjs #!/usr/bin/env zx // ... 本文中的示例代码zx ./script.mjswithin() 如何隔离上下文
API 文档对within()的定义是一句话:Creates a new async context(创建一个新的异步上下文)。其实现位于 src/core.ts:
export function within<R>(callback: () => R): R { return storage.run({ ...getStore() }, callback) }架构文档解释了背后的机制:$工厂通过Proxy和AsyncLocalStorage把每次调用绑定到快照(snapshot)上下文上;within()执行时把当前配置复制一份({ ...getStore() }),再在这份拷贝上运行回调。因此回调内部对$的修改只落在拷贝上,不会写回外部上下文。
两个直接影响写法的特点(均有文档依据):
- 回调既可以是同步的,也可以是异步的,隔离行为一致;
within()返回回调的返回值,可以把上下文内命令的输出(如ProcessPromise)带出来继续用。
实战一:临时切换工作目录与自定义配置
下面是 api.md 中的原始示例,代码可直接运行;注释中的输出为文档示例输出,实际路径取决于你脚本启动时所在的目录:
await $`pwd` // => /home/path $.foo = 'bar' within(async () => { $.cwd = '/tmp' $.foo = 'baz' setTimeout(async () => { await $`pwd` // => /tmp $.foo // baz }, 1000) }) await $`pwd` // => /home/path $.foo // still 'bar'各行的作用与判断依据:
- 第 2 行在外部上下文设置
$.foo = 'bar',这是全局修改,会持续生效; within回调内的$.cwd = '/tmp'让回调中所有$进程的工作目录变成/tmp,回调内的$.foo也变成'baz';- 注意
setTimeout延迟 1 秒后触发的回调仍然读到/tmp和'baz',说明隔离是按异步上下文维持的,不是"回调同步执行完就失效"; - 最后一行:
within结束后await $pwd`` 回到原目录,$.foo仍是'bar'——这就是隔离成功的判定依据。
实战二:让单条命令运行在另一个 Node 版本下
api.md 的第二个示例演示用$.prefix临时加载 nvm 环境、把 Node 切到 16,只影响within内的那一条命令:
await $`node --version` // => v20.2.0 const version = await within(async () => { $.prefix += 'export NVM_DIR=$HOME/.nvm; source $NVM_DIR/nvm.sh; nvm use 16;' return $`node --version` }) echo(version) // => v16.20.0要点:
$.prefix是拼接到每条命令开头的命令片段,configuration 文档说明其默认值是set -euo pipefail;。示例用+=追加 nvm 加载语句,所以默认 prefix 仍然保留;- 回调
return $node --version`` 把上下文内的命令输出带出来,赋值给version,用echo(version)打印; - 注释中的
v20.2.0/v16.20.0是文档示例输出,实际取决于你本机的 Node 与 nvm 环境;判断成功的标准是:version与外部node --version不同,且外部后续执行node时版本不受影响。
嵌套 within()
多个within()可以嵌套,每一层各自隔离,内层的修改不会渗透到外层。以下用例摘自 test/core.test.js 的isolates nested context and returns cb result:
within(async () => { const res = await within(async () => { $.verbose = true return within(async () => { assert.equal($.verbose, true) $.verbose = false return 'foo' }) }) assert.equal($.verbose, false) assert.equal(res, 'foo') })它同时验证了两件事:外层修改($.verbose = true)对下一层可见,但下一层再改成false后,回到上一层时$.verbose仍是false这一层自己的值;每层within都原样返回其回调的返回值('foo')。
运行后如何验证
完整脚本跑完后,可以按文档给出的现象逐项核对:
within结束后的await $pwd`` 输出与脚本开头一致(原工作目录);- 外部上下文中设置的属性(示例里的
$.foo)保持原值,未被回调内的赋值覆盖; - 通过
return带出的结果可用echo()打印,且与外部直接执行同一命令的结果不同(如两个 Node 版本); - 在 test/core.test.js 的
just works用例中,官方用assert.equal($.verbose, false)断言within结束后外部$.verbose未被回调内的$.verbose = true污染——这是项目自带的验证方式。
边界与注意事项
cd()与$.cwd不是一回事。api.md 提示:cd()内部调用process.chdir(),会影响全局进程上下文;而within对上下文内的$调用保留自己的工作目录引用——test/core.test.js 的keeps the cwd ref for internal $ calls用例中,within内cd('/tmp')后,即使外部process.chdir('/'),within上下文中后续$命令仍然在/tmp执行。- 隔离针对的是配置(
$选项),不是 shell 会话。within不启动子 shell,它隔离的是AsyncLocalStorage中的选项上下文,这与$.prefix里执行的 shell 语句是两回事。 - 同步回调同样可用:
within(() => { ... })不带async也能隔离(见 test/core.test.js),回调返回值同样会原样返回。
如果只是想给单条命令传配置而不需要一块连续的作用域,也可以按 api.md 的$({...})工厂用法写$({cwd: '/tmp'})cmd,它只对那一次调用生效;within()` 适合"一段流程内多条命令共用临时配置"的场景。
【免费下载链接】zxA tool for writing better scripts项目地址: https://gitcode.com/GitHub_Trending/zx/zx
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考