rolldown/rolldown · error · TypeError

MagicString.prototype.replaceAll called with a non-global…

Error message

MagicString.prototype.replaceAll called with a non-global RegExp argument

What it means

binding-magic-string.ts implements replaceAll to match the JS magic-string/String.prototype.replaceAll spec: a RegExp argument must have the global (g) flag. If a non-global RegExp is passed, it throws this TypeError instead of silently replacing only the first match like the native binding would.

Solutions

  1. Add the g flag to the RegExp (e.g. /pattern/g)
  2. If you only want one replacement, call replace() instead of replaceAll()
  3. When building RegExp dynamically, pass 'g' in the flags argument: new RegExp(pattern, 'g')

Example fix

// before
s.replaceAll(/foo/, 'bar'); // TypeError
// after
s.replaceAll(/foo/g, 'bar');
Defensive patterns

Strategy: validation

Validate before calling

function assertGlobalRegExp(re) {
  if (re instanceof RegExp && !re.global) throw new TypeError('replaceAll requires a global RegExp (g flag)');
}

Type guard

function isGlobalRegExp(v: unknown): v is RegExp & { global: true } {
  return v instanceof RegExp && v.global;
}

Try / catch

try {
  s.replaceAll(searchValue, replacement);
} catch (e) {
  if (e instanceof TypeError && /non-global RegExp/.test(e.message)) {
    s.replaceAll(new RegExp(searchValue.source, 'g'), replacement);
  } else throw e;
}

Prevention

When it happens

Trigger: Calling magicString.replaceAll(/pattern/, replacement) where /pattern/ lacks the `g` flag — `if (!searchValue.global) throw new TypeError(...)` fires.

Common situations: Porting code from replace() to replaceAll() without adding the g flag; constructing a RegExp from a string pattern and forgetting { flags: 'g' }; copying regexes used with String.replace into replaceAll.

Related errors


AI-assisted analysis of rolldown/rolldown@91b44b9d7b (2026-09-07). Data as JSON: /api/errors/6c82789c5a5b69bc. Report an issue: GitHub.

Appendix: source

Thrown at packages/rolldown/src/binding-magic-string.ts:130

  // - Non-global sticky: advance to match end, or reset to 0 on miss
  // - Non-global non-sticky: lastIndex is not modified by .replace()
  if (searchValue.global) {
    searchValue.lastIndex = 0;
  } else if (searchValue.sticky) {
    searchValue.lastIndex = lastMatchEnd === -1 ? 0 : lastMatchEnd;
  }
  return this;
};

NativeBindingMagicString.prototype.replaceAll = function (
  searchValue: string | RegExp,
  replacement: string,
): any {
  if (typeof searchValue === 'string') {
    return nativeReplaceAll.call(this, searchValue, replacement);
  }
  if (!searchValue.global) {
    throw new TypeError(
      'MagicString.prototype.replaceAll called with a non-global RegExp argument',
    );
  }
  searchValue.lastIndex = 0;
  (this as any).replaceRegex(searchValue, replacement);
  searchValue.lastIndex = 0;
  return this;
};

export interface RolldownMagicString extends NativeBindingMagicString {
  readonly isRolldownMagicString: true;
  /** Accepts a string or RegExp pattern. RegExp supports `$&`, `$$`, and `$N` substitutions. */
  replace(from: string | RegExp, to: string): this;
  /** Accepts a string or RegExp pattern. RegExp must have the global (`g`) flag. */
  replaceAll(from: string | RegExp, to: string): this;
}

type RolldownMagicStringConstructor = Omit<typeof NativeBindingMagicString, 'prototype'> & {

View on GitHub (pinned to 91b44b9d7b)