Getting Started With Sweet.js Macros
Sweet.js is a macro system for JavaScript that lets you extend the language itself rather than generating code through build tools. It works at parse time. You define rules that transform syntax before the JS engine ever sees the output. The main tradeoff is that your mental model has to account for hygienic macro expansion, which behaves differently from what most developers expect after working with C preprocessors or Python decorators. The core package is sweet.js, and you almost never use it directly from the command line. The real workflow runs through a build pipeline. For a standalone project you can use gulp-sweet.js or a Rollup plugin, but the simplest entry point that most people actually end up using is the sweet CLI bundled with the compiler. Run npm install -g sweet.js to get the binary, then point it at your .js src files. If you are migrating an existing codebase, set up a compile step that watches your source directory and outputs to a separate build folder. Sweet.js files are .js files syntactically. The compiler distinguishes macro definitions from regular code by context. This means you can gradually introduce macros without restructuring your tree.
Writing Your First Macro
Macro definitions start with macro. Here is a minimal example that strips the ceremony out of a common pattern: macro unless { case {$cond:expr => $body:block} => { quasiquote do { if (!($cond)) $body } } } This compiles into an if (!condition) statement. It is trivial but it demonstrates how pattern matching works inside macro definitions. The case branches match against a template. Variables like $cond and $body capture subexpressions from the call site and inject them into the quasiquote result. Nothing magical. It is literal AST manipulation disguised as string syntax.
I ran into a real problem early on when I tried to write a macro that generated a default parameter structure for function declarations. The macro compiled fine in isolation but broke whenever called from within another module. The issue was spurious identifier binding. Sweet.js hygienic expansion was renaming my injected variable names to avoid collisions, which meant the generated code referenced identifiers that did not exist in the outer scope. The workaround was to explicitly mark those identifiers with #bind so the hygienic system knew they were supposed to resolve to existing bindings rather than fresh ones. This cost me about three hours of debugging before I figured out what was actually happening.
Get the Full Details

Understanding Hygiene
Hygiene is the single most important concept to internalize. Every identifier that a macro introduces gets a unique internal tag. When your macro expands, those tagged identifiers cannot accidentally collide with variables in the caller's scope. This is good for correctness. It is also very confusing if you expected your macro to behave like a template engine that simply pastes text into the source. There are three keywords that control identifier behavior: #bind, #unbound, and #free. Use #bind when you want the macro to reference an existing binding from the caller. Use #free when the identifier should be resolved in the macro's own scope. Use #unbound when you want a truly fresh name. Most beginners overuse #free and then wonder why closures behave strangely.
Common Pitfalls
The first pitfall is whitespace sensitivity. Sweet.js tokenization is strict about certain operators and delimiters. A space between a keyword and its argument can change the parse tree entirely in ways that are not obvious from error messages. The error you get will often point to a line far from where the actual issue lives. The second pitfall is trying to write macros that depend on runtime values. Sweet.js macros run at compile time. If your macro needs data from a configuration file, import that data into the macro definition scope rather than trying to access it during expansion. I learned this after wasting a morning trying to pass a JSON config object through a quasiquote template. The compiler rejected it silently and produced broken output that passed type checking but failed at runtime. The third pitfall is overcomplicating things. Sweet.js macros are not worth it for simple code generation. They shine when you are defining new control structures or domain-specific syntax that would otherwise require heavy boilerplate. If your macro is more than twenty lines, ask whether a regular helper function would do the same job with less cognitive overhead. In my experience, about sixty percent of macros I wrote ended up being candidates for extraction into normal utility functions instead.
When Sweet.js Fails Completely
There are scenarios where Sweet.js is the wrong tool. If you are working in an environment that does not support custom build pipelines, such as certain locked-down CDN setups or browser-only projects without a bundler, macros are impossible to use. You also hit a wall when you need macros to reason about types. Sweet.js has no type system of its own and cannot validate the structures it generates. For that you need a tool like TypeScript or Flow on top of it, and the integration is not seamless. Another hard limitation is debugging. Stack traces from macro-expanded code reference synthetic locations. Source maps help but they are imperfect. When a macro expansion produces a runtime error, the line number in the error will point to a generated line, not the line in your source file where you called the macro. Learning to read through the generated output by printing intermediate quasiquote results is the standard workaround.

Alternatives Worth Considering
If Sweet.js feels too rigid for what you need, consider template-based approaches like Babel plugins or PostCSS-style processor chains. They operate at a different stage in the pipeline and give you more direct access to the AST without the hygiene constraints. If you only need simple code reuse rather than new syntax, plain ES modules with factory functions handle most cases. Sweet.js is specifically for extending the language grammar itself. The package is available on npm as sweet.js. The official documentation lives at sweetjs.org and the source repository is on GitHub. The community is small but the people who use it tend to stick with it because once you understand the hygiene model, the macro system is consistent and predictable. The friction is entirely in the learning curve. After about a week of actual use, writing macros feels less like fighting the compiler and more like writing normal code.