Node.jsでファイル操作用のスクリプトを書いていると、参考記事やサンプルコードに mjs 拡張子が出てくることがあります。
今まで普通に node コマンドで実行してきたけど、「そもそもこれ何だっけ?」と後から気になる。ES Modules も CommonJS も両方書いてきたけれど、違いを意識するのはだいたい実行エラーが出てから。
この記事は、そんな自分のような人向けに mjs 拡張子を整理していきます。
mjs 拡張子とは何か
mjs 拡張子は、Node.js に対して「このファイルは ES Modules として扱ってください」と明示するための JavaScript ファイルの拡張子です。
中身は JavaScript なので、見た目だけでは普通の js ファイルとほとんど違いがありません。そのため、最初に mjs を見たときに「拡張子が違うだけで、何が変わるのだろう?」と感じる方も多いと思います。
mjs の役割は、コードの内容そのものではなく、Node.js がどの読み込み方式で実行するかを判断するための目印です。
Node.js には、大きく分けて次の2つのモジュール方式があります。
- CommonJS
- ES Modules
従来の Node.js では CommonJS が標準で、require や module.exports を使う書き方が一般的でした。一方で、ES Modules は JavaScript の標準仕様として定義されており、import や export を使います。
mjs 拡張子は、「このファイルは ES Modules ですよ」と Node.js に確実に伝えるために用意されたものです。
拡張子が mjs であるだけで、Node.js は import/export を前提にファイルを読み込みます。
逆に言うと、mjs は「新しい文法の JavaScript」という意味ではありません。ES Modules という読み込みルールを使うことを、拡張子で明確にしているだけです。
そのため、mjs を初めて見たときに、
- 今までの node.js の書き方と何が違うのか
- なぜ js ではなく mjs なのか
- 普通に実行できているけど、本当にこれでいいのか
と疑問に思うのは、ごく自然な反応だと思います。
なぜ Node.js を書いていて突然 mjs が出てくるのか
Node.js でファイル操作用のスクリプトを書いていると、ある日ふと参考資料やサンプルコードの中に mjs 拡張子が出てくることがあります。
それまで普通に js ファイルとして書いて実行してきた場合、「急に何が変わったのだろう」と戸惑いやすいポイントです。
この背景には、Node.js を取り巻く環境の変化があります。
もともと Node.js の世界では CommonJS が長く使われてきました。require を使った書き方はシンプルで、スクリプト用途では今でも十分実用的です。そのため、特に困ることなく js ファイルを書き続けてきた方も多いと思います。
一方で、JavaScript 全体の流れとしては ES Modules が標準仕様として定着してきました。ブラウザ側の JavaScript では import / export が当たり前になり、公式ドキュメントや新しめの記事も ES Modules 前提で書かれることが増えています。
その結果として、
- 公式ドキュメントのサンプルが mjs になっている
- AWS Lambda などの環境で index.mjs を見かける
- 技術記事やライブラリの例が import 前提になっている
といった状況に、自然と遭遇するようになりました。
自分の場合も、Node.js でファイル操作をするスクリプトをよく書いていて、参考資料を見ているうちに mjs が出てきたのが最初のきっかけでした。
今まで通り node コマンドで実行できてしまうので、その場では特に深く考えず、「まあ動いているからいいか」と流してしまいます。
そして後から、「そもそも mjs って何だったのだろう」「js と何が違うのだろう」と気になり始める、という流れになりがちです。
ES Modules も CommonJS もどちらも書いてきた場合、特にこの状態に陥りやすいと感じています。
コード自体は理解できるのに、拡張子や実行時のルールは、実行してエラーが出てから初めて意識することが多いからです。
mjs 拡張子は、知らなくても動く場面が多い一方で、知らないままだと「なぜ動かないのか」「なぜエラーになるのか」で時間を使ってしまう要素でもあります。
だからこそ、後追いでも一度整理しておくと、今後の Node.js 作業がかなり楽になります。
CommonJS と ES Modules の違いを実行視点で整理
mjs 拡張子を理解するうえで避けて通れないのが、CommonJS と ES Modules の違いです。
仕様としての違いを細かく追いかけることもできますが、ここでは「実行するときに何が変わるのか」という視点で整理します。
自分自身、どちらの書き方も使ってきましたが、違いを強く意識するのはだいたい実行時です。
書いている最中はあまり気にせず、エラーが出てから「あ、読み込み方式が違うのか」と気づくことが多いです。
CommonJS の基本形
CommonJS は、従来の Node.js で標準的に使われてきたモジュール方式です。
js ファイルとしてそのまま実行でき、require を使ってモジュールを読み込みます。
たとえば、ファイルを読み込む簡単なスクリプトは次のようになります。
const fs = require('fs');
const data = fs.readFileSync('sample.txt', 'utf-8');
console.log(data);
この書き方は長年使われてきたため、情報量も多く、スクリプト用途では今でも安心感があります。
自分も長い間、この書き方を好んで使ってきました。
ES Modules の基本形
ES Modules は、JavaScript の標準仕様として定義されているモジュール方式です。
Node.js では mjs 拡張子を使うことで、明示的に ES Modules として扱われます。
同じ処理を ES Modules で書くと、次のようになります。
import fs from 'fs';
const data = fs.readFileSync('sample.txt', 'utf-8');
console.log(data);
処理内容はまったく同じですが、読み込みの書き方が変わっています。
import / export は構文としての見通しがよく、個人的には最近はこちらの方が書きやすいと感じています。
両方書いてきた人ほど混乱しやすい理由
CommonJS と ES Modules の厄介な点は、コードの見た目が似ているのに、実行ルールが違うことです。
- 拡張子が js か mjs かで挙動が変わる
- require が使えるかどうかは実行時に決まる
- エラーを見るまで気づかないことが多い
ES Modules も CommonJS も経験があると、「このくらいなら大丈夫だろう」と無意識に混ぜてしまいがちです。そして、実行したタイミングで初めて違いを意識することになります。
mjs 拡張子は、この混乱を減らすために「これは ES Modules です」とはっきり示す役割を持っています。
実行してから悩む回数を減らすための、目印のようなものだと考えると分かりやすいです。
js / mjs / cjs はどう使い分ければいいか
mjs 拡張子の説明を読んでも、「結局どれを使えばいいのか」が分からないままだと、実際の作業ではまた迷ってしまいます。
ここでは、厳密な仕様ではなく、実務で判断しやすい使い分けの考え方を整理します。
まず、js 拡張子です。
js は、Node.js では状況によって CommonJS としても ES Modules としても扱われます。
この「どちらにもなり得る」という点が便利でもあり、混乱の原因にもなります。
既存のスクリプトをそのまま動かしたい場合や、require を使ったコードを書きたい場合は、js を使い続けても特に問題ありません。
小さなファイル操作スクリプトなどでは、今でもよく使われています。
次に、mjs 拡張子です。
mjs は、ES Modules として実行することを明示したいときに使います。
import / export を前提に書きたい場合や、「これは ES Modules だ」と迷わせたくない場合に向いています。
新しく書くスクリプトで、最初から ES Modules を使うつもりなら、mjs にしておくと後から悩まなくて済みます。
実行前に挙動がはっきりしている、という安心感があります。
最後に、cjs 拡張子です。
cjs は、CommonJS であることを明示するための拡張子です。
ES Modules が混在する環境で、「このファイルは必ず require で動かしたい」と示したい場合に使われます。
普段のスクリプト作成では、必ずしも必要になる場面は多くありませんが、拡張子で役割をはっきり分けたい場合には選択肢になります。
まとめると、考え方はシンプルです。
- 今まで通りの Node.js スクリプトを書きたい → js
- import / export を使って書きたい → mjs
- CommonJS であることを明示したい → cjs
どれが正解という話ではなく、「自分が迷わない」「後から見たときに分かりやすい」選択をするのが大切だと感じています。
個人的な好みと、最近の書きやすさの話
ここまで整理しておいてこう言うのも変ですが、実際のところ、どの拡張子を使うかは好みの部分も大きいと感じています。
自分自身は、長い間 CommonJS を使ってきました。
Node.js でファイル操作をする小さなスクリプトを書くことが多く、require を使った書き方に特に不満がなかったからです。
考えなくても手が動く、という意味ではかなり慣れていました。
一方で、ES Modules もそれなりに書いてきました。
最初は「書き方が違うだけ」と思っていたのですが、最近は import / export の方が読みやすく、書きやすいと感じる場面が増えています。
理由としては、次のような点があります。
- 読み込み関係がファイルの先頭にまとまる
- 何を使っているのかが一目で分かる
- サンプルコードとそのまま合わせやすい
特に、公式ドキュメントや新しい記事のサンプルコードは ES Modules 前提のものが多く、そのまま写して動かせるのは楽です。
ただし、これは「ES Modules の方が正しい」という話ではありません。
CommonJS が悪いわけでも、今すぐ書き換えるべきという話でもないです。
実際には、
- 既存コードは CommonJS のまま
- 新しく書くものは mjs で ES Modules
というように、自然に混ざっていくことが多いと思います。
自分も、実行してから「あ、これは mjs の方がよかったな」と気づくことが何度もありました。
mjs 拡張子を理解しておくと、こうした「後からの気づき」を減らせます。
書く前に選べるようになる、というだけでも十分価値があると感じています。
Node.js 周りで「これ何?」となったときの向き合い方
Node.js を触っていると、mjs のように「動いてはいるけれど、正体がよく分からないもの」に出会うことがよくあります。
その場ではスルーできてしまう分、後から気になったり、別の環境で動かなくなって初めて調べることになります。
公式ドキュメントを読めば正しい情報は載っていますが、いきなり仕様を追うのは少し大変です。
実際の作業では、
- とりあえず動かす
- 後から気になった点を整理する
- 次に同じ状況になったら迷わない
という流れを繰り返している方が多いのではないでしょうか。
自分も、mjs 拡張子についてはまさにこのパターンでした。
Node.js でファイル操作のスクリプトを書き、参考資料のコードを動かし、実行できてから「これは何だろう」と立ち止まる。
ES Modules も CommonJS も知ってはいるけれど、拡張子と実行ルールまでは深く意識していなかった、という状態です。
こうした「あとから整理する知識」は、派手ではありませんが、積み重なると作業のストレスを確実に減らしてくれます。
mjs 拡張子も、その一つです。
このサイトでは、Node.js 周りで出てきがちな「これ何?」を、実行視点・実務視点で整理する記事を書いています。
もし同じような疑問で立ち止まったことがあれば、気軽に相談してもらえたらうれしいです。
仕様を完璧に暗記するより、「迷ったときに立ち戻れる場所」がある方が、長く楽に書き続けられると感じています。
参考リンク
Node.js や mjs 拡張子について、より正確な仕様や背景を確認したい場合は、以下の資料が参考になります。
・MDN Web Docs
JavaScript の ES Modules 仕様についての公式ドキュメントです。
import / export の基本的な考え方を確認したい場合に向いています。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Modules
・Node.js 公式ドキュメント
Node.js における ES Modules と CommonJS の扱いについて説明されています。
拡張子や実行時の挙動を正確に把握したい場合はこちらが一次情報になります。
https://nodejs.org/api/esm.html
・Qiita「拡張子が mjs と js のファイルの違いについて」
実際の使用感に近い視点で整理された記事です。
mjs に初めて遭遇したときの疑問と近い内容が書かれています。
https://qiita.com/taisei-ide-lyd/items/f3673e913e8b658946a6
・Zenn「mjs ファイルとは?」
短くまとまっており、概要をざっと掴みたいときに読みやすい記事です。
https://zenn.dev/mk_mokumoku/articles/81f2a350957758
本文でも触れた通り、最初から公式仕様をすべて理解する必要はありません。
「気になったときに戻れるリンク」として置いておく、という位置づけで十分だと思います。
コメントを残す