症状
ある日、いつものように TypeScript を使って開発をしていました。
フォルダ構造はこんな感じ。
project
├ src
│ └ test.ts
└ resource
└ data.json
src
の中にソースコードを置き、resource
の中にデータを設置する、よくあるプロジェクトです。
ビルド設定として、build
フォルダ内にビルド結果を出力するようにします。
結果としてはこんな感じ。
project
src
test.ts
resource
data.json
+ build
+ src
+ test.ts
たとえばsrc/test.ts
に書いたコードは、ビルドを通してbuild/src/test.js
に置かれる、といった具合ですね。
そしたらコーディングをしていきます。
test.ts
の中身はこんな感じ。
import fs from 'fs';
import path from 'path';
// 相対パス
const filePath = path.join(__dirname, '../resource/deck.json');
// data.json ファイルを読み込み
let fileContents = fs.readFileSync(filePath, 'utf8');
let data = JSON.parse(fileContents);
fs
モジュールで json ファイルの中身を読み込む処理です。
パスは path
モジュールを使った、test.ts
からの相対パスで書いています。
そしたら実行してみます。
npx tsc
して、npm run start
すると...
Error: ENOENT: no such file or directory, open 'C:\hogehoge\project\build\src\resource\deck.json'
あれれ...エラーが出てしまいました。
原因
このエラーが発生する原因は、相対パスにあります。
ビルド前のtest.ts
とビルド後のtest.js
の、data.json
までの相対パスを比較してみます。
-
test.ts
( ビルド前 ) :../resource/data.json
-
test.js
( ビルド後 ) :../../resource/data.json
そう、原因はここにあります。
ビルドファイルのほうが階層が一つ深いために、元のコードとずれが生じてしまうわけですね。
対処法
もっともシンプルなアイデアとして、 resource
フォルダをビルド時にbuild
フォルダにコピーする というのがあります。
「相対パスの先にフォルダがないなら作っちゃえばいいじゃん!」って話ですね。
実際、ほかのビルドツール(Gradleなど)ではこの方針を取っていた記憶があります。
というわけで実践です。
実際には、以下のような手順で実現していきます。
-
fs-extra
というライブラリをインストールする -
resource
フォルダをコピーするスクリプト、copy-files.js
を書く - ビルド時に
copy-files.js
を一緒に呼ぶようにする
fs-extra
をインストールする
ファイル・フォルダのコピーには、fs-extra
を使います。
早い話が「fsの機能を拡張するライブラリ」ですね。
プロジェクトフォルダにcd
し、以下のコマンドを実行してください。
npm i fs-extra
これでOK。
copy-files.js
を書く
そしたら、ビルド時に実行するスクリプトを書いていきます。
置き場所はどこでもよいです。僕はプロジェクトフォルダ直下に置きます。
project
├ src
│ └ test.ts
├ resource
│ └ data.json
├ build
│ └ src
│ └ test.ts
+ └ copy-files.js
そしたらスクリプトを書きます。
以下のような感じ。
const fs = require('fs-extra');
const path = require('path');
// ビルドディレクトリへのパス
const buildPath = path.join(__dirname, 'build');
// フォルダをコピーする
fs.copySync(path.join(__dirname, 'resource'), path.join(buildPath, 'resource'));
8行目でresource
フォルダをまるごとbuild
の中にコピーしてるわけですね。
ビルド時にcopy-files.js
を呼び出し
最後に、ビルド時にこのスクリプトを呼び出すようにします。
方法は簡単、package.json
の中のnpm run build
の内容にちょっと付け足すだけ。
"scripts": {
+ "build": "tsc && node copy-files.js",
"start": "node ./build/app.js"
},
build
コマンドにnode copy-files.js
を追加して、ビルド時に呼ぶようにしています。
この状態でビルドすると...
build
フォルダの中に、resource
がコピーされていますね。