JavaScript拡張子の違いとは?.js/.mjs/.cjsを正しく使い分ける実践テクニック

javascript icon
JavaScript

こんにちは、とまだです。

JavaScriptファイルの拡張子で悩んだことはありませんか?

「.jsファイルだけじゃダメなの?」 「.mjsや.cjsを見かけるけど、違いがよく分からない」

そんな疑問を持つ方は多いはずです。

今回は、JavaScriptの3つの拡張子について解説します。

それぞれの特徴と使い分け方を理解すれば、プロジェクトでの適切な選択ができるようになりますよ。

JavaScriptの拡張子が複数ある理由

JavaScriptの拡張子を理解するには、モジュールシステムの違いを知る必要があります。

JavaScriptには主に2つのモジュールシステムが存在します。

CommonJS(従来の形式)

// モジュールの読み込み
const fs = require('fs');

// モジュールのエクスポート
module.exports = { myFunction };

ES Modules(新しい形式)

// モジュールの読み込み
import fs from 'fs';

// モジュールのエクスポート
export { myFunction };

この2つの形式が混在する環境で、どちらの形式で書かれているかを明確にするために拡張子を使い分けるんです。

なぜ拡張子の使い分けが重要なのか

拡張子を適切に使い分けないと、次のような問題が発生します。

まず、実行時エラーが起きやすくなります。

Node.jsがファイルの形式を誤認識すると、構文エラーが発生してしまうんです。

また、開発効率も下がってしまいます。

どのファイルがどの形式で書かれているか分からないと、コードを読む際に混乱しますよね。

さらに、チーム開発では統一性が重要です。

拡張子のルールを決めておけば、メンバー間での認識のずれを防げます。

3つの拡張子の特徴と使い方

それでは、各拡張子の特徴を見ていきましょう。

.js - 汎用的な拡張子

.jsは最も一般的な拡張子です。

// utils.js
export function greet(name) {
    return `Hello, ${name}!`;
}

// または
function greet(name) {
    return `Hello, ${name}!`;
}
module.exports = { greet };

特徴:

  • 設定によってES ModulesにもCommonJSにもなる
  • package.jsonのtypeフィールドで挙動が変わる
  • 既存プロジェクトとの互換性が高い

ただし、どちらの形式で書かれているか一目で分からないという欠点もあります。

.mjs - ES Modules専用

.mjsはES Modules専用の拡張子です。

// modern-utils.mjs
import fs from 'fs';

export function readConfig() {
    const data = fs.readFileSync('config.json', 'utf8');
    return JSON.parse(data);
}

export const version = '1.0.0';

特徴:

  • 必ずES Modulesとして扱われる
  • import/export構文のみ使用可能
  • 最新のJavaScript機能を活用しやすい

明確にモダンなコードであることを示せるのが利点です。

.cjs - CommonJS専用

.cjsはCommonJS専用の拡張子です。

// legacy-utils.cjs
const fs = require('fs');

function readConfig() {
    const data = fs.readFileSync('config.json', 'utf8');
    return JSON.parse(data);
}

module.exports = {
    readConfig,
    version: '1.0.0'
};

特徴:

  • 必ずCommonJSとして扱われる
  • require/module.exports構文のみ使用可能
  • レガシーなライブラリとの互換性が高い

古いライブラリを使う際に必要になることが多いです。

実務での使い分けパターン

実際のプロジェクトでは、どのように使い分ければ良いのでしょうか。

よく使われるパターンを3つ紹介します。

パターン1:すべて.jsで統一

最もシンプルな方法です。

// package.json
{
  "type": "module"
}
// utils.js(ES Modulesとして動作)
export function calculate(a, b) {
    return a + b;
}

// main.js
import { calculate } from './utils.js';
console.log(calculate(2, 3));

このパターンは小規模プロジェクトに最適です。

設定がシンプルで、チームメンバーが混乱しにくいのがメリットですね。

パターン2:拡張子で明確に区別

形式ごとに拡張子を使い分ける方法です。

// api.mjs(ES Modules)
import fetch from 'node-fetch';

export async function getUser(id) {
    const response = await fetch(`/api/users/${id}`);
    return response.json();
}

// config.cjs(CommonJS)
const fs = require('fs');

module.exports = {
    loadConfig() {
        const data = fs.readFileSync('config.json', 'utf8');
        return JSON.parse(data);
    }
};

大規模プロジェクトではこの方法が推奨されます。

一目でモジュール形式が分かるため、保守性が高くなります。

パターン3:段階的移行

既存プロジェクトを徐々にモダン化する方法です。

// 既存のCommonJSコード
// old-utils.cjs
module.exports = {
    oldFunction() {
        return 'legacy code';
    }
};

// 新機能はES Modules
// new-features.mjs
export const newFunction = () => 'modern code';

// 橋渡し用のファイル
// bridge.js
import { newFunction } from './new-features.mjs';
const { oldFunction } = require('./old-utils.cjs');

export { newFunction, oldFunction };

リスクを抑えながら移行できるのが最大のメリットです。

よくあるエラーと対処法

拡張子の使い分けで遭遇しやすいエラーを紹介します。

エラー1:Cannot use import statement outside a module

このエラーは、ES Modulesの構文を使っているのにNode.jsがCommonJSとして解釈している時に発生します。

解決方法:

package.jsonに以下を追加します。

{
  "type": "module"
}

または、ファイル名を.mjsに変更しましょう。

エラー2:require is not defined

ES Modules環境でCommonJSのrequireを使うと発生します。

解決方法:

// こうではなく
const fs = require('fs');

// こう書く
import fs from 'fs';

あるいは、該当ファイルを.cjsに変更します。

エラー3:Module not found

拡張子の指定が正しくない場合に発生します。

解決方法:

ES Modulesでは拡張子の明示が必要です。

// こうではなく
import { util } from './utils';

// こう書く
import { util } from './utils.js';

プロジェクトでの選択基準

どの方式を選ぶべきか迷った時の判断基準を紹介します。

新規プロジェクトの場合

基本的にはES Modulesを採用しましょう。

{
  "type": "module"
}

すべて.jsで統一し、ES Modulesで書くのがおすすめです。

将来性があり、最新の機能を使いやすいからです。

既存プロジェクトの場合

まずは現状を把握することが大切です。

CommonJSが多い場合は、新機能から徐々に.mjsを導入していきましょう。

一気に移行すると、動作確認が大変になってしまいます。

チーム開発の場合

チーム内でルールを明文化することが重要です。

// プロジェクトのディレクトリ構造例
src/
├── config/           # 設定関連(.cjs)
│   └── database.cjs
├── utils/            # ユーティリティ(.mjs)
│   └── validators.mjs
├── services/         # ビジネスロジック(.js)
│   └── userService.js
└── index.js         # エントリポイント

役割ごとに拡張子を使い分けると、整理しやすくなります。

まとめ

今回は、JavaScriptの拡張子について解説しました。

重要なポイントをおさらいしましょう。

まず、拡張子はモジュール形式を明示するためのものです。

そして、それぞれの特徴は以下の通りです。

  • .js - 設定次第で柔軟に対応
  • .mjs - ES Modules専用で明確
  • .cjs - CommonJS専用で互換性重視

プロジェクトの規模や状況に応じて、適切な使い分けが必要です。

特に重要なのは、チーム内でのルール統一です。

技術的に正しくても、メンバーが理解できなければ意味がありません。

もしJavaScriptのモジュールシステムについてさらに深く学びたい方は、Learning Next SchoolのJavaScriptコースがおすすめです。

モジュールの仕組みから実践的な使い方まで、体系的に学習できます。

適切な拡張子選択で、保守性の高いコードを書いていきましょう。

共有:

著者について

とまだ

とまだ

フルスタックエンジニア

Learning Next の創設者。Ruby on Rails と React を中心に、プログラミング教育に情熱を注いでいます。初心者が楽しく学べる環境作りを目指しています。

著者の詳細を見る →