作成日:2025/6/17,最終更新日:2025/6/25

JavaScript / TypeScriptのモジュールとスクリプト構成について

scriptタグの使い方とモジュール化の基本をわかりやすく解説

この記事では、JavaScriptやTypeScriptでコードを書くときの「読み込み方法」や「ファイルの分け方」について解説します。
HTMLとの連携に不可欠な scriptタグの使い方 や、コードを整理するための モジュール構成 の基本を丁寧に紹介します。


実例つきで、実務でも頻繁に使われる defertype="module" の違い、import / export を使ったファイル分割方法などを詳しく解説していきます。


1. scriptタグの基本構文

解説: JavaScriptやTypeScriptをHTMLで使うためには、scriptタグを正しく記述する必要があります。
ここでは、代表的な2つの指定方法とその違いを整理しておきましょう。




● 基本構文と使い方

代表的な2つの記述方法は、以下のとおりです。

// defer を使う場合
<script src="main.js" defer></script>

// モジュールとして読み込む場合
<script type="module" src="main.js"></script>

どちらもHTMLの <head> の中や <body> の末尾に記述できますが、defertype="module" を使うことで、HTMLの読み込みが終わってから安全にスクリプトを実行できます。


● deferとmoduleの違い

  • defer:外部スクリプトをHTMLの解析後に実行(順番を保つ)
  • type="module":モジュールとして読み込む。import/exportが使える。スクリプトは自動的にdeferされる

補足:
type="module" を使うと、自動で defer のような振る舞いになります。つまり、HTMLの構築後にスクリプトが実行されるため、DOMの取得なども安全に行えます。


2. ファイル分割とモジュール構成の基本

解説: プログラムが長くなってくると、すべてを1つのファイルに書いておくのは管理しづらくなります。
そこで登場するのが「モジュール」の考え方です。




● モジュールとは?

JavaScriptでは、type="module" を使うことで、複数のファイルにコードを分割して管理できるようになります。
ファイル間で関数や変数をやり取りできるようになるため、保守性や再利用性が高まります。
※scriptタグの基本構文を参照


● import / export の基本構文

まずはシンプルな例で見てみましょう。

// utils.js というファイルに定義
export function hello(name) {
  return `こんにちは、${name}さん!`;
}

これを別のファイルから使うには、次のように import を書きます:

// main.js 側で読み込む
import { hello } from './utils.js';

console.log(hello('太郎')); // 結果 : こんにちは、太郎さん!

● ファイル構成の一例

実際のプロジェクトでは、次のような構成が一般的です:

project/
├── index.html
├── main.js             ← エントリーポイント
├── utils/
│      └── helper.js   ← 各種関数の管理
└── modules/
        └── user.js     ← ユーザー関連処理

このように機能ごとにファイルを分けることで、後からのメンテナンスやチーム開発もスムーズに進められます。
※ちなみに、ファイル名はなんでも大丈夫です。特にルールないです。(←私は、地味に躓きました、笑)
 上記のファイル名もあくまで一例です。


● 注意点:モジュールはローカルファイルでも動かせる?

モジュール構文( import / export )は、ローカルファイルで直接開くとエラーになることがあります。
正しく動作させるには、ローカルサーバーで実行する必要があります。たとえば:

  • ・VSCode の「Live Server」拡張機能
  • ・Node.jsを使ったローカルサーバー
  • ・Python の python -m http.server コマンド

ブラウザのセキュリティ上、file:// から直接開くと CORSエラー(リソース読み込み制限)になる点に注意しましょう。


3. import / export の応用と注意点

解説: 基本的な import / export の使い方に慣れてきたら、もう少し柔軟な書き方も覚えておきましょう。
実務では、デフォルトエクスポート別名(エイリアス) を使うケースも多くあります。




● デフォルトエクスポート(default export)

ファイル内で1つだけエクスポートする場合は、default キーワードを使うことができます。

// message.js
export default function() {
  return 'こんにちは!';
}

インポートする側は、波かっこなしで名前を自由につけられます:

// main.js
import showMessage from './message.js';

console.log(showMessage()); // 結果 : こんにちは!

● 別名(エイリアス)を使った読み込み

エクスポートされた関数名を変更して、自分の好きな名前でインポートすることもできます。

// utils.js
export function sum(a, b) {
  return a + b;
}

次のように、import時に名前を変更できます:

// main.js
import { sum as add } from './utils.js';

console.log(add(2, 3)); // 結果 : 5

実際には、使う機会はあまり多くないかもしれません。(←私は経験ゼロ、笑)
自作関数ならそのまま使えますし、チーム開発では名前を変更すると可読性が下がることもあるため注意が必要です!笑


● import / export の混在

1つのファイルで、複数の関数を通常エクスポートしつつ、1つだけをデフォルトエクスポートすることも可能です。

// mathematics.js
export function multiply(a, b) { return a * b; }

export function division(a, b) { return a / b; }

export default function(a, b) { return a + b; }

このとき、インポート側では次のように書き分けます:

// main.js
import add, { multiply, division } from './mathematics.js';

console.log(add(3, 4));      // 結果 : 7
console.log(multiply(3, 4)); // 結果 : 12
console.log(division(3, 4)); // 結果 : 0.75

● 注意点:ファイル拡張子は省略できる?

モジュール構文では、相対パスの拡張子(.js)を省略できません

// これはNG(拡張子なし)
import { hello } from './utils'

// これはOK(.jsを明記)
import { hello } from './utils.js'

