仅使用内置模块和异步编写Nodejs脚本的提示
#javascript #node #async #promises

Tip for writing nodejs scripts using only built-in modules and async

在过去的几年中,我花了很大一部分时间在nodejs编写脚本作为我的默认脚本语言。我也有点涉足bash,但是它仍然很奇怪,很难测试,而且我是一个打字稿的风扇,我通常更喜欢尽可能多地走打字稿。

下面是使编写nodejs脚本变得有趣,快速且容易的技巧列表。

使用ES模块

是!您现在可以做到!但是,thar be dragons

一般而言,如果要在节点中使用ES模块,则可以...

  1. .mjs在其末尾命名您的文件
  2. 在最接近package.json具有"type": "module"
  3. 的地方使用.js文件
  4. 使用node --eval并通过--input-type=module

类似的规则适用于保留文件使用commonjs

  1. .cjs命名您的文件7
  2. 在最接近的package.json具有"type": "commonjs"
  3. 的地方使用.js文件
  4. 使用node --eval并通过--input-type=commonjs

有几个条款,a,一对 quid pro pro

Tip for writing nodejs scripts using only built-in modules and async

切换到节点中的ES模块时,必须:

  • 始终将扩展名包括在您的导入中...
import { foo } from "./bar.js";
  • 您必须使用import()而不是require
  • 这不起作用... __dirname

但是,这确实...

import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

您可能还会遇到您导入的node_modules的某些 问题,因此,请小心。但是,如果您通常坚持本地节点模块和实用程序,那么您应该很好。

使用顶级等待

这在14.8中可用。在最长的时间里,每次我准备编写一个node.js脚本...
时,我总是写一个包装器

const main = async () => {
  // await some stuff in here
};

main().catch((err) => {
  console.error(err);
  process.exit(1);
});

我经常这样做,以至于我为此编写了Vscode摘要。嘿。

但是,很酷的事情是,您现在只能在JS文件中的任何地方await

注意:您只能在“模块”上下文中执行此操作,即请参见上面的“使用ES模块”

import fs from 'node:fs/promises';

const pkg = await fs.readFile('./package.json');

console.log(JSON.parse(pkg).name);

将基于新的承诺的API与节点前缀一起使用

从节点v14.18和v16开始,您现在可以要求 andation node模块,即pathfs,etc et in inde inde indimptim或需要声明。这将阻止剧本作家意外进口的实用程序第三方版本。

import fs from "node:fs/promises";
import stream from "node:stream/promises";
import dns from "node:dns/promises";
import timers from "node:timers/promises";

另外,对于采用标准节点(err, results) => {}回调的所有其他所有内容,您始终可以回到promisify方法上。

import fs from 'node:fs/promises';
import { exec as _exec } from 'node:child_process';
import { promisify } from 'node:util';

const exec = promisify(_exec);

const out = await exec('git status');

console.log(out.stdout);

有效地使用child_process

child_process模块是脚本编写者背包中最有用的模块之一。您可以使用其spawnexec方法在计算机上运行任何任意内容。但是,知道何时以及如何使用其中任何一个都是必不可少的。

spawnexec之间的最根本区别是...

spawn创建并返回一个子过程,您可以通过流轻松地与之交互

exec创建A运行A子过程,以最大为200KB

返回输出和错误流

因此,大多数时间exec都会执行您需要的事情,但是如果您需要某种交互性或具有一堆输出,则可以使用spawn

上面的以前示例之一使用exec调用git status。另一个示例可能是对像find这样的unix方法的抽象。

import fs from 'node:fs/promises';
import { exec as _exec } from 'node:child_process';
import { promisify } from 'node:util';

const exec = promisify(_exec);
const [,, filename, directory] = process.argv;

const out = await exec(`find ${directory} -type f -name ${filename} `);

console.log(out.stdout);

// run with...
// node ./script.mjs filename ./directory

这是使用spawn做某事的示例。

在这种情况下,我们将在我们的存储库中的另一个软件包中生成一个npm install

import { spawn } from 'node:child_process';

const install = () => {
  return new Promise((resolve, reject) => {
    const proc = spawn('npm', ['install'], {
      stdio: ['inherit', 'pipe', 'pipe'],
      env: {
        ...process.env,
      }
    });

    proc.stdout.on('data', (data) => {
      if (data.includes('npm WARN')) {
        // Do something
      }
    });

    proc.stderr.on('data', (data) => {
      console.error(`stderr: ${data}`);
    });

    proc.on('close', (code) => {
      if (code !== 0) {
        return reject(code);
      }

      resolve();
    });
  });
};

try {
  await install();
} catch(e) {
  console.error("Something went wrong with the install.");
  process.exit(1);
}

首先要注意的是,我们要创建一个函数,并在return中使用Promise构造函数。这样我们就可以在顶层进行await

让我们看一下我们传递给spawn的选项。 (您可以看到Spawn here的整个API)第一个是顶级命令,在这种情况下为npm。第二个选项是我们要传递给npm的参数的数组,只是install。但是,如果我们想将多个事物传递给npm,我们需要将它们添加为每个元素,例如['install', '--save-dev', '--legacy-peer-deps']

第三个参数是options对象。默认情况下,当您产生一个过程时,stdio选项设置为pipe

pipe选项意味着从spawn返回的proc将在其上具有proc.stdoutproc.stdinproc.stderr。这些是streams,因此您可以使用onone等方法来响应data等事件。

stdin参数可以采用pipeinherit或与您要控制的过程的哪个部分相对应的数组...

如果您不需要需要使用stdinstderr,则可以从pipe切换到inherit

stdin: [
  'inherit' /* stdin */,
  'pipe' /* stdout */,
  'inherit' /* stderr */ 
],

像那样传递stdinð会提供proc.stdout,但不提供proc.stdinproc.stderr

最后,您可以注意要使用proc.on('close', () => {});关闭的过程。

另外一个不错的选择是,您可以通过cwd选项控制该过程的哪个工作目录。这使您可以使用process.cwd(),或设置一些实际想要产生该过程的其他路径。

使用Typescript进行更酷的脚本写作体验

通常,我更喜欢在这些天(包括脚本)编写任何内容时使用Typescript。编写这样的脚本时,有几种不同的方式可以用来利用Typescript在代码中的额外的检查毯子。

首先,如果您要使用打字稿和节点,请确保将@types/node软件包安装为"devDependency"。这将确保所有本机模块都有正确的类型信息。

Tip for writing nodejs scripts using only built-in modules and async
IntelliSense for参数

Tip for writing nodejs scripts using only built-in modules and async
intellisense for Options

本身就是超级有用的。

真正值得注意的是,您不必将文件以.ts扩展为止。您可以为tsconfig.json添加几个选项。它们是"allowJs""checkJs"。将它们都设置为真,您就可以了。

typeScript与4.7 release一起使用,现在也通过读取可以添加到package.json文件中的"type": "module"来支持ES模块。您还可以通过将"module"选项设置为node16nodenext来手动告诉Typescript使用模块。所有这些记录的here

如果您想使用.ts而不是使用允许J和CheckJS,则可能还需要安装诸如@babel/registerts-node之类的内容,甚至可能是esbuild-runner。我仍然个人喜欢Babel route

结论

在Frontend和Devops(又名DivOps)中,编写脚本是不可避免的。在JavaScript / TypeScript中编写脚本可让您使用所具有的JS技能作为前端开发人员与计算机进行交互,以执行各种自动化,管道等和奖励点,因为您也可以编写单元测试。出去并在JavaScript中写下所有脚本!