ES2025におけるimport文の拡張 前編 JSONファイルのインポート

ES2025で標準化されたJSONファイルのインポート機能と、その基盤となるインポート属性について解説します。前編では、JSONインポートの基本的な使い方を紹介します。

発行

著者 宇野 陽太 フロントエンド・エンジニア
ES2025におけるimport文の拡張 シリーズの記事一覧

はじめに

昨今のWebアプリケーション開発において、JSON形式のデータは、切っても切れない存在となっています。

バックエンドAPIとの通信フォーマットとしてはもちろん、Local Storageへのデータ保存、アプリケーションの初期設定や起動用データなど、あらゆる場面でJSONが活用されています。

JSONは、「JavaScript Object Notation」の略で、JavaScriptと同じくEcma Internationalで標準化された仕様です。JavaScriptの言語機能として、JSON.parse()JSON.stringify()といったメソッドが標準で提供されていたりと、JavaScriptとの親和性が高いです。

JSONを取得するには、Local Storageなどに保存した場合を除き、Fetch APIなどで非同期的に取得するのが一般的です。

しかし、アプリケーションの起動に必要な設定ファイルなどの「静的なデータ」であっても、動的に読み込まざるを得ないという状況は、開発者にとって少なからず負担となっていました。

ES2015でモジュールシステムが標準化され、JavaScriptでもimportexportといった構文でモジュールを扱えるようになりましたが、モジュールとして読み込めるのはJSファイルのみであり、JSONファイルは読み込むことができませんでした。

そんな中、ES2025において、import文を用いてJSONファイルを直接読み込む機能が標準化されました。これにより、JavaScriptのコード内から他モジュールと同じように、JSONファイルをモジュールとして扱えるようになりました。

本シリーズでは、JSONインポートを取り巻く仕様と、その基盤となっている「インポート属性」について解説します。

これまでの設定ファイルの読み込みとその問題点

静的なデータの例として、アプリケーションのAPIエンドポイントや、機能の有効無効のフラグなどを定義した設定ファイルを挙げてみます。

設定ファイル(config.json)には、以下のように静的な設定値が書かれています。

config.jsonの内容例

{
  "apiBaseUrl": "https://api.example.com/v1",
  "featureFlags": {
    "newUI": true,
    "betaFeatures": false
  },
  "timeout": 5000
}

Fetch APIでの動的な読み込み

これまでJavaScriptの標準でこのファイルを読み込むためには、Fetch APIを使用するのが一般的でした。

Fetch APIを使用した従来の設定ファイル読み込み

import { initializeApp } from './app.js';

async function loadConfig() {
  try {
    const response = await fetch('./config.json');
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  } catch (error) {
    console.error('設定ファイルの読み込みに失敗しました:', error);
  }
}

initializeApp(await loadConfig());

しかし、この方法には、いくつか課題がありました。

まず、読み込みが動的であるという点です。

fetch()はJavaScriptの実行時に初めて行われます。つまり、JavaScriptエンジンがスクリプトを解析し、実行を開始するその瞬間まで、設定ファイルが存在するかどうか、あるいは正しい形式であるかどうかは保証されません。

次に、非同期処理の管理が必要になります。

設定ファイルはアプリケーションの起動に必須であることが多いですが、その読み込み完了を待ってからメインの処理を開始するという制御フローを、開発者が自前で実装しなければなりません。

もしここで、設定ファイルが読み込まれていないのにメインの処理が実行されてしまうと、正しくアプリケーションを起動することができません。「設定ファイルがないとアプリが動かない」という前提条件があるにもかかわらず、コード上では「実行してみないとわからない」状態になっているのです。

Fetch APIを使わず、scriptタグで読み込む方法もありますが、JavaScript実行時に何らかの方法で読み込みチェックが必要である、という点は変わりません。

このように、開発者の中には、JavaScriptを実行する前にJSONファイルの存在を保証したい、というニーズがありました。

import文を使ったJSONファイルの読み込み

ES2025で、新しくimport文の拡張構文が追加されました。

この構文の追加に併せて、import文でJSONファイルを直接読み込むことができるようになりました。JSONファイルの読み込みを、JavaScriptにおけるモジュールの仕組みに乗せることで、JSONファイルをJavaScriptを実行する前に読み込むことができるようになったのです。

JavaScriptファイルの読み込みとは異なり、JSONファイルを読み込む際は、これまでのimport文の後ろにwith { type: "json" }という指定を付与します。

import文でJSONをインポートする

