Babel plugin to ensure function declarations at the block level are block scoped
npx @tessl/cli install tessl/npm-babel-plugin-transform-es2015-block-scoped-functions@6.22.0Babel plugin to ensure function declarations at the block level are block scoped. This plugin transforms ES2015 block-scoped function declarations to prevent hoisting issues and maintain proper block scope semantics by converting function declarations within block statements to let-scoped variable declarations with function expressions.
npm install --save-dev babel-plugin-transform-es2015-block-scoped-functionsThe plugin exports a single default function that creates the Babel plugin configuration:
const plugin = require("babel-plugin-transform-es2015-block-scoped-functions");For use in Babel configuration:
// Via require (CommonJS)
require("babel-core").transform("code", {
plugins: ["transform-es2015-block-scoped-functions"]
});.babelrc
{
"plugins": ["transform-es2015-block-scoped-functions"]
}babel --plugins transform-es2015-block-scoped-functions script.jsrequire("babel-core").transform("code", {
plugins: ["transform-es2015-block-scoped-functions"]
});The plugin transforms function declarations within block statements to prevent ES5-style hoisting behavior:
Input:
{
function foo() {
return "block scoped";
}
if (condition) {
function bar() {
return "conditional";
}
}
}Output:
{
let foo = function () {
return "block scoped";
};
if (condition) {
let bar = function () {
return "conditional";
};
}
}The main export that creates the Babel plugin configuration.
/**
* Creates a Babel plugin for transforming block-scoped function declarations
* @param {Object} context - Babel plugin context
* @param {Object} context.types - Babel types utility object (destructured as 't')
* @returns {PluginConfig} Babel plugin configuration with visitor methods
*/
export default function ({ types: t }) {
return {
visitor: {
BlockStatement(path) { /* transforms function declarations in block statements */ },
SwitchCase(path) { /* transforms function declarations in switch cases */ }
}
};
}The object returned by the plugin factory function containing AST visitor methods.
interface PluginConfig {
visitor: {
/** Handles function declarations within block statements */
BlockStatement(path: NodePath<BlockStatement>): void;
/** Handles function declarations within switch case statements */
SwitchCase(path: NodePath<SwitchCase>): void;
};
}Transforms function declarations within block statements, excluding function bodies and export declarations.
/**
* Processes BlockStatement nodes to transform function declarations
* @param {NodePath} path - Babel AST path for the BlockStatement node
*/
function BlockStatement(path) {
const { node, parent } = path;
// Skip if parent is a function body or export declaration
if (t.isFunction(parent, { body: node }) || t.isExportDeclaration(parent)) {
return;
}
// Transform function declarations in the body
statementList("body", path);
}Transforms function declarations within switch case consequent statements.
/**
* Processes SwitchCase nodes to transform function declarations
* @param {NodePath} path - Babel AST path for the SwitchCase node
*/
function SwitchCase(path) {
// Transform function declarations in the consequent
statementList("consequent", path);
}The internal helper function that performs the actual transformation:
/**
* Internal helper that transforms function declarations in statement arrays
* @param {string} key - Property name containing the statement array
* @param {NodePath} path - Babel AST path object
*/
function statementList(key, path) {
const paths = path.get(key);
for (const path of paths) {
const func = path.node;
if (!path.isFunctionDeclaration()) continue;
// Create let variable declaration with function expression
const declar = t.variableDeclaration("let", [
t.variableDeclarator(func.id, t.toExpression(func))
]);
// Set hoisting priority
declar._blockHoist = 2;
// Remove original function name
func.id = null;
// Replace the function declaration
path.replaceWith(declar);
}
}interface BabelContext {
/** Babel types utility object for AST manipulation */
types: BabelTypes;
}
interface BabelTypes {
/** Check if node is a function */
isFunction(node: Node, opts?: Object): boolean;
/** Check if node is an export declaration */
isExportDeclaration(node: Node): boolean;
/** Create a variable declaration node */
variableDeclaration(kind: string, declarations: VariableDeclarator[]): VariableDeclaration;
/** Create a variable declarator node */
variableDeclarator(id: Identifier, init: Expression): VariableDeclarator;
/** Convert a function declaration to function expression */
toExpression(node: Node): Expression;
}interface NodePath<T = Node> {
/** The AST node at this path */
node: T;
/** Parent node */
parent: Node;
/** Get child paths for a property */
get(key: string): NodePath[];
/** Check if node is a function declaration */
isFunctionDeclaration(): boolean;
/** Replace this node with another node */
replaceWith(node: Node): void;
}The plugin specifically processes these AST node types:
The plugin ensures proper ES2015 block scoping by:
let variable declarations_blockHoist = 2)