neoclide/coc.nvim · error · Error

ERR_REQUIRE_ESM

ERR_REQUIRE_ESM

Error message

require() of ES Module ${url} not supported. Use dynamic import() instead.

What it means

Extension require() refuses to load an ES module via CommonJS require. When module resolution finds a file whose package.json declares "type": "module" (an ESM .js or .mjs), the loader throws ERR_REQUIRE_ESM telling the extension author to use dynamic import().

Source

Thrown at src/extension/loader.ts:241

  /**
   * Node-compatible `require.resolve.paths` for a request from a parent
   * module. Returns null for builtins, like Node does.
   */
  public resolvePaths(request: string, parent: ExtensionCommonJSModule): string[] | null {
    return Module._resolveLookupPaths(request, this.parentModule(parent))
  }

  /**
   * Extension-local require: API injection, builtins, then modules in this
   * runtime.
   */
  public require(request: string, parent: ExtensionCommonJSModule): unknown {
    if (request === 'coc.nvim') return this.runtime.api
    if (this.isBuiltin(request)) return this.loadBuiltin(request)
    let resolved = resolveExtensionModule(this.runtime, request, parent.filename, 'require')
    if (resolved.type === 'file' && resolved.format === 'module') {
      throw requireESMError(resolved.filename)
    }
    // Builtins and coc.nvim are handled above, so resolution always yields a
    // file module here.
    return this.load((resolved as any).filename, parent)
  }

  public load(filename: string, parent?: ExtensionCommonJSModule, isMain = false): unknown {
    const cacheKey = this.normalizeFilename(filename)
    const ext = path.extname(cacheKey).toLowerCase()
    if (ext === '.json') return this.loadJson(cacheKey, parent)
    if (ext === '.node') return this.loadNative(cacheKey, parent)
    if (ext === '.js' || ext === '.cjs' || ext === '') return this.loadJavaScript(cacheKey, parent)
    if (ext === '.mjs') throw requireESMError(cacheKey)
    throw new Error(`Unsupported module type "${ext}" for ${cacheKey}`)
  }

  /**
   * Load a native addon outside the VM. The addon is dlopen'd by Node and its

View on GitHub (pinned to 50e974d969)

Solutions

  1. Use await import('some-pkg') instead of require in the extension code.
  2. Pin/downgrade the dependency to the last CommonJS-compatible version.
  3. Switch the extension itself to ESM if the loader configuration supports it.
  4. Use a dual-format build (e.g. tsup/unbuild) so a CJS entry exists to require.

Example fix

// before
const chalk = require('chalk')
// after
const { default: chalk } = await import('chalk')
Defensive patterns

Strategy: fallback

Validate before calling

const pkg = JSON.parse(fs.readFileSync(require.resolve('some-pkg/package.json'), 'utf8'))
if (pkg.type === 'module') throw new Error('ESM-only dependency; use import()')

Try / catch

let mod
try { mod = require('some-pkg') } catch (e) { if (e.code === 'ERR_REQUIRE_ESM') mod = await import('some-pkg').then(m => m.default ?? m); else throw e }

Prevention

When it happens

Trigger: Extension code calls require('some-pkg') where the resolved package's package.json has "type": "module", or requires a .mjs file directly.

Common situations: A dependency upgraded to an ESM-only major version (e.g. chalk 5+, got 12+, node-fetch 3+) while extension code still uses require(); loading a .mjs file; mixing require of a dual package from a CommonJS extension.

Related errors


AI-assisted analysis of neoclide/coc.nvim@50e974d969 (2026-08-31). Data as JSON: /api/errors/31ea8b686f28554b. Report an issue: GitHub.