Skip to content

Instantly share code, notes, and snippets.

@maxholman
Last active June 19, 2021 11:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save maxholman/8a2fbd745819a194d5112239a44445d5 to your computer and use it in GitHub Desktop.
Save maxholman/8a2fbd745819a194d5112239a44445d5 to your computer and use it in GitHub Desktop.
jscodeshift/codemod to add .js extensions to relative module specifiers

Description

jscodeshift/codemod transform to add .js extensions to relative module specifiers

Uses Nodes module resolution algorithm so it can handle import './example' -> import './example/index.js'

Usage

For Typescript files

$ npx jscodeshift \
  --parser tsx \
  --extensions ts,tsx \
  -t https://gist.githubusercontent.com/maxholman/8a2fbd745819a194d5112239a44445d5/raw/esm-imports-add-ext.ts \
  ./path/to/files

For Javascript files

$ npx jscodeshift \
  -t https://gist.githubusercontent.com/maxholman/8a2fbd745819a194d5112239a44445d5/raw/esm-imports-add-ext.ts \
  ./path/to/files
import {
ExportAllDeclaration,
ExportNamedDeclaration,
FileInfo,
ImportDeclaration,
Transform,
} from 'jscodeshift';
import { dirname, relative, resolve } from 'path';
function maybeResolvedDeclaration<
T extends ImportDeclaration | ExportNamedDeclaration | ExportAllDeclaration,
>(file: FileInfo, node: T, fn: ({ path: string }) => T) {
if (
typeof node.source?.value === 'string' &&
node.source.value.startsWith('.') &&
!node.source.value.endsWith('.js')
) {
const base = process.cwd();
const importLocation = node.source.value;
const absoluteSourcePath = resolve(base, file.path);
const absoluteImportPath = resolve(
dirname(absoluteSourcePath),
importLocation.endsWith('/') ? `${importLocation}index` : importLocation,
);
// in case resolve throws (missing dep)
try {
const absoluteResolvedImportPath = require.resolve(absoluteImportPath);
const relativeResolvedImportPath = relative(
dirname(absoluteSourcePath),
// TS specific hack
absoluteResolvedImportPath.match(/\.tsx?$/)
? absoluteResolvedImportPath.replace(/\.tsx?$/, '.js')
: absoluteResolvedImportPath,
);
// path/relative will strip the ./ prefix if they are in same location
const path = relativeResolvedImportPath.startsWith('.')
? relativeResolvedImportPath
: `./${relativeResolvedImportPath}`;
return fn({
path,
});
} catch (err) {
process.exitCode = 1;
console.warn(err, {
importLocation,
absoluteSourcePath,
absoluteImportPath,
});
}
}
return node;
}
const transform: Transform = (file, api) => {
const j = api.jscodeshift;
const root = j(file.source);
// https://github.com/facebook/jscodeshift/blob/master/recipes/retain-first-comment.md
// Save the comments attached to the first node
const getFirstNode = () => root.find(j.Program).get('body', 0).node;
const firstNode = getFirstNode();
const { comments } = firstNode;
root.find(j.ImportDeclaration).replaceWith((astPath) => {
const { node } = astPath;
return maybeResolvedDeclaration(file, node, ({ path }) => {
node.source = j.literal(path);
return node;
// return j.importDeclaration(node.specifiers, j.literal(path));
});
});
root.find(j.ExportNamedDeclaration).replaceWith((astPath) => {
const { node } = astPath;
return maybeResolvedDeclaration(file, node, ({ path }) => {
node.source = j.literal(path);
return node;
// return j.exportNamedDeclaration(
// node.declaration || null,
// node.specifiers,
// j.literal(path),
// );
});
});
root.find(j.ExportAllDeclaration).replaceWith((astPath) => {
const { node } = astPath;
return maybeResolvedDeclaration(file, node, ({ path }) => {
node.source = j.literal(path);
return node;
// return j.exportAllDeclaration(j.literal(path), node.exported || null);
});
});
// https://github.com/facebook/jscodeshift/blob/master/recipes/retain-first-comment.md
// If the first node has been modified or deleted, reattach the comments
const maybeFirstNode = getFirstNode();
if (maybeFirstNode !== firstNode) {
maybeFirstNode.comments = comments;
}
return root.toSource();
};
export default transform;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment