いつまで経っても使い分けが分からないので調べてみました。
モジュールとは
まずは「モジュール」について理解する必要があります。
モジュールとは JavaScript プログラムの一部を分割したものです。
何かアプリを作成するときには1つのファイルに全てを書き込むのではなく、メインのファイル + 一部機能に特化したファイル という構成にしますよね。このうち一部の機能に特化したファイルをモジュールといいます。
当然分割しっぱなしではいけないので、メインのファイルからモジュールを読み込んで使う必要がありますね。
import/export や require/exports はそんなモジュールを利用するために存在する構文です。
import/export
ネイティブの JavaScript モジュール機能は、import と export 文を利用します。
JavaScript モジュール - JavaScript | MDN
つまり Node.js 環境である必要はなく、ブラウザの JavsScript で動作します。2つの種類があります。
- 複数の値をエクスポートする「名前付きエクスポート」
- 1つの値をエクスポートする「デフォルトエクスポート」
さっそく試してみましょう。
※ file:// の URL ではモジュールが使えず CORS エラーとなってしまうため、LiveServer などで立ち上げましょう
<!DOCTYPE html>
<html lang="js">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>モジュールのテスト</title>
</head>
<body>
<p>HTMLページです</p>
<script type="module" src="./script.js"></script>
</body>
</html>
こんな感じで HTML ページを作成しました。 script タグを読み込む際に type="module"
とつけるのがポイントです。
script.js
はこのように書いてみましょう。
const body = document.body;
const p = document.createElement("p");
p.innerText = "メインの js ファイルです";
body.appendChild(p);
こんな感じでページが表示されたかと思います。
では import/export
でモジュールの読み込みを試してみましょう! 手元で試す場合、以下のようなファイル構成にしてください。
├── module/
│ ├── module.js
├── index.html
├── script.js
では module.js
を書いていきましょう。
const p = document.createElement("p");
p.innerText = "モジュールの js ファイルです";
export default p;
ここで使っているのは デフォルトエクスポート です。変数 p
のみをエクスポートしています。ではインポート側を見てみましょう。 script.js
を少し編集します。
// + ここを追加
import moduleP from "./module/module.js"
const body = document.body;
const p = document.createElement("p");
p.innerText = "メインの js ファイルです";
body.appendChild(p);
// + ここを追加
body.appendChild(moduleP);
p
という変数名が被ってしまうので、 import moduleP from "./module/module.js"
というふうにモジュール側の変数名を変更しています。便利ですね。
実行すると以下のようになるはずです。
index.html
から読み込みしていない、 module.js
で作成した p タグを表示できていますね!
ではもう1つのエクスポート方法、名前付きエクスポートを使ってみましょう。module からさらに他のものをエクスポートしたいとしましょう。
const moduleP = document.createElement("p");
moduleP.innerText = "モジュールの js ファイルです";
// + ここを追加
const clickHandler = (e) => {
console.log("clicked")
}
// - ここを削除
// export default p;
// + ここを追加
export { moduleP, clickHandler };
p タグにイベントハンドラを追加してからエクスポートすればいい話なのであまりいい例ではないですが、インポートしてみましょう。
// + ここを追加
import * as module from "./module/module.js"
const { moduleP, clickHandler } = module;
// - ここを削除
// import moduleP from "./module/module.js"
const body = document.body;
const p = document.createElement("p");
p.innerText = "メインの js ファイルです";
// + ここを追加
moduleP.addEventListener("click", clickHandler);
body.appendChild(p);
body.appendChild(moduleP);
名前付きエクスポートされた変数たちはオブジェクトの形でインポートされるので、分割代入を使って取り出しています。
このように動作するはずです。
関数もエクスポートすることができましたね!
ちなみに2つのエクスポート方法は併用可能です。
また import/export
はブラウザでしか使えないという表現は正確ではなく、Babel などでトランスパイルすれば Node.js 環境でも使えます。
require/exports
こちらは Node.js の環境で動作するモジュールを読み込む構文です。
外部のライブラリを読み込むためにも使われますが、今回は import/export と比較するために自分でエクスポートしたものを要求(require)してみましょう。
以下のようなファイル
├── package.json
├── module/
│ ├── module.js
├── index.js
const modules = require("./modules/module");
const sayHello = () => {
return "Hello!";
}
const { sayMorning } = modules;
console.log(sayHello());
console.log(sayMorning());
exports.sayMorning = () => {
return "Good morning!";
}
以下のようになったでしょうか。
index.js
と module.js
、両方の関数の実行結果がコンソールに出力されましたね。
注目すべきはやはり exports
と export
がややこしい! ということでしょうか。
正確な由来はわからないのですが、 exports
の性質として exports.〇〇
でいくらでもエクスポートできる点が挙げられます。
export
はデフォルトエクスポートで使う場合もあるので複数形とは限らない、と一旦こじつけて覚えようと思います。
require/exports
はブラウザでそのまま使うことができず、やはりこちらもトランスパイルする必要があります。
まとめ
- import/export: ブラウザの JavaScript で使える
- デフォルトエクスポートは1つだけ変数をエクスポートできる
- 名前付きエクスポートならオブジェクトの形で複数の変数をエクスポートできる
- require/exports: Node.js 環境で使える
-
exports.〇〇
, const obj = require("{ path }")` の形で使う
-
-> そもそも使う環境が違う!
※ トランスパイルしてもう片方の環境で使うことも可能