Skip to content

Instantly share code, notes, and snippets.

@ArminBu
Created August 2, 2019 12:19
Show Gist options
  • Save ArminBu/e03c79291de585b0d732b1e2d198d8aa to your computer and use it in GitHub Desktop.
Save ArminBu/e03c79291de585b0d732b1e2d198d8aa to your computer and use it in GitHub Desktop.
This script should replace all absolute alias imports with relative imports. It expects 1 argument for the root directory
const path = require("path");
const args = process.argv;
const rootName = args[2];
const rootPath = path.resolve(process.cwd(), rootName);
const alias = "@";
if (!rootPath || !alias) return;
const { promisify } = require("util");
const fs = require("fs");
const readFileAsync = promisify(fs.readFile);
const readDirAsync = promisify(fs.readdir);
const writeFileAsync = promisify(fs.writeFile);
const statsAsync = promisify(fs.stat);
function testForAliasImport(file) {
if (!file.content) return file;
const regex = /"@(\/\w+[\w\/.]+)"/gi;
let match,
search = file.content;
while ((match = regex.exec(search))) {
const matchString = match[0];
console.log(`found alias import ${matchString} in ${file.filepath}`);
file.content = file.content.replace(
matchString,
aliasToRelative(file, matchString)
);
search = search.substring(match.index + matchString.length);
}
return file;
}
function aliasToRelative(file, importString) {
let importPath = importString
.replace(alias, "")
.split('"')
.join("");
const hasExtension = !!path.parse(importString).ext;
if (!hasExtension) {
importPath += ".ext";
}
const filepath = file.filepath
.replace(rootPath, "")
.split("\\")
.join("/");
let relativeImport = path.posix.relative(path.dirname(filepath), importPath);
if (!hasExtension) {
relativeImport = relativeImport.replace(".ext", "");
}
if (!relativeImport.startsWith("../")) {
relativeImport = "./" + relativeImport;
}
relativeImport = `"${relativeImport}"`;
console.log(`replaced alias import ${importString} with ${relativeImport}`);
return relativeImport;
}
async function writeFile(file) {
if (!file || !file.content || !file.filepath) return file;
try {
console.log(`writing new contents to file ${file.filepath}...`);
await writeFileAsync(file.filepath, file.content);
} catch (e) {
console.error(e);
}
}
async function prepareFile(filepath) {
const stat = await statsAsync(filepath);
return { stat, filepath };
}
async function processFile(file) {
if (file.stat.isFile()) {
console.log(`reading file ${file.filepath}...`);
file.content = await readFileAsync(file.filepath);
file.content = file.content.toString();
} else if (file.stat.isDirectory()) {
console.log(`traversing dir ${file.filepath}...`);
await traverseDir(file.filepath);
}
return file;
}
async function traverseDir(dirPath) {
try {
const filenames = await readDirAsync(dirPath);
const filepaths = filenames.map(name => path.join(dirPath, name));
const fileStats = await Promise.all(filepaths.map(prepareFile));
const files = await Promise.all(fileStats.map(processFile));
await Promise.all(files.map(testForAliasImport).map(writeFile));
} catch (e) {
console.error(e);
}
}
traverseDir(rootPath)
.then(() => console.log("done"))
.catch(console.error);
@maksnester
Copy link

Thanks for that script 🙏

Worked perfectly for me except that I had to change " with ' so would be great to have a quote sign option. Also, I had to change path.posix.relative with path.relative – the path.posix didn't work for me on mac. Result script is

const path = require('path');
const args = process.argv;

const rootName = args[2];
const rootPath = path.resolve(process.cwd(), rootName);

console.log('root path', rootPath);

const alias = '@';

if (!rootPath || !alias) return;

const { promisify } = require('util');
const fs = require('fs');

const readFileAsync = promisify(fs.readFile);
const readDirAsync = promisify(fs.readdir);
const writeFileAsync = promisify(fs.writeFile);
const statsAsync = promisify(fs.stat);

function testForAliasImport(file) {
  if (!file.content) return file;

  const regex = /'@(\/\w+[\w\/.]+)'/gi;

  let match,
    search = file.content;

  while ((match = regex.exec(search))) {
    const matchString = match[0];
    console.log(`found alias import ${matchString} in ${file.filepath}`);
    file.content = file.content.replace(matchString, aliasToRelative(file, matchString));
    search = search.substring(match.index + matchString.length);
  }

  return file;
}

function aliasToRelative(file, importString) {
  let importPath = importString.replace(alias, '').split(`'`).join('');
  const hasExtension = !!path.parse(importString).ext;

  if (!hasExtension) {
    importPath += '.ext';
  }

  const filepath = file.filepath.replace(rootPath, '').split('\\').join('/');

  let relativeImport = path.relative(path.dirname(filepath), importPath);

  if (!hasExtension) {
    relativeImport = relativeImport.replace('.ext', '');
  }

  if (!relativeImport.startsWith('../')) {
    relativeImport = './' + relativeImport;
  }

  relativeImport = `'${relativeImport}'`;

  console.log(`replaced alias import ${importString} with ${relativeImport}`);
  return relativeImport;
}

async function writeFile(file) {
  if (!file || !file.content || !file.filepath) return file;
  try {
    console.log(`writing new contents to file ${file.filepath}...`);
    await writeFileAsync(file.filepath, file.content);
  } catch (e) {
    console.error(e);
  }
}

async function prepareFile(filepath) {
  const stat = await statsAsync(filepath);
  return { stat, filepath };
}

async function processFile(file) {
  if (file.stat.isFile()) {
    console.log(`reading file ${file.filepath}...`);
    file.content = await readFileAsync(file.filepath);
    file.content = file.content.toString();
  } else if (file.stat.isDirectory()) {
    console.log(`traversing dir ${file.filepath}...`);
    await traverseDir(file.filepath);
  }
  return file;
}

async function traverseDir(dirPath) {
  try {
    const filenames = await readDirAsync(dirPath);
    const filepaths = filenames.map(name => path.join(dirPath, name));
    const fileStats = await Promise.all(filepaths.map(prepareFile));
    const files = await Promise.all(fileStats.map(processFile));
    await Promise.all(files.map(testForAliasImport).map(writeFile));
  } catch (e) {
    console.error(e);
  }
}

traverseDir(rootPath)
  .then(() => console.log('done'))
  .catch(console.error);

Used it as "replaceimports": "node rename.js src", though for some reason I had to rerun it a lot of times because it replaced imports partially (not every import was replaced at once, but several per file). Didn't dig into it.

@milahu
Copy link

milahu commented Feb 15, 2024

no. using regex to parse sources is just wrong

eslint is the best tool for javascript codemods, so...

https://github.com/johvin/eslint-import-resolver-alias - resolve alias imports, ...
https://github.com/dword-design/eslint-plugin-import-alias - enforce the use of import aliases
https://github.com/joshuajaco/eslint-plugin-workspaces/blob/main/lib/rules/no-absolute-imports.js - absolute to relative imports
https://github.com/joshuajaco/eslint-plugin-workspaces/blob/main/lib/rules/no-relative-imports.js - relative to absolute imports
https://github.com/qdanik/eslint-plugin-path/blob/main/lib/rules/no-relative-imports.js - relative to absolute imports

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment