swc-project/swc · error

failed to downgrade Flow type-only module to script because

Error message

failed to downgrade Flow type-only module to script because module declarations remain after stripping

What it means

When a file is parsed as Flow in 'script-like module' mode, swc strips Flow types and then downgrades the Program::Module to a Program::Script (crates/swc/src/lib.rs). This bail fires in the pre-conversion scan when module body still contains ModuleItem::ModuleDecl - real runtime module syntax (import/export) survived stripping, so the module cannot be presented as a script.

Source

Thrown at crates/swc/src/lib.rs:294

            .body
            .first()
            .map(Spanned::span)
            .unwrap_or(module.span);
        FlowScriptLikeModuleKind::RuntimeModule(span)
    }
}

fn downgrade_flow_script_like_module(program: Program) -> Result<Program, Error> {
    let Program::Module(module) = program else {
        return Ok(program);
    };

    if module
        .body
        .iter()
        .any(|module_item| matches!(module_item, ModuleItem::ModuleDecl(..)))
    {
        bail!(
            "failed to downgrade Flow type-only module to script because module declarations \
             remain after stripping"
        );
    }

    let Module {
        span,
        body,
        shebang,
    } = module;

    let body = body
        .into_iter()
        .map(|module_item| match module_item {
            ModuleItem::Stmt(stmt) => Ok(stmt),
            ModuleItem::ModuleDecl(..) => bail!(
                "failed to downgrade Flow type-only module to script because module declarations \
                 remain after stripping"

View on GitHub (pinned to 5176682b65)

Solutions

  1. Use the matching parser for the file: typescript (with tsx as needed) or ecmascript, instead of flow
  2. Scope the `flow` syntax config with `test` patterns so it only applies to genuine Flow files
  3. Upgrade swc/swc_core - Flow module-decl stripping behavior has changed across versions
  4. If embedding swc, avoid enabling the flow script-like downgrade path for files with runtime module syntax

Example fix

// before (.swcrc)
{ "jsc": { "parser": { "syntax": "flow" } } }
// after (.swcrc)
{
  "test": "\\.(js|jsx|ts|tsx)$",
  "jsc": { "parser": { "syntax": "typescript", "tsx": true } }
}
Defensive patterns

Strategy: try-catch

Try / catch

try {
  result = await transformFile(file, flowOptions);
} catch (e) {
  if (/downgrade Flow type-only module/.test(String(e?.message ?? e))) {
    // File has runtime import/export: recompile as TypeScript/ECMAScript
    result = await transformFile(file, esOptions);
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Configuring `jsc.parser.syntax: flow` for a file that contains actual runtime `import`/`export` statements (not just Flow type-only syntax), so after type stripping module declarations remain and the script downgrade is impossible.

Common situations: Pointing the Flow parser at TypeScript or plain ESM files in mixed codebases; sharing one .swcrc between Flow sources and ESM .js files; swc_core versions where Flow stripping of certain module-level type constructs is incomplete.

Related errors


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