UNPKG

babel-plugin-transform-define

Version:

Babel plugin that replaces member expressions and typeof statements with strings

135 lines (113 loc) 4.82 kB
"use strict"; const traverse = require("traverse"); const { get, has, find, memoize } = require("lodash"); /** * Return an Array of every possible non-cyclic path in the object as a dot separated string sorted * by length. * * Example: * getSortedObjectPaths({ process: { env: { NODE_ENV: "development" } } }); * // => [ "process.env.NODE_ENV", "process.env" "process" ] * * @param {Object} obj A plain JavaScript Object * @return {Array} Sorted list of non-cyclic paths into obj */ const getSortedObjectPaths = memoize((obj) => { if (!obj) { return []; } return traverse(obj) .paths() .filter((arr) => arr.length) .map((arr) => arr.join(".")) .sort((a, b) => b.length - a.length); }); /** * Replace a node with a given value. If the replacement results in a BinaryExpression, it will be * evaluated. For example, if the result of the replacement is `var x = "production" === "production"` * The evaluation will make a second replacement resulting in `var x = true` * @param {function} replaceFn The function used to replace the node * @param {babelNode} nodePath The node to evaluate * @param {*} replacement The value the node will be replaced with * @return {undefined} */ const replaceAndEvaluateNode = (replaceFn, nodePath, replacement) => { nodePath.replaceWith(replaceFn(replacement)); if (nodePath.parentPath.isBinaryExpression()) { const result = nodePath.parentPath.evaluate(); if (result.confident) { nodePath.parentPath.replaceWith(replaceFn(result.value)); } } }; /** * Finds the first replacement in sorted object paths for replacements that causes comparator * to return true. If one is found, replaces the node with it. * @param {Object} replacements The object to search for replacements * @param {babelNode} nodePath The node to evaluate * @param {function} replaceFn The function used to replace the node * @param {function} comparator The function used to evaluate whether a node matches a value in `replacements` * @return {undefined} */ // eslint-disable-next-line max-params const processNode = (replacements, nodePath, replaceFn, comparator) => { const replacementKey = find(getSortedObjectPaths(replacements), (value) => comparator(nodePath, value)); if (has(replacements, replacementKey)) { replaceAndEvaluateNode(replaceFn, nodePath, get(replacements, replacementKey)); } }; /** * Checks if the given identifier is an ES module import * @param {babelNode} identifierNodePath The node to check * @return {boolean} Indicates if the provided node is an import specifier or references one */ const isImportIdentifier = (identifierNodePath) => { const containerType = get(identifierNodePath, ["container", "type"]); return containerType === "ImportDefaultSpecifier" || containerType === "ImportSpecifier"; }; const memberExpressionComparator = (nodePath, value) => nodePath.matchesPattern(value); const identifierComparator = (nodePath, value) => nodePath.node.name === value; const unaryExpressionComparator = (nodePath, value) => nodePath.node.argument.name === value; const TYPEOF_PREFIX = "typeof "; const plugin = function ({ types: t }) { return { visitor: { // process.env.NODE_ENV; MemberExpression(nodePath, state) { processNode(state.opts, nodePath, t.valueToNode, memberExpressionComparator); }, // const x = { version: VERSION }; Identifier(nodePath, state) { const binding = nodePath.scope.getBinding(nodePath.node.name); if ( binding // Don't transform import identifiers. This is meant to mimic webpack's // DefinePlugin behavior. || isImportIdentifier(nodePath) // Do not transform Object keys / properties unless they are computed like {[key]: value} || nodePath.key === "key" && nodePath.parent.computed === false || nodePath.key === "property" && nodePath.parent.computed === false ) { return; } processNode(state.opts, nodePath, t.valueToNode, identifierComparator); }, // typeof window UnaryExpression(nodePath, state) { if (nodePath.node.operator !== "typeof") { return; } const { opts } = state; const keys = Object.keys(opts); const typeofValues = {}; keys.forEach((key) => { if (key.substring(0, TYPEOF_PREFIX.length) === TYPEOF_PREFIX) { typeofValues[key.substring(TYPEOF_PREFIX.length)] = opts[key]; } }); processNode(typeofValues, nodePath, t.valueToNode, unaryExpressionComparator); } } }; }; // Exports. module.exports = plugin; module.exports.default = plugin; module.exports.getSortedObjectPaths = getSortedObjectPaths;