JavaScript Macros and Abstract Code Generation
Master JavaScript code generation techniques for compile-time and runtime metaprogramming. Covers AST manipulation, Babel plugin authorship, tagged template literals as macros, code generation pipelines, source-to-source transformation, compile-time evaluation, and safe eval alternatives.
JavaScript lacks native macro support, but compile-time tools (Babel plugins, build-time transforms) and runtime techniques (tagged templates, Function constructor, code generation) provide powerful code generation capabilities.
For the metaprogramming patterns that complement code generation, see JS Metaprogramming Advanced Architecture Guide.
Tagged Template Literals as Macros
// Tagged templates transform template literals at call time
// They receive raw strings and interpolated values separately
function sql(strings, ...values) {
// Build parameterized query (prevents SQL injection)
const params = [];
let query = "";
for (let i = 0; i < strings.length; i++) {
query += strings[i];
if (i < values.length) {
params.push(values[i]);
query += `$${params.length}`; // PostgreSQL-style parameter
}
}
return {
text: query.trim(),
values: params,
// For debugging
toString() {
return this.text;
}
};
}
const name = "Alice";
const age = 30;
const query = sql`
SELECT * FROM users
WHERE name = ${name}
AND age > ${age}
ORDER BY created_at DESC
`;
console.log(query.text);
// SELECT * FROM users WHERE name = $1 AND age > $2 ORDER BY created_at DESC
console.log(query.values); // ["Alice", 30]
// HTML TEMPLATE WITH AUTO-ESCAPING
function html(strings, ...values) {
const escape = (str) =>
String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """);
let result = "";
for (let i = 0; i < strings.length; i++) {
result += strings[i]; // Raw HTML (trusted)
if (i < values.length) {
const value = values[i];
if (value && value.__safe) {
result += value.content; // Pre-sanitized content
} else {
result += escape(value); // Escape user input
}
}
}
return { content: result, __safe: true };
}
function raw(content) {
return { content, __safe: true };
}
const userInput = '<script>alert("xss")</script>';
const output = html`
<div class="user-content">
<h1>${"Welcome"}</h1>
<p>${userInput}</p>
${raw('<hr class="divider">')}
</div>
`;
console.log(output.content);
// <div class="user-content">
// <h1>Welcome</h1>
// <p><script>alert("xss")</script></p>
// <hr class="divider">
// </div>
// CSS-IN-JS GENERATOR
function css(strings, ...values) {
const className = `css-${hashCode(strings.join(""))}`;
let rules = "";
for (let i = 0; i < strings.length; i++) {
rules += strings[i];
if (i < values.length) {
rules += typeof values[i] === "function" ? values[i].name : values[i];
}
}
return {
className,
rules: `.${className} { ${rules.trim()} }`,
toString() { return this.className; }
};
}
function hashCode(str) {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0;
}
return Math.abs(hash).toString(36);
}
const buttonStyle = css`
padding: 8px 16px;
background: ${"#3b82f6"};
color: white;
border-radius: 4px;
border: none;
`;
console.log(buttonStyle.className); // "css-abc123"
console.log(buttonStyle.rules); // ".css-abc123 { padding: 8px 16px; ... }"AST-Based Code Generation
// Build Abstract Syntax Trees and generate code from them
// Simple AST node constructors
const AST = {
program(body) {
return { type: "Program", body };
},
variableDeclaration(kind, name, init) {
return {
type: "VariableDeclaration",
kind,
declarations: [{
type: "VariableDeclarator",
id: { type: "Identifier", name },
init
}]
};
},
functionDeclaration(name, params, body) {
return {
type: "FunctionDeclaration",
id: { type: "Identifier", name },
params: params.map(p => ({ type: "Identifier", name: p })),
body: { type: "BlockStatement", body }
};
},
returnStatement(argument) {
return { type: "ReturnStatement", argument };
},
binaryExpression(operator, left, right) {
return { type: "BinaryExpression", operator, left, right };
},
identifier(name) {
return { type: "Identifier", name };
},
literal(value) {
return { type: "Literal", value };
},
callExpression(callee, args) {
return {
type: "CallExpression",
callee: typeof callee === "string" ? { type: "Identifier", name: callee } : callee,
arguments: args
};
},
ifStatement(test, consequent, alternate = null) {
return {
type: "IfStatement",
test,
consequent: { type: "BlockStatement", body: Array.isArray(consequent) ? consequent : [consequent] },
alternate: alternate ? { type: "BlockStatement", body: Array.isArray(alternate) ? alternate : [alternate] } : null
};
}
};
// CODE GENERATOR: AST -> JavaScript source code
function generate(node, indent = 0) {
const pad = " ".repeat(indent);
switch (node.type) {
case "Program":
return node.body.map(n => generate(n, indent)).join("\n");
case "VariableDeclaration":
return `${pad}${node.kind} ${node.declarations.map(d =>
`${d.id.name}${d.init ? ` = ${generate(d.init)}` : ""}`
).join(", ")};`;
case "FunctionDeclaration":
return `${pad}function ${node.id.name}(${
node.params.map(p => p.name).join(", ")
}) {\n${generate(node.body, indent + 1)}\n${pad}}`;
case "BlockStatement":
return node.body.map(n => generate(n, indent)).join("\n");
case "ReturnStatement":
return `${pad}return ${generate(node.argument)};`;
case "BinaryExpression":
return `${generate(node.left)} ${node.operator} ${generate(node.right)}`;
case "Identifier":
return node.name;
case "Literal":
return JSON.stringify(node.value);
case "CallExpression":
return `${generate(node.callee)}(${
node.arguments.map(a => generate(a)).join(", ")
})`;
case "IfStatement":
let code = `${pad}if (${generate(node.test)}) {\n${generate(node.consequent, indent + 1)}\n${pad}}`;
if (node.alternate) {
code += ` else {\n${generate(node.alternate, indent + 1)}\n${pad}}`;
}
return code;
default:
return `/* unknown: ${node.type} */`;
}
}
// GENERATE A FUNCTION FROM A SCHEMA
function generateValidator(schema) {
const checks = [];
for (const [field, rules] of Object.entries(schema)) {
if (rules.type) {
checks.push(
AST.ifStatement(
AST.binaryExpression("!==",
AST.callExpression("typeof", [AST.identifier(`obj.${field}`)]),
AST.literal(rules.type)
),
[AST.returnStatement(AST.literal(`${field} must be a ${rules.type}`))]
)
);
}
if (rules.required) {
checks.push(
AST.ifStatement(
AST.binaryExpression("==",
AST.identifier(`obj.${field}`),
AST.literal(null)
),
[AST.returnStatement(AST.literal(`${field} is required`))]
)
);
}
}
checks.push(AST.returnStatement(AST.literal(null)));
const ast = AST.functionDeclaration("validate", ["obj"], checks);
return generate(ast);
}
const validatorCode = generateValidator({
name: { type: "string", required: true },
age: { type: "number" },
email: { type: "string", required: true }
});
console.log(validatorCode);
// function validate(obj) {
// if (typeof obj.name !== "string") { return "name must be a string"; }
// if (obj.name == null) { return "name is required"; }
// if (typeof obj.age !== "number") { return "age must be a number"; }
// if (typeof obj.email !== "string") { return "email must be a string"; }
// if (obj.email == null) { return "email is required"; }
// return null;
// }Runtime Code Generation
// Safe alternatives to eval for runtime code generation
// FUNCTION CONSTRUCTOR (safe scope isolation)
function createCompiledExpression(expression, variables) {
// Validate: only allow safe characters
if (/[;{}]/.test(expression)) {
throw new Error("Expression contains unsafe characters");
}
const paramNames = Object.keys(variables);
const paramValues = Object.values(variables);
// Create function with specified parameters only
const fn = new Function(...paramNames, `return (${expression});`);
return fn(...paramValues);
}
console.log(createCompiledExpression("a + b * c", { a: 1, b: 2, c: 3 })); // 7
console.log(createCompiledExpression("x > 10 ? 'big' : 'small'", { x: 15 })); // "big"
// TEMPLATE COMPILER
function compileTemplate(template) {
// Convert template string to a render function
const parts = [];
let current = 0;
// Find all {{ expression }} blocks
const regex = /\{\{(.+?)\}\}/g;
let match;
while ((match = regex.exec(template)) !== null) {
// Add text before the expression
if (match.index > current) {
parts.push({ type: "text", value: template.slice(current, match.index) });
}
// Add the expression
parts.push({ type: "expr", value: match[1].trim() });
current = match.index + match[0].length;
}
// Add remaining text
if (current < template.length) {
parts.push({ type: "text", value: template.slice(current) });
}
// Generate render function
const body = parts.map(part => {
if (part.type === "text") {
return JSON.stringify(part.value);
}
return `String(ctx.${part.value})`;
}).join(" + ");
return new Function("ctx", `return ${body || "''"};`);
}
const render = compileTemplate(
"Hello, {{ name }}! You have {{ count }} new messages."
);
console.log(render({ name: "Alice", count: 5 }));
// "Hello, Alice! You have 5 new messages."
// COMPILED SCHEMA VALIDATOR (generates optimized function)
function compileValidator(schema) {
const lines = ["const errors = [];"];
for (const [field, rules] of Object.entries(schema)) {
if (rules.required) {
lines.push(`if (data.${field} == null) errors.push("${field} is required");`);
}
if (rules.type) {
lines.push(
`if (data.${field} != null && typeof data.${field} !== "${rules.type}") ` +
`errors.push("${field} must be ${rules.type}");`
);
}
if (rules.min !== undefined) {
lines.push(
`if (typeof data.${field} === "number" && data.${field} < ${rules.min}) ` +
`errors.push("${field} must be >= ${rules.min}");`
);
}
if (rules.max !== undefined) {
lines.push(
`if (typeof data.${field} === "number" && data.${field} > ${rules.max}) ` +
`errors.push("${field} must be <= ${rules.max}");`
);
}
if (rules.pattern) {
lines.push(
`if (typeof data.${field} === "string" && !${rules.pattern}.test(data.${field})) ` +
`errors.push("${field} format is invalid");`
);
}
}
lines.push("return errors.length > 0 ? errors : null;");
return new Function("data", lines.join("\n"));
}
const validate = compileValidator({
name: { type: "string", required: true },
age: { type: "number", min: 0, max: 150 },
email: { type: "string", required: true, pattern: /^[^\s@]+@[^\s@]+$/ }
});
console.log(validate({ name: "Alice", age: 30, email: "alice@example.com" }));
// null (valid)
console.log(validate({ name: null, age: -5, email: "invalid" }));
// ["name is required", "age must be >= 0", "email format is invalid"]Source-to-Source Transformation
// Transform JavaScript source code patterns
class CodeTransformer {
#transforms = [];
addTransform(pattern, replacement) {
this.#transforms.push({
pattern: typeof pattern === "string" ? new RegExp(escapeRegex(pattern), "g") : pattern,
replacement
});
return this;
}
transform(source) {
let result = source;
for (const { pattern, replacement } of this.#transforms) {
result = result.replace(pattern, replacement);
}
return result;
}
}
function escapeRegex(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
// DEAD CODE ELIMINATION (simplified)
function eliminateDeadCode(source) {
const transformer = new CodeTransformer();
// Remove if(false) blocks
transformer.addTransform(
/if\s*\(\s*false\s*\)\s*\{[^}]*\}/g,
"/* dead code removed */"
);
// Remove console.log in production
transformer.addTransform(
/console\.(log|debug|info)\([^)]*\);?\n?/g,
""
);
return transformer.transform(source);
}
const code = `
function process(x) {
console.log("processing", x);
if (false) {
doExpensiveThing();
}
return x * 2;
}
`;
console.log(eliminateDeadCode(code));
// function process(x) {
// /* dead code removed */
// return x * 2;
// }
// CODE GENERATION PIPELINE
class CodePipeline {
#stages = [];
addStage(name, transform) {
this.#stages.push({ name, transform });
return this;
}
run(source) {
let result = source;
const log = [];
for (const stage of this.#stages) {
const before = result;
result = stage.transform(result);
log.push({
stage: stage.name,
changed: before !== result,
length: result.length
});
}
return { output: result, log };
}
}
const pipeline = new CodePipeline()
.addStage("strip-comments", src => src.replace(/\/\/.*$/gm, ""))
.addStage("strip-console", src => src.replace(/console\.\w+\([^)]*\);?\n?/g, ""))
.addStage("minify-whitespace", src => src.replace(/\n\s*\n/g, "\n").trim());
const { output, log } = pipeline.run(`
// This is a helper function
function helper(x) {
console.log("debug:", x);
// Transform the value
return x * 2;
}
`);
console.log(output);
console.log(log);Build-Time Macro Patterns
// Patterns that mimic macros using build-time processing
// COMPILE-TIME CONSTANTS (replaced during build)
// In a build plugin, these would be replaced with actual values
const __DEV__ = process.env.NODE_ENV !== "production";
const __VERSION__ = "1.0.0";
function devOnly(fn) {
if (__DEV__) return fn;
return () => {}; // No-op in production
}
const debugLog = devOnly((msg) => console.log(`[DEBUG] ${msg}`));
// COMPILE-TIME TYPE STRIPPING (simulated)
function stripTypeAnnotations(source) {
// Remove TypeScript-style type annotations
return source
.replace(/:\s*(string|number|boolean|any|void|object)\b/g, "")
.replace(/:\s*\w+\[\]/g, "")
.replace(/<[^>]+>/g, "")
.replace(/\bas\s+\w+/g, "")
.replace(/interface\s+\w+\s*\{[^}]*\}/g, "")
.replace(/type\s+\w+\s*=\s*[^;]+;/g, "");
}
const tsCode = `
interface User {
name: string;
age: number;
}
function greet(user: User): string {
const message: string = "Hello " + user.name;
return message as string;
}
`;
console.log(stripTypeAnnotations(tsCode).trim());
// function greet(user) {
// const message = "Hello " + user.name;
// return message;
// }
// PRE-COMPUTED LOOKUP TABLES
function generateLookupTable(entries) {
const lines = ["const LOOKUP = {"];
for (const [key, value] of Object.entries(entries)) {
// Pre-compute values at build time
const computed = typeof value === "function" ? value() : value;
lines.push(` ${JSON.stringify(key)}: ${JSON.stringify(computed)},`);
}
lines.push("};");
lines.push("export default LOOKUP;");
return lines.join("\n");
}
const lookupCode = generateLookupTable({
sin30: () => Math.sin(Math.PI / 6),
cos45: () => Math.cos(Math.PI / 4),
sqrt2: () => Math.sqrt(2),
phi: () => (1 + Math.sqrt(5)) / 2
});
console.log(lookupCode);
// const LOOKUP = {
// "sin30": 0.49999999999999994,
// "cos45": 0.7071067811865476,
// "sqrt2": 1.4142135623730951,
// "phi": 1.618033988749895,
// };
// export default LOOKUP;| Technique | Phase | Safety | Use Case |
|---|---|---|---|
| Tagged templates | Runtime | High (no eval) | SQL, HTML, CSS generation |
| AST construction | Build/Runtime | High (structured) | Code generators, compilers |
| Function constructor | Runtime | Medium (scoped) | Expression evaluation, templates |
| Source transforms | Build | High (no execution) | Minification, polyfills |
| Babel plugins | Build | High (AST-level) | Language extensions, optimizations |
| eval / indirect eval | Runtime | Low (full access) | Avoid in production |
Rune AI
Key Insights
- Tagged template literals act as runtime macros, receiving raw strings and values separately for safe domain-specific processing: They prevent injection attacks in SQL, HTML, and CSS by design
- AST-based code generation builds structured syntax trees that are converted to source code, ensuring syntactic correctness: This approach is used by compilers, transpilers, and code generation tools
- The Function constructor creates scoped functions from strings, providing a safer alternative to eval with explicit parameter binding: It cannot access calling scope variables, limiting the attack surface
- Source-to-source transformations using regex or AST visitors enable dead code elimination, minification, and language extensions: Build-time transforms have zero runtime overhead
- Code generation pipelines compose multiple transformation stages for systematic source processing: Each stage can be independently tested, logged, and debugged
Frequently Asked Questions
Are tagged template literals real macros?
When should I use the Function constructor vs eval?
How do Babel plugins transform code?
What are the security risks of code generation?
Conclusion
JavaScript code generation spans compile-time AST transforms to runtime tagged templates. Tagged templates provide safe, practical macros for SQL, HTML, and CSS. AST-based generation enables type-safe code synthesis. Runtime compilation via Function constructor creates optimized validators and template renderers. For the self-modifying patterns that build on code generation, see Writing Self-Modifying Code in JS Architecture. For the AST structures that V8 uses internally, revisit JavaScript ASTs and Parse Trees Explained.
More in this topic
OffscreenCanvas API in JS for UI Performance
Master the OffscreenCanvas API to offload rendering from the main thread. Covers worker-based 2D and WebGL rendering, animation loops inside workers, bitmap transfer, double buffering, chart rendering pipelines, image processing, and performance measurement strategies.
Advanced Web Workers for High Performance JS
Master Web Workers for truly parallel JavaScript execution. Covers dedicated and shared workers, structured cloning, transferable objects, SharedArrayBuffer with Atomics, worker pools, task scheduling, Comlink RPC patterns, module workers, and performance profiling strategies.
Creating Advanced UI Frameworks in JavaScript
Build a modern UI framework from scratch in JavaScript. Covers virtual DOM implementation, diff algorithms, reactive state management, component lifecycle, template compilation, event delegation, batched rendering, hooks system, server-side rendering, and hydration.