Skip to content

Instantly share code, notes, and snippets.

@donaldpipowitch
Last active November 13, 2023 22:08
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save donaldpipowitch/5f37b808909cfdde242e73830be7cf6d to your computer and use it in GitHub Desktop.
Save donaldpipowitch/5f37b808909cfdde242e73830be7cf6d to your computer and use it in GitHub Desktop.
Rewrite relative import paths with a codemod

⚠️ This is for an internal project, but shared publicly as a reference for myself.

If you want to have a general introduction how to write such a codemod have a look at this Gist from me.

What to do?

Place the usage.js and prettiermod.js in the root of the project. Run:

$ pnpm add globby@^11.0.0 @babel/traverse
$ node usage.js
$ pnpm lint

Now remove usage.js and prettiermod.js again and reset package.json and pnpm-lock.yaml.

(FYI: $ pnpm lint never finished for me, because of ESLint. Not sure why - seems to be fine in CI. I ran $ pnpm eslint --no-eslintrc --parser "@typescript-eslint/parser" --parser-options "{ecmaVersion: 6,sourceType: 'module',ecmaFeatures: {modules: true, jsx: true}}" --plugin "import" --plugin "react-hooks" --rule "{'import/order': ['error', { alphabetize: { order: 'asc' } }]}" --fix "src/**/*.{ts,tsx}" which only runs the 'import/order' rule in the code base. That one worked fine and fast.)

// @ts-check
const globby = require('globby'); // v11
const { readFile, writeFile } = require('fs/promises');
const prettier = require('prettier');
const traverse = require('@babel/traverse');
/**
* @typedef {{
* patterns: string | string[];
* write?: boolean;
* prettierConfig: any;
* visitors: any[];
* }} PrettiermodOptions
*/
module.exports = async function prettiermod(
/** @type {PrettiermodOptions} */ {
patterns,
write = false,
prettierConfig,
visitors,
}
) {
const files = await globby(patterns);
await Promise.all(
files.map(async (file) => {
const code = await readFile(file, 'utf-8');
const result = prettier.format(code, {
...prettierConfig,
parser(code, { 'babel-ts': babel }) {
const ast = babel(code);
visitors.forEach((visitor) =>
traverse.default(ast, visitor({ file }))
);
return ast;
},
});
if (write) {
await writeFile(file, result);
} else {
console.log(result);
}
})
);
};
const { resolve, join, dirname } = require('path');
const prettiermod = require('./prettiermod');
const prettierConfig = require('./prettier.config');
const rewriteImportPathsVisitor = ({ file }) => ({
ImportDeclaration(path) {
if (path.node.source.type !== 'StringLiteral') return;
updateImportPath(path, file, 'hooks');
updateImportPath(path, file, 'components');
updateImportPath(path, file, 'utils');
updateImportPath(path, file, 'types');
},
});
const updateImportPath = (path, file, directory) => {
const { value } = path.node.source;
// ignore imports where this directory is not part of a relative import
if (!value.includes(`./${directory}`)) return;
// ignore imports where this directory is not refering to the top-level
if (!join(dirname(file), value).startsWith(`src/${directory}`)) return;
const newValue = resolve(file, '..', value).replace(`${__dirname}/`, '');
path.node.source.extra.rawValue = newValue;
path.node.source.extra.raw = `'${newValue}'`;
path.node.source.value = newValue;
};
prettiermod({
patterns: ['src/index.tsx'],
// patterns: ['{src,stories,tests}/**/*.{ts,tsx}'],
prettierConfig,
visitors: [rewriteImportPathsVisitor],
write: true,
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment