Macro system for JavaScript and TypeScript.
Have you ever used Rust? Rust has an impressive macro system. I'm wondering why the JavaScript world can't have one. Now you got it.
It uses swc's parser and printer, and does not make any conversions other than macors to your project by default. Of course, it can also be used as a swc plugin, so we can transform typescript and jsx.
The project is still in the beta stage, and there are more macros under development.
Expression macros may be a very familiar macro type. It converts a CallExpression
into any other Expression.
$Eval
compile time eval.
$Ast
convert anything into an ast.
$Env
read env and replace with result.
$Stringify
convert any expression or type into string
$Span
get a span like [line, column]
$Line
get current line number
$Column
get current column number
$ID
generate unique id based on [line, column, filename]
$Include
include another js, ts
file into current script.
$IncludeStr
include a string from any path
$IncludeJSON
include a json, also support include a json value with key.
$WriteFile
compile time write to file
$Concat
compile time concat string
$Expr
turn code into an expression
$Quote
a template macro for create macro
$UnImplemented
mark code as unImplemented, log error when compile, throw error in client side.
$Todo
mark code as todo, log warning when compile, throw error in client side.
$UnReachable
mark code as unreachable, throw error in client side.
...
LabeledMacros are very interesting feature. It even allows you to invent your own grammar. For example:
debug: {
console.log("something")
}
or creating states:
state: {
var count = 0
var name = "macro"
}
or using function decorator:
function myFunc() {
decorator: [yourFunctionDecorator]
doSomething()
}
you can even create macro with it.
macro: {
var __DEV__ = true
var add = $ExprMacro<(a: number, b: number) => number>(function(args) {
const a = this.printExpr(args[0])
const b = this.printExpr(args[1])
if (+a < 0) return args[1]
return this.parseExpr(`${a} + ${b}`)
})
}
if (__DEV__) {
const a = add(1, 2)
}
transform to
if (true) {
const a = 1 + 2
}
To use Labeled Macros, you may need some extra config for typescript and eslint.
{
"compilerOptions": {
// ...
"allowUnusedLabels": true,
},
}
module.exports = {
rules: {
// ...
"no-labels": 0,
}
}
transform/transformAst/transformAsync
transform code with macro plugins.
createSwcPlugin
use as swc plugin.
walk
walk an ast node or node list
print/printAsync/printExpr/printType
print ast to code
parse/parseAsync/parseExpr/parseType
parse code to ast
defineConfig
define config with types
createMacro
create a macro
createLitMacro
create a literal macro
createExprMacro
create an expression macro
createTypeMacro
create a type macro
createTmplMacro
create a template macro
createLabeledMacro
create a labeled macro
isMacroPlugin
check if input is a macro plugin
Some people may argue about labeldMacros, confusing the original semantics of JavaScript. But in fact, labeledStatement
and var
are both forgotten or discarded syntax of JavaScript. So we bring something new to it. Personally, I don't think this is a bad thing.
In fact, the beginning of this project based on babel's parser and generator. When I switched to swc, I did a simple test and found that the speed was at least tripled, so it must be faster.
This project is based on the AST type of swc and is not compatible with Babel's AST. And the project focuses on transforming macros. If you need the support of something like browserlist, you need to use it with swc.
You can regard it as an enhanced version of swc with macro support, as well as better aapi. However, we do not use the .swcrc
configuration file. The json configuration is not friendly for macros, and you should define all your swc configuration in macros.config.js
or macros.config.ts
.
Copyright (c) 2023, Raven Satir