エントリポイント

エントリポイントとは、アプリケーションの中で一番最初に呼び出される部分のことです。

Ajax通信:エントリポイント」のユースケースでは、エントリポイントはHTML(index.html)のみでした。 まずHTMLが読み込まれ、次にHTMLの中に書かれているscript要素で指定したJavaScriptファイルが読み込まれます。

今回のTodoアプリはJavaScriptの処理をモジュール化し、それぞれのモジュールを別々のJavaScriptファイルとして作成していきます。 JavaScriptモジュールはHTMLから<script type="module">で読み込むことができますが、script要素毎に別々のモジュールスコープを持ちます。 モジュールスコープとは、モジュールのトップレベルに自動的に作成されるスコープで、グローバルスコープの下に作られます。 JavaScriptモジュールを別々のscript要素で読み込むと、モジュール同士でスコープが異なるため、モジュール同士で連携できません。

次のコードは、それぞれの<script type="module">同士のスコープが異なるため、別のscript要素で定義した変数にアクセス出来ないことを示しています。 これはJavaScriptモジュールをファイルにしてsrc属性で読み込んだ場合も同様です。

  <script type="module">
    export const scopeA = "A";
  </script>
  <script type="module">
    // 異なるmoduleスコープの変数には直接はアクセスできない
    console.log(scopeA); // => ReferenceError: scopeA is not defined
  </script>

このようにモジュールを別々のscript要素で扱うとモジュール同士は連携できません。 そのため、HTMLではscript要素でindex.jsのみを読み込み、このindex.jsからimport文で他のモジュールを読み込みます。 import文を使うことで、モジュール間は1つの<script type="module">のスコープ内に収まるため、モジュール同士で連携できます。 このHTMLから読み込むJavaScriptファイル(index.js)をJavaScriptにおけるエントリポイントとします。

つまり、今回作成するTodoアプリではエントリポイントとしてHTMLとJavaScriptの2つを用意します。

  • index.html: もっとも最初に読み込まれるファイル、index.jsを読み込む
  • index.js: index.htmlから読み込まれるファイル、JavaScript間においては最初に読み込まれる

このセクションでは、この2つのエントリポイントを作成し読み込むところまでを確認します。

プロジェクトディレクトリを作成

今回作成するアプリにはHTMLやJavaScriptなど複数のファイルが必要となります。 そのため、まずそれらのファイルを置くためのディレクトリを作成します。

ここではtodoappという名前で新しいディレクトリを作成します。 ここからは作成したtodoappディレクトリ以下で作業していきます。

またこのプロジェクトで作成するファイルは、必ず文字コード(エンコーディング)をUTF-8、改行コードをLFにしてファイルを保存します。

HTMLファイルの用意

エントリポイントとして、まずは最低限の要素だけを配置したHTMLファイルを作成しましょう。 エントリポイントとなるHTMLとしてindex.htmltodoappディレクトリに作成し、次のような内容にします。 body要素の一番下でscript要素を使い読み込んでいるindex.jsが、今回のアプリケーションの処理を記述するJavaScriptファイルです。

index.html

<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>Todo App</title>
  </head>
  <body>
    <h1>Todo App</h1>
    <script type="module" src="index.js"></script>
  </body>
</html>

次にindex.jstodoappディレクトリに作成し、次のような内容にします。 index.jsにはスクリプトが正しく読み込まれたことを確認できるように、コンソールにログを出力する処理だけを書いておきます。

index.js

console.log("index.js: loaded");

ここでのtodoappディレクトリのファイル配置は次のようになっていれば問題ありません。

todoapp
├── index.html
└── index.js

次はこのindex.htmlをブラウザで開いて、コンソールにログが出力されることを確認していきます。

ローカルサーバーでHTMLを確認する

ウェブブラウザでindex.htmlを開く前に、開発用のローカルサーバーを準備します。 ローカルサーバーを立ち上げずに直接HTMLファイルを開くこともできますが、その場合file:///から始まるURLになります。 fileスキーマではSame Origin Policyにより、JavaScriptモジュールが正しく動作しません。 そのため、本章はローカルサーバーを立ち上げた上で、httpから始まるURLでアクセスすることを前提としています。

コマンドラインでtodoappディレクトリへ移動し、次のコマンドでローカルサーバーを起動します。 npxコマンドを使い、この書籍用に作成された@js-primer/local-serverというローカルサーバーモジュールをダウンロードと同時に実行します。 まだnpxコマンドが用意できていなければ、先に「アプリケーション開発の準備」の章を参照してください。

# todoapp/ディレクトリに移動する
$ cd todoapp/
# todoapp/をルートにしたローカルサーバを起動する
$ npx @js-primer/local-server

todoappのローカルサーバを起動しました。
次のURLをブラウザで開いてください。

  URL: http://localhost:3000

起動したローカルサーバーのURL(http://localhost:3000)へブラウザでアクセスしてみましょう。 ブラウザにはindex.htmlの内容が表示され、開発者ツールのコンソールにindex.js: loadedというログが出力されていることが確認できます。

Webコンソールにログが表示されている

開発者ツールでのコンソールログの確認方法

Console APIで出力したログを確認するには、ウェブブラウザの開発者ツールを開く必要があります。 ほとんどのブラウザで開発者ツールが同梱されていますが、本章ではFirefoxを使って確認します。 開発者ツールのコンソールタブを開くとConsole APIで出力したログを確認できます。

Firefoxの開発者ツールは次のいずれかの方法で開きます。

  • Firefox メニュー(メニューバーがある場合やmacOSでは、ツールメニュー)の Web 開発サブメニューで "Web コンソール" を選択する
  • キーボードショートカットCtrl+Shift+K(macOSではCommand+Option+K)を押下する

詳細はMDNの「Webコンソールを開く」を参照してください。

コンソールログが表示されない

HTMLは表示されるがコンソールログにindex.js: loadedが表示されない場合は、次のような問題に該当してないかを確認してください。

[エラー例] index.jsの読み込みに失敗している

script要素のsrc属性指定したindex.jsのパスにファイルが存在しているかを確認してください。 <script type="module" src="index.js">としてした場合はindex.htmlindex.jsは同じディレクトリに配置する必要があります。

また、CORS policy Invalidのようなエラーがコンソールに表示されている場合は、Same Origin Policyによりindex.jsの読み込みが失敗しています。 先ほども紹介したように、file:から始まるページ上からはJavaScriptモジュールは正しく動作しません。 そのため、ローカルサーバーを起動し、ローカルサーバー(http:から始まるURL)にアクセスしていることを確認してください。

[エラー例] JavaScriptモジュールに非対応のブラウザを利用している

JavaScriptモジュールはまだ新しい機能であるため、バージョンが60以上のFirefoxが必要です。 バージョンが60未満のFirefoxでは、JavaScriptモジュールであるindex.jsが読み込めないためコンソールログは出力されません。

今回のTodoアプリでは、ネイティブでJavaScriptモジュールに対応しているブラウザが必要です。 Can I UseにてネイティブでJavaScriptモジュールに対応しているブラウザがまとめられています。 非対応のブラウザでもBundlerと呼ばれるツールを使うことで対応できますが、本章では省略します。


モジュールのエントリポイントの作成

最後にエントリポイントとなるindex.jsから別のJavaScriptファイルをモジュールとして読み込んでみましょう。 このアプリではJavaScriptモジュールが複数登場するためsrc/というディレクトリを作り、src/の下にJavaScriptモジュールを書くことにします。 今回はsrc/App.jsにファイルを作成し、これをindex.jsからモジュールとして読み込みます。

ここでのtodoappディレクトリは、次のようなファイル配置となるようにsrc/App.jsを作成していきます。

todoapp
├── index.html
├── index.js
└── src
    └── App.js

src/App.jsにファイルを作成し、次のような内容のJavaScriptモジュールとします。 App.jsAppというクラスを名前付きエクスポートしているモジュールです。 また、Appクラスのコンストラクタにはコンソールログを出力するコードを確認用に書いておきます。

src/App.js

console.log("App.js: loaded");
export class App {
    constructor() {
        console.log("App initialized");
    }
}

次に、このsrc/App.jsindex.jsから利用するためにimportします。 index.jsを次のように書き換え、App.jsからAppクラスをインポートしてインスタンス化します。

index.js

import { App } from "./src/App.js";
const app = new App();

再度ローカルサーバーのURL(http://localhost:3000)にブラウザでアクセスし、リロードしてみましょう。 コンソールログには、次のように処理の順番どおりのログが出力されます。

App.js: loaded
App initialized

まずindex.jsからsrc/App.jsが名前付きエクスポートしているAppクラスを名前付きインポートしています。 次にAppクラスがインスタンス化されていることがログから確認できます。

これでHTMLとJavaScriptそれぞれのエントリポイントの作成と動作を確認できました。

App.jsの読み込みに失敗する

ここまでのJavaScriptモジュールの読み込みでエラーが発生して動かない場合には次のことを確認します。

ディレクトリ構造やimport文で指定したファイルパスが異なると、ファイルを読み込むことができずにエラーとなってしまいます。 この場合は開発者ツールを開き、コンソールにエラーが出ていないかを確認してみてください。

import文を使ったJavaScriptのモジュール読み込み時に起きる典型的なエラーと対処を次にまとめています。

[エラー例] SyntaxError: import declarations may only appear at top level of a module

import宣言はモジュールのトップレベルでしか利用できません」というエラーがでています。 このエラーがでているということは、import文を使える条件を満たしていないということです。 つまり、import文がトップレベルではない所に書かれている、またはモジュールではない実行コンテキストで実行されているということです。

関数の中などにimport宣言していると、import宣言がトップレベルではないためエラーが発生します。 この場合はimport文をトップレベル(プログラムの直下)に移動させてみてください。

モジュールではない実行コンテキストで実行されているというのは、裏を返せば実行コンテキストがScriptとなっているということです。 JavaScriptには実行コンテキストとしてScriptとModuleがあります。 import文は実行コンテキストがModuleでないと利用できません。 そのため、script要素のtype属性にmodule指定を忘れていないかをチェックしてみてください。

実行コンテキストをモジュールとして実行するには<script type="module" src="index.js">のようにtype=moduleを指定する必要があります。 (index.jsからimport文で読み込んだApp.jsは実行コンテキストを引き継ぐため、モジュールの実行コンテキストで処理されます。)

[エラー例] モジュールのソース “http://localhost:3000/src/App” の読み込みに失敗しました。

App.jsが読み込めていないというエラーがでています。 エラーメッセージをよく見るとAppとなっていてApp.jsではありません。

import文では、読み込むファイルの拡張子を省略しません。 そのため、Appのように拡張子(.js)を省略して書いている場合はこのエラーが発生します。

// エラーとなる例
import { App } from "./src/App";

正しくは次のように拡張子まで含めたパスを記述します。 また指定したパス(./src/App.js)にファイルが存在するかを確認してください。

// 正しい例
import { App } from "./src/App.js";

まとめ

このセクションでは、エントリポイントとなるHTMLを作成し、JavaScriptモジュールのエントリポイントとなるJavaScriptファイルを読み込むところまでを実装しました。

このセクションのチェックリスト

  • todoappという名前のプロジェクトディレクトリを作成した
  • エントリポイントとなるindex.htmlを作成した
  • JavaScriptのエントリポイントとなるindex.jsを作成しindex.htmlから読み込んだ
  • ローカルサーバーを使ってindex.htmlが表示できた
  • src/App.jsを作成し、index.jsからimport文で読み込めるのを確認した

ここまでのTodoアプリは次のURLで確認できます。