A Babel plugin that transforms ES2015 block-scoped declarations (const and let) into ES5-compatible var declarations while preserving correct scoping semantics. It handles complex scenarios including temporal dead zones (TDZ), loop closures, variable capturing, and binding conflicts.
npm install --save-dev @babel/plugin-transform-block-scoping// Import as Babel plugin (most common)
const blockScopingPlugin = require("@babel/plugin-transform-block-scoping");ES Module:
import blockScopingPlugin from "@babel/plugin-transform-block-scoping";// babel.config.js
module.exports = {
plugins: [
"@babel/plugin-transform-block-scoping"
]
};With options:
// babel.config.js
module.exports = {
plugins: [
["@babel/plugin-transform-block-scoping", {
tdz: false,
throwIfClosureRequired: false
}]
]
};import { transform } from "@babel/core";
import blockScopingPlugin from "@babel/plugin-transform-block-scoping";
const result = transform(code, {
plugins: [
[blockScopingPlugin, { tdz: true }]
]
});Transforms ES2015 let and const declarations to ES5 var while preserving block scoping behavior.
/**
* Main plugin export - Babel plugin factory function
* Requires @babel/core ^7.0.0-0 as peer dependency
*/
declare function blockScopingPlugin(
api: any,
options: Options
): BabelPlugin;
export default blockScopingPlugin;
interface Options {
/** Enable temporal dead zone enforcement (default: false) */
tdz?: boolean;
/** Throw error when closure wrapping is required (default: false) */
throwIfClosureRequired?: boolean;
}
interface BabelPlugin {
name: string;
visitor: Visitor;
}Runtime Helpers: When TDZ is enabled or const violations occur, the plugin uses Babel runtime helpers:
temporalUndefined: Sentinel value for uninitialized TDZ variablestemporalRef: Runtime check for TDZ accesstdz: Throws ReferenceError for definite TDZ violationsreadOnlyError: Throws TypeError for const reassignmentsInput:
{
let x = 1;
const y = 2;
}Output:
{
var x = 1;
var y = 2;
}When tdz: true is enabled, the plugin enforces temporal dead zone behavior by injecting runtime checks using Babel's helper functions (temporalRef, temporalUndefined, tdz).
Input with TDZ enabled:
console.log(x); // Should throw ReferenceError
let x = 1;Output with TDZ:
var _temporalUndefined = {};
var x = _temporalUndefined;
console.log((x !== _temporalUndefined ? x : (() => {
throw new ReferenceError('x is not defined - temporal dead zone');
})())); // Throws at runtime
x = 1;
### Loop Variable Handling
Special processing for block-scoped variables in loops, with automatic closure wrapping when variables are captured.
**Input:**
```javascript
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}Output (with closure wrapping):
var _loop = function _loop() {
setTimeout(() => console.log(i), 100);
};
for (var i = 0; i < 3; i++) {
_loop();
}Runtime errors for const reassignments to maintain ES2015 semantics. The plugin uses Babel's readOnlyError helper to generate appropriate error messages.
Input:
const x = 1;
x = 2; // Should throw TypeErrorOutput:
var x = 1;
x = ((() => {
throw new TypeError('"x" is read-only');
})(), 2);Error Types:
"varName" is read-only)tdz: true (cannot access 'varName' before initialization)Implements ECMAScript Annex B.3.3 function hoisting behavior for compatibility.
Input:
{
function foo() {}
}
console.log(foo); // Should be accessibleOutput:
{
var foo = function foo() {};
}
console.log(foo);tdz (boolean, default: false)Enable temporal dead zone enforcement with runtime checks.
// Enable TDZ
{
"plugins": [["@babel/plugin-transform-block-scoping", { "tdz": true }]]
}When enabled:
ReferenceError for TDZ violationsthrowIfClosureRequired (boolean, default: false)Throw compilation error when closure wrapping would be required for loop variables.
// Strict mode - no closure wrapping allowed
{
"plugins": [["@babel/plugin-transform-block-scoping", { "throwIfClosureRequired": true }]]
}When enabled:
let/const to var with proper scopingconst reassignmentstdz: true)throwIfClosureRequired: true)This plugin enables let and const syntax to work in environments that don't support ES2015 block scoping:
export interface Options {
/** Enable temporal dead zone enforcement */
tdz?: boolean;
/** Throw error when closure wrapping is required */
throwIfClosureRequired?: boolean;
}
interface BabelPlugin {
name: string;
visitor: Visitor;
}
interface Visitor {
Loop(path: NodePath<t.Loop>, state: PluginPass): void;
VariableDeclaration(path: NodePath<t.VariableDeclaration>, state: PluginPass): void;
ClassDeclaration(path: NodePath<t.ClassDeclaration>): void;
}