本文已参与「新人创作礼」活动,一起开启掘金创作之路。
谷歌 zx 脚手架模块中文文档
zx是 2021 gibhub上的一个新的明星项目,它让我们可以便捷的使用 JavaScript / TypeScript(该项目包含TypeScript类型声明)替代 bash 搭建命令行脚本。该脚本对于掌握前端开发的人员来说,提供了搭建脚手架上的便利,最近关注了该项目,翻译了其使文档,作为笔记分享之。
zx 官方 Github 主页:github.com/google/zx
zx 官方 npm 主页:www.npmjs.com/package/zx
本文地址:
CSDN: blog.csdn.net/qq_28550263…
翻译者:jcLee95
目 录
zx
🐚 zx
#!/usr/bin/env zx
await $`cat package.json | grep name`
let branch = await $`git branch --show-current`
await $`dep deploy --branch=${branch}`
await Promise.all([
$`sleep 1; echo 1`,
$`sleep 2; echo 2`,
$`sleep 3; echo 3`,
])
let name = 'foo bar'
await $`mkdir /tmp/${name}`
Bash 很棒,但是在编写脚本时,人们通常会选择更方便的编程语言。
JavaScript 是一个完美的选择,但是标准Node.js库在使用之前需要额外的麻烦。zx包围绕child_process提供了有用的包装器,对参数进行转义并给出合理的默认值。
安装
npm i -g zx
要求: Node 版本 >= 16.0.0
文档
在扩展名为.mjs的文件中编写脚本,以便能够在顶层使用await。如果您喜欢使用. js 扩展名,可以将您的脚本包装成类似于 void async function () {...}()。
将以下声明添加到您的 zx 脚本的开头:
#!/usr/bin/env zx
现在,您将能够像这样运行您的脚本:
chmod +x ./script.mjs
./script.mjs
或者通过 zx 可执行文件:
zx ./script.mjs
所有函数($、cd、fetch等)都可以直接使用,无需任何导入。
或者显式导入全局变量(为了在VS代码中更好地自动完成)。
import 'zx/globals'
$`command`
使用child_process包中的 spawn函数执行给定字符串,并返回ProcessPromise<ProcessOutput>。
一切都通过 ${...} 将被自动转义并引用。
let name = 'foo & bar'
await $`mkdir ${name}`
不需要额外添加引号。 在 quotes 中阅读更多相关信息
如果需要,您可以传递一组参数:
let flags = [
'--oneline',
'--decorate',
'--color',
]
await $`git log ${flags}`
如果执行的程序返回非零退出代码,将抛出 ProcessOutput。
try {
await $`exit 1`
} catch (p) {
console.log(`Exit code: ${p.exitCode}`)
console.log(`Error: ${p.stderr}`)
}
ProcessPromise
class ProcessPromise<T> extends Promise<T> {
readonly stdin: Writable
readonly stdout: Readable
readonly stderr: Readable
readonly exitCode: Promise<number>
pipe(dest): ProcessPromise<T>
kill(signal = 'SIGTERM'): Promise<void>
}
pipe() 方法可用于重定向stdout:
await $`cat file.txt`.pipe(process.stdout)
阅读更多关于 pipelines的信息。
ProcessOutput
class ProcessOutput {
readonly stdout: string
readonly stderr: string
readonly exitCode: number
readonly signal: 'SIGTERM' | 'SIGKILL' | ...
toString(): string
}
函数
cd()
更改当前工作目录。
cd('/tmp')
await $`pwd` // outputs /tmp
fetch()
node-fetch 包的包装。
let resp = await fetch('https://wttr.in')
if (resp.ok) {
console.log(await resp.text())
}
question()
readline 包的包装器。
用法:
let bear = await question('What kind of bear is best? ')
let token = await question('Choose env variable: ', {
choices: Object.keys(process.env)
})
在第二个参数中,可以指定制表符自动完成的选项数组。
function question(query?: string, options?: QuestionOptions): Promise<string>
type QuestionOptions = { choices: string[] }
sleep()
setTimeout函数的包装。
await sleep(1000)
nothrow()
将 $ 的行为更改为不在非零退出代码上引发异常。
function nothrow<P>(p: P): P
用法:
await nothrow($`grep something from-file`)
// 一个 pipe() 内部:
await $`find ./examples -type f -print0`
.pipe(nothrow($`xargs -0 grep something`))
.pipe($`wc -l`)
如果只需要exitCode ,可以使用下一个代码:
if (await $`[[ -d path ]]`.exitCode == 0) {
...
}
// 相当于:
if ((await nothrow($`[[ -d path ]]`)).exitCode == 0) {
...
}
quiet()
更改 $ 的行为以禁用详细输出。
function quiet<P>(p: P): P
用法:
await quiet($`grep something from-file`)
// 不会显示命令和输出。
包
以下软件包无需导入内部脚本即可使用。
chalk 包
chalk 包。
console.log(chalk.blue('Hello world!'))
yaml 包
yaml 包。
console.log(YAML.parse('foo: bar').foo)
fs 包
fs-extra 包。
let content = await fs.readFile('./package.json')
globby 包
globby 包
let packages = await globby(['package.json', 'packages/*/package.json'])
let pictures = globby.globbySync('content/*.(jpg|png)')
此外,globby可通过 glob 快捷方式获得:
await $`svgo ${await glob('*.svg')}`
os 包
os 包
await $`cd ${os.homedir()} && mkdir example`
path 包
path 包。
await $`mkdir ${path.join(basedir, 'output')}`
minimist 包
minimist 包。
作为全局常量 argv 提供。
配置
$.shell
指定使用什么 shell 。默认为 which bash。
$.shell = '/usr/bin/bash'
或者使用一个 CLI 参数: --shell=/bin/bash
$.prefix
指定将作为所有运行命令的前缀的命令。
默认为: set -euo pipefail;.
或者使用一个 CLI 参数:--prefix='set -e;'
$.quote
指定在命令替换期间转义特殊字符的函数。
$.verbose
指定详细程度。
默认值为 true.
在详细模式下, zx打印所有执行的命令及其输出。
或者使用一个 CLI 参数:--quiet 来设置 $.verbose = false.
Polyfills
__filename & __dirname
在 ESM 模块中,Node.js不提供 __filename 和 __dirname 全局变量。因为这样的全局变量在脚本中非常方便,所以 zx 提供了这些用于 .mjs 文件中(当使用 zx 可执行文件时)。
require()
在 ESM 模块中, require() 函数没有被定义。
zx 提供require() 函数,因此它可以与 .mjs 文件中的导入一起使用(当使用 zx 可执行文件时)。
let {version} = require('./package.json')
实验性的
zx还提供了一些实验功能。请在讨论中留下关于这些功能的反馈。
retry()
重试命令几次。将在第一次成功尝试后返回,或将在指定的尝试次数后引发。
import {retry} from 'zx/experimental'
let {stdout} = await retry(5)`curl localhost`
echo()
可以接受 ProcessOutput 的console.log()替代项。
import {echo} from 'zx/experimental'
let branch = await $`git branch --show-current`
echo`Current branch is ${branch}.`
// 或者
echo('Current branch is', branch)
startSpinner()
启动一个简单的CLI微调器,并返回 stop() 函数。
import {startSpinner} from 'zx/experimental'
let stop = startSpinner()
await $`long-running command`
stop()
FAQ
传递环境变量
process.env.FOO = 'bar'
await $`echo $FOO`
传递值数组
如果值的数组作为参数传递给 $,数组的项将被单独转义并通过空格连接。
例如:
let files = [...]
await $`tar cz ${files}`
从其他脚本导入
通过显式导入可以使用 $和其他函数:
#!/usr/bin/env node
import {$} from 'zx'
await $`date`
没有扩展名的脚本
如果脚本没有文件扩展名 (例如 .git/hooks/pre-commit), zx 将假定它是一个 ESM 模块。
Markdown 脚本
zx 可以执行用 markdown 编写的脚本。
zx docs/markdown.md
TypeScript 脚本
import {$} from 'zx'
// Or
import 'zx/globals'
void async function () {
await $`ls -la`
}()
使用 ts-node 作为一个 esm node loader.
node --loader ts-node/esm script.ts
你必须i在 package.json 中设置 "type": "module" 以及在 tsconfig.json 中设置"module": "ESNext" 。
{
"type": "module"
}
{
"compilerOptions": {
"module": "ESNext"
}
}
执行远程脚本
如果 zx 可执行文件的参数以 https:// 开头,则文件将被下载并执行。
zx https://medv.io/example-script.mjs
zx https://medv.io/game-of-life.mjs
从stdin执行脚本
zx支持从stdin执行脚本。
zx <<'EOF'
await $`pwd`
EOF
License
免责声明: 这不是官方支持的谷歌产品。
将参数传递给 ${...} 不需要加引号。如果需要,将自动添加。
let name = 'foo & bar'
await $`mkdir ${name}`
对于引号,zx 使用特殊的bash语法(接下来的命令是有效的bash):
mkdir $'foo & bar'
$'ls' $'-la'
如果添加引号 "${name}",将会产生错误的命令。
如果你需要添加额外的东西,考虑把它放在花括号里。
await $`mkdir ${'path/to-dir/' + name}`
这也将正常工作:
await $`mkdir path/to-dir/${name}`
参数数组
zx 也可以在 ${...} 中接受数组或参数。数组中的项将被单独引用,并通过空格连接起来。
不要添加 .join(' ')。
let flags = [
'--oneline',
'--decorate',
'--color',
]
await $`git log ${flags}`
如果你已经有了一个带有数组的字符串,那么正确解析它并区分不同的参数就是你的责任了。比如像这样:
await $`git log ${'--oneline --decorate --color'.split(' ')}`
globbing 和 ~
当一切都通过${...} 将被转义,不能使用 ~ 或 glob 语法。
为了实现这个目的,zx提供了 globby package.
而不能这样:
let files = '~/dev/**/*.md' // 错
await $`ls ${files}`
使用 glob 函数和 os”包:
let files = await glob(os.homedir() + '/dev/**/*.md')
await $`ls ${files}`
zx 支持 Node.js 流(stream),特殊的 pipe() 方法可用于重定向标准输出。
await $`echo "Hello, stdout!"`
.pipe(fs.createWriteStream('/tmp/output.txt'))
await $`cat /tmp/output.txt`
用 $ 创建的进程从 process.stdin 获取stdin,但是我们也可以写入子进程:
let p = $`read var; echo "$var";`
p.stdin.write('Hello, stdin!\n')
let {stdout} = await p
管道可用于显示程序的实时输出:
$.verbose = false
await $`echo 1; sleep 1; echo 2; sleep 1; echo 3;`
.pipe(process.stdout)
通过管道传输stdout和stderr:
let echo = $`echo stdout; echo stderr 1>&2`
echo.stdout.pipe(process.stdout)
echo.stderr.pipe(process.stdout)
await echo
此外,pipe() 方法可以组合 $ 程序。与bash中的 | 相同:
let greeting = await $`printf "hello"`
.pipe($`awk '{printf $1", world!"}'`)
.pipe($`tr '[a-z]' '[A-Z]'`)
console.log(greeting.stdout)
使用pipe()和 nothrow():
await $`find ./examples -type f -print0`
.pipe(nothrow($`xargs -0 grep ${'missing' + 'part'}`))
.pipe($`wc -l`)
使用markdown编写脚本是可能的。zx只执行代码块。
您可以运行这个markdown 文件:
zx docs/markdown.md
await $`whoami`
await $`echo ${__dirname}`
__filename 将指向 markdown.md:
console.log(chalk.yellowBright(__filename))
我们也可以在这里使用导入:
await import('chalk')
bash代码(带有 bash 或sh语言标签)也将被执行:
VAR=$(date)
echo "$VAR" | wc -c
其他代码块被忽略:
body .hero {
margin: 42px;
}