@qzlin/ts-cmacro
v1.0.0
Published
Type-checked macro-expanded scripting for hostile JavaScript runtimes - flatten TypeScript modules into a single file
Downloads
24
Maintainers
Readme
ts-cmacro
Type-checked macro-expanded scripting for hostile JavaScript runtimes
ts-cmacro lets you write large, maintainable scripts in TypeScript—with full type checking, IntelliSense, and modular structure—then compile them into a single, flat, top‑level TypeScript file that can be further transpiled to JavaScript for environments which do not support modules, bundlers, IIFE wrappers, or modern JS semantics.
If your runtime only understands:
function main(config) { /* ... */ }…but you want to author your script like a real project, this tool is for you.
Installation
# Install globally
npm install -g ts-cmacro
# or
pnpm add -g ts-cmacro
# Install as dev dependency
npm install -D ts-cmacro
# or
pnpm add -D ts-cmacroUsage
CLI
# Basic usage (outputs TypeScript)
ts-cmacro src/main.ts -o dist/script.ts
# Compact output (remove unnecessary whitespace)
ts-cmacro src/main.ts -o dist/script.ts -c
# Output to stdout
ts-cmacro src/main.ts
# Show help
ts-cmacro --helpProgrammatic API
import { build } from 'ts-cmacro';
const code = build({
entry: './src/main.ts',
compact: true
});
console.log(code);Why this exists
Many real-world JavaScript runtimes are hostile to modern tooling:
- No
import/export - No module loader
- No filesystem or network I/O
- No control over the execution context
- Fragile or non-standard global scope handling
Examples include:
- Clash Verge / Clash Meta global scripts (boa engine)
- Surge / Loon / Quantumult X scripts
- Embedded JS engines (routers, IoT, NAS)
- WebView / injection-based environments
- Game scripting engines
In these environments, bundlers break, IIFEs break, and even globalThis may be unreliable.
The only stable contract is:
- A single script file
- With top-level declarations
- And a known entry function (e.g.
main)
ts-macro-script embraces this reality instead of fighting it.
Core idea
Treat TypeScript as a macro language, not a module system.
importis for humans and tooling, not the runtime- The global scope is the linker
- All complexity is resolved at build time
- The runtime receives flat, boring, predictable JavaScript (after transpilation)
Think:
- C + preprocessor + linker
- Lisp macros
- Old-school embedded scripting
…but with modern TypeScript ergonomics.
What this tool does
Given an entry file like:
import { buildRules } from "./rules";
import type { ClashConfig } from "./types";
function main(config: ClashConfig) {
config.rules = buildRules(config.rules ?? []);
return config;
}ts-cmacro will:
- Parse the TypeScript program using the TypeScript Compiler API
- Resolve
importdependencies (relative paths only) - Topologically sort source files
- Remove all
importandexportsyntax - Concatenate declarations into a single output file
Resulting TypeScript:
function buildRules(old: string[]) {
return ["DOMAIN-SUFFIX,baidu.com,DIRECT", ...old];
}
function main(config: ClashConfig) {
config.rules = buildRules(config.rules ?? []);
return config;
}Then transpile to JavaScript using your preferred tool:
# Using tsc
tsc dist/script.ts --outFile dist/script.js --target ES2018
# Using esbuild
esbuild dist/script.ts --outfile=dist/script.js --target=es2018
# Using swc
swc dist/script.ts -o dist/script.jsNo wrappers. No modules. No runtime helpers.
Non-goals (by design)
This project intentionally does not:
- ❌ Bundle dependencies like Webpack/Rollup
- ❌ Emit IIFE or UMD wrappers
- ❌ Polyfill runtime features
- ❌ Provide a module loader
- ❌ Modify runtime globals
- ❌ Optimize for browsers or Node
If you want a bundler, use a bundler.
This tool exists specifically for environments where bundlers do not work.
Design principles
Runtime minimalism The output must be as simple as possible.
Build-time maximalism Complexity is allowed—encouraged—at build time.
Deterministic output The same input always produces the same script.
Explicit over clever No magic globals, no hidden runtime behavior.
Hostile runtime first If it works in a broken engine, it will work anywhere.
Intended workflow
src/
├─ rules.ts
├─ utils.ts
└─ main.ts ← entry
↓
ts-cmacro src/main.ts -o dist/script.ts
↓
dist/script.ts ← flat, top-level TypeScript
↓
tsc dist/script.ts --outFile dist/script.js
↓
dist/script.js ← flat, top-level JavaScriptYou keep full IDE support:
- Type checking
- Go-to-definition
- Refactoring
- Code navigation
The runtime gets none of the complexity.
Status
🚧 Early design / MVP stage
The initial version focuses on:
- Single entry point
- Relative imports
- Import/export stripping
- Ordered concatenation
Future features are explicitly out of scope until the core is proven stable.
Who this is for
You may want this tool if:
- You write JS for constrained or embedded environments
- You maintain large configuration scripts
- You are tired of "just copy-paste everything into one file"
- You want TypeScript ergonomics without runtime cost
If your runtime supports modern ESM—you probably don’t need this.
Philosophy summary
Use modern tools to generate primitive code.
That’s it.
License
GPLV3
