oxc-project/oxc · warning · OxcDiagnostic

Top-level await is not available in the configured target en

Error message

Top-level await is not available in the configured target environment.

What it means

Oxc's ES2022 transform emits this warning when it finds an `await` expression at module top level while the configured target environments do not support top-level await. The `top_level_await` flag is derived from a browserslist/engine target query (crates/oxc_transformer/src/options/env.rs:160) via `has_feature(ES2022TopLevelAwait)`, which returns true when at least one target engine lacks the feature and it therefore needs lowering. Oxc has no transform that lowers top-level await to older syntax, so it warns instead of silently emitting code that will fail at runtime in the target.

Source

Thrown at crates/oxc_transformer/src/es2022/mod.rs:133

    fn enter_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) {
        if let Some(class_properties) = &mut self.class_properties {
            class_properties.enter_static_block(block, ctx);
        }
    }

    fn exit_static_block(&mut self, block: &mut StaticBlock<'a>, ctx: &mut TraverseCtx<'a>) {
        if let Some(class_properties) = &mut self.class_properties {
            class_properties.exit_static_block(block, ctx);
        }
    }

    fn enter_await_expression(
        &mut self,
        node: &mut AwaitExpression<'a>,
        ctx: &mut TraverseCtx<'a>,
    ) {
        if self.options.top_level_await && Self::is_top_level(ctx) {
            let warning = OxcDiagnostic::warn(
                "Top-level await is not available in the configured target environment.",
            )
            .with_label(node.span);
            ctx.state.error(warning);
        }
    }
}

impl ES2022<'_> {
    fn is_top_level(ctx: &TraverseCtx) -> bool {
        ctx.current_hoist_scope_id() == ctx.scoping().root_scope_id()
    }
}

View on GitHub (pinned to e1e7af627c)

Solutions

  1. Raise the target query so every engine supports top-level await (Chrome >= 89, Firefox >= 89, Safari >= 15, Node >= 14.8), e.g. `EnvOptions::from_target_list(&["chrome 89","node 14.8"])` or update .browserslistrc, so `has_feature(ES2022TopLevelAwait)` returns false
  2. Remove the top-level await: move the await into an async function and call it (fire-and-forget or via an init function the entry point awaits)
  3. If the warning is noise because a downstream bundler (esbuild/webpack) handles TLA itself, configure oxc to preserve the syntax (empty/modern targets) and let the bundler lower it
  4. Wrap the async work in a dynamic-import chain: move the awaiting module behind `await import("./module.mjs")` executed from an async entry

Example fix

// before (targets = chrome 60, module top level)
const config = await loadConfig();
export default config;

// after (no TLA)
async function init() {
  const config = await loadConfig();
  return config;
}
export default init(); // Promise; consumers do `await init()` or (await import(...))
Defensive patterns

Strategy: validation

Validate before calling

// Before transforming, check whether any target lacks top-level await
use oxc_transformer::EnvOptions;

let env = EnvOptions::from_target_list(&["chrome 89", "firefox 89", "safari 15", "node 14.8"])?;
// If you cannot raise targets, scan the source for top-level await first:
fn uses_top_level_await(src: &str) -> bool {
    // cheap heuristic: parse with oxc_parser and walk Program-level
    // statements for ExpressionStatement(AwaitExpression) or `for await`
    oxc_parser::parse(src).program_has_top_level_await()
}

Type guard

// TypeScript consumers: narrow the transform setup at compile time
type Targets = string[];
const TLA_SAFE: Targets = ["chrome>=89", "firefox>=89", "safari>=15", "node>=14.8"];
function assertTlaSafe(targets: Targets): void {
  // fail the build config early instead of per-file diagnostics
  if (!TLA_SAFE.every(t => targets.some(x => x.startsWith(t.split(">")[0])))) {
    throw new Error('targets include engines without top-level await');
  }
}

Try / catch

// Diagnostics are collected, not thrown: after running the transformer,
// inspect the returned diagnostics and fail or gate on severity
let result = transformer.build(source);
for d in &result.errors {
    if d.message.contains("Top-level await is not available") {
        // route to a legacy no-TLA build path or fail the target
    }
}

Prevention

When it happens

Trigger: Running the oxc transformer with a `targets`/browserslist query that includes engines without top-level await (e.g. `chrome >= 60`, older node, legacy safari), causing `ES2022Options::top_level_await` to be set, and then transforming a module whose top-level hoist scope contains an `AwaitExpression` (e.g. `const data = await fetch(url);` at module top level). The check is `enter_await_expression` in crates/oxc_transformer/src/es2022/mod.rs:127 — it fires only when `options.top_level_await && current_hoist_scope_id == root_scope_id`, so awaits inside functions are unaffected.

Common situations: Modernizing a codebase that uses top-level await for config/env loading while CI still pins `targets` to browserslist `defaults` or an old `.browserslistrc`; switching a bundler pipeline from esbuild/swc to oxc with copied-over legacy target lists; Node CLIs targeting Node < 14.8; shared library code consumed by both modern and legacy bundles. The internal comment at env.rs:76 notes that enabling this flag errors for ALL top-level awaits, so any single TLA usage trips it.

Related errors


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