ビルドツールなしでJSを分割するのはあり?実際に検証してみた

本記事ではViteWebpackなどのビルドツールを使用しない静的サイトにおいて、JavaScriptを分割する方法と、そのメリットとデメリットについて考察しています。

個人で受託している案件のうち、LPや小規模サイトなどのシンプルなものについては、ビルドツールを使わず静的サイトとして実装することが多いです。そのようなケースにおいてJavaScriptを分割すると、パフォーマンスを中心にどのような影響があるのか気になったので検証してみました。

ES Modulesで関数を分割する方法

ES Modulesの機能の一つであるimportexportを使うと、関数をファイル毎に分割することが可能です。

関数をexportする

今回はhello.jsというファイルを作成して、helloという関数を書き出します。関数宣言の前にexportを付けることで、その関数を外部ファイルから読み込めるようになります。

js/utility/hello.js
export const hello = () => {
console.log('hello');
}

関数をimportする

上記の書き出した関数を、別ファイルで参照します。importを使うことで、外部ファイルから書き出した関数を読み込み、その関数を実行することができます。

js/main.js
import { hello } from './utility/hello.js';
hello();

注意点としては、関数をインポートして実行するmain.jsは、次のようにtype="module"を付けて読み込む必要があります。type="module"をつけることでES Modulesを有効化し、ファイルの読み込みができるようになります。

index.html
<script type="module" src="js/main.js"></script>

以上がJavaScriptの関数を分割する最もシンプルな方法です。

Web制作でよく使う下記の処理などを機能ごとに分割しておくと、制作のスピードアップにつながることが想像できます。

  • ハンバーガーメニュー
  • スムーススクロール
  • トップへ戻るボタン
  • モーダル
  • アコーディオン
js/main.js
import { hamburgerMenu } from './components/hamburgerMenu.js';
import { smoothScroll } from './components/smoothScroll.js';
import { topButton } from './components/topButton.js';
import { modal } from './components/modal.js';
import { accordion } from './components/accordion.js';
hamburgerMenu();
smoothScroll();
topButton();
modal();
accordion();

JavaScriptを分割した場合のメリットとデメリット

とても便利そうな機能ですが、Web制作の現場でのメリットとデメリットについて考えてみます。

冒頭でも触れた通り、あくまでもビルドツールを使用しない環境におけるメリットとデメリットを想定しています。ビルドツールありの場合では、それぞれのメリットとデメリットは異なる可能性があります。

メリット

主に次のようなメリットが考えられます。

  • 開発体験・可読性の向上:script.jsを見るだけで何を初期化しているか一目でわかります。component/utility/でUIとロジックを分離することで「どこに何を書くか」のルールが生まれ、コードの置き場所で迷いにくくなります。
  • 保守性・再利用性の向上:ハンバーガーメニューを修正したければhamburgermenu.jsだけを触れば良く、影響範囲が限定されます。smoothscroll.jsのようなユーティリティは別ページや別プロジェクトからもimportして使い回せます。

デメリット

一方で次のようなデメリットも考えられます。

  • 通信コストが増える:ファイルを分割しただけ、リクエスト数が増加します。HTTP/2以上の環境では並列取得できるため影響は小さいですが、HTTP/1.1環境では表示速度の低下につながる可能性があります。
  • ブラウザの互換性:ES Modules非対応のブラウザでは動作しません。現代的なブラウザのみをターゲットにできる環境が前提となります。

両者の比較

分割した場合と分割しない場合を比較すると次のようになります。

項目JS分割ありJS分割なし
開発体験・可読性◎ 明確に向上する△ ファイルが肥大化しやすい
保守性・再利用性◎ ファイルが役割ごとに独立し、再利用しやすい△ 変更時に全体へ影響する可能性がある
通信コスト△ リクエスト数が増える(HTTP/2なら影響軽減)◎ 1ファイルでリクエストが少ない
ブラウザ互換性△ モダンブラウザ限定になる◎ 幅広いブラウザに対応

ファイルを分割した場合、開発体験や保守性は上がる一方、通信コストやブラウザの互換性は下がることがわかります。

ブラウザの互換性については、type="module"のグローバル普及率は97%以上(2026年2月時点)のため、現在は互換性の問題はほとんどないでしょう。主要ブラウザではほぼ対応しており、非対応なのは実質IE11等のレガシーブラウザのみで、現在の開発においてこれらをサポート対象にするケースはあまりないと思います。

一方で通信コストについては実際にどの程度パフォーマンスに影響があるのか疑問に思い、簡易的に検証してみました。

パフォーマンスへの影響を実際に検証してみた

