Skip to main content

Import manipulation

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.

Transform:

export default function transformer(file, { jscodeshift: j }, options) {
const source = j(file.source);

const reactImportDeclaration = source
.find(j.ImportDeclaration) // Find all nodes that match a type of `ImportDeclaration`
.filter(path => path.node.source.value === 'react'); // Filter imports by source equal to the target ie "react"

// Do something here
console.log(reactImportDeclaration);

return source.toSource();
}

Input:

import React from 'react';

const Button = props => <button {...props} />;

Output (unchanged):

import React from 'react';

const Button = props => <button {...props} />;

Inserting an import declaration

Transform:

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();
}

Input:

import React from 'react';

const Button = props => <button {...props} />;

Output:

+import Foo from 'bar';
import React from 'react';

const Button = props => <button {...props} />;

Inserting 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.

Transform:

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')
.insertAfter(newImport); // Insert the new import after all react imports

return source.toSource();
}

Input:

import React from 'react';

const Button = props => <button {...props} />;

Output:

import React from 'react';
+import Foo from 'bar';

const Button = props => <button {...props} />;

Removing an import declaration

Transform:

export default function transformer(file, { jscodeshift: j }, options) {
const source = j(file.source);

source
.find(j.ImportDeclaration) // Find all nodes that match a type of `ImportDeclaration`
.filter(path => path.node.source.value === 'react') // Filter imports by source equal to the target ie "react"
.remove(); // Removes all found import declarations

return source.toSource();
}

Input:

import React from 'react';

const Button = props => <button {...props} />;

Output:

-import React from 'react';

const Button = props => <button {...props} />;

Replace/Rename an import declaration

Transform:

export default function transformer(file, { jscodeshift: j }, options) {
const source = j(file.source);

const reactImports = source
.find(j.ImportDeclaration) // Find all nodes that match a type of `ImportDeclaration`
.filter(path => path.node.source.value === 'react'); // Filter imports by source equal to the target ie "react"

reactImports.forEach(
(
reactImport, // Iterate over react imports
) =>
// 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, // copy over the existing import specificers
j.stringLiteral('hot-new-library'), // Replace the source with our new source
),
),
);

return source.toSource();
}

Input:

import React from 'react';

const Button = props => <button {...props} />;

Output:

-import React from 'react';
+import React from 'hot-new-library';

const Button = props => <button {...props} />;

Import 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.

Transform:

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(path => path.node.imported.name === 'useEffect'); // Filter by name "useEffect"

// Do something here
console.log(useEffectSpecifier);

return source.toSource();
}

Input:

import React, { useEffect } from 'react';

const Button = props => <button {...props} />;

Output (unchanged):

import React, { useEffect } from 'react';

const Button = props => <button {...props} />;

Inserting an import specifier

Transform:

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 specificer
reactImport.node.source,
),
),
);

return source.toSource();
}

Input:

import React, { useEffect } from 'react';

const Button = props => <button {...props} />;

Output:

+import React, { useEffect, useContext } from 'react';
-import React, { useEffect } from 'react';

const Button = props => <button {...props} />;

Inserting 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'),
);

Transform:

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 =>
j(reactImport).replaceWith(
j.importDeclaration(
[...reactImport.node.specifiers, importSpecifier], // Insert our new import specificer
reactImport.node.source,
),
),
);

return source.toSource();
}

Input:

import React, { useEffect } from 'react';

const Button = props => <button {...props} />;

Output:

+import React, { useEffect, useContext as useFoo } from 'react';
-import React, { useEffect } from 'react';

const Button = props => <button {...props} />;

Removing an import specifier

Transform:

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();
}

Input:

import React, { useEffect } from 'react';

const Button = props => <button {...props} />;

Output:

+import React from 'react';
-import React, { useEffect } from 'react';

const Button = props => <button {...props} />;

Replace/Rename an import specifier

Transform:

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')
.replaceWith(newImport); // Insert our new import specifier

return source.toSource();
}

Input:

import React, { useEffect } from 'react';

const Button = props => <button {...props} />;

Output:

+import React, { useFoo } from 'react';
-import React, { useEffect } from 'react';

const Button = props => <button {...props} />;