これらの応用テクニックを使いこなせば、より柔軟で読みやすいコードが書けるようになります。
ただし、チームで開発するときは命名のルールを揃えることも大切です。 少しずつ慣れていけばOKです!


4. モジュール構成のベストな考え方

解説: ファイルを分けて管理できるようになったら、次は「どう分けるか」を考えるステージです。
実務では、モジュールを適切に分割しておくことで、保守性や再利用性が大きく向上します。




● 機能ごとにファイルを分けよう

例えば、画面に関する処理、API通信、ユーティリティ関数など、役割ごとにファイルを分けるのが基本です。

// src/ui/display.js(画面に関する処理はこれ)
export function renderHeader() { ... }
export function renderFooter() { ... }

// src/api/fetchData.js(API通信に関する処理はこれ)
export async function getUserData() { ... }

// src/utils/calc.js(ユーティリティ関数、、、便利にする処理はこれ)
export function sum(a, b) { return a + b; }

● index.js をまとめ役に

複数のファイルがあるときは、それらを集約する index.js を用意すると便利です。

// src/utils/index.js
export { sum } from './calc.js';
export { average } from './average.js';
// 「export { ○○○ } from './○○○.js'」と書くことで、
// 「index.js」へのインポートと他ファイルへエクスポートをまとめて実行できます!

上記のようにすると、使う側はindex.jsからまとめでインポートできるので書く量が減ります!

import { sum, average } from './utils/index.js';

実は「index.js」はファイル名を省略して書けます(Node.jsやモダン環境では自動的に読み込まれます)。

import { sum, average } from './utils';

● パスが深くなる問題とエイリアスでの解決法

import構文では、通常は相対パスでモジュールを読み込みます。
ですが、ファイルの構成が複雑になると、次のような読みにくい深いパスが頻出します

// 深い階層からのインポート例
import { sum } from '../../../lib/utils/math.js';

このようなパスは可読性が悪く、保守やリファクタリングが面倒になります。
そんなときに便利なのが、「パスエイリアス」です。


● パスエイリアスとは?

パスエイリアスを使うと、特定のディレクトリに「短い別名」をつけて、それを使ってインポートできるようになります。

// エイリアスを設定した例
import { sum } from '@utils/math.js';

@utilssrc/lib/utils などの長いパスを指しているイメージです。


● 設定例(Viteの場合)

Viteを使っている場合は、vite.config.js で次のように設定します:

// vite.config.js
import { defineConfig } from 'vite';
import path from 'path';

export default defineConfig({
  resolve: {
    alias: {
      '@utils': path.resolve(__dirname, 'src/lib/utils'),
    },
  },
});

これで、どこからでも「@utils/math.js」でインポートできます。


● TypeScriptの場合は tsconfig.json にも設定

TypeScriptで型チェックや補完を正しく効かせるには、tsconfig.json にも以下のように書きます:

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@utils/*": ["src/lib/utils/*"]
    }
  }
}

エイリアスを使うことで、見通しの良いパス設計ができるようになります。
チーム開発や中〜大規模プロジェクトでは、特に効果を発揮します!
構成や命名に正解はありませんが、「迷わず使える構造か?」を常に意識しましょう。
とくにチーム開発では、命名・配置ルールの統一がスムーズな連携につながります。


※実装例は下記から!
ここまで学んだモジュール構成やファイル分割の内容を活かした実装例を、別記事にまとめています。
実際に動くサンプルも載せているので、ぜひ参考にしてみてください!
≫実装例はこちら≪


5. よくあるエラーとその原因

解説: モジュール構成やscriptタグの使い方を理解していても、環境や書き方によって思わぬエラーが発生することがあります。
ここでは、実際によく遭遇する3つのエラーについて、その原因と対処法を紹介します。




● エラー1:CORS(クロスオリジン)エラー(ローカルで開くときに注意)

モジュールを使うと、ローカルファイル(file://)で開いたときに、次のようなエラーが出ることがあります:

Access to script at 'file:///path/to/module.js' from origin 'null' has been blocked by CORS policy

これはセキュリティ上の制限で、ローカルファイルからモジュールを読み込むことが許可されていないためです。
対策としては、ローカルサーバーを使って起動するのが基本です。


  • ✅ VSCode の Live Server 拡張を使う(個人的にはこれが一番手軽でオススメ!)
  • ✅ Node.js で npx serve などの簡易サーバーを起動
  • ✅ Python を使って python -m http.server

● エラー2:相対パスの書き間違い

import時のパス指定はとても厳密です。特によくあるのが以下のミスです:

// NG(拡張子がない)
import { hello } from './utils'

// OK(.jsを明記)
import { hello } from './utils.js'

モジュール構文では、必ず「相対パス」と「拡張子」を明記する必要があります。
Node.jsやReactなどのフレームワークと違い、ブラウザは拡張子を自動補完してくれません


● エラー3:deferとtype="module"の併用

scriptタグでよく使われる defer 属性ですが、type="module" を指定している場合は不要です。

// これはOK
<script type="module" src="main.js"></script>

// これは冗長(deferは付けても無意味)
<script type="module" src="main.js" defer></script>

モジュールは自動的にdeferと同じ挙動(HTMLのパース完了後に実行)になるため、わざわざdeferを付ける必要はありません。
モジュールはすでに defer と同じ挙動を持っているため、defer を付けても意味がなく、基本的には 省略するのが推奨されます。


これらのエラーは、モジュールに慣れていないうちは特につまずきやすいポイントです。
でも大丈夫!原因と対処法を知っておけば、スムーズに開発が進められます。