Skip to content

Commit b08184e

Browse files
feat(rolldown): add isRolldownMagicString property for reliable native detection (#8614)
## Summary External packages like `rolldown-string` need to detect native `BindingMagicString` instances, but the existing `obj.constructor.name` check breaks under minification or bundling. This PR adds a reliable `isRolldownMagicString` boolean property to the `BindingMagicString` prototype via `Object.defineProperty` (non-writable, non-configurable). A new `binding-magic-string.ts` module centralizes the patching and re-exports the class with proper TypeScript typings. All internal imports now go through this module to ensure consistency. Tests cover prototype presence, instance access, and detection without importing rolldown. ## Related #7522 🤖 Generated with [Claude Code](https://claude.com/claude-code)
1 parent d88eb83 commit b08184e

File tree

7 files changed

+60
-13
lines changed

7 files changed

+60
-13
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { BindingMagicString as NativeBindingMagicString } from './binding.cjs';
2+
3+
// Set `isRolldownMagicString` so external packages (e.g. rolldown-string) can
4+
// detect native BindingMagicString instances without importing rolldown:
5+
// obj.isRolldownMagicString === true
6+
// This replaces the fragile `obj.constructor.name` check which breaks with
7+
// minification or bundling.
8+
Object.defineProperty(NativeBindingMagicString.prototype, 'isRolldownMagicString', {
9+
value: true,
10+
writable: false,
11+
configurable: false,
12+
});
13+
14+
export interface BindingMagicString extends NativeBindingMagicString {
15+
readonly isRolldownMagicString: true;
16+
}
17+
18+
type BindingMagicStringConstructor = Omit<typeof NativeBindingMagicString, 'prototype'> & {
19+
new (...args: ConstructorParameters<typeof NativeBindingMagicString>): BindingMagicString;
20+
prototype: BindingMagicString;
21+
};
22+
23+
export const BindingMagicString = NativeBindingMagicString as BindingMagicStringConstructor;

packages/rolldown/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { build, type BuildOptions } from './api/build';
33
import { rolldown } from './api/rolldown';
44
import type { RolldownBuild } from './api/rolldown/rolldown-build';
55
import { watch } from './api/watch';
6+
import { BindingMagicString } from './binding-magic-string';
67
import type {
78
RolldownWatcher,
89
RolldownWatcherEvent,
@@ -118,7 +119,7 @@ import type { BundleError } from './utils/error';
118119

119120
export { RUNTIME_MODULE_ID, VERSION } from './constants';
120121
export { build, defineConfig, rolldown, watch };
121-
export { BindingMagicString } from './binding.cjs';
122+
export { BindingMagicString };
122123
export type {
123124
AddonFunction,
124125
BundleError,

packages/rolldown/src/plugin/bindingify-build-hooks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type {
66
BindingPluginContext,
77
BindingPluginOptions,
88
} from '../binding.cjs';
9-
import { BindingMagicString } from '../binding.cjs';
9+
import { BindingMagicString } from '../binding-magic-string';
1010
import { parseAst } from '../parse-ast-index';
1111
import { bindingifySourcemap, type ExistingRawSourceMap } from '../types/sourcemap';
1212
import { aggregateBindingErrorsIntoJsError } from '../utils/error';

packages/rolldown/src/plugin/bindingify-output-hooks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { BindingHookFilter, BindingPluginOptions } from '../binding.cjs';
2-
import { BindingMagicString } from '../binding.cjs';
2+
import { BindingMagicString } from '../binding-magic-string';
33
import { bindingifySourcemap } from '../types/sourcemap';
44
import { aggregateBindingErrorsIntoJsError, unwrapBindingResult } from '../utils/error';
55
import { normalizeHook } from '../utils/normalize-hook';

packages/rolldown/src/plugin/index.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import type { Program } from '@oxc-project/types';
22
import type { InputOptions, OutputOptions } from '..';
3-
import type {
4-
BindingHookResolveIdExtraArgs,
5-
BindingMagicString,
6-
BindingTransformHookExtraArgs,
7-
} from '../binding.cjs';
3+
import type { BindingHookResolveIdExtraArgs, BindingTransformHookExtraArgs } from '../binding.cjs';
4+
import type { BindingMagicString } from '../binding-magic-string';
85
import type { BuiltinPlugin } from '../builtin-plugin/utils';
96
import type { DefinedHookNames } from '../constants/plugin';
107
import type { DEFINED_HOOK_NAMES } from '../constants/plugin';

packages/rolldown/src/plugin/transform-plugin-context.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
import type {
2-
BindingMagicString,
3-
BindingPluginContext,
4-
BindingTransformPluginContext,
5-
} from '../binding.cjs';
1+
import type { BindingPluginContext, BindingTransformPluginContext } from '../binding.cjs';
2+
import type { BindingMagicString } from '../binding-magic-string';
63
import {
74
type LoggingFunctionWithPosition,
85
type LogHandler,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import assert from 'node:assert';
2+
import { BindingMagicString as MagicString } from 'rolldown';
3+
import { describe, it } from 'vitest';
4+
5+
describe('MagicString isRolldownMagicString', () => {
6+
it('should have isRolldownMagicString on prototype', () => {
7+
assert.strictEqual(MagicString.prototype.isRolldownMagicString, true);
8+
});
9+
10+
it('should be accessible on instances', () => {
11+
const s = new MagicString('hello');
12+
assert.strictEqual(s.isRolldownMagicString, true);
13+
});
14+
15+
it('should allow detection without importing rolldown', () => {
16+
const s = new MagicString('hello');
17+
// This is the primary use case: external packages can detect native
18+
// BindingMagicString instances using isRolldownMagicString instead of
19+
// the fragile `s.constructor.name === 'RolldownMagicString'`
20+
const isNative = (obj: unknown): boolean =>
21+
typeof obj === 'object' &&
22+
obj !== null &&
23+
(obj as Record<string, unknown>).isRolldownMagicString === true;
24+
assert.ok(isNative(s));
25+
assert.ok(!isNative({}));
26+
assert.ok(!isNative('string'));
27+
assert.ok(!isNative(null));
28+
});
29+
});

0 commit comments

Comments
 (0)