import { initializeApp } from "./app.js";

// JSONファイルの読み込みには`with { type: "json" }`指定を付与する
import config from "./config.json" with { type: "json" };

initializeApp(config);

このwith { type: "json" }の部分が、ES2025で新たに追加された構文です。この指定の意味については、後編で詳しく解説します。

このように書くことで、設定ファイルは他のJavaScriptファイルと同じように、1つのモジュールとして扱われます。

JavaScriptにおいて、モジュールとして読み込まれるファイルは、JavaScriptの実行前に、ブラウザによって依存関係が解決されます。この過程で、ファイルの読み込みと検証が行われます。もし、この準備段階でファイルが存在しなかったり、JSONの形式が間違っていたりすると、その時点で即座にエラーが発生します。この段階では、まだJavaScriptは実行されていません。

つまり、モジュールとして読み込むことで、JavaScriptを実行する前に、「正しい形式のJSONが読み込まれている」という状態を保証できるようになったのです。

その結果、アプリケーションのコードは、設定ファイルが確実に存在している、ということを前提に設計することができるようになりました。不確実なものへの対策が必要なくなったので、設計をシンプルにできます。

動的インポート

JavaScriptでモジュールを読み込む方法には、上述したimport文による静的インポートの他に、import()関数を使ってJavaScript実行時に読み込む、動的インポートがあります。

今回の仕様追加では、import()関数を使ってJSONファイルを読み込むこともできるようになっています。

import()関数を使った動的なJSON読み込み

import { initializeApp } from "./app.js";

initializeApp(await import("./config.json", { with: { type: "json" } }));

静的インポートとは違い、JavaScript実行前に存在が保証されるといった利点はありませんが、モジュールシステムの恩恵を受けつつ読み込むことができる点は、同じです。

JSONインポートの注意点

このように、静的・動的どちらのインポート方法でも、JSONをインポートすることができるようになりました。

では、これからはすべてのJSONを、import文あるいはimport()関数で読み込んだほうがいいのかというと、そうではありません。

ここまで解説には、設定ファイルの読み込みを例に挙げてきました。そして、実際に利用する対象も、設定ファイルくらいの小さく、更新頻度が低いJSONとするのがよいでしょう。しかし、一般的にページの大部分に使われるようなJSONは、モジュールとして読み込むのには適しません。

その理由は次の通りです。

静的インポートはエラーハンドリングができない

Fetch APIなどで通信を行うとき、もしその通信に失敗したり、返されたデータが意図したものと異なる場合には、その状況をエラーとして処理し、適切なフォールバックを行うのが一般的です。

このとき「エラーとして処理」するのも、「フォールバックを行う」のも、JavaScriptの仕事です。JavaScript実行前にファイルを読み込む静的インポートでは、そもそもエラーハンドリングができないのです。そのため、動的なデータやサードパーティが持っているデータなど、こちら側でエラーの有無をコントロールしにくいリソースには不向きです。

それであれば、JavaScript実行時に読み込む動的インポートなら、この問題はクリアできそうに思えますが、それは次の理由で適いません。

モジュールは一度読み込んだら更新も破棄もされない

静的・動的インポートを問わず、モジュールとして読み込んだリソースは、ページ遷移やリロードされるまで、更新されません。同じリソースに対して何度import()を実行しても、読み込まれるのは最初の1回のみです。

そのため、更新頻度の高いリソースを読み込むのには適していません。

また、JavaScriptには、不要になったデータを自動的に破棄するガベージコレクションという仕組みがあります。ガベージコレクションがあるおかげで、不要なデータによってメモリを圧迫しないようになっています。ですが、モジュールとして読み込んだリソースは、ガベージコレクションによって破棄されません。

そのため、大きなリソースをモジュールとして読み込んでしまうと、たとえそのデータが不要になったとしても、ページ遷移やリロードされるまでメモリ上に残り続けます。

以上の理由から、大きなリソースを読み込むのにも適していません。

このように、モジュールシステムの仕様上、扱うリソースとして適したもの/適さないものというのが存在しています。

モジュールとして扱うJSONは、設定ファイルなどのような、小さく更新頻度の低いものに留め、それ以外は今までどおり、Fetch APIなどを利用して読み込むとよいでしょう。

さて、ここまでで、ES2025で可能になった、JSONファイルをインポートする機能について説明してきました。しかし、JSONファイルのインポートは、バンドルツールを用いた環境では以前から可能でした。後編では、それらとの違いや背景にある仕様、インポート属性について解説します。