swc-project/swc · error

swc does not support module expressions

Error message

swc does not support module expressions

What it means

Swcify in swc_estree_compat maps swc_estree_ast nodes onto swc_ecma_ast. ModuleExpression (the `module { ... }` inline-module proposal) has no SWC AST representation, so the impl declares `Output = Never` and panics. Conversion of an AST containing a module expression is impossible by design.

Source

Thrown at crates/swc_estree_compat/src/swcify/expr.rs:1146

    fn swcify(self, _: &Context) -> Self::Output {
        panic!("swc does not support record expressions")
    }
}

impl Swcify for TupleExpression {
    type Output = Never;

    fn swcify(self, _: &Context) -> Self::Output {
        panic!("swc does not support tuple expressions")
    }
}

impl Swcify for ModuleExpression {
    type Output = Never;

    fn swcify(self, _: &Context) -> Self::Output {
        panic!("swc does not support module expressions")
    }
}

impl Swcify for TSAsExpression {
    type Output = TsAsExpr;

    fn swcify(self, ctx: &Context) -> Self::Output {
        TsAsExpr {
            span: ctx.span(&self.base),
            expr: self.expression.swcify(ctx),
            type_ann: self.type_annotation.swcify(ctx),
        }
    }
}

impl Swcify for TSTypeAssertion {
    type Output = TsTypeAssertion;

View on GitHub (pinned to 5176682b65)

Solutions

  1. Turn off the module-blocks syntax plugin in the upstream ESTree parser
  2. Transform `module { ... }` code into a function/IIFE returning an importable object before conversion
  3. Parse with swc_ecma_parser directly, avoiding ESTree conversion of proposal syntax
  4. Pre-scan the ESTree JSON for `type: "ModuleExpression"` and surface a user-facing error

Example fix

// before
let m = module { export default 1; }; // module-blocks syntax
ast.swcify(&ctx);                      // panics: swc does not support module expressions

// after
let m = (() => ({ default: 1 }))();   // lowered to standard JS
ast.swcify(&ctx);                      // ok
Defensive patterns

Strategy: type-guard

Validate before calling

let mut bad = Vec::new();
find_unsupported(&estree_json, &mut bad); // same walk as error 200
if bad.iter().any(|t| t == "ModuleExpression") {
    return Err("module expressions (module { ... }) are not supported by swcify".into());
}

Type guard

fn is_unsupported_estree_type(t: &str) -> bool {
    matches!(
        t,
        "ModuleExpression" | "RecordExpression" | "TupleExpression"
            | "PipelinePrimaryTopicReference" | "BindExpression" | "DoExpression"
    )
}

Try / catch

let out = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
    estree_expr.swcify(&ctx)
}));
if out.is_err() {
    return Err("swcify cannot convert this ESTree tree (module expression present)".into());
}

Prevention

When it happens

Trigger: Calling `.swcify(ctx)` on an ESTree tree that includes a ModuleExpression node, i.e. code using `module { ... }` parsed with the module-blocks syntax plugin (babel syntax-plugin-module-blocks or similar) before being converted to SWC.

Common situations: Experimenting with module expressions in a babel pipeline that later hands the AST to SWC; shared parser configs enabling stage-1 syntax; codemod tools that accept arbitrary ESTree and convert it.

Related errors


AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17). Data as JSON: /api/errors/ebb41c4fbff361e5. Report an issue: GitHub.