👌

mjsファイルとは? 🧐

に公開

.mjs ファイルが登場した背景 📜

歴史的に、Node.jsなどのJavaScript実行環境では CommonJS という異なるモジュールシステムが広く使われてきました。CommonJSでは require()module.exports を使用します。

しかし、ウェブブラウザや新しいJavaScriptの仕様では、ES Modules が標準となりました。ここで問題が発生しました。

  • .js ファイルの曖昧さ: ある .js ファイルがCommonJS形式なのか、ES Modules形式なのかを、ファイルの内容を解析せずに判断するのは難しいです。
  • Node.jsの互換性問題: Node.jsはCommonJSが主流だったため、ES Modulesの導入には互換性の課題がありました。

この問題を解決するために、Node.jsコミュニティを中心に .mjs 拡張子("Module JavaScript" の略と言われることが多い)が導入されました。これにより、ファイル拡張子を見ただけで、それがES Modules形式のファイルであることが明確にわかるようになりました。


.mjs ファイルと .js ファイルの主な違い ✨

特徴 .mjs ファイル .js ファイル(デフォルト)
モジュール形式 ECMAScript Modules (ES Modules) として扱われることが保証されます。import / export 構文が機能します。 Node.js環境では、デフォルトで CommonJS として扱われます(ただし、package.json"type": "module" が設定されているディレクトリ内ではES Modulesとして扱われることもあります)。<br>ブラウザ環境では、HTMLの <script type="module"> で読み込まれた場合はES Modules、それ以外は従来のスクリプトとして扱われます。
厳格モード デフォルトで厳格モード (strict mode) で実行されます。 デフォルトでは厳格モードではありません(ただし、CommonJSモジュールは暗黙的に厳格モードになることがあります)。
this の挙動 最上位スコープの thisundefined です。 最上位スコープの thismodule.exports を指します。
解決ルール 異なる解決ルールが適用される場合があります(例: .js, .json, .node の順で解決するなど)。 異なる解決ルールが適用される場合があります(例: .js, .json, .node の順で解決するなど)。

.mjs ファイルの活用シナリオ 🚀

  1. Node.js 環境でのES Modulesの使用:
    Node.jsの古いバージョンや、package.json"type": "commonjs" が設定されているプロジェクトで、ES Modulesを明示的に使用したい場合に .mjs 拡張子を利用します。

    // my-module.mjs (ES Modules)
    export function greet(name) {
      return `Hello, ${name}!`;
    }
    
    // app.js (CommonJS環境から.mjsを読み込む場合)
    // Node.js v13.2.0 以降では、動的 import() で .mjs を読み込めます
    async function run() {
      const { greet } = await import('./my-module.mjs');
      console.log(greet('World')); // Hello, World!
    }
    run();
    
  2. ブラウザ環境での明確化:
    ブラウザで直接ES Modulesを読み込む場合、HTMLの <script type="module" src="app.mjs"></script> とすることで、そのファイルがES Modulesであることをより明確に示せます(ただし、.js 拡張子でも type="module" があればES Modulesとして扱われます)。

  3. モジュールバンドラーとの連携:
    WebpackやRollupなどのモジュールバンドラーは、.mjs 拡張子をES Modulesとして正しく認識し、処理してくれます。これにより、様々なモジュール形式が混在するプロジェクトでも、バンドラーが適切に処理できるようになります。


.mjs ファイルのサンプルコードと使用した場合・使用しなかった場合の比較 📊

ここでは、Node.js環境におけるES Modulesの扱いを比較してみましょう。

📌 サンプルコード (.mjs ファイルを使用した場合)

message.mjs:

// これはES Modules形式
export const hello = "Hello from .mjs!";
export function sayGoodbye() {
  return "Goodbye from .mjs!";
}

app.js:

// CommonJS環境のファイル
// .mjs ファイルは、Node.jsの動的 import() を使って読み込む
async function run() {
  try {
    const { hello, sayGoodbye } = await import('./message.mjs');
    console.log(hello);         // 出力: Hello from .mjs!
    console.log(sayGoodbye());  // 出力: Goodbye from .mjs!
  } catch (error) {
    console.error("Error importing .mjs:", error);
  }
}

run();

// CommonJS形式で何かをエクスポートする例 (app.js自体はCommonJS)
module.exports = {
  appName: "My Hybrid App"
};

実行コマンド:
node app.js

この場合、app.js はCommonJSモジュールとして実行されつつ、import() を使って .mjs ファイルをES Modulesとして非同期に読み込み、そのエクスポートされた値を利用できます。


📌 .mjs ファイルを使用しなかった場合の比較(.js ファイルと type: "module"

Node.jsでES Modulesを.js ファイルで扱いたい場合、最も一般的な方法は、プロジェクトの package.json"type": "module" を追加することです。これにより、そのディレクトリ内(およびサブディレクトリ)のすべての .js ファイルがデフォルトでES Modulesとして扱われるようになります。

package.json:

{
  "name": "my-es-module-app",
  "version": "1.0.0",
  "type": "module", // ✨ これが重要 ✨
  "main": "app.js",
  "scripts": {
    "start": "node app.js"
  }
}

message.js:

// package.jsonで "type": "module" が設定されているので、これはES Modules形式
export const hello = "Hello from .js (ESM)!";
export function sayGoodbye() {
  return "Goodbye from .js (ESM)!";
}

app.js:

// package.jsonで "type": "module" が設定されているので、これもES Modules形式
import { hello, sayGoodbye } from './message.js'; // ✨ .js 拡張子が必要になることが多い ✨

console.log(hello);         // 出力: Hello from .js (ESM)!
console.log(sayGoodbye());  // 出力: Goodbye from .js (ESM)!

// ES Modules形式で何かをエクスポートする例
export const appVersion = "1.0";

実行コマンド:
node app.js

この設定では、すべての .js ファイルがES Modulesとして扱われるため、CommonJSの require()module.exports を使用するとエラーになります。プロジェクト全体をES Modulesに移行する場合に適しています。


Discussion