vitejs/vite · error · Error

Not implemented property

Error message

Not implemented property: ${prop}

What it means

Thrown from the `buildResult` Proxy in `generateBundle` (pluginConverter.ts:149). The converter exposes only `metafile`, `mangleCache`, and (in write mode) `outputFiles` on the synthesized esbuild BuildResult; accessing any other string property throws. This guards esbuild onEnd callbacks from relying on unsupported BuildResult fields.

Solutions

  1. Read only `result.metafile`, `result.mangleCache`, or `result.outputFiles` (write mode) from onEnd.
  2. Collect warnings/errors via onStart/onEnd with your own accumulator inside onResolve/onLoad instead of from the result.
  3. Switch to a rolldown-native plugin using rolldown's bundle/output hooks for richer result data.
  4. If you need output content in non-write mode, switch the build to write mode or use rolldown's `generate` API directly.

Example fix

// before
build.onEnd((result) => {
  for (const w of result.warnings) console.warn(w.text)
})

// after
const warnings = []
build.onResolve({ filter: /.*/ }, (args) => { /* push warnings */ })
build.onEnd(() => { for (const w of warnings) console.warn(w) })
Defensive patterns

Strategy: validation

Validate before calling

const SUPPORTED_RESULT = new Set(['metafile', 'mangleCache', 'outputFiles'])
function assertOnEndSafe(plugin: { setup: (b: any) => void }) {
  const probe: any = new Proxy({ metafile: undefined, mangleCache: undefined, outputFiles: undefined }, {
    get(t, p) { if (SUPPORTED_RESULT.has(p as string) || typeof p === 'symbol') return (t as any)[p]; throw new Error(`onEnd reads unsupported result.${String(p)}`) },
  })
  plugin.setup({ onStart() {}, onEnd(cb: (r: any) => void) { cb(probe) }, onResolve() {}, onLoad() {}, onDispose() {}, initialOptions: {}, resolve() {} } as any)
}
// assertOnEndSafe(myEsbuildPlugin) before registering

Type guard

function onEndReadsSupportedOnly(plugin: { setup: (b: any) => void }): boolean {
  let ok = true
  const probe = new Proxy({ metafile: undefined, mangleCache: undefined, outputFiles: undefined }, { get(t, p) { if (p in t || typeof p === 'symbol') return (t as any)[p]; ok = false; return undefined } })
  try { plugin.setup({ onEnd: (cb: (r: any) => void) => cb(probe), onResolve() {}, onLoad() {}, onStart() {}, onDispose() {}, initialOptions: {}, resolve() {} } as any) } catch { /* ignore */ }
  return ok
}

Try / catch

try {
  await build()
} catch (e) {
  if (e instanceof Error && /Not implemented property:/.test(e.message)) {
    const prop = e.message.split('Not implemented property:')[1]
    console.error(`onEnd callback reads unsupported BuildResult.${prop}; use metafile/outputFiles only.`)
  }
  throw e
}

Prevention

When it happens

Trigger: An esbuild plugin's `onEnd(result)` callback reads `result.<unsupportedProp>` (e.g. `result.warnings`, `result.errors`, `result.replay`, `result.toString`). The proxy get trap throws `Not implemented property: <prop>`.

Common situations: Plugins that inspect build output metadata beyond metafile/outputFiles in onEnd. Plugins ported from esbuild expecting warnings/errors arrays on the result. Plugins reading `result.outputFiles` in non-write (generate) mode, which the proxy omits.

Related errors


AI-assisted analysis of vitejs/vite@b4d66fee14 (2026-08-11). Data as JSON: /api/errors/129a423c20c238d3. Report an issue: GitHub.

Appendix: source

Thrown at packages/vite/src/node/optimizer/pluginConverter.ts:149

      }

      for (const cb of onStartCallbacks) {
        cb()
      }
    },
    generateBundle(_outputOpts, _bundle, isWrite) {
      const buildResult = new Proxy(
        {
          metafile: undefined,
          mangleCache: undefined,
          ...(isWrite ? { outputFiles: undefined } : {}),
        } as esbuild.BuildResult,
        {
          get(_target, prop) {
            if (prop in _target || typeof prop === 'symbol') {
              return (_target as any)[prop]
            }
            throw new Error('Not implemented property: ' + prop)
          },
        },
      )
      for (const cb of onEndCallbacks) {
        cb(buildResult)
      }
    },
    async resolveId(id, importer, opts) {
      for (const handler of resolveIdHandlers) {
        const result = await handler.call(this, id, importer, opts)
        if (result) {
          if (typeof result === 'object' && result.namespace) {
            usedNamespaces.add(result.namespace)
          }
          return result
        }
      }
      if (usedNamespaces.size) {

View on GitHub (pinned to b4d66fee14)