Skip to content

Instantly share code, notes, and snippets.

@tibdex
Created December 9, 2016 19:22
Show Gist options
  • Save tibdex/f491a2d264ba14af5643de300957b4f9 to your computer and use it in GitHub Desktop.
Save tibdex/f491a2d264ba14af5643de300957b4f9 to your computer and use it in GitHub Desktop.
jscodeshift codemod to change imports of internal modules using absolute path to relative path
/**
* This codemod expects 2 CLI arguments:
* - packageDir: the path of the directory containing the package.json. It's used to detect whether a path points
* to a dependency or an internal module.
* - rootDirs: root directory paths separated by a comma if you have more than one root.
* Let's say that you have two files:
* - src/component.js
* - src/index.js containing the import "import Component from 'component.js';"
* To have the transformation successfully working you need to call jscodeshift with the "--rootDirs ./src" argument.
*/
const {existsSync} = require('fs');
const {_builtinLibs} = require('repl'); // List of Node.js built in modules.
const path = require('path');
const makeIsDependency = packageDir => {
const {dependencies, devDependencies, peerDependencies} = require(path.resolve(packageDir, 'package.json'));
const allDependencies = [..._builtinLibs, ...Object.keys(dependencies), ...Object.keys(devDependencies), ...Object.keys(peerDependencies)];
return importedModulePath => allDependencies.some(name => (importedModulePath === name || importedModulePath.startsWith(`${name}/`)));
};
const isRelative = importedModulePath => importedModulePath.startsWith('./') || importedModulePath.startsWith('../');
const makeGetDiskPathFromImportPath = rootDirs => {
const importPathToDiskPath = {};
return importedModulePath => {
let diskPath = importPathToDiskPath[importedModulePath];
if (diskPath) {
return diskPath;
}
const importedModuleRoot = rootDirs.find(root => existsSync(path.join(root, importedModulePath)));
if (importedModuleRoot) {
diskPath = path.join(importedModuleRoot, importedModulePath);
importPathToDiskPath[importedModulePath] = diskPath;
return diskPath;
}
throw new Error(`Cannot find root for imported module ${importedModulePath}`);
};
};
const makeChangePathToRelativeIfNeeded = (currentModuleDirectoryPath, isDependency, rootDirs) => {
const getDiskPathFromImportPath = makeGetDiskPathFromImportPath(rootDirs);
return importedModulePath => {
if (isRelative(importedModulePath) || isDependency(importedModulePath)) {
return importedModulePath;
}
const diskPath = getDiskPathFromImportPath(importedModulePath);
const relativePath = path.relative(currentModuleDirectoryPath, diskPath).replace(/\\/g, '/');
return relativePath.startsWith('../') ? relativePath : `./${relativePath}`;
};
};
const sortImportsAlphabetically = imports => {
imports.sort((a, b) => a.path.localeCompare(b.path));
};
const sortImportDeclarations = (importDeclarations, changePathToRelativeIfNeeded) => {
let lastEndOfImportLine = undefined;
const importGroups = [];
importDeclarations.forEach(path => {
const node = path.value;
const isFirstImport = lastEndOfImportLine === undefined;
const isNewImportBlock = !isFirstImport && lastEndOfImportLine < (node.loc.start.line - 1);
if (isFirstImport || isNewImportBlock) {
importGroups.push([]);
}
lastEndOfImportLine = node.loc.end.line;
const currentImportPath = node.source.value;
const newImportPath = changePathToRelativeIfNeeded(currentImportPath);
importGroups[importGroups.length - 1].push({
path: newImportPath,
specifiers: node.specifiers
});
});
importGroups.forEach(sortImportsAlphabetically);
const flattenedImports = [].concat.apply([], importGroups);
return flattenedImports;
};
const replaceBySortedImportDeclarations = (j, importDeclarations, sortedImportDeclarations) => {
return importDeclarations
.forEach((path, index) => {
const newImport = sortedImportDeclarations[index];
j(path).replaceWith(
j.importDeclaration(newImport.specifiers, j.literal(newImport.path))
);
});
};
module.exports = (fileInfo, api, options) => {
const currentModuleDirectoryPath = path.dirname(path.resolve(fileInfo.path));
const isDependency = makeIsDependency(options.packageDir);
const rootDirs = options.rootDirs.split(',').map(rootPath => path.resolve(rootPath));
const changePathToRelativeIfNeeded = makeChangePathToRelativeIfNeeded(currentModuleDirectoryPath, isDependency, rootDirs);
const j = api.jscodeshift;
const root = j(fileInfo.source);
const {comments} = root.find(j.Program).get('body', 0).node;
const importDeclarations = root.find(j.ImportDeclaration);
const sortedImportDeclarations = sortImportDeclarations(importDeclarations, changePathToRelativeIfNeeded);
replaceBySortedImportDeclarations(j, importDeclarations, sortedImportDeclarations);
root.get().node.comments = comments;
return root.toSource({
objectCurlySpacing: false,
quote: 'single'
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment