neoclide/coc.nvim · critical
coc.nvim requires Node.js VM modules support for ESM extensi
Error message
coc.nvim requires Node.js VM modules support for ESM extensions; start Node with --experimental-vm-modules
What it means
ensureVMModules guards ESM extension loading: Node's vm module must expose SourceTextModule and SyntheticModule, which only exist when Node runs with --experimental-vm-modules. Without that flag, loading ESM extensions inside the VM runtime is impossible, so it throws immediately.
Source
Thrown at src/extension/esm.ts:32
* bridged with `vm.SyntheticModule`. Node must start with
* `--experimental-vm-modules` for the VM module API to be available.
*/
export type ExtensionModuleFormat = 'commonjs' | 'module' | 'json' | 'native'
export type ResolvedExtensionModule =
| { type: 'builtin'; id: string }
| { type: 'coc-api'; id: 'coc.nvim' }
| { type: 'file'; filename: string; format: ExtensionModuleFormat }
const Module: any = require('module')
/**
* Fail only when ESM VM support is actually required.
*/
export function ensureVMModules(): void {
if (typeof vm.SourceTextModule !== 'function' || typeof vm.SyntheticModule !== 'function') {
throw new Error(
'coc.nvim requires Node.js VM modules support for ESM extensions; ' +
'start Node with --experimental-vm-modules'
)
}
}
const packageTypeCache = new Map<string, 'module' | 'commonjs' | undefined>()
function getPackageType(dirname: string): 'module' | 'commonjs' | undefined {
let dir = dirname
while (true) {
let pkgFile = path.join(dir, 'package.json')
if (fs.existsSync(pkgFile)) {
let key = pkgFile
if (packageTypeCache.has(key)) return packageTypeCache.get(key)
let type: 'module' | 'commonjs' | undefined
try {
let obj = JSON.parse(fs.readFileSync(pkgFile, 'utf8'))View on GitHub (pinned to 50e974d969)
Solutions
- Start Node with --experimental-vm-modules (e.g. set NODE_OPTIONS=--experimental-vm-modules).
- Upgrade to Node 22+ where VM modules no longer require the flag.
- Check coc's node binaryPath configuration points at the intended Node version.
- Alternatively, use the CommonJS build of the extension instead of its ESM entry.
Example fix
// before $ vim # node started without vm module support // after $ export NODE_OPTIONS="--experimental-vm-modules" $ vim # or use Node >= 22 which enables vm modules by default
Defensive patterns
Strategy: validation
Validate before calling
const vm = require('vm')
if (typeof vm.SourceTextModule !== 'function' || typeof vm.SyntheticModule !== 'function') {
// set NODE_OPTIONS=--experimental-vm-modules or upgrade to Node >= 22 before loading ESM extensions
process.env.NODE_OPTIONS = (process.env.NODE_OPTIONS || '') + ' --experimental-vm-modules'
} Type guard
function hasVmModules(v: typeof import('vm')): boolean {
return typeof (v as any).SourceTextModule === 'function' && typeof (v as any).SyntheticModule === 'function'
} Try / catch
try {
await loadEsmExtension(entry)
} catch (e) {
if (e.message.includes('--experimental-vm-modules')) {
logger.error('Restart Node with --experimental-vm-modules or use Node >= 22; falling back to CJS loader')
return loadCjsExtension(entry)
} else throw e
} Prevention
- Launch coc's Node with --experimental-vm-modules (NODE_OPTIONS) when using ESM extensions.
- Prefer Node >= 22 where vm modules work without the flag.
- Document the flag requirement wherever a custom node binaryPath is configured.
- Feature-check vm.SourceTextModule at startup instead of failing mid-load.
When it happens
Trigger: Loading an ESM extension (loadSourceTextModule, createSyntheticModule, or loadESMEntry paths) on a Node.js build started without --experimental-vm-modules, or on a Node version where the flags/classes don't exist.
Common situations: Launching vim/neovim with a default node binary lacking the flag; using a Node version where the experimental flag was renamed/removed (Node >= 22 needs no flag, very old Node lacks support); custom node binaryPath in coc config missing the flag.
Related errors
- ESM import of native addon ${resolved.filename} is not suppo
- Required root pattern not resolved.
- Unexpected messageDialogKind: ${this.messageDialogKind}
- Unexpected messageReportKind: ${msgReportKind}
- required capabilities do not exist.
AI-assisted analysis of neoclide/coc.nvim@50e974d969 (2026-08-31).
Data as JSON: /api/errors/5b7356496b8dac82.
Report an issue: GitHub.