Skip to content

Instantly share code, notes, and snippets.

@pastak
Last active November 26, 2020 04:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pastak/228c5fa05315e4e50c65605826d0c138 to your computer and use it in GitHub Desktop.
Save pastak/228c5fa05315e4e50c65605826d0c138 to your computer and use it in GitHub Desktop.
Change import style `import foo from 'lodash/foo'` from `import _ from 'lodash'` or `import {add} from 'lodash'` for Tree shaking
const MUST_ADD_PREFIX = false;
const PREFIX = "_";
const sameProperties = (props, a, b) => {
return props.every((p) => a[p] === b[p]);
};
const sameLocation = (loc1, loc2) => sameProperties(["line", "column", "token"], loc1.start, loc2.start) && sameProperties(["line", "column", "token"], loc1.end, loc2.end);
const findBinding = (path, name) => {
const namedBind = path.scope.getBindings()[name];
if (namedBind) return namedBind[0];
if (!path.parent) return;
return findBinding(path.parent, name);
};
/* https://github.com/cpojer/js-codemod/blob/master/transforms/underscore-to-lodash-native.js */
const NATIVE_METHODS = {
forEach: "forEach",
each: "forEach",
map: "map",
collect: "map",
filter: "filter",
select: "filter",
every: "every",
some: "some",
find: "find",
detect: "find",
contains: "includes",
reduce: "reduce",
inject: "reduce",
indexOf: "indexOf",
lastIndexOf: "lastIndexOf",
first: (j, identifier) => j.memberExpression(identifier, j.literal(0)),
last: (j, identifier) => j.memberExpression(identifier, j.binaryExpression("-", j.memberExpression(identifier, j.identifier("length")), j.literal(1)))
};
function transformExpression(j, options) {
return (ast) => {
const methodName = ast.node.callee.property.name;
const nativeMapping = NATIVE_METHODS[methodName];
if (nativeMapping) {
if (typeof nativeMapping === "function") {
transformNativeSpecial(j, ast);
} else {
transformNativeMethod(j, ast);
}
}
};
}
function transformNativeSpecial(j, ast) {
const methodName = ast.node.callee.property.name;
const nativeMapping = NATIVE_METHODS[methodName];
j(ast).replaceWith(nativeMapping(j, ast.node.arguments[0]));
}
function transformNativeMethod(j, ast) {
const methodName = ast.node.callee.property.name;
const nativeMapping = NATIVE_METHODS[methodName];
const arg = ast.node.arguments.slice(1).map((a) => (a.type === "StringLiteral" ? j.arrowFunctionExpression(
[j.identifier("t")],
j.identifier(`t.${a.value}`)
) : a));
j(ast).replaceWith(j.callExpression(j.memberExpression(ast.node.arguments[0], j.identifier(nativeMapping)), arg));
}
const isUnderscoreExpression = (name) => (node) => {
return node.type === "CallExpression" && node.callee.type === "MemberExpression" && node.callee.object && node.callee.object.name === name;
};
/* https://github.com/cpojer/js-codemod/blob/master/transforms/underscore-to-lodash-native.js */
export default function transformer(file, api) {
const j = api.jscodeshift;
j.__methods = {};
const root = j(file.source);
let lodashCallExpPaths = [];
let defaultImportPath;
let removeImportPaths = [];
const importMap = new Map();
root.find(j.ImportDeclaration).forEach((_importDeclarationPath) => {
if (_importDeclarationPath.value.source.value !== "lodash") return;
const isDefaultImport = _importDeclarationPath.value.specifiers.find((s) => s.type === "ImportDefaultSpecifier");
if (isDefaultImport) {
defaultImportPath = _importDeclarationPath;
}
if (!(isDefaultImport && root.find(j.CallExpression, { callee: { name: _importDeclarationPath.node.specifiers[0].local.name } }).length > 0)) {
removeImportPaths.push(_importDeclarationPath);
}
const { specifiers } = _importDeclarationPath.node;
specifiers.forEach((specifier) => {
const assignedLocalName = specifier.local.name;
if (specifier.imported) {
importMap.set(assignedLocalName, specifier.imported.name);
} else {
if (!isDefaultImport) importMap.set(assignedLocalName, assignedLocalName);
}
if (isDefaultImport) root.find(j.CallExpression, isUnderscoreExpression(assignedLocalName)).forEach(transformExpression(j));
const callExps = j(file.source).find(j.CallExpression);
const callExpsCalleMaybeLodashIndexes = callExps
.nodes()
.map((node, index) =>
isDefaultImport
? node.callee.object && node.callee.object.type === "Identifier" && node.callee.object.name === assignedLocalName && index
: node.callee.type === "Identifier" && node.callee.name === assignedLocalName && index
)
.filter((_) => _ !== false);
const lodashCallExpsIndexes = callExps
.paths()
.map((path, index) => {
if (!callExpsCalleMaybeLodashIndexes.includes(index)) return false;
const binding = findBinding(path, assignedLocalName);
return binding && sameLocation(binding.node.loc, specifier.loc) && index;
})
.filter((_) => typeof _ === "number");
callExps
.filter((_, index) => lodashCallExpsIndexes.includes(index))
.paths()
.forEach((p) => lodashCallExpPaths.push(p));
});
});
const nameMap = new Map();
root
.find(j.CallExpression)
.filter((callExpr) => {
return lodashCallExpPaths.some((lodashCallExpPath) => callExpr.node.loc && sameLocation(lodashCallExpPath.node.loc, callExpr.node.loc));
})
.replaceWith((path) => {
const node = path.value;
const propertyName = node.callee.name || node.callee.property.name;
if (!nameMap.has(propertyName)) {
const isConfused = !!root.findVariableDeclarators(propertyName).length;
const newName = MUST_ADD_PREFIX || isConfused ? `${PREFIX}${propertyName}` : propertyName;
nameMap.set(propertyName, newName);
}
const newName = nameMap.get(propertyName);
const newNode = j.callExpression(j.identifier(newName), node.arguments);
return newNode;
});
importMap.forEach((local, imported) => {
nameMap.set(local, imported);
});
if (removeImportPaths.length > 0) {
nameMap.forEach((newName, propertyName) => {
removeImportPaths[0].insertAfter(`import ${newName} from "lodash/${propertyName}";`);
});
removeImportPaths.forEach((p) => j(p).remove());
}
return root.toSource();
}
import _ from "lodash"
import {add, diff, sum as execSum, aaa as bbb} from "lodash"
_.bar();
_.hage();
_.foo();
bbb()
_.each([1,2], (a => _.foo(a)))
(() => {
const _ = document
_.getElementById()
});
(() => {
_.hoge()
const add = () => {}
add()
});
add();
const assignedDiff = diff
window.sum = execSum;
document.getElementById()
_.map(arr, 'foo')
_.filter(arr, 'bar')
_.map(arr, p => p.foo)
import bbb from "lodash/aaa";
import execSum from "lodash/sum";
import diff from "lodash/diff";
import add from "lodash/add";
import hoge from "lodash/hoge";
import foo from "lodash/foo";
import hage from "lodash/hage";
import bar from "lodash/bar";
bar();
hage();
foo();
bbb()
[1,2].forEach(a => foo(a))
(() => {
const _ = document
_.getElementById()
});
(() => {
hoge()
const add = () => {}
add()
});
_add();
const assignedDiff = diff
window.sum = execSum;
document.getElementById()
arr.map(t => t.foo)
arr.filter(t => t.bar)
arr.map(p => p.foo)
@pastak
Copy link
Author

pastak commented Jul 30, 2020

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment