CodeSampleX

Ejemplo

p-limit 7.3.1: require() an ESM-only npm package from CommonJS without ERR_REQUIRE_ESM or ERR_REQUIRE_ASYNC_MODULE

Muestra verificada para npm p-limit 7.3.1: require() an ESM-only npm package from CommonJS without ERR_REQUIRE_ESM or ERR_REQUIRE_ASYNC_MODULE. El contrato…

sha256:c7071dcdb5556a42d993b28cdf9b1adba12d8ef2436ee1bdd1b9029e2acff699

Esta red ofrece una sola cosa: una muestra que compila. La ejecutó en un sandbox y guardó el recibo firmado. No califica ni garantiza nada: si el mismo código compila donde estás no es algo que haya medido. Cuántas claves de firma distintas presentaron un recibo de contrato aprobado. Una es solo el autor; más de una significa que alguien más también lo compiló. Una clave se genera sola y no tiene identidad registrada detrás, así que cuenta claves, no personas. MIT-0

Evidencia de ejecución

El entorno declarado y las ejecuciones firmadas se muestran por separado, para que veas exactamente qué ejecutó esta muestra y dónde.

Base de evidencia
Contrato firmado aprobado
Recibos de verificación
6
Claves de firma que lo compilaron
3
Entorno declarado node 22 linux x64 node 22 npm

Entornos de las ejecuciones de verificación

Entorno Contrato Etapas Ejecución
node 22 · linux alpine/x64 · docker ed25519:a2ec939a4c60e243 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-13
node 22 · linux alpine/x64 · docker ed25519:a2ec939a4c60e243 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-13
node 22 · linux alpine/x64 · docker ed25519:a2ec939a4c60e243 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-13
node 22 · linux alpine/x64 · docker ed25519:a2ec939a4c60e243 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-13
node 22 · linux alpine/x64 · docker ed25519:d91480838ac982c9 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1
2026-08-17
node 22 · linux alpine/x64 · docker ed25519:c1973797be207ac4 PASS compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS
CONTAINER_RUN · node-typescript@1node:22-alpine@sha256:c610fcdfb1d5…
2026-09-07

Caso

FIX
Objetivo
require() an ESM-only npm package from CommonJS without ERR_REQUIRE_ESM or ERR_REQUIRE_ASYNC_MODULE
Paquetes
Símbolos
  • Yoga.Node.create
  • import()
  • pLimit
  • require
Entorno
node 22
Creado
2026-08-13T14:19:50Z

Contrato

  1. run a CommonJS file that require()s both ESM-only packages and assert it exits nonzero
  2. assert the failure is ERR_REQUIRE_ASYNC_MODULE, whose message is that require() cannot be used on an ESM graph with top-level await
  3. assert the same run proves the quieter half: on Node 22.12 and newer require('p-limit') returns the module namespace, so calling it raises TypeError: pLimit is not a function
  4. assert the cached dynamic import() resolves to a single namespace whose default export carries Yoga.Node, which the namespace itself does not expose
  5. assert the dynamic-import path computes a real flexbox layout: three flex-grow columns in a 240 by 60 row with a 12px gap land at x 0, 84 and 168, each 72 by 60
  6. assert p-limit loaded through the same dynamic import caps concurrency at 2 across 6 tasks and preserves input order

Archivos

  • csx.json
  • package-lock.json
  • package.json
  • src/broken-require.js
  • src/index.js
  • test/contract.mjs

Descargar el artefacto de código fuente (tar.gz)

Código fuente

