JavaScriptでは、exportやimportを使って関数やクラスを他のファイルから呼び出し/読み込みすることができますが、いろんな書き方があって何がどう違うのかごちゃごちゃしませんか?
少なくとも私はごちゃごちゃしちゃうので、備忘録も兼ねてまとめたいと思います。
es2015とNode.jsでの書き方の違い
まず最初の混乱ポイントは、export
なのか exports(module.exports)
なのか?
読み込みは import
、require
どっちなの?
めちゃくちゃややこしいですよね。けどこれ実は別物で、export/import
は、ES2015(ES6)での書き方exports/require
は、Node.js(CommonJS)での書き方なんです。
参考:CommonJSって何?という方は @naoki_mochizukiさんが書かれた JavaScriptが辿った変遷という記事を読んでみてください!JavaScriptの歴史がとてもわかりやすく説明されていて、CommonJSの正体もスッと理解できると思います!
以下に書き方を例を使ってまとめていきます。
export 方法一覧
ES2015(ES6)での書き方
1. 個々の機能をエクスポート
exportconstname="xxx";exportfunctionfuncName(){...}exportclassclassName{...}
2. 事前に宣言された機能をまとめてエクスポート
export{name,funcName,className};
3. デフォルトとして個別の機能をエクスポート
exportdefaultfunction(){…}// orexportdefault{name,funcName,className}
Node.js(CommonJS)での書き方
4. 単一のモジュールをエクスポート
module.exports=xxx;
5. エクスポートしたい個別の機能を指定し、オブジェクトとしてエクスポート
module.exports={xxx,yyy};module.exports={xxxName:xxx,yyyName:yyy};
import 方法一覧
ES2015(ES6)での書き方
1. モジュールから特定のエクスポートをインポートする
import{xxx}from"./lib"import{xxx,yyy}from"./lib"
2. モジュールのコンテンツすべてをインポート
import*aslibfrom"./lib"
3. デフォルトをインポート
importxxxfrom"./lib"
Node.js(CommonJS)での書き方
4. エクスポートされたものをまとめてインポート
constxxx=require("./lib")
5. エクスポートされたものを個別にプロパティ名を指定してインポート
const{xxx,yyy}=require("./lib")
使用例
それでは、実際のコードの例を見て見ましょう。
ES2015(ES6)の場合
例1
exportconstadd=(num1,num2)=>{returnnum1+num2;};exportconstminus=(num1,num2)=>{returnnum1-num2;};
import{add,minus}from'./export1.js';add(1,2);// => 3minus(1,2);// => -1
個々の機能をエクスポートする場合、インポートしたい物を {} で括って個別に呼び出します。
例2
constadd=(num1,num2)=>{returnnum1+num2;};constminus=(num1,num2)=>{returnnum1-num2;};export{add,minus};
import{add,minus}from'./export2.js';add(1,2);// => 3minus(1,2);// => -1
事前に宣言された機能をまとめてエクスポートした場合も同じ。
インポートしたい物を {} で括って個別に呼び出します。
例3
constadd=(num1,num2)=>{returnnum1+num2;};constminus=(num1,num2)=>{returnnum1-num2;};export{add,minus};
import*ascalcfrom'./export3.js';console.log(calc);// => [Module] { add: [Function: add], minus: [Function: minus] }calc.add(1,2);// => 3calc.minus(1,2);// => -1
これは、calc
という名前で export3.js のファイルのモジュールからのエクスポートすべてインポートします、という書き方です。
例4
constadd=(num1,num2)=>{returnnum1+num2;};constminus=(num1,num2)=>{returnnum1-num2;};exportdefault{add,minus};
importcalcfrom'./export4.js';console.log(calc);// => { add: [Function: add], minus: [Function: minus] }calc.add(1,2);// => 3calc.minus(1,2);// => -1
export default
でエクスポートすると、インポート時に {} で括る必要がなく、まとめてインポートされます。
例5
exportdefaultfunctionadd(num1,num2){returnnum1+num2;}
importaddfrom'./export5.js';console.log(add(1,2));// => 3
export default
する時には、個別の機能をダイレクトにエクスポートすると、インポートしたものをそのまま使用できます。
注意
コマンドでこれらのファイルを実行する時に、下記のように node
コマンドで実行しても、エラーが出て期待通りに実行できません。
$ node import5.js
/User/xxx/import5.js:1
import add from './export5.js';
^^^^^^
SyntaxError: Cannot use import statement outside a module
(エラーが続きます...)
なぜなら、ES2015(ES6) で書かれたコードは node コマンドでそのまま実行できないからです。
実行したい場合は、babel などのトランスパイラを使って ES2015(ES6) で書かれた JavaScript を Node.js が理解してくれるようにトランスパイルするなどの一手間を加える必要があります。
けど、今回はその環境を作るのがちょっとめんどくさかったので、下記オプションを付けて確認しました。
(--experimental-modules
の説明は割愛します。)
$ node --experimental-modules import5.js
(node:10746) ExperimentalWarning: The ESM module loader is experimental.
3
Node.js(CommonJS)の場合
例6
constadd=(num1,num2)=>{returnnum1+num2;};constminus=(num1,num2)=>{returnnum1-num2;};module.exports=add;
constadd=require('./export6');add(2,3);// => 5
import6.js でインポートする時にconst { add } = require('./export');
という風に{} で括ってしまうと、add(2, 3)
実行時にエラーになってしまいます。
なぜでしょうか??
exportされているのは add
。
すなわち、関数自体が export されているのですが、add
というプロパティ名でエクスポートされたものをインポートしようとしているので、そんなプロパティは存在しないよ、とエラーが発生してしまいます。
では次の例はどうなるでしょうか。
例7
constadd=(num1,num2)=>{returnnum1+num2;};constminus=(num1,num2)=>{returnnum1-num2;};module.exports={add,minus};
constadd=require('./export7');add.add(2,3)// => 5add.minus(2,3)// => -1add(2,3);// エラー
exports7.js では個々の関数をオブジェクトとしてエクスポートしているので、import7-1.jsのaddには、{ add: [Function: add], minus: [Function: minus] }
というオブジェクトが代入されているのです。
ではもし、下記のように {} で括ってインポートしたらどうなるでしょうか。
const{add,minus}=require('./export7');add(2,3)// => 5minus(2,3)// => -1
それぞれの変数には対応するプロパティの値、すなわち関数が代入されるので、そのまま関数として実行できるようになります。
最後に、これは少し極端な例を見てみましょう。
例8
constadd=(num1,num2)=>{returnnum1+num2;};constminus=(num1,num2)=>{returnnum1-num2;};module.exports={addName:add,minusName:minus};
const{addName,minusName}=require('./export8');addName(2,3)// => 5minusName(2,3)// => -1
もう何が起こっているかはわかりますね!
export8.js で、add
という名前の関数を addName
というプロパティ名でエクスポートしているので、{ addName }
でインポートしてあげる必要がありますね。
もしこれを const func = require('./export');
とインポートしたら、func.addName(2, 3)
といった感じで呼び出してあげればOKです!