oxc-project/oxc · warning · OxcDiagnostic

Top-level `await` prevents this module from being loaded wit

Error message

Top-level `await` prevents this module from being loaded with `require(esm)`.

What it means

Diagnostic from oxlint rule node/no-top-level-await (restriction). Node.js v20.19 introduced require(esm), but an ES module containing top-level await cannot be loaded that way — require() throws ERR_REQUIRE_ASYNC_MODULE. The rule flags `await` expressions, `for await...of` loops, and `await using` declarations at module top level (any depth NOT nested in a function) so published packages stay loadable from both CommonJS and ESM consumers. The diagnostic's own note says it targets published packages; private apps may disable it.

Source

Thrown at crates/oxc_linter/src/rules/node/no_top_level_await.rs:15

use oxc_ast::AstKind;
use oxc_diagnostics::OxcDiagnostic;
use oxc_macros::declare_oxc_lint;
use oxc_span::Span;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::{
    AstNode,
    context::LintContext,
    rule::{DefaultRuleConfig, Rule},
};

fn no_top_level_await_diagnostic(span: Span) -> OxcDiagnostic {
    OxcDiagnostic::warn("Top-level `await` prevents this module from being loaded with `require(esm)`.")
        .with_help("Move the `await` inside an `async` function, as ES modules with top-level `await` cannot be loaded with `require(esm)`.")
        .with_note("This rule is intended for published packages. Consider disabling it if this package is private.")
        .with_label(span)
}

#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", default, deny_unknown_fields)]
struct NoTopLevelAwaitConfig {
    /// If `true`, top-level `await` is allowed in files that start with a
    /// hashbang (`#!`), which marks them as executable scripts rather than
    /// importable modules.
    ignore_bin: bool,
}

#[derive(Debug, Default, Clone, Deserialize, Serialize)]
pub struct NoTopLevelAwait(NoTopLevelAwaitConfig);

declare_oxc_lint!(

View on GitHub (pinned to e1e7af627c)

Solutions

  1. Move the awaited work into an exported async function the caller invokes: export async function init() { ... }
  2. Initialize lazily: export a promise or an init() rather than awaiting at module load
  3. For executable scripts, keep the hashbang and configure { "ignoreBin": true }
  4. For private apps never loaded via require(), disable the rule in .oxlintrc.json as the diagnostic note suggests

Example fix

// before (top level of module)
const foo = await import('foo');

// after
export async function load() {
  const foo = await import('foo');
  return foo;
}
Defensive patterns

Strategy: validation

Validate before calling

// .oxlintrc.json
"rules": { "node/no-top-level-await": ["error", { "ignoreBin": true }] }

// pre-publish gate for dual-format packages
npx oxlint -c .oxlintrc.json --deny-warning . && npm publish --dry-run

Prevention

When it happens

Trigger: run() reports when a node is an AwaitExpression, a ForOfStatement with r#await == true, or a VariableDeclaration of await-using kind, AND no ancestor is a Function or ArrowFunctionExpression. Suppressed only when the ignoreBin option is true and the file starts with '#!' (an executable script, not an importable module). Triggers: 'const foo = await import("foo");' or a top-level 'for await (const e of gen())'.

Common situations: Dual-format (CJS+ESM) libraries preparing for require(esm) on Node 20.19+/22; top-level await used for config/db initialization during ESM migration; executable scripts with a hashbang that legitimately top-level-await (use ignoreBin); private apps where the constraint is irrelevant.

Related errors


AI-assisted analysis of oxc-project/oxc@e1e7af627c (2026-08-20). Data as JSON: /api/errors/cde7328c9552322b. Report an issue: GitHub.