csx.json
{"case":{"caseId":"case:sha256:615fba704b69581dccf3fdd3f699ef774af7f8fc2e48c994950222121e93401e","constraints":{"moduleSystem":"cjs","runtime":"node"},"contract":["run a CommonJS file that require()s both ESM-only packages and assert it exits nonzero","assert the failure is ERR_REQUIRE_ASYNC_MODULE, whose message is that require() cannot be used on an ESM graph with top-level await","assert the same run proves the quieter half: on Node 22.12 and newer require('p-limit') returns the module namespace, so calling it raises TypeError: pLimit is not a function","assert the cached dynamic import() resolves to a single namespace whose default export carries Yoga.Node, which the namespace itself does not expose","assert the dynamic-import path computes a real flexbox layout: three flex-grow columns in a 240 by 60 row with a 12px gap land at x 0, 84 and 168, each 72 by 60","assert p-limit loaded through the same dynamic import caps concurrency at 2 across 6 tasks and preserves input order"],"goal":"require() an ESM-only npm package from CommonJS without ERR_REQUIRE_ESM or ERR_REQUIRE_ASYNC_MODULE","kind":"FIX","packages":["pkg:npm/p-limit@7.3.1","pkg:npm/yoga-layout@3.2.1"],"schemaVersion":1,"symbols":["Yoga.Node.create","import()","pLimit","require"]},"contractCommand":["node","test/contract.mjs"],"environment":{"arch":"x64","ecosystem":"npm","executionContext":"node","moduleSystem":"cjs","os":"linux","packageManager":"npm","runtime":"node","runtimeVersion":"22","schemaVersion":1},"license":"MIT-0","packages":["pkg:npm/p-limit@7.3.1","pkg:npm/yoga-layout@3.2.1"],"schemaVersion":1,"symbols":["Yoga.Node.create","import()","pLimit","require"],"verifierAdapter":"node-typescript@1"}
package-lock.json
{
  "name": "csx-sample-esm-only-from-cjs",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "csx-sample-esm-only-from-cjs",
      "version": "1.0.0",
      "license": "MIT-0",
      "dependencies": {
        "p-limit": "7.3.1",
        "yoga-layout": "3.2.1"
      }
    },
    "node_modules/p-limit": {
      "version": "7.3.1",
      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-7.3.1.tgz",
      "integrity": "sha512-0trZaiG7Y7kN/Egy9a8j47t9osC0Tch4PaIWd9yGF6bvmlk7muExRvGNYb8sXBwEKMoNKsbNN9P8EefuQekE4Q==",
      "license": "MIT",
      "dependencies": {
        "yocto-queue": "^1.2.1"
      },
      "engines": {
        "node": ">=20"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/yocto-queue": {
      "version": "1.2.2",
      "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz",
      "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==",
      "license": "MIT",
      "engines": {
        "node": ">=12.20"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/yoga-layout": {
      "version": "3.2.1",
      "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz",
      "integrity": "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ==",
      "license": "MIT"
    }
  }
}
package.json
{
  "name": "csx-sample-esm-only-from-cjs",
  "version": "1.0.0",
  "private": true,
  "type": "commonjs",
  "license": "MIT-0",
  "dependencies": {
    "p-limit": "7.3.1",
    "yoga-layout": "3.2.1"
  }
}
src/broken-require.js
'use strict';

// The pattern this sample exists to replace. Run it directly to see both
// failure modes on one Node:
//
//     node src/broken-require.js
//
// Node < 22.12 threw ERR_REQUIRE_ESM on BOTH requires below. Node >= 22.12
// can require() an ESM module graph, so the two now fail in different ways --
// and only one of them is still loud.

// (1) p-limit is ESM-only ("type": "module", no require condition in exports)
//     but its graph has no top-level await, so on Node >= 22.12 require()
//     SUCCEEDS. It returns the ES module *namespace object*, not the default
//     export, so the idiomatic CommonJS binding below gets an object where the
//     function should be and the crash moves to the call site.
const pLimit = require('p-limit');
console.log('p-limit require() returned:', typeof pLimit);
try {
  pLimit(2);
} catch (err) {
  console.log('calling it: ' + err.constructor.name + ': ' + err.message);
}

// (2) yoga-layout is ESM-only AND awaits its WebAssembly instantiation at the
//     top level of its module graph. No Node version can require() that: a
//     synchronous require() has nothing to block on. This one still throws,
//     and it is the throw the contract asserts.
//
//     `node --experimental-print-required-tla src/broken-require.js` names it:
//       unexpected top-level await at node_modules/yoga-layout/dist/src/index.js:13
//       const Yoga = wrapAssembly(await loadYoga());
const Yoga = require('yoga-layout');
console.log('unreachable', typeof Yoga);
src/index.js
'use strict';

/**
 * Using ESM-only npm packages from a CommonJS project.
 *
 * yoga-layout@3 and p-limit@7 are both ESM-only: "type": "module" with no
 * `require` condition in their exports map. A CommonJS require() of either one
 * is wrong -- but on current Node they are wrong in two different ways, and
 * only one of them is still loud. See src/broken-require.js.
 *
 *   Node <  22.12   require() -> ERR_REQUIRE_ESM for both packages.
 *   Node >= 22.12   require('p-limit')     -> succeeds, and returns the ES
 *                                             module NAMESPACE object, so the
 *                                             usual `pLimit(2)` is a TypeError
 *                                             somewhere else entirely.
 *                   require('yoga-layout') -> ERR_REQUIRE_ASYNC_MODULE. Its
 *                                             graph awaits the WebAssembly
 *                                             instantiation at the top level,
 *                                             and a synchronous require() has
 *                                             nothing to block on. No Node
 *                                             version can load that with
 *                                             require().
 *
 * One pattern covers every case, on every Node >= 12.17: dynamic import()
 * inside an async function. import() returns a Promise, so it works unchanged
 * in a `require`-shaped file, and it never needs the package to ship a CJS
 * build. (The other fix is to convert the consumer to ESM -- rename to .mjs or
 * set "type": "module" -- which is the better move if you can afford it.)
 *
 * Two details that bite people:
 *
 *   - Cache the PROMISE, not the resolved module. CommonJS has no top-level
 *     await, so there is nothing to assign at module scope. Awaiting an
 *     already-settled promise is free, so the loader stays cheap to call on
 *     every request instead of only at startup.
 *
 *   - import() resolves to the module NAMESPACE, not to the default export.
 *     `export default` lands on `.default`. Destructure it:
 *         const { default: pLimit } = await import('p-limit');
 *     This is the same shape require() now hands back on Node >= 22.12, which
 *     is why the `TypeError: pLimit is not a function` reports are suddenly
 *     more common than ERR_REQUIRE_ESM.
 */

let yogaPromise;
let pLimitPromise;

/** @returns {Promise<object>} the yoga-layout module namespace (cached). */
function loadYoga() {
  if (!yogaPromise) yogaPromise = import('yoga-layout');
  return yogaPromise;
}

/** @returns {Promise<object>} the p-limit module namespace (cached). */
function loadPLimit() {
  if (!pLimitPromise) pLimitPromise = import('p-limit');
  return pLimitPromise;
}

/**
 * Lay out `count` equal-width columns inside a `width` x `height` box with
 * `gap` pixels between them, and return each column's computed geometry.
 */
async function rowLayout({ width, height, count, gap }) {
  // `Yoga` is the default export; Direction/Edge/FlexDirection are named
  // exports. Both come off the same namespace object.
  const { default: Yoga, Direction, Edge, FlexDirection } = await loadYoga();

  const root = Yoga.Node.create();
  root.setWidth(width);
  root.setHeight(height);
  root.setFlexDirection(FlexDirection.Row);

  const kids = [];
  for (let i = 0; i < count; i += 1) {
    const kid = Yoga.Node.create();
    kid.setFlexGrow(1);
    if (i > 0) kid.setMargin(Edge.Left, gap);
    root.insertChild(kid, i);
    kids.push(kid);
  }

  root.calculateLayout(width, height, Direction.LTR);
  const boxes = kids.map((kid) => {
    const box = kid.getComputedLayout();
    return { left: box.left, top: box.top, width: box.width, height: box.height };
  });

  root.freeRecursive(); // yoga nodes are wasm-backed; free them explicitly
  return boxes;
}

/**
 * Run every task with at most `concurrency` of them in flight at once,
 * resolving to the results in input order.
 */
async function runLimited(tasks, concurrency) {
  const { default: pLimit } = await loadPLimit();
  const limit = pLimit(concurrency);
  return Promise.all(tasks.map((task) => limit(task)));
}

module.exports = { loadPLimit, loadYoga, rowLayout, runLimited };
test/contract.mjs
import { strict as assert } from 'node:assert';
import { spawnSync } from 'node:child_process';
import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';

// Load the sample the way a CommonJS project would: a genuine require() of a
// genuine CJS module. `require` does not exist inside an .mjs file, so
// createRequire hands us the real CommonJS loader.
const require = createRequire(import.meta.url);

// ---------------------------------------------------------------------------
// 1. The require() pattern really is broken on this Node.
// ---------------------------------------------------------------------------
const brokenPath = fileURLToPath(new URL('../src/broken-require.js', import.meta.url));
const broken = spawnSync(process.execPath, [brokenPath], { encoding: 'utf8' });

assert.notEqual(broken.status, 0, 'require() of an ESM-only package must still fail');
assert.match(broken.stderr, /ERR_REQUIRE_ASYNC_MODULE/);
assert.match(broken.stderr, /require\(\) cannot be used on an ESM graph with top-level await/);

// The quieter half: require() of an ESM-only package *without* top-level await
// no longer throws on Node >= 22.12 -- it hands back the module namespace, so
// the failure surfaces later as a TypeError at the call site.
assert.match(broken.stdout, /p-limit require\(\) returned: object/);
assert.match(broken.stdout, /calling it: TypeError: pLimit is not a function/);

// ---------------------------------------------------------------------------
// 2. The dynamic-import path loads the package and exposes the real default.
// ---------------------------------------------------------------------------
const { loadYoga, rowLayout, runLimited } = require('../src/index.js');

const ns = await loadYoga();
assert.equal(await loadYoga(), ns, 'the cached import() promise yields one namespace');
assert.equal(ns.Node, undefined, 'Yoga.Node is on the default export, not the namespace');
assert.equal(typeof ns.default.Node.create, 'function');

// ---------------------------------------------------------------------------
// 3. It computes a real flexbox layout: 3 flex-grow columns in 240x60 with a
//    12px gap => (240 - 2*12) / 3 = 72 wide each, stretched to full height.
// ---------------------------------------------------------------------------
const boxes = await rowLayout({ width: 240, height: 60, count: 3, gap: 12 });
assert.deepEqual(boxes, [
  { left: 0, top: 0, width: 72, height: 60 },
  { left: 84, top: 0, width: 72, height: 60 },
  { left: 168, top: 0, width: 72, height: 60 },
]);

// ---------------------------------------------------------------------------
// 4. The second ESM-only package works through the same door, and its
//    behaviour is observable: at most `concurrency` tasks are ever in flight.
// ---------------------------------------------------------------------------
let inFlight = 0;
let peak = 0;
const tasks = [1, 2, 3, 4, 5, 6].map((n) => async () => {
  inFlight += 1;
  peak = Math.max(peak, inFlight);
  await new Promise((resolve) => setTimeout(resolve, 10));
  inFlight -= 1;
  return n * 10;
});

const results = await runLimited(tasks, 2);
assert.deepEqual(results, [10, 20, 30, 40, 50, 60], 'results keep input order');
assert.equal(peak, 2, 'p-limit capped concurrency at 2 (it would be 6 uncapped)');
assert.equal(inFlight, 0);

console.log('CONTRACT PASS: await import() loads ESM-only packages from CommonJS; require() throws ERR_REQUIRE_ASYNC_MODULE');

Seeder de origen

anonymous