Import manipulation
Learn how to use codemods to make effective and efficient changes to import statements. This guide covers updating the location of imported code, and modifying import structures.
Modifying imports is one of the first and most common operations you are likely to do when writing codemods.
In this guide, we will explore how codemods can be used to make effective and efficient changes to javascript import statements. From changing import names to updating the location of imported code, and improving overall import structure. By following the steps outlined in this guide, you'll be able to maintain a well-organized, consistent codebase and unlock the many benefits that come with using codemods.
Import declarations
An ImportDeclaration
refers to an entire import statement for example:
import React, { useEffect } from 'react';
The anatomy of an ImportDeclaration
includes:
- An array of
specifiers
ImportDefaultSpecifier
:React
ImportSpecifier
:useEffect
- A
source
which can either be a module name or path:react
Note: @hypermod/utils
provides utilities for import manipulation, please see the docs
Finding an import declaration
Import declarations can be found with the jscodeshift.ImportDeclaration
type.
In this example we're seaching this file for the React
import.
export default function transformer(file, { jscodeshift: j }, options) { const source = j(file.source); const reactImportDeclaration = source // Find all nodes that match a type of `ImportDeclaration` .find(j.ImportDeclaration) // Filter imports by source equal to the target ie "react" .filter((path) => path.node.source.value === 'react'); // Do something here console.log(reactImportDeclaration); return source.toSource(); }
import React from 'react'; const Button = (props) => <button {...props} />;
loading
Read-onlyInserting an import declaration
export default function transformer(file, { jscodeshift: j }, options) { const source = j(file.source); // Build a new import const newImport = j.importDeclaration( [j.importDefaultSpecifier(j.identifier('Foo'))], j.stringLiteral('bar') ); // Insert it at the top of the document source.get().node.program.body.unshift(newImport); return source.toSource(); }
import React from 'react'; const Button = (props) => <button {...props} />;
loading
Read-onlyInserting an import declaration before/after a node
Sometimes you might want to insert an import before another import.
For that you can use insertBefore
, insertAfter
methods.
export default function transformer(file, { jscodeshift: j }, options) { const source = j(file.source); // Build a new import const newImport = j.importDeclaration( [j.importDefaultSpecifier(j.identifier('Foo'))], j.stringLiteral('bar') ); const reactImportDeclaration = source .find(j.ImportDeclaration) .filter((path) => path.node.source.value === 'react') // Insert the new import after all react imports .insertAfter(newImport); return source.toSource(); }
import React from 'react'; const Button = (props) => <button {...props} />;
loading
Read-onlyRemoving an import declaration
export default function transformer(file, { jscodeshift: j }, options) { const source = j(file.source); source // Find all nodes that match a type of `ImportDeclaration` .find(j.ImportDeclaration) // Filter imports by source equal to the target ie "react" .filter((path) => path.node.source.value === 'react') // Removes all found import declarations .remove(); return source.toSource(); }
import React from 'react'; const Button = (props) => <button {...props} />;
loading
Read-onlyReplace/Rename an import declaration
export default function transformer(file, { jscodeshift: j }, options) { const source = j(file.source); const reactImports = source // Find all nodes that match a type of `ImportDeclaration` .find(j.ImportDeclaration) // Filter imports by source equal to the target ie "react" .filter((path) => path.node.source.value === 'react'); // Iterate over react imports reactImports.forEach((reactImport) => // Replace the existing node with a new one j(reactImport).replaceWith( // Build a new import declaration node based on the existing one j.importDeclaration( // copy over the existing import specificers reactImport.node.specifiers, // Replace the source with our new source j.stringLiteral('hot-new-library') ) ) ); return source.toSource(); }
import React from 'react'; const Button = (props) => <button {...props} />;
loading
Read-onlyImport specifiers
Import specifiers are the actual variables and functions being imported
import React, { useEffect } from 'react';
Generally, within an import declaration import specifiers are defined as an array of either ImportSpecifier
or ImportDefaultSpecifier
.
So in the above case import specifiers are React
& useEffect
.
Finding an import specifiers
Finding an import is the same as finding any other node.
export default function transformer(file, { jscodeshift: j }, options) { const source = j(file.source); // Finding all react import declarations const reactImports = source .find(j.ImportDeclaration) .filter((path) => path.node.source.value === 'react'); // Here we narrow our search to only relevant import nodes const useEffectSpecifier = reactImports .find(j.ImportSpecifier) // Filter by name "useEffect" .filter((path) => path.node.imported.name === 'useEffect'); // Do something here console.log(useEffectSpecifier); return source.toSource(); }
import React, { useEffect } from 'react'; const Button = (props) => <button {...props} />;
loading
Read-onlyInserting an import specifier
export default function transformer(file, { jscodeshift: j }, options) { const source = j(file.source); // Finding all react import declarations const reactImports = source .find(j.ImportDeclaration) .filter((path) => path.node.source.value === 'react'); // Build our new import specifier const importSpecifier = j.importSpecifier(j.identifier('useContext')); // Iterate over react imports reactImports.forEach((reactImport) => // Replace the existing node with a new one j(reactImport).replaceWith( // Build a new import declaration node based on the existing one j.importDeclaration( [...reactImport.node.specifiers, importSpecifier], // Insert our new import specifier reactImport.node.source ) ) ); return source.toSource(); }
import React, { useEffect } from 'react'; const Button = (props) => <button {...props} />;
loading
Read-onlyInserting an aliased (as) import specifier
Sometimes you might want to import something under an alias.
For example: import { useEffect as useFoo } from 'react'
.
In this case, simply passing an identifier into the 'local' argument of j.importSpecifier(imported, local)
will do the trick.
const importSpecifier = j.importSpecifier( j.identifier('useContext'), j.identifier('useFoo') );
export default function transformer(file, { jscodeshift: j }, options) { const source = j(file.source); const reactImports = source .find(j.ImportDeclaration) .filter((path) => path.node.source.value === 'react'); // Build our new import specifier const importSpecifier = j.importSpecifier( j.identifier('useContext'), j.identifier('useFoo') ); reactImports.forEach((reactImport) => // Insert our new import specifier j(reactImport).replaceWith( j.importDeclaration( [...reactImport.node.specifiers, importSpecifier], reactImport.node.source ) ) ); return source.toSource(); }
import React, { useEffect } from 'react'; const Button = (props) => <button {...props} />;
loading
Read-onlyRemoving an import specifier
Removing an import specifier can be done by first finding the import declaration and then
filtering the import specifiers and removing them with the .remove()
method.
export default function transformer(file, { jscodeshift: j }, options) { const source = j(file.source); source .find(j.ImportDeclaration) .filter((path) => path.node.source.value === 'react') .find(j.ImportSpecifier) .filter((path) => path.node.imported.name === 'useEffect') .remove(); return source.toSource(); }
import React, { useEffect } from 'react'; const Button = (props) => <button {...props} />;
loading
Read-onlyReplace/Rename an import specifier
Replacing an import specifier is similar to replacing an import declaration.
You can use the .replaceWith()
method to replace the existing node with a new one.
It is important to note that you need to build a new import specifier node based on the existing one,
this is recommended, however it is also possible to modify the reference of the original node.
export default function transformer(file, { jscodeshift: j }, options) { const source = j(file.source); // Build an import specifier const newImport = j.importSpecifier(j.identifier('useFoo')); source .find(j.ImportDeclaration) .filter((path) => path.node.source.value === 'react') .find(j.ImportSpecifier) .filter((path) => path.node.imported.name === 'useEffect') // Insert our new import specifier .replaceWith(newImport); return source.toSource(); }
import React, { useEffect } from 'react'; const Button = (props) => <button {...props} />;
loading
Read-only