TypeScriptのimportを相対パスから@/からはじまる絶対パスに変換するスクリプト(雑め)
#TypeScript
やりたいこと
import ... from '../../components/hogehoge'のような相対パスのimportを
import .. from '@/components'のような絶対パスのimportに変えたい。
相対パスだと現在の.tsのパスを意識する必要が常にあり、importをコピーして他のファイルにコピーしたり移動させるときにその相対パスを変更する必要がある。絶対パスで書けばimportのポータビリティが向上すると思う。
スクリプト
以下のJavaScriptを保存して、以下のコマンドを変更したい.tsや.vueのあるルートのディレクトリで実行する。
code:bash
node to-abs.js
code:to-abs.js
const path = require('path');
const fs = require('fs');
// You can change the filter
const fileFilter = p => (p.endsWith(".ts") || p.endsWith(".vue")) && !p.endsWith(".d.ts") && !p.startsWith("node_modules");
// Allow "./ooo"
const allowRelativeSibling = true;
// (base: https://gist.github.com/kethinov/6658166)
function walkSync(dir, filelist=[]) {
const files = fs.readdirSync(dir);
for (const file of files) {
const fpath = path.join(dir, file);
if (fs.statSync(fpath).isDirectory()) {
filelist = walkSync(fpath, filelist);
} else {
filelist.push(fpath);
}
}
return filelist;
};
function toAbsoluteImport(basePath, p, allowRelativeSibling) {
const shouldRewrite = (p) => {
if (allowRelativeSibling) {
return p.startsWith('../');
}
return p.startsWith('.');
}
const parentDirPath = path.dirname(p);
const content = fs.readFileSync(p).toString();
const prevDir = process.cwd();
process.chdir(parentDirPath);
const newContent = content.split("\n").map(line => {
const matched = line.match(/import(.*)('")(.*)('")/)
if (matched) {
const importPath = matched3;
if (shouldRewrite(importPath)) {
const absImportPath = path.resolve(importPath);
const newRelative = path.relative(basePath, absImportPath);
const newImport = import${matched[1]}${matched[2]}@/${newRelative}${matched[4]}
console.log({newImport});
return newImport;
}
}
return line;
}).join("\n");
process.chdir(prevDir);
fs.writeFileSync(p, newContent);
}
// TODO: I'm not sure this is right...
// When tsconfig.json is like the following, base should be "src"
// {
// "compilerOptions": {
// ...
// "paths": {
// "@/*": [
// "src/*"
// ]
// },
const base = JSON.parse(fs.readFileSync('tsconfig.json')).compilerOptions.paths"@/*"0.replace(/\/\*$/, '');
const basePath = path.resolve(base);
const paths = walkSync('.');
const filterPaths = paths.filter(fileFilter)
for (const p of filterPaths) {
toAbsoluteImport(basePath, p, allowRelativeSibling);
}
fileFilterは適宜.tsxなど含めたりすると変換対象になる。
allowRelativeSiblingがtrueだと同じ階層のファイルのみ相対パスを許す。./oooのような。
標準のライブラリだけを使ったのでNode.jsが使えれば動く。
スクリプトをクリップボードにコピーすればpbcopy | nodeのように.jsファイルを作らずに実行できる。
おまけ
「雑め」とタイトルに含めたのはASTを作って変換したりせず、正規表現の一致を使っているから。
以下のようなtsconfig.jsonがある場合、srcを起点とした絶対パスのようなimportになる。
code:tsconfig.json
{
"compilerOptions": {
...
"paths": {
"@/*": [
"src/*"
]
},
...
本当は、eslintでfixするだけで絶対パスに変更してくれるみたいな既存のものを使いたい。探したけど見当たらなかった。
ただしこれを書いている人は上記のcompilerOptions.pathsの仕様を完全に理解しているわけではなく、"@/*"以外来る可能性があるのかなどはよく知らない。typescript - How to use paths in tsconfig.json? - Stack Overflowを見る感じいろいろきそう。"@/*"を慣習的なのかよく使われている印象があるのでそういうtsconfig.jsonに対して動くスクリプトになっているはず。