A Babel plugin that compiles ES2015 computed properties to ES5-compatible code. It transforms dynamic property names (using expressions in square brackets) into runtime assignments using either Object.defineProperty for spec compliance or simple property assignments in loose mode.
npm install --save-dev babel-plugin-transform-es2015-computed-properties// Via .babelrc configuration (recommended)
{
"plugins": ["transform-es2015-computed-properties"]
}
// Via Node API
const babel = require("babel-core");
const plugin = require("babel-plugin-transform-es2015-computed-properties");Without options:
{
"plugins": ["transform-es2015-computed-properties"]
}With options:
{
"plugins": [
["transform-es2015-computed-properties", {
"loose": true
}]
]
}babel --plugins transform-es2015-computed-properties script.jsrequire("babel-core").transform("code", {
plugins: ["transform-es2015-computed-properties"]
});Input:
var obj = {
["x" + foo]: "heh",
["y" + bar]: "noo",
foo: "foo",
bar: "bar"
};Output (spec mode):
var _obj;
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
var obj = (
_obj = {},
_defineProperty(_obj, "x" + foo, "heh"),
_defineProperty(_obj, "y" + bar, "noo"),
_defineProperty(_obj, "foo", "foo"),
_defineProperty(_obj, "bar", "bar"),
_obj
);Output (loose mode):
var _obj;
var obj = (
_obj = {},
_obj["x" + foo] = "heh",
_obj["y" + bar] = "noo",
_obj.foo = "foo",
_obj.bar = "bar",
_obj
);The main plugin factory function that creates a Babel plugin configuration.
/**
* Main plugin factory function that creates a Babel plugin
* @param {Object} api - Babel API objects
* @param {Object} api.types - Babel types utilities
* @param {Function} api.template - Babel template builder
* @returns {Object} Babel plugin configuration with visitor pattern
*/
function plugin({ types, template }) {
return {
visitor: {
ObjectExpression: {
exit(path, state) { /* transformation logic */ }
}
}
};
}
// ES6 export (source)
export default plugin;
// CommonJS export (built)
module.exports = plugin;Configuration options passed via Babel configuration.
/**
* Plugin options interface
*/
interface PluginOptions {
/**
* Use simple property assignments instead of Object.defineProperty
* @default false
*/
loose?: boolean;
}The plugin uses Babel's visitor pattern to transform object expressions.
interface PluginVisitor {
/** Visitor for ObjectExpression AST nodes */
ObjectExpression: {
/** Transform object expressions containing computed properties */
exit(path: NodePath<ObjectExpression>, state: PluginState): void;
};
}
interface PluginState {
/** Plugin configuration options */
opts: PluginOptions;
/** Add Babel runtime helper function */
addHelper(name: string): Identifier;
}The plugin supports two transformation modes:
Uses Object.defineProperty for standards-compliant property definitions.
/**
* Spec-compliant transformation mode
* Uses Object.defineProperty for property definitions
* Handles special cases like __proto__ properties
* @param {TransformationInfo} info - Transformation context
* @returns {CallExpression|undefined} Single expression for single property or undefined for multiple
*/
function spec(info: TransformationInfo): CallExpression | undefined;Uses simple property assignments for better performance.
/**
* Loose transformation mode
* Uses simple property assignments instead of Object.defineProperty
* Better performance but less standards-compliant
* @param {TransformationInfo} info - Transformation context
*/
function loose(info: TransformationInfo): void;The plugin handles different types of object properties and usage contexts:
Regular object properties with values.
// Input
var obj = { [key]: value };
// Output (spec mode)
Object.defineProperty(obj, key, value);
// Output (loose mode)
obj[key] = value;Getter and setter properties with computed names.
// Input
var obj = {
get [foobar]() {
return "foobar";
},
set [foobar](x) {
console.log(x);
}
};
// Output uses mutator map and defineEnumerableProperties helper
var _obj, _mutatorMap;
_obj = {};
_mutatorMap = {};
_mutatorMap[foobar] = _mutatorMap[foobar] || {};
_mutatorMap[foobar].get = function() { return "foobar"; };
_mutatorMap[foobar].set = function(x) { console.log(x); };
babelHelpers.defineEnumerableProperties(_obj, _mutatorMap);Object methods with computed names.
// Input
var obj = {
[foobar]() {
return "foobar";
}
};
// Methods are converted to function expressions and processed as data properties
var _obj;
_obj = {};
_defineProperty(_obj, foobar, function() { return "foobar"; });Objects with both computed and non-computed properties.
// Input
var obj = {
foo: "foo",
[computed]: "computed",
bar: "bar"
};
// Non-computed properties before first computed property stay in object literal
// All properties after first computed property are handled separately/**
* Transformation context information
*/
interface TransformationInfo {
/** Generated object identifier */
objId: Identifier;
/** Array of transformation statements */
body: Statement[];
/** Array of computed property nodes */
computedProps: ObjectProperty[];
/** Initial object expression for non-computed properties */
initPropExpression: ObjectExpression;
/** Function to get mutator map identifier */
getMutatorId(): Identifier;
/** Current transformation scope */
scope: Scope;
/** Plugin transformation state */
state: PluginState;
}
/**
* Babel AST node types used by the plugin
*/
type ObjectProperty = {
kind: "get" | "set" | "init";
computed: boolean;
key: Expression;
value: Expression;
};
type ObjectMethod = {
kind: "method" | "get" | "set";
computed: boolean;
key: Expression;
params: Pattern[];
body: BlockStatement;
generator: boolean;
async: boolean;
};
type ObjectExpression = {
properties: (ObjectProperty | ObjectMethod)[];
};
type NodePath<T> = {
node: T;
parent: Node;
scope: Scope;
replaceWith(node: Node): void;
replaceWithMultiple(nodes: Node[]): void;
};
type Scope = {
generateUidIdentifierBasedOnNode(node: Node): Identifier;
generateUidIdentifier(name: string): Identifier;
maybeGenerateMemoised(node: Node): Identifier | null;
};The plugin handles various edge cases during transformation:
__proto__ properties are always handled with assignmentThe plugin uses several internal utility functions for transformation:
/**
* Build template for mutator map assignments
*/
const buildMutatorMapAssign = template(`
MUTATOR_MAP_REF[KEY] = MUTATOR_MAP_REF[KEY] || {};
MUTATOR_MAP_REF[KEY].KIND = VALUE;
`);
/**
* Extract value from property node
* @param {ObjectProperty|ObjectMethod} prop - Property node
* @returns {Expression} Property value expression
*/
function getValue(prop: ObjectProperty | ObjectMethod): Expression;
/**
* Push property assignment to transformation body
* @param {Identifier} objId - Object identifier
* @param {ObjectProperty} prop - Property node
* @param {Statement[]} body - Transformation statements array
*/
function pushAssign(objId: Identifier, prop: ObjectProperty, body: Statement[]): void;
/**
* Push mutator property definition to transformation body
* @param {TransformationInfo} info - Transformation context
* @param {ObjectProperty} prop - Property node with getter/setter
*/
function pushMutatorDefine(info: TransformationInfo, prop: ObjectProperty): void;The plugin requires these Babel packages:
babel-template@^6.24.1: Template building utilitiesbabel-runtime@^6.22.0: Runtime helper functionsRuntime helpers used:
defineProperty: For Object.defineProperty calls in spec modedefineEnumerableProperties: For getter/setter properties