この記事をおすすめしたい人
- Node.jsでファイルを出力しようとしている人
- ファイル出力しようとすると「is not a function」のエラーが出る人
- つまりオレ
今回はNuxt.jsで出たこの↓エラーを解決します。
fs.writefilesync is not a function.
fsモジュールを読み込んで、ファイルを出力しようとした際に出たエラーです。
エラーメッセージで検索すると
国内外問わず出てくる、出てくる…。
にも関わらず解決していないケースが多いっぽかったので、僕なりの原因究明と解決法をメモしておきます。
開発環境はNuxt.js(Vue.js)ですが、Reactでも同様のエラーが出るとあったので、Node.js共通の問題?なのかな?
スーパー素人なのでこの辺、整理できてません。
VueやらReactやらでファイル出力しようとすること自体がレアなケースのようなので、僕がやろうとしていたこと含めて経緯も書きます。 興味ない人はすっ飛ばしてください。
fsモジュールを使おうとした経緯
Nuxt.jsで大きなデータを使うサイトを作ろうとしていました。
ファイルサイズが大きいと明らかに動作がもっさりしてきたので、プロパティ名を短くしたり、不要なデータを削除したり、構造を単純化したりと、開発環境でシェイプアップしたデータを出力して、本番でそれを読み込もうと考えました。
開発環境のロード時にこの整形作業をやらせていると、変更を加える度に整形作業にかかる時間(ほぼ一瞬)、整形後のデータを再読込する時間(長い)で、開発作業がかなりグダグダになってしまいました。
そこで、開発環境専用の特定のページを開いた時のみ整形作業を行わせる仕様に変更しました。
そしたら「not a function」ですよ。
海外のフォーラムでも
Oh... fsをconsole.logして中身を確認したのかい?
とか煽られながら、
そんなもん、最初にやったわい!!
なぜ「not a function」となるのか
いくつかの環境でテストを行った結果、仮説を書きます。
まず、上の例のように、ページロード時にファイル出力しようとすると100%エラーが出ました。 当然、ファイルは出力されません。
次に、上記環境へ変更前。 「nuxt.config.js」に登録したプラグインからモジュールを呼び出して、モジュール内でファイル出力を行っていたケース。
なぜプラグインで直接ファイル出力をしていなかったかは忘れてしまいましたが、モジュール内じゃないと何かが出来なかったんです。 プラグインだけで完結させられる人は適宜読み替えてください。
で、このケースがちょっと特殊な結果になっていて、「not a function」のエラーは出るのにファイルが出力されていたんです。
さらに、ファイルベースのデータではなく、サーバミドルウェアを使ってデータベースからデータを取得していたケース。 このケースでは、エラーが出ずにファイルを出力できていました。
以上のことから。
クライアントから呼び出した場合にはfsモジュールが使えないんじゃないかなと。
よくよく考えてみるとブラウザ経由で、ダウンロード以外の方法でファイル保存ができてしまうと危ないから、そういう仕様なんではないでしょうか。
これが真だとすると、サーバミドルウェア経由でエラーが出ないのも理解できるし、ページロードでエラーになるのも納得できます。
モジュール経由の場合は、サーバサイドでモジュールを読み込んだ時にファイルが出力されて、クライアントサイドで読み込んだ時にエラーが出たのかなーと。
てことは、特定のページロード時にサーバミドルウェアを呼んで、そこでファイル出力させてやればエラーが出ないはず。
ページロード時にサーバミドルウェア経由でファイルを出力
以下、Nuxt.jsでの書き方なので他の開発環境の人は読み替えてください。
Nuxt.jsのプロジェクトフォルダ内「server」にテキトーなファイル、例えば「output.js」を作ります。
「nuxt.config.js」の「serverMiddleware」の項目に上のファイルを登録します。
serverMiddleware: [
'~/server/output.js',
],
「output.js」の中身は、僕はサーバミドルウェアをexpress経由でMySQLからデータを持ってくるのにしか使ったことがなくて、その書き方ベースになっていて、無駄が多いと思います。 サーバミドルウェアの書き方が分かる人は無駄な部分を削ってください。
書いたコードはこんな↓感じ。
const express = require('express')
const app = express()
module.exports = { path: '/api', handler: app }
app.get('/output', async (req, res) => {
//ここで処理
})
これでブラウザから「http://192.168.0.X:8000/api/output」のようにアクセスするとサーバミドルウェアを呼び出せます。
処理部分はこんな↓感じ。
const json = require('../json/data.json')
//ここでJSONを整形
const fs = require('fs')
fs.writeFileSync('server/api/test.json', JSON.stringify(json))
元データをプロジェクトフォルダ内の「json」フォルダにある「data.json」としています。 相対パスにしないとrequire()がファイルが見つからないというので相対パスで書きました。
元データを読み込んで整形後、fsモジュールを読み込んで、writeFileSync()で出力しています。
ファイルはプロジェクトフォルダ内の「server」にある「api」フォルダ内に「test.json」として保存されます。
最後に、ロード時にファイル出力をしたいページのSCRIPT内で、こんな↓感じのコードを書けば終了です。
import axios from 'axios'
export default {
mounted() {
axios.get('http://192.168.0.X:8000/api/output')
},
}
IPアドレスは開発環境に合わせてください。
このやり方で、
無事にページロード時にファイルを出力させることができました!
僕は無知なのでこんなやり方になっていますが、クライアントサイドではfsモジュールが使えないことだけ意識すればもっとスマートなやり方があるんじゃないかと思います。