モジュール構文

【JavaScript】 export/importでモジュール化とファイル分割する方法

更新日:2020/03/19

JavaScriptで趣味レベルのプログラムなら、1ファイルですむことが多い。
でも時々見通しが悪くなって、機能ごとにファイル分割したくなることがあります。

しかし問題となるのが、依存性。
ブラウザが順番に読み込んでくれないとエラーになってしまいます。
そこで考えたのが、次の記事。
【Javascript】スクリプトファイルを任意の順番で動的に読み込む方法
scriptタグを動的に作成して、強引に読み込ませています。

ですがこんなことしなくてもファイルをモジュール化して、export/importという機能を使えば依存性の問題をクリアできるそうです。

なんだってーーー!!

そこでJavaScriptのexport/importについて、少し興奮気味に調べてみました。

 

export/importで混乱しないために

JavaScriptのexport/importは、ECMAScript2015(JavaScriptの仕様の2015年版)で策定された、比較的新しい機能です。
ですが、それ以前からサーバー向け(PHPのようにWebの処理をおこなう)JavaScriptでexport/importに似た機能が実装されています。

現に『JavaScript モジュール』で検索すると、Node.jsやCommonJSとか出てきます。
これらはサーバー側のJavaScriptやそれに関する仕様です。

さらにサーバー側のJavaScriptにも、以前からある機能にプラスして、ECMAScript2015のexport/importが実装されています。

そのためネットで検索すると同じような言葉が出てきて、JavaScriptのプログラム環境事情に詳しくない人は情報の取捨選択ができないのが現状です。

初心者のうちは見極めるのが難しいですが、サーバーサイドで動作するJavaScriptがあり、ブラウザ版とは少し異なるということを知っておいてください。

業務で使っている人は、サーバーサイドが多い気がしますね。
ネットの情報も、そちらが多くなるのかな?

参考:【JavaScript】 ECMAScriptってなに?

 

export/importの注意点

JavaScriptのexport/importは、いくつか注意点があります。

機能実装の問題

JavaScriptのexport/importは、ECMAScript2015(JavaScriptの仕様の2015年版)で策定された、比較的新しい機能です。
そのため当時はブラウザの対応が間に合わず、限定的な使用にとどまっていました。

今現在(2020年)はブラウザの対応が進んでいて、現在もアップデートされているブラウザなら使用できます。

しかし古いブラウザに対応する必要がある場合、使用しないほうがよいです。

記事を公開したのはは2020年でした。
今は気にしなくてよさそうです。

テスト環境の問題

export/importを使用するモジュールは、簡単な動作検証でもWebサーバー上でテストをする必要があります。

ブラウザは今表示しているWebページと異なるURLからの情報かどうかをチェックしています。
この時、一部の情報が遮断されます。

importするモジュールは、この遮断する情報に含まれているので、別のサーバーに置くことができません。

またローカル環境でファイルシステム上から呼び出しても、遮断されます。

簡単な動作検証でも、Webサーバー上でテストをする必要があるのです。

サーバー側で許可できれば、使用できます。

 

モジュールファイルについて

モジュール(ファイル)の拡張子

モジュールは基本的にファイル単位です。
拡張子は『.mjs』にしようねとGoogleさんは言っています。

ただし、『サーバーがmjsファイルをJavaScriptファイルとしてブラウザに送信してくれることが前提』とも言っています。
(Content-Typeをtext/javascriptで送信)

サーバーに適切な設定がされていないと、次のようなエラーがでます。

chrome:
『Failed to load module script: The server responded with a non-JavaScript MIME type of "". Strict MIME type checking is enforced for module scripts per HTML spec.』

Firefox:
『MIME タイプ (“”) が許可されていないため、“https://xxxx/xxx.mjs” からのモジュールの読み込みがブロックされました。』

サーバーの設定を自分でできない場合は、拡張子を『.js』にした方がよさそうですね。

※この記事では、拡張子を『.js』に統一しています。

モジュールを呼び出すコードもモジュール指定する

モジュールを呼び出すJavaScriptファイルは、次のようにtype="module"を付加してモジュール指定する必要があります。

ソース呼び出し

<script  type="module" src="https://affi-sapo-sv.com/tools/test.js"></script>

指定しないと、次のエラーが出ます。

Chrome:
Uncaught SyntaxError: Cannot use import statement outside a module

Firefox:
SyntaxError: import declarations may only appear at top level of a module

html上でコードを記述するときは、次のようにします。

インライン

<script  type="module" >
          ・・・コード
</script>

html上に記述するのはトップレベルのモジュール

scriptタグで呼び出すのは、トップレベルのモジュールだけです。
下位のモジュールは、トップレベルのモジュール内で読み込みます。

例えば次のような構造のモジュールがあるとします。

ディレクトリ構造 ┬ index.html
                         ├ top.js
                         └ module(ディレクトリ)
                                 ├ module1.js
                                 └ module2.js

index.html内で呼び出すのは、top.jsです。
module1.jsとmodule2.jsは、top.js内で呼び出します。

 

export/importの使い方

モジュール外部で参照できる要素にexportを付加

外部で参照させたいものにexportをつけます。
変数でも関数でもオブジェクトでもクラスでも、exportできます。

個別にexport

export const a = 10; // モジュール外部からアクセス可

const b = 100; // exportなし。モジュール外部からアクセス不可

export function c( ) { // モジュール外部からアクセス可
 console.log( b );
}

exportがないものは別のファイルからアクセスできません。
この性質を利用すると、モジュール内のプライベート変数としてカプセル化できます。

