unplugin-op-overloading
v0.0.6
Published
Operator Overloading for JavaScript and TypeScript
Maintainers
Readme
Operator overloading for JavaScript and TypeScript using a build-time transformation.
Features
- ✨ Natural Syntax - Write
a + binstead ofa[Symbol.for('+')](b) - 🔧 Build-time Transformation - Zero runtime overhead, pure JavaScript output
- 📦 Universal - Works with Vite, Webpack, Rollup, esbuild, Rspack, and Rolldown
- 🎯 Opt-in - Uses
"use operator overloading"directive for explicit control - 🛡️ Type-safe - Includes TypeScript Language Service Plugin for IDE support
- ⚡ Fast - Uses oxc-parser for blazing-fast AST parsing
Installation
npm i -D unplugin-op-overloading
# or
bun add -d unplugin-op-overloadingUsage
Quick Start
- Add the plugin to your build tool:
// vite.config.ts
import { defineConfig } from 'vite'
import OperatorOverloading from 'unplugin-op-overloading/vite'
export default defineConfig({
plugins: [
OperatorOverloading({
equality: 'both', // If transforming ==, !=, ===, !==
}),
],
})- Add the directive to files using operator overloading:
'use operator overloading'
class Vector {
constructor(x, y) {
this.x = x
this.y = y
}
[Symbol.for('+')](other) {
return new Vector(this.x + other.x, this.y + other.y)
}
}
const v1 = new Vector(3, 4)
const v2 = new Vector(1, 2)
const sum = v1 + v2 // Vector(4, 6) ✨Build Tool Configuration
// vite.config.ts
import OperatorOverloading from 'unplugin-op-overloading/vite'
export default defineConfig({
plugins: [
OperatorOverloading({
equality: 'both',
debug: false,
}),
],
})// rollup.config.js
import OperatorOverloading from 'unplugin-op-overloading/rollup'
export default {
plugins: [
OperatorOverloading({
equality: 'both',
}),
],
}// rolldown.config.js
import OperatorOverloading from 'unplugin-op-overloading/rolldown'
export default {
plugins: [
OperatorOverloading({
equality: 'both',
}),
],
}import { build } from 'esbuild'
import OperatorOverloading from 'unplugin-op-overloading/esbuild'
build({
plugins: [
OperatorOverloading({
equality: 'both',
}),
],
})// webpack.config.js
import OperatorOverloading from 'unplugin-op-overloading/webpack'
export default {
plugins: [
OperatorOverloading({
equality: 'both',
}),
],
}// rspack.config.js
import OperatorOverloading from 'unplugin-op-overloading/rspack'
export default {
plugins: [
OperatorOverloading({
equality: 'both',
}),
],
}Plugin Options
| Option | Type | Default | Description |
| ---------- | ---------------------------------------- | --------------------- | ------------------------------------- |
| equality | 'off' \| 'loose' \| 'strict' \| 'both' | 'off' | Equality operator transformation mode |
| debug | boolean | false | Enable debug logging |
| include | FilterPattern | [/\.[cm]?[jt]sx?$/] | Files to include |
| exclude | FilterPattern | [/node_modules/] | Files to exclude |
Equality Modes
'off'- Don't transform equality operators (default)'loose'- Transform==and!='strict'- Transform===and!=='both'- Transform all four equality operators
Supported Operators
Arithmetic Operators
;[Symbol.for('+')](other) // a + b
[Symbol.for('-')](other) // a - b
[Symbol.for('*')](other) // a * b
[Symbol.for('/')](other) // a / b
[Symbol.for('%')](other) // a % b
[Symbol.for('**')](other) // a ** bComparison Operators
;[Symbol.for('<')](other) // a < b
[Symbol.for('>')](other) // a > b
[Symbol.for('<=')](other) // a <= b
[Symbol.for('>=')](other) // a >= bEquality Operators
;[Symbol.for('==')](other) // a == b
[Symbol.for('!=')](other) // a != b
[Symbol.for('===')](other) // a === b
[Symbol.for('!==')](other) // a !== bUnary Operators
;[Symbol.for('minus')]() // -a
[Symbol.for('plus')]() // +aExamples
Vector Mathematics
'use operator overloading'
class Vector {
constructor(x, y) {
this.x = x
this.y = y
}
[Symbol.for('+')](other) {
return new Vector(this.x + other.x, this.y + other.y)
}
[Symbol.for('-')](other) {
return new Vector(this.x - other.x, this.y - other.y)
}
[Symbol.for('*')](scalar) {
return new Vector(this.x * scalar, this.y * scalar)
}
[Symbol.for('minus')]() {
return new Vector(-this.x, -this.y)
}
magnitude() {
return Math.hypot(this.x, this.y)
}
[Symbol.for('<')](other) {
return this.magnitude() < other.magnitude()
}
}
const v1 = new Vector(3, 4)
const v2 = new Vector(1, 2)
const sum = v1 + v2 // Vector(4, 6)
const diff = v1 - v2 // Vector(2, 2)
const scaled = v1 * 2 // Vector(6, 8)
const negated = -v1 // Vector(-3, -4)
const isLess = v2 < v1 // true (magnitude 2.236 < 5)
const chained = v1 + v2 * 2 // Vector(5, 8)Complex Numbers
'use operator overloading'
class Complex {
constructor(real, imag) {
this.real = real
this.imag = imag
}
[Symbol.for('+')](other) {
return new Complex(this.real + other.real, this.imag + other.imag)
}
[Symbol.for('*')](other) {
return new Complex(
this.real * other.real - this.imag * other.imag,
this.real * other.imag + this.imag * other.real,
)
}
toString() {
const sign = this.imag >= 0 ? '+' : ''
return `${this.real}${sign}${this.imag}i`
}
}
const c1 = new Complex(1, 2)
const c2 = new Complex(3, 4)
const sum = c1 + c2 // 4+6i
const product = c1 * c2 // -5+10iMatrix Multiplication
'use operator overloading'
function Matrix(rows) {
return {
rows, // [[a, b], [c, d]]
[Symbol.for('*')](other) {
// Row × Column multiplication
const result = [
[
this.rows[0][0] * other.rows[0][0] +
this.rows[0][1] * other.rows[1][0],
this.rows[0][0] * other.rows[0][1] +
this.rows[0][1] * other.rows[1][1],
],
[
this.rows[1][0] * other.rows[0][0] +
this.rows[1][1] * other.rows[1][0],
this.rows[1][0] * other.rows[0][1] +
this.rows[1][1] * other.rows[1][1],
],
]
return Matrix(result)
},
}
}
const A = Matrix([
[1, 2],
[3, 4],
])
const B = Matrix([
[5, 6],
[7, 8],
])
const C = A * B // Matrix([[19, 22], [43, 50]])How It Works
Transformation Process
The plugin transforms operator expressions into method calls at build time:
Input:
'use operator overloading'
const sum = a + bOutput:
const sum = (() => {
'operator-overloading disabled'
const __lhs = a
const __rhs = b
const __sym = Symbol.for('+')
return __lhs != null && __lhs[__sym] !== undefined
? __lhs[__sym](__rhs)
: __lhs + __rhs
})()Key Features
- IIFE Wrapping - Each operator is wrapped in an immediately-invoked function expression to avoid variable conflicts
- Fallback Behavior - Falls back to native JavaScript operators if no custom implementation exists
- Null Safety - Checks for null/undefined before calling operator methods
- Directive Removal - The
"use operator overloading"directive is removed from output
TypeScript Support
This package includes two TypeScript plugins that work together to provide comprehensive operator overloading support:
1. Language Service Plugin (IDE Support)
Suppresses TypeScript errors in your editor for a clean development experience.
What it does:
- ✅ Removes red squiggles for operator overloading in VS Code, WebStorm, and other TypeScript-aware IDEs
- ✅ Works automatically with files containing
"use operator overloading" - ✅ No
@ts-expect-errorcomments needed - ⚠️ IDE-only: Does not affect
tsccompilation
Setup:
Add to your tsconfig.json:
{
"compilerOptions": {
"plugins": [{ "name": "unplugin-op-overloading/typescript-plugin" }]
}
}VS Code Configuration:
Create .vscode/settings.json:
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}Restart TypeScript server: Cmd/Ctrl + Shift + P → "TypeScript: Restart TS Server"
2. TypeScript Transformer Plugin (Compile-time Transformation)
Transforms operator overloading syntax during TypeScript compilation.
What it does:
- ✅ Transforms code when using
tscdirectly - ✅ Alternative to using build tool plugins (Vite/Webpack/Rollup)
- ✅ Useful for libraries or projects that rely on TypeScript's native compilation
- ✅ Enables operator overloading without bundler configuration
Setup:
Add to your tsconfig.json (in addition to the Language Service Plugin):
{
"compilerOptions": {
"plugins": [
{
"name": "unplugin-op-overloading/typescript-plugin"
},
{
"transform": "unplugin-op-overloading/typescript-plugin/transformer",
"transformProgram": true
}
]
}
}Which Plugin Configuration Should I Use?
For most projects using build tools (Vite/Webpack/Rollup/esbuild):
{
"compilerOptions": {
"plugins": [{ "name": "unplugin-op-overloading/typescript-plugin" }]
}
}- Use the Language Service Plugin for IDE support
- Use the build tool plugin (configured separately) for actual transformation
- The Transformer Plugin is optional in this case
For projects using tsc directly (libraries, standalone TypeScript):
{
"compilerOptions": {
"plugins": [
{ "name": "unplugin-op-overloading/typescript-plugin" },
{
"transform": "unplugin-op-overloading/typescript-plugin/transformer",
"transformProgram": true
}
]
}
}- Use both plugins for complete support
- Language Service Plugin provides IDE support
- Transformer Plugin handles code transformation during
tsccompilation
Important Notes
⚠️ The Language Service Plugin only affects IDE experience. Running tsc directly will still show errors unless you also use the Transformer Plugin or a build tool plugin.
✅ Recommended: Use build tools (Vite/Webpack/Rollup) instead of tsc for most projects. They provide better bundling, optimization, and full operator overloading support.
TypeScript Example
'use operator overloading'
export class Vector {
constructor(
public x: number,
public y: number,
) {}
[Symbol.for('+')](other: Vector): Vector {
return new Vector(this.x + other.x, this.y + other.y)
}
}
const v1 = new Vector(3, 4)
const v2 = new Vector(1, 2)
const sum = v1 + v2 // No IDE errors with TypeScript plugin!Working Example
See the complete working example in examples/vite-typescript/ featuring:
- ✅ Vector operations with 7 operators
- ✅ Complex number arithmetic
- ✅ 2×2 Matrix multiplication
- ✅ Interactive frontend demo
- ✅ TypeScript Language Service Plugin configured
- ✅ Zero build warnings
cd examples/vite-typescript
bun install
bun run devArchitecture
┌─────────────────────────────────────────┐
│ Source Code (.js, .ts, .tsx) │
│ 'use operator overloading' │
│ const sum = a + b │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ unplugin-op-overloading │
│ - Detects directive │
│ - Parses with oxc-parser │
│ - Transforms operators │
│ - Generates sourcemaps │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ Transformed Output │
│ const sum = (() => { │
│ const __lhs = a, __rhs = b │
│ return __lhs?.[Symbol.for('+')](...) │
│ })() │
└─────────────────────────────────────────┘Performance
- ⚡ Parsing: Uses oxc-parser - one of the fastest JavaScript parsers
- 🎯 Selective: Only processes files with
"use operator overloading"directive - 📦 Zero Runtime: Pure compile-time transformation, no runtime library needed
- 🗺️ Source Maps: Full source map support for debugging
Limitations
- Directive Required: Files must include
"use operator overloading"at the top (within first 3 lines) - TypeScript CLI:
tscwill still show errors - use build tools for production - Operator Precedence: Follows standard JavaScript operator precedence
- Not a Language Feature: This is a build-time transformation, not native JavaScript
Testing
# Run all tests
bun run test
# Type checking
bun run typecheck
# Linting
bun run lint
# Build
bun run buildDocumentation
License
Apache-2.0 License © 2025-PRESENT Arafat Husayn
