JavaScriptのモジュール
今のJavaScriptでは、あるスクリプトファイルから他のファイルに書かれているスクリプトを読み出すことができるようになっています。別のファイルから読みだすことができるようになっているコードは「モジュール」とよばれます。しかし、このモジュールのコーディングはブラウザで動かす場合と、サーバーサイドで利用する場合とで書き方が違います。
ブラウザ用とサーバーサイドの区別があいまいだと、ネットに書いてある方法通りにコードを書いても動かないということがあります。
今回はその違いについて触れながら、JavaScriptでモジュールを作る方法を書き残しておきます。
ECMAScript(ES)
先に、JavaScriptの種類に触れておきます。ひとつめにECMAScript(ES)があります。ブラウザで動いているのがこれに当たります。ECMAScriptはブラウザ毎に独自仕様が認められているJavaScriptの基準として定められるものです。ES2015(ES6)とともに記事がかかれています。これはES5からES6への変更が大きかったことに由来します。ES2015の2015は西暦です、ES6の6は6世代目の意味です。ES2015(ES6)以降からは世代ではなく西暦をつける方が正式名称とされています。毎年改定されこの記事を書いている時点ではES2020が存在します。
次で示す、ExportとImportはこのES2015以降に対応したブラウザでないと利用できません。scriptタグ内で用いる時は呼び出す側もtype属性値に"module"を指定する必要があります。
ExportとImport
ImportとExportはブラウザのコーディングで用います。あるJavaScriptファイルから他のファイルに記述されているJavaScriptコードを読み出します。
読みだされる側はExportとして用意しておき、読み出す側はImportで読み出します。
JavaScriptの定数や変数、関数、クラスはexportディレクティブを付けることで、exportが可能になります。また宣言時にexportせず、あとでまとめて記述することも可能です。
...
export { weekday };
インポートする際の書式はimport文を使います。
元のファイルがexportとして設定してあるすべての定数や変数、関数、クラスをimportする場合は*(ワイルドカード)を使います。
「as objects」の部分ですが、importしたものを受け入れるオブジェクトを自由に名付けてasの後に指定します。そうすることで、元のファイルからインポートしたものを、「objects.holiday」という形で利用できるようになります。
export時に「export default」と指定すると利用の仕方が少し変わります。
default exportされたものをimportする際は次のようにします。
defaultを指定しないときは{}がありましたが、ここではそれがありません。また「object」には好きな名前を設定できます。そこにはdefaultに指定されていいるものが入ります。つまり、元の名前に依存しません。
ただし、コーディングルールとして「object」の部分には元のファイル名、ここでは「ソースファイル」とすることが定められています。
また「* as objects」と、*を使ってdefaultを受け取った場合は「objects.default」とすることでオブジェクトを取得できます。
注意しないといけないのはexportを記述したJavaScriptファイルは通常の形でHTMLから読みだせないことです。<script type="module" src="./script.js">とすると、exportディレクティブの部分でのエラーは回避できますが、読み込んだJavaScriptは通常のようには利用できません。
HTMLからモジュール読みだしたい場合は、<script type="module">タグの中に、先の手続きを書いて利用します。
またESM(ES Module)が記述されているファイルは.jsではなく.mjsという拡張子を利用するように決められたのですが、scriptタグで.mjsファイルを読み込もうとするとブラウザによってはエラーになるようです。
この部分に関しては、「エクスポートとインポート」を参考にさせていただきました。ありがとうございます。
exportsとrequire(CommonJS)
他のファイルに記述されているJavaScriptコードを読み出す仕組みには、先の「export/import」とは別のアプローチがあります。「exportsとrequire」を使うもので、これらは「CommonJS」という「ECMAScript」とは別の仕様に基づいています。
ブラウザ用の仕様のECMAScriptとは違うので、ブラウザではexportsとrequireの入ったコードは動きません。
しかし、webpack等の複数のモジュールを一つにまとめるモジュールバンドラを経由して、Webブラウザ用のJavaScriptを生成することもあり、Web用のスクリプトでも内部ではrequireでモジュールが読みだされている場合もあります。
これらの使い方ですが、読みだされる側は「exports」というオブジェクトのプロパティとして設定します。
exports.getSomethingNew= function(param) {...}
読みだす側では「require」を使い対象となるexportsを任意のオブジェクトに読み出します。
console.log(obj.holiday);
この時、一括でなく個別に読み出すこともできます。
こちらに関してはQiita:「# CommonJS と ES6の import/export で迷うなら」を参考にさせていただきました。ありがとうございます。
またCommonJSとESの違いについてもっと詳しく知りたい場合は「.mjs とは何か、またはモジュールベース JS とエコシステムの今後」を読むといいと思います。
モジュールを作成する
まずECMAScriptのimport/exportで記述してみます。関数や変数名の衝突の可能性を考慮し、クラスを作成します。
samplelib.js
export default class Samplelib{
constructor(params) {
this.params=params || {};
}
static toUpperCase(str) {
return str.toUpperCase();
}
toLowerCase(str) {
return
str.toLowerCase();
}
}
クラスには初期化をするためのconstructor、各値を保持するプロパティ、関数(メソッド)があります。
メソッドにはstatic(静的)とそうでないものがあります。staticでないメソッドはインスタンスからしか呼び出せません。インスタンスとは、let c = new ClassName();としたときのcを指します。逆にstaticメソッドは、インスタンス(c)からは呼び出せません。ClassNameから呼び出します。
sample.js
import Samplelib from './samplelib.js';
let slib = new Samplelib();
console.log(slib.toLowerCase("DEF"));//def
console.log(Samplelib.toUpperCase("ghi"));//GHI
//staticの挙動をわかりやすくするためのサンプルです。次の2行の記述はエラーになります。
console.log(slib.toUpperCase("abc"));//not
a functionエラー
console.log(Samplelib.toLowerCase("JKL"));//not a functionエラー
クラスはHoisting(巻き上げ)されないので、使用前にインポートや定義をしておく必要があります。
HTML内のスクリプトタグの内側にインポートを書こうとして「Uncaught SyntaxError: Cannot use import statement outside a module」のエラーがでた際はスクリプトタグを<script>を<script type="module">とします。
先ほどのスクリプトをCommonJS形式で記述しなおすと次のようになります(エラーとなる部分は省略します)。このスクリプトはサーバーサイドスクリプトであるNode.js等で動かすことができますが、ブラウザでは動きません。また、Node.jsでexport/import形式で書かれたスクリプト動かすためには、拡張子をjsからmjsに変更する必要があります。
sampleNode.js
let SamplelibNode = require('./samplelibNode.js');
let slib = new SamplelibNode.Samplelib();
console.log(slib.toLowerCase("DEF"));
console.log(SamplelibNode.Samplelib.toUpperCase("gui"));
samplelibNode.js
class Samplelib{
constructor(params) {
this.params=params || {};
}
static toUpperCase(str) {
return str.toUpperCase();
}
toLowerCase(str) {
return
str.toLowerCase();
}
}
exports.Samplelib=Samplelib;