exportは個別にする必要はありません。
つぎのようにすると、最後にまとめてexportできます。

まとめてexport

export { a , c };

importの仕方

importは、次のように記述します。

import { a , c } from "https://xxxx/xxx.js";
console.log( a );
c( );

{ }内は、インポートしたモジュールでexport指定されているものを記述します。
全て記述する必要はありません。
必要なものだけでOKです。

また『名前 as 別名』で、別の名前に変更できます。

import { a as a1 , c } from "https://xxxx/xxx.js";
console.log( a1 );
c( );

『* as 名前』とすることで、一つのモジュールオブジェクトにまとめてインポートできます。
*を使用する場合、{ }が必要ありません。

import * as xxx from "https://xxxx/xxx.js";
console.log( xxx.a );
xxx.c( );

モジュールを階層化する

複数のモジュールを一つにまとめてインポートできます。

次のようなディレクトリ構造があるとします。

ディレクトリ構造 ┬ index.html
                         ├ top.js
                         └ module
                                 ├ module1.js
                                 ├ module2.js
                                 ├ module1(ディレクトリ)
                                 │        ├ module1a.js
                                 │        └ module1b.js
                                 └ module2(ディレクトリ)
                                           ├ module2a.js
                                           └ module2b.js

module1a.jsとmodule1b.jsは次のように変数aをexportしています。

module1a.js

export const a = 100;

module1b.js

export const a = 200;

module1.jsで、ひとつにまとめます。

module1.js

export * as test1a  from "https://xxxx/module/module1/test1a.js";
export * as test1b  from "https://xxxx/module/module1/test1b.js";

ここではimportではなくて、そのまま外部にexportしています。
このモジュール内では、test1aとtest1bを使用できません。

場合によっては次のようにimportした後、必要なものだけをexportします。

備考:必要なものだけexport

import * as test1a  from "https://xxxx/module/module1/test1a.js";
import * as test1b  from "https://xxxx/module/module1/test1b.js";
export const a = test1a.a * test1a.b;

module2.jsも同じように、ひとつにまとめます。

最後にtop.jsで、module1.jsとmodule2.jsをimportします。

top.js

import * as test1  from "https://xxxx/module/module1.js";
import * as test2  from "https://xxxx/module/module2.js";

console.log( test1.test1a.a ); // 100
console.log( test1.test1b.a ); // 200

 

デフォルトエクスポート

モジュールはひとつだけデフォルト指定できます。

module1.js

const a = 100;
export  default  a;
export const b = 200;

export default const a = 100;とするとエラーとなるので、宣言とエクスポートをわけています。

これを別のファイルにインポートします。

import * as test1 from "https://xxx/test1.js";
console.log( test1);

test1は次のようになっています。

test1{
        b : 200
        default : 100
}

変数aがdefaultという名前に置き換わっています!
default指定したexportデータは、インポートされるとdefaultという名前になるのです。

defaultのままだと使いにくいので、asで名前を変更します。

import {default as a , b } from "https://xxx/test1.js";
console.log( a );

これで元の変数名でアクセスできるようになりました。

この使い方だと、名前が変わるだけなのでdefault指定の意味がありませんね。

実はdefaultのみインポートするなら、次のように書けます。

import 新しい名前 from "https://xxx/test1.js";
console.log( a );

default指定したものは、{default as 新しい名前}と書くところを、新しい名前のみでOKになります。

そのあと、default以外をインポートしたいときは、次のように書きます。

import { b } from "https://xxx/test1.js";
console.log( a );

僕は複数のエクスポートで一つだけdefaultにするのは、使いにくい気がします。

defaultは、それ一つだけエクスポートするモジュールで使用した方がいいと思います。

 

モジュールはstrictモードで動作する

モジュール指定したファイルはstrictモードで動作します。
クラスもそうでしたが、JavaScriptの仕様策定者は、非strictモードを本当になかったことにしたいようです。

今後はモジュール以外でもstrictモードを使用したほうがいいかもしれませんね。

 

動的インポート

モジュールは動的インポートすることが可能です。

動的インポートの対応状況

動的インポートはECMAScript2015で策定されたexport/importよりも、もっと新しい機能です。
ECMAScript2020で策定されました。

今2020年なので、今年ですよ?
ブラウザに実装されていないのでは?

記事を公開したのはは2020年でした。

正式に策定される前に、ブラウザへの実装が進んでいます。
とはいっても、主要なブラウザで使用できるようになったのが2019年なので、動的インポートの使用は慎重に検討する必要がありそうです。

対応状況:https://caniuse.com/#feat=es6-module-dynamic-import

import()メソッド

動的インポートは、import()でおこないます。

構文:
import( url ).then( 成功コールバック , 失敗コールバック );

import()はモジュールへのパスを引数で受け取り、Promiseオブジェクトを返します。

そのため結果をPromiseオブジェクトのthen()で受け取ることができます。

インポートが成功すると、インポートしたモジュールを含むモジュールオブジェクトを引数として、コールバック関数が実行されます。

使用例は、こちらを見てください!

【JavaScript】 複数のモジュールを動的に順次読み込みしてみる

 

まとめ

export/importと動的インポートimport()は、新しい機能です。

使用するときは魔法の言葉 、『最新のブラウザでご利用ください』を活用しましょう!

更新日:2020/03/19

書いた人(管理人):けーちゃん

スポンサーリンク

記事の内容について

null

こんにちはけーちゃんです。
説明するのって難しいですね。

「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。

掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。

ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php

 

このサイトは、リンクフリーです。大歓迎です。