neoclide/coc.nvim · error

ESM import of native addon ${resolved.filename} is not suppo

Error message

ESM import of native addon ${resolved.filename} is not supported

What it means

loadESMModule resolves an imported module's format and supports source-text, commonjs bridge, and JSON modules. Native addons (.node binaries) cannot be loaded inside a VM SourceTextModule/SyntheticModule, so the 'native' case throws. This is an intentional limitation of the VM-based ESM loader.

Source

Thrown at src/extension/esm.ts:241

  return pathToFileURL(tryRealpath(filename)).href
}

/**
 * Load (and link) one ESM module in the extension runtime.
 */
export async function loadESMModule(runtime: ExtensionRuntime, request: string, parentIdentifier: string): Promise<VMModule> {
  let resolved = resolveExtensionModule(runtime, request, parentIdentifier, 'import')
  if (resolved.type === 'coc-api') return createCocApiModule(runtime)
  if (resolved.type === 'builtin') return createBuiltinModule(runtime, resolved.id)
  switch (resolved.format) {
    case 'module':
      return loadSourceTextModule(runtime, resolved.filename)
    case 'commonjs':
      return createCommonJSBridgeModule(runtime, resolved.filename)
    case 'json':
      return createJsonModule(runtime, resolved.filename)
    case 'native':
      throw new Error(`ESM import of native addon ${resolved.filename} is not supported`)
  }
}

/**
 * Dynamic `import()` path: load, link and evaluate the target module so its
 * namespace is fully initialized.
 */
export async function dynamicImportModule(runtime: ExtensionRuntime, request: string, parentIdentifier: string): Promise<VMModule> {
  let module = await loadESMModule(runtime, request, parentIdentifier)
  instantiateModule(module)
  await module.evaluate()
  return module
}

/**
 * Load an ESM file with `vm.SourceTextModule`, caching it before linking so
 * ESM cycles see the partially initialized module.
 */

View on GitHub (pinned to 50e974d969)

Solutions

  1. Use the CommonJS loading path for the extension (load it as CJS so native addons load normally).
  2. Replace the native-addon dependency with a pure-JS or WASM alternative.
  3. Prebuild/patch the extension so the native module is require()'d from a CJS wrapper.
  4. File/track an upstream issue; VM-module native loading is not supported by Node's vm API.

Example fix

// before (package.json of extension)
"exports": { "import": "./native/index.js" }  // imports better-sqlite3 addon
// after
"exports": { "require": "./native/index.cjs" } // CJS entry require()s the addon
Defensive patterns

Strategy: fallback

Validate before calling

import { isBuiltin } from 'module'
import path from 'path'
function isNativeAddon(resolved: string): boolean {
  return path.extname(resolved) === '.node'
}
// avoid the ESM path when isNativeAddon(resolved.filename) is true; require() it via CJS instead

Try / catch

try {
  ns = await loadESMModule(runtime, resolved)
} catch (e) {
  if (e.message.includes('native addon')) {
    ns = createRequire(import.meta.url)(resolved.filename) // CJS bridge for native addons
  } else throw e
}

Prevention

When it happens

Trigger: An ESM extension (or its dependency graph) does import of a resolved module whose format is 'native' — i.e. a compiled .node addon file reached through loadSourceTextModule or dynamic import paths.

Common situations: Extension depends on packages like better-sqlite3, sharp, or node-gyp addons while loaded via the ESM loader; a dependency's exports map routes the native binary through the import path.

Related errors


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