Last active
July 2, 2020 11:41
-
-
Save maksimr/e24236c284d40f4f1b571710b81171a4 to your computer and use it in GitHub Desktop.
Programmatically updat imports inside javascript code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Example of how to programmatically update imports inside javascript code | |
* when you move files | |
*/ | |
function main() { | |
const assert = require('assert'); | |
const filePath = '/a/b/c.js'; | |
const fromPath = '../foo/foo.js'; | |
const toPath = '../foo/bar.js'; | |
assert.equal(updateImports('require("./foo");', filePath, '/a/b/foo.js', '/a/b/bar.js'), 'require("./bar");'); | |
assert.equal(updateImports('require("./foo/foo/foo");', filePath, '/a/b/foo/foo/foo.js', '/a/b/foo/foo/index.js'), 'require("./foo/foo");'); | |
assert.equal(updateImports('require("../foo/foo.js");', filePath, fromPath, toPath), 'require("../foo/bar");'); | |
assert.equal(updateImports('require("foo");', filePath, 'foo', 'bar'), 'require("bar");'); | |
assert.equal(updateImports('require("foo/foo");', filePath, 'foo/foo', 'foo/bar'), 'require("foo/bar");'); | |
assert.equal(updateImports('const r = require; r("../foo/foo.js");', filePath, fromPath, toPath), 'const r = require; r("../foo/bar");'); | |
assert.equal(updateImports('import foo from "../foo/foo.js";', filePath, fromPath, toPath), 'import foo from "../foo/bar";'); | |
assert.equal(updateImports('import("../foo/foo.js");', filePath, fromPath, toPath), 'import("../foo/bar");'); | |
assert.equal(updateImports('require("../foo/foo");', filePath, fromPath, toPath), 'require("../foo/bar");'); | |
assert.equal(updateImports('require("../foo/foo");', filePath, fromPath, '../foo/index.js'), 'require("../foo");'); | |
assert.equal(updateImports('require("../foo");', filePath, '../foo/index.js', '../foo/foo.js'), 'require("../foo/foo");'); | |
assert.equal(updateImports('require(["../foo/foo", "../foo/foo"], () => null);', filePath, fromPath, toPath), 'require(["../foo/bar", "../foo/bar"], () => null);'); | |
assert.equal(updateImports('require(["../foo/foo", "../foo/foo"], () => require("../foo/foo"));', filePath, fromPath, toPath), 'require(["../foo/bar", "../foo/bar"], () => require("../foo/bar"));'); | |
assert.equal(updateImports('require(require("../foo/foo"));', filePath, fromPath, toPath), 'require(require("../foo/bar"));'); | |
assert.equal(updateImports('require();', filePath, fromPath, toPath), 'require();'); | |
assert.equal(updateImports('require(foo);', filePath, fromPath, toPath), 'require(foo);'); | |
assert.equal(updateImports('require([]);', filePath, fromPath, toPath), 'require([]);'); | |
assert.equal(updateImports('require(\'foo\');', filePath, 'foo', 'bar'), 'require(\'bar\');'); | |
function updateImports(code, filePath, fromPath, toPath) { | |
const recast = require('recast'); | |
const ast = recast.parse(code, { | |
parser: require('recast/parsers/babel') | |
}); | |
return recast.print(updateImportsTransformer(ast, filePath, fromPath, toPath)).code; | |
} | |
function updateImportsTransformer(ast, filePath, fromPath, toPath) { | |
const recast = require('recast'); | |
const namedTypes = recast.types.namedTypes; | |
return recast.visit(ast, { | |
visitImportDeclaration(path) { | |
visitLiteral(path.node); | |
return false; | |
}, | |
visitCallExpression(path) { | |
let node = path.node.callee; | |
let scope = path.scope; | |
while (scope = path.scope.lookup(node.name)) { | |
const path = scope.bindings[node.name][0]; | |
node = path.value; | |
if (namedTypes.Identifier.check(node) && namedTypes.VariableDeclarator.check(path.parentPath.value)) { | |
node = path.parentPath.value; | |
if (namedTypes.Identifier.check(node.init)) { | |
node = node.init; | |
continue; | |
} | |
} | |
break; | |
} | |
if (node.name === 'require' || namedTypes.Import.check(node)) { | |
if (path.node.arguments.length > 0) { | |
path.node.arguments[0] = visitLiteral(path.node.arguments[0]); | |
} | |
} | |
this.traverse(path); | |
} | |
}); | |
function visitLiteral(node) { | |
return recast.visit(node, { | |
visitStringLiteral(path) { | |
if (resPath(path.node.value) === resPath(fromPath)) { | |
const modulePath = relPath(resPath(toPath)); | |
// Preserve quote mark from source | |
// https://github.com/benjamn/recast/issues/171 | |
const quote = path.node.extra.raw[0]; | |
path.replace(recast.types.builders.stringLiteral(new String(quote + modulePath + quote))); | |
} | |
return false; | |
function resPath(it) { | |
return isModulePath(it) ? it : | |
normalizePath(require('path').resolve(dir(filePath), it)); | |
} | |
function relPath(it) { | |
if (isModulePath(it)) { | |
return it; | |
} | |
const p = require('path').relative(dir(filePath), it); | |
return isModulePath(p) ? './' + p : p; | |
} | |
function isModulePath(it) { | |
return !/^(\.)*\//.test(it); | |
} | |
function dir(it) { | |
return require('path').dirname(it); | |
} | |
function normalizePath(path) { | |
return path | |
.replace(/\.js$/, '') | |
.replace(/\/index$/, ''); | |
} | |
} | |
}); | |
} | |
} | |
} | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment