Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ Cargo.lock
# idea
.idea/

# Serena
.serena

*.node
lib
artifacts
Expand Down
138 changes: 138 additions & 0 deletions bench/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import fs from 'node:fs'
import { spawnSync } from 'node:child_process'
import { resolve } from 'node:path'

import { bench, boxplot, run } from 'mitata'

const rootDir = resolve(__dirname, '..')
const fixtureDir = resolve(__dirname, 'fixture')
const fixtureTsconfig = resolve(fixtureDir, 'tsconfig.json')

const tsxCli = require.resolve('tsx/cli')
const swcNodeCli = resolve(rootDir, 'packages', 'cli', 'index.js')
const cacheRoot = resolve(rootDir, 'bench', '.cache')
const swcCacheDir = resolve(cacheRoot, 'swc-node')
const tsxTmpDir = resolve(cacheRoot, 'tmp')

const workloads = [
{
name: 'rxjs',
entrypoint: resolve(fixtureDir, 'rxjs.ts'),
},
{
name: 'typescript',
entrypoint: resolve(fixtureDir, 'ts.ts'),
},
] as const

type RunMode = 'cached' | 'uncached'

type Runner = {
name: 'tsx' | 'swc-node' | 'node'
cliFile: string
envForMode?: (mode: RunMode) => NodeJS.ProcessEnv
}

const runners: Runner[] = [
{
name: 'node',
cliFile: '', // Use node directly without a CLI wrapper
},
{
name: 'tsx',
cliFile: tsxCli,
envForMode: (mode) => ({
...process.env,
TMPDIR: tsxTmpDir,
TSX_DISABLE_CACHE: mode === 'uncached' ? '1' : '0',
}),
},
{
name: 'swc-node',
cliFile: swcNodeCli,
envForMode: (mode) => ({
...process.env,
SWC_NODE_PROJECT: fixtureTsconfig,
SWC_NODE_CACHE_DIR: swcCacheDir,
SWC_NODE_CACHE: mode === 'uncached' ? '0' : '1',
}),
},
]

function runCli(name: string, cliFile: string, entrypoint: string, env: NodeJS.ProcessEnv) {
const result = spawnSync(process.execPath, [cliFile, entrypoint].filter(Boolean), {
cwd: fixtureDir,
env,
stdio: 'pipe',
encoding: 'utf8',
})

if (result.status !== 0) {
const stderr = result.stderr?.trim() || '(empty stderr)'
throw new Error(`${name} failed with exit code ${result.status}: ${stderr}`)
}
}

function resetCacheDirectories() {
fs.rmSync(cacheRoot, { recursive: true, force: true })
fs.mkdirSync(swcCacheDir, { recursive: true })
fs.mkdirSync(tsxTmpDir, { recursive: true })
}

resetCacheDirectories()

for (const workload of workloads) {
for (const runner of runners) {
if (runner.envForMode) {
runCli(
`${runner.name} (${workload.name}, cached prewarm)`,
runner.cliFile,
workload.entrypoint,
runner.envForMode('cached'),
)
runCli(
`${runner.name} (${workload.name}, uncached preflight)`,
runner.cliFile,
workload.entrypoint,
runner.envForMode('uncached'),
)
} else {
runCli(`${runner.name} (${workload.name},preflight)`, runner.cliFile, workload.entrypoint, process.env)
}
}
}

boxplot(() => {
for (const workload of workloads) {
for (const runner of runners) {
if (runner.envForMode) {
bench(`${runner.name} uncached (${workload.name})`, () =>
runCli(
`${runner.name} uncached (${workload.name})`,
runner.cliFile,
workload.entrypoint,
runner.envForMode!('uncached'),
),
)

bench(`${runner.name} cached (${workload.name})`, () =>
runCli(
`${runner.name} cached (${workload.name})`,
runner.cliFile,
workload.entrypoint,
runner.envForMode!('cached'),
),
).baseline(runner.name === 'node')
} else {
bench(`${runner.name} (${workload.name})`, () =>
runCli(`${runner.name} (${workload.name})`, runner.cliFile, workload.entrypoint, process.env),
).baseline(runner.name === 'node')
}
}
}
})

run({ throw: true }).catch((error) => {
console.error(error)
process.exitCode = 1
})
234 changes: 234 additions & 0 deletions bench/fixture/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
/* ---------------------------------------------------
Basic Types and Interfaces
--------------------------------------------------- */

export type ID = string | number

export interface Timestamped {
createdAt: Date
updatedAt?: Date
}

export interface User extends Timestamped {
id: ID
name: string
email?: string
role: UserRole
metadata?: Record<string, unknown>
}

export type PartialUser = Partial<User>
export type ReadonlyUser = Readonly<User>

/* ---------------------------------------------------
Enums
--------------------------------------------------- */

export type UserRole = (typeof UserRole)[keyof typeof UserRole]
export const UserRole = {
ADMIN: 'admin',
EDITOR: 'editor',
VIEWER: 'viewer',
}

export type LogLevel = (typeof LogLevel)[keyof typeof LogLevel]
export const LogLevel = {
INFO: 'INFO',
WARN: 'WARN',
ERROR: 'ERROR',
}

/* ---------------------------------------------------
Constants
--------------------------------------------------- */

export const APP_NAME = 'MockTSModule'
export const VERSION = '2.3.1'

export const DEFAULT_USER: ReadonlyUser = {
id: '0',
name: 'Guest',
role: UserRole.VIEWER,
createdAt: new Date(),
}

/* ---------------------------------------------------
Utility Types
--------------------------------------------------- */

export type Nullable<T> = T | null
export type AsyncResult<T> = Promise<{ ok: true; value: T } | { ok: false; error: Error }>

export type ExtractArrayType<T> = T extends (infer U)[] ? U : never

export type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]
}

/* ---------------------------------------------------
Generics + Functions
--------------------------------------------------- */

export function identity<T>(value: T): T {
return value
}

export function assertNever(x: never): never {
throw new Error(`Unexpected value: ${x}`)
}

export async function simulateAsync<T>(value: T, delay = 50): Promise<T> {
return new Promise((resolve) => {
setTimeout(() => resolve(value), delay)
})
}

export function mergeObjects<A extends object, B extends object>(a: A, b: B): A & B {
return { ...a, ...b }
}

export function greet(user: User): string {
return `Hello ${user.name} (${user.role})`
}

/* ---------------------------------------------------
Class with Generics
--------------------------------------------------- */

export class Repository<T extends { id: ID }> {
private items = new Map<ID, T>()

add(item: T): void {
this.items.set(item.id, item)
}

get(id: ID): T | undefined {
return this.items.get(id)
}

remove(id: ID): boolean {
return this.items.delete(id)
}

list(): T[] {
return [...this.items.values()]
}

clear(): void {
this.items.clear()
}

get size(): number {
return this.items.size
}
}

/* ---------------------------------------------------
Abstract Classes
--------------------------------------------------- */

export abstract class Logger {
abstract log(level: LogLevel, message: string): void

info(msg: string) {
this.log(LogLevel.INFO, msg)
}

warn(msg: string) {
this.log(LogLevel.WARN, msg)
}

error(msg: string) {
this.log(LogLevel.ERROR, msg)
}
}

export class ConsoleLogger extends Logger {
log(level: LogLevel, message: string) {
console.log(`[${LogLevel[level]}] ${message}`)
}
}

/* ---------------------------------------------------
Class
--------------------------------------------------- */

export class UserService {
private repo = new Repository<User>()

create(user: User) {
this.repo.add(user)
}

find(id: ID) {
return this.repo.get(id)
}

list() {
return this.repo.list()
}
}

/* ---------------------------------------------------
Function Overloads
--------------------------------------------------- */

export function format(value: number): string
export function format(value: Date): string
export function format(value: string): string
export function format(value: number | Date | string): string {
if (typeof value === 'number') return value.toFixed(2)
if (value instanceof Date) return value.toISOString()
return value.trim()
}

/* ---------------------------------------------------
Symbol Usage
--------------------------------------------------- */

export const INTERNAL_TOKEN = Symbol('internal')

/* ---------------------------------------------------
Tuple + Advanced Types
--------------------------------------------------- */

export type Point = readonly [number, number]

export function distance([x1, y1]: Point, [x2, y2]: Point): number {
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
}

/* ---------------------------------------------------
Async Generator
--------------------------------------------------- */

export async function* asyncCounter(limit: number) {
for (let i = 0; i < limit; i++) {
await new Promise((r) => setTimeout(r, 10))
yield i
}
}

/* ---------------------------------------------------
Module Augmentation Example
--------------------------------------------------- */

declare global {
interface Window {
__MOCK_TS_MODULE__?: boolean
}
}

/* ---------------------------------------------------
Default Export
--------------------------------------------------- */

export default function initialize(): UserService {
return new UserService()
}

/* ---------------------------------------------------
Re-exports
--------------------------------------------------- */

export { randomUUID as generateId } from 'crypto'
9 changes: 9 additions & 0 deletions bench/fixture/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "swc-node-bench-rxjs-fixture",
"private": true,
"type": "module",
"dependencies": {
"rxjs": "^7.8.2",
"typescript": "^5.9.3"
}
}
Loading
Loading