[追記] Node.js 14 から警告なしに拡張子 .mjs は export / import できるようになります。
背景
Ecma 262 + JScript でユニットテストを書いて から 10 年以上が経ち、途中 Rails 3 + CoffeeScript いいじゃないか、Underscore.js いいじゃないか期を経て現在は ES2015 が標準で当たり前ですよね、な時代になった。
ES2015 自体はなんでもかんでも式になってくれないと CoffeeScript の代わりにはならないよ、return 書くの面倒くさいよという気持ち以外はまぁそんなもんかなという程度の理解だったんだけど、Mithril を試した時 のように無理やり Browserfiy で CoffeeScript を挟んだりせずにそろそろ 素直に ES2015 をフルに使って書きつつ、それ以外に本当に必要なライブラリのセットをまとめる作業を行った方が UI 側の実装に弾みがつくかなと思って触ってみたらどうも ES2015 がフル実装されている処理系はないっぽいので、結局ややこしさが残っているのだなということが分かってきた。
ということでそろそろ少なくとも ES2015 でよく分からんなと思っていた部分を整理していこうと思う。これ以上時間が経つと情報が見つからなくなるかもしんないし。
これを書いている時点での自分の理解をまとめると Node.js と Browserify を適当に使っている範囲のもので、Ecma 262 3rd 以降に加わったものはちゃんと追えておらず、以下のような状態。
- Webフロントエンドのツールチェインはブラウザなしに動く Node.js であれこれ処理するのが基本になった
- 伝統的な JS にはライブラリを読み込む機能はなく、ホスト側でまかなっていた(<script>)が、XHR + eval などで動的に読み込むこともできる
- Node.js 登場以降 module と require の概念が持ち込まれて function 以外のスコープが増えた
- Browserify は Node.js のこのメリットをブラウザの世界に持ち込んだもの
- ついでに Node.js 上でしか動かない機能以外は組み込めるようになった
- Babel は先行案の JS の文法を使ったコードを変換して Ecma 5 相当にしてくれる
ここから、もうちょっとつっこんだ部分の理解を深めたいと思う。
ちなみに、以降の確認は主に以下の環境で行っている。
- Node.js v6.9.5
- Yarn 0.19.1
- Babel 6.23.1
import / export
import とかよく見るじゃないですか、React とか Babel 前提になったコードで。でもなんとなく見よう見まねの域を出なくて気持ち悪いと思っていたのでした。
- Learn ES2015 · Babel
- import - JavaScript | MDN
- export - JavaScript | MDN
- ブラウザには実装されていないからトランスパイラ使えと
- ES Modules と Node.js について - from scratch
- 議論の最中ということで、こういうのは結論待ってもあんまり意味なさげ。
2017-02 時点で結論から言うとまず Babel を使えと。そういう状況らしい。
ES Modules(ES2015) import -> Babel -> CommonJS require
結局 require に書き換えて読み込むことになる。
安定して動かせる import は今のところこの手の変換以外ないみたい。
単純な import
import './export' -> require('./export')
無名で(defaultで)定義して import 側で名前の割り当て
export default で名前のない function や class を export できるので、import 側で名前を割り当てて利用する。
※ 以降、<name> となっている部分は <> 含めて実際の名前に読み替えplz
import.js
import <name> from './export'
<name>()
export.js
export default function() {
...
}
を babel で変換すると
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = function () {
...
};
てな感じで exports.default に function が入る。class の場合はもうちょっと複雑になるけど
export default class {
method {
...
}
}
が
var _class = function() {
...
_createClass(_class, [{
key: <method>,
value: function <method>() {
...
}
}]);
return _class;
}();
exports.default = _class;
てな感じで、やはり exports.default に class が入る。
名前付きで定義したものを export / import
{ } で囲む。
※ as は割愛
import.js
import { <name> } from './export'
<name>()
export.js
function <name>() {
...
}
export { <name> }
class もまったく同様。
'use strict'
Ecmascript 5 で追加された機能で Babel で変換したコードには現時点では自動で追加される。
- 意図せぬグローバル汚染を防止
- 代入の失敗は例外に
- 削除できないプロパティの削除をエラーに
- オブジェクトリテラルのプロパティ重複もエラーに
- 関数の引き数名の重複もエラーに
- 8進数表記の禁止
基本的には予期せぬエラーが起きにくくなる改善なんだけど、一つ問題があって strict モードでの動作の指定をグローバルに行おうとした場合、strict モードでの動作を意図したコードと strict モードでの動作を意図していないコードを混ぜると、先に決まったモードで動くのでお互いに意図せぬ動作をする場合があること。
したがってどんなコードを混ぜ込んでいるか分からない、伝統的な JS のコピペベースのライブラリの利用を行った場合、この指定は function 単位で行うことが望ましい。でないとどこで何が起きるかよく分からなくなってしまう。
いや、面倒だよねそれ。
というところまで分かったところで Babel に戻ると、Babel は es2015 の preset で Ecmascript 5 に変換する際に自動的に 'use strict' を必ずファイルの先頭に置くので、自動的にすべて strict モードで動作するように変換される。
ということで Ecmascript 5 を前提にするなら Babel 使えという話に帰ってくるのであった。
class / メソッド定義
JavaScript MDN</a> - class定義は事前に済んでいないと呼び出せない
- なんか PHP 3 までの制限のような?
- constructor, extends, super なんか PHP というか Java っぽい?
- name: function() {} が 単に name() {} で書けるようになった
- オブジェクトリテラルでもclass式でも同様
- やった! function 地獄からの解放?
- でも割とブラウザの対応状況はよくない(まぁ Babel 通すならどっちでもいいけど)
メソッドとグローバルな関数の定義はベツモノで function を書かずに済むようになったわけではないうえに => (arrow function)もまたベツモノ。
いったんここまで。