A Babel plugin for constant folding optimization that evaluates compile-time expressions and replaces them with their computed values
npx @tessl/cli install tessl/npm-babel-plugin-minify-constant-folding@0.5.0A Babel plugin that performs constant folding optimization during JavaScript compilation. It evaluates expressions at compile-time that can be computed statically and replaces them with their literal values, reducing runtime computation and improving performance.
npm install babel-plugin-minify-constant-folding --save-devAs a Babel plugin, this package is typically imported and used through Babel configuration:
const constantFoldingPlugin = require("babel-plugin-minify-constant-folding");Dependencies:
babel-helper-evaluate-path: Used internally for safe expression evaluation.babelrc
{
"plugins": ["minify-constant-folding"]
}babel --plugins minify-constant-folding script.jsconst babel = require("@babel/core");
const result = babel.transform("code", {
plugins: ["minify-constant-folding"]
});// With options
{
"plugins": [
["minify-constant-folding", { "tdz": true }]
]
}The plugin operates through Babel's visitor pattern, transforming AST nodes during compilation:
babel-helper-evaluate-path to determine if expressions can be safely computedThe plugin performs various compile-time optimizations as demonstrated in these examples:
Basic arithmetic and string concatenation:
// Input
"a" + "b"; // → "ab"
2 * 3; // → 6
4 | 3; // → 7
"b" + a + "c" + "d" + g + z + "f" + "h" + "z"; // → "b" + a + "cd" + g + z + "fhz"Array literal optimizations:
// Input
[1, 2, 3].length; // → 3
[1, 2, 3][1]; // → 2
[1, 2, 3].join(); // → "1,2,3"
["a", "b", "c"].join("@"); // → "a@b@c"
[1, 2, 3].pop(); // → 3
[1, 2, 3].shift(); // → 2 (new length after removing first element)
[1, 2, 3].slice(0, 2); // → [1, 2]String literal optimizations:
// Input
"abc"[0]; // → "a"
"abc".charAt(1); // → "b"
"abc".charCodeAt(1); // → 98
"abc".length; // → 3
"a,b,c".split(","); // → ["a", "b", "c"]The main export that creates a Babel plugin configuration. The plugin uses a replacement system to handle array and string literal optimizations.
/**
* Creates a Babel plugin for constant folding optimization
* @param {object} babel - Babel core object containing types and utilities
* @returns {object} Plugin configuration with name and visitor methods
*/
function constantFoldingPlugin(babel);
/** Symbol used to mark processed nodes and prevent infinite recursion */
const SEEN_SYMBOL: symbol;
/** Symbol used as fallback handler for dynamic property access */
const FALLBACK_HANDLER: symbol;Returns: Plugin configuration object with the following structure:
interface PluginConfig {
name: "minify-constant-folding";
visitor: {
BinaryExpression: (path: NodePath) => void;
Expression: (path: NodePath, state: { opts?: PluginOptions }) => void;
CallExpression: (path: NodePath) => void;
MemberExpression: (path: NodePath) => void;
};
}interface PluginOptions {
/** Account for Temporal Dead Zone when evaluating expressions (default: false) */
tdz?: boolean;
}The plugin optimizes various types of expressions:
"a" + "b" → "ab"2 * 3 → 64 | 3 → 7"b" + a + "c" + "d" → "b" + a + "cd"Array methods are replaced with computed values when the array is static:
// Array method optimizations (when array has no spread elements)
interface ArrayOptimizations {
/** ["a", "b", "c"].join() → "a,b,c" */
join(separator?: string): string;
/** [1, 2, 3].length → 3 */
length: number;
/** [1, 2, 3][1] → 2 */
[index: number]: any;
/** [1, 2, 3].slice(0, 2) → [1, 2] */
slice(start?: number, end?: number): any[];
/** [1, 2, 3].shift() → returns new length after removing first element (2) */
shift(): number | undefined;
/** [a, b, c].pop() → c */
pop(): any;
/** [a, b, c].reverse() → [c, b, a] */
reverse(): any[];
/** [a, b, c].push(d, e) → returns new length */
push(...items: any[]): number;
/** [1, 2, 3].splice(1) → [2, 3], [1, 2, 3, 4].splice(1, 2) → [2, 3] */
splice(start: number, deleteCount?: number, ...items: any[]): any[];
}String methods are replaced with computed values when the string is static:
// String method optimizations
interface StringOptimizations {
/** "abc".length → 3 */
length: number;
/** "abc"[0] → "a", "abc"[4] → undefined */
[index: number]: string | undefined;
/** "abc".charAt() → "a", "abc".charAt(1) → "b" */
charAt(index?: number): string;
/** "abc".charCodeAt() → 97, "abc".charCodeAt(1) → 98 */
charCodeAt(index?: number): number;
/** "abc".codePointAt() → 97, "abc".codePointAt(1) → 98 */
codePointAt(index?: number): number;
/** "a,b,c".split(",") → ["a", "b", "c"], "a,b,c".split() → ["a,b,c"] */
split(separator?: string | undefined): string[];
}The plugin includes several safety mechanisms to prevent incorrect optimizations:
-0 vs 0 distinction!0/!1 patterns and void 0 for undefinedThe plugin uses a sophisticated replacement system to handle array and string method optimizations:
interface ReplacementSystem {
/** Handlers for ArrayExpression nodes */
ArrayExpression: {
/** Check if array can be safely replaced (no spread elements) */
canReplace(): boolean;
/** Member access handlers (length, [index]) */
members: Record<string | symbol, Function>;
/** Method call handlers (join, push, pop, etc.) */
calls: Record<string, Function>;
};
/** Handlers for StringLiteral nodes */
StringLiteral: {
/** Member access handlers (length, [index]) */
members: Record<string | symbol, Function>;
/** Method call handlers (split, charAt, etc.) */
calls: Record<string, Function>;
};
}
/** Fallback handler for dynamic property access */
interface FallbackHandler {
/** Called when specific property handler not found */
[FALLBACK_HANDLER]: (propertyName: string | number) => any;
}/** Babel NodePath representing an AST node */
interface NodePath {
node: any;
parent: any;
replaceWith(node: any): void;
get(key: string): NodePath;
isLiteral(): boolean;
isPure(): boolean;
isBinaryExpression(opts?: object): boolean;
isStringLiteral(): boolean;
// ... other Babel NodePath methods
}
/** Babel types utility object */
interface BabelTypes {
stringLiteral(value: string): any;
numericLiteral(value: number): any;
arrayExpression(elements: any[]): any;
unaryExpression(operator: string, argument: any, prefix?: boolean): any;
valueToNode(value: any): any;
isMemberExpression(node: any): boolean;
isExportSpecifier(node: any): boolean;
isUnaryExpression(node: any, opts?: object): boolean;
isNumericLiteral(node: any, opts?: object): boolean;
isIdentifier(node: any, opts?: object): boolean;
// ... other Babel type methods
}