次の条件で検証してみました。

  • ブラウザ:Chrome 147
  • デプロイ先:Cloudflare Pages
  • プロトコル:HTTP/3
  • モジュールのファイルサイズ:746バイト
  • 計測方法:DevToolsのNetworkパネルとPerformanceパネル

構成Aでは外部の関数を一つ読み込んでいます。

js/main.js
import { hello } from './utility/hello.js';
hello();

一方で構成Bでは外部の関数を50個読み込んでいます。

js/main.js
import { hello1 } from './utility/hello1.js';
import { hello2 } from './utility/hello2.js';
import { hello3 } from './utility/hello3.js';
import { hello4 } from './utility/hello4.js';
import { hello5 } from './utility/hello5.js';
// 残り45個続く
hello1();
hello2();
hello3();
hello4();
hello5();
// 残り45個続く

今回はCloudflare PagesでHTTP/3を利用しています。HTTP/3は複数ファイルを同時取得できるため、モジュール分割によるリクエスト増加がパフォーマンス低下に直結しにくい最新環境で検証しました。

ページのコンテンツは「こんにちは」の5文字のテキストを表示しただけのシンプルなものです。

ブラウザのキャッシュを無効化(Disable Cache)した状態でリロードし、DomContentLoaded等の各項目について10回ずつ計測を行い、平均化した数値を算出しました。その結果がこちらです。

通常速度では次のようになりました。

項目構成A(1モジュール)構成B(50モジュール)差分
DomContentLoaded177ms233ms+56ms
Load185ms240ms+55ms
LCP0.15s0.16s+0.01s

低速モード(slow 4G)では次のようになりました。

項目構成A(1モジュール)構成B(50モジュール)差分
DomContentLoaded1.74s2.16s+0.42s
Load1.75s2.16s+0.41s
LCP0.66s0.69s+0.03s

超低速モード(3G)でも念の為確認してみました。

項目構成A(1モジュール)構成B(50モジュール)差分
DomContentLoaded6.22s7.56s+1.34s
Load6.23s7.57s+1.34s
LCP2.12s2.13s+0.01s

今回の結果から、JSファイルの分割はDOMContentLoaded等には影響するが、ユーザーが実際に画面を見られるタイミング(LCP)にはほとんど影響しないということがわかります。

LCPとは

LCP(Largest Contentful Paint)は、ページの読み込み中に表示される最大の画像またはテキストブロックが画面に描画されるまでの時間を表す指標です。Core Web Vitalsの一つで、ユーザーが「主要なコンテンツをいつ頃見られたか」の目安になります。

LCPに影響が少ないのは、JSの読み込みがレンダリングをブロックしていないためだと考えられます。type="module"は自動的にdeferと同等の挙動になり、HTMLのパースを止めずにJSを取得するため、画面の描画自体は遅延しません。

一方でDOMContentLoadedに差が出るのは、deferスクリプトの実行完了を待ってから発火するイベントだからです。モジュール数が増えるほど依存関係の解決に時間がかかるため、その分だけ発火が遅れます。通常速度での差分は+56ms程度であり、この程度の遅延はユーザーが知覚できるレベルではないと考えられます。

なお、以下の点に留意する必要があります。

  • 検証環境がCloudflare(HTTP/3)という非常に高速なCDNであり、一般的なレンタルサーバーではパフォーマンスへの影響がより大きくなる可能性がある
  • モジュールのファイルサイズが小さい(746バイト)ため、影響が小さく留まった可能性がある
  • モジュール数50個は極端なケースで、現実的な10〜20個では影響はさらに小さくなる
  • ネットワークが遅い環境(モバイル回線など)ではDOMContentLoadedLoadへの影響が大きくなりやすい

まとめ

結局のところビルドツールなしでJSを分割するのはありなのか?

LPや小規模サイトレベルの規模で10〜20モジュール程度であればLCPへの影響はほぼなく、開発体験の向上を考えるとありだと言えます。

逆にモジュール数が増えるほどDOMContentLoadedの遅延は大きくなるため、規模が大きいサイトではビルドツールでモジュールをバンドル(結合・最適化)することを検討した方が良さそうです。

参考サイト

この記事を書いた人

写真:ブール 代表 西川雅人

Masato Nishikawa

ウェブ制作会社・デザイナー様向けに、コーディングやWordPress実装を承っているフリーランスです。繁忙期のサポートや、継続的な外注先をお探しでしたら、お気軽にお声がけください。

コーディングや
WordPress実装の
ご相談を承っています。

新規制作から運用フェーズの修正まで柔軟に対応します。
お見積りや実装可否の確認など、お気軽にご連絡ください。