ECMAScript 2015 (ES6) in Node.jsでimportとexportがエラーになる

Node.jsがECMAScript 2015をサポートしているということで、Node.jsだけで動かすものはECMAScript 2015のまま実行してしまおうとしたところ、importのところがエラーになるので調べてみました。

環境

  • OS X El Capitan バージョン 10.11.3
  • Node.js バージョン 5.5.0

ECMAScript 2015 (ES6) in Node.js?

Which features ship with Node.js by default (no runtime flag required)?

  • Block scoping
    • let (strict mode only)
    • const
    • function-in-blocks (strict mode only [1])
  • Classes (strict mode only)
  • Collections
    • Map
    • WeakMap
    • Set
    • WeakSet
  • Typed arrays
  • Generators
  • Binary and Octal literals
  • Object literal extensions (shorthand properties and methods)
  • Promises
  • New String methods
  • Symbols
  • Template strings
  • Arrow Functions
  • new.target [2]
  • Object.assign
  • Spread operator [2]

Which features are behind the –es_staging flag?

  • Symbol.toStringTag (user-definable results for Object.prototype.toString, behind flag –harmony_tostring)
  • Array.prototype.includes (determine whether an array includes a certain element, behind flag –harmony_array_includes) [2]
  • Rest Parameters (represent an indefinite number of arguments as an array, behind flag –harmony_rest_parameters) [2]

Which features are in progress?

New features are constantly being added to the V8 engine. Generally speaking, expect them to land on a future Node.js release, although timing is unknown.

ECMAScript 2015 (ES6) | Node.js

これを見ると、Node.jsのサポートする3段階あるうちのデフォルトではimportってないですし、StagedにもIn progressにもないですよね。 GitHub - callemall/material-ui: React Components that Implement Google's Material Design.とかソースを見ているとimportを使っているから、悪意なくそのまま使い始めてしまっていたんですけど、こういう意識が危険で長期的な成長がないと感じ始めているところです。

Module Loaders

Not part of ES2015 This is left as implementation-defined within the ECMAScript 2015 specification. The eventual standard will be in WHATWG's Loader specification, but that is currently a work in progress. What is below is from a previous ES2015 draft.

Module Loaders - Learn ES2015 · Babel

こちらにはModule LoadersがES2015の一部ではないことが記載されていました。

そして、現在の自分なりの暫定的結論として、まだimportやexportは使わずにNode.jsでそのまま実行できる形のrequireとexportsを使うのがいいと思っています。 ECMAScriptで正式に決定したらそれを使うようにしようと思っています。

importとexportの確認

importとexportを使ってみます。

// a.js
import b from './b';

(() => {
  console.log(`b: ${b()}`);
})();
// b.js
export default () => {
  return 'b!';
};

実行するとimportのところでエラーになります。

$ node a.js
a.js:1
(function (exports, require, module, __filename, __dirname) { import b from './b';
                                                              ^^^^^^

SyntaxError: Unexpected token import
    at exports.runInThisContext (vm.js:53:16)
    at Module._compile (module.js:387:25)
    at Object.Module._extensions..js (module.js:422:10)
    at Module.load (module.js:357:32)
    at Function.Module._load (module.js:314:12)
    at Function.Module.runMain (module.js:447:10)
    at startup (node.js:139:18)
    at node.js:999:3

requireに修正

importとしていたところをrequireに修正します。

// a.js
const b = require('./b');

(() => {
  console.log(`b: ${b()}`);
})();

今度はexportのところがエラーになりました。

$ node a.js
b.js:1
(function (exports, require, module, __filename, __dirname) { export default () => {
                                                              ^^^^^^

SyntaxError: Unexpected token export
    at exports.runInThisContext (vm.js:53:16)
    at Module._compile (module.js:387:25)
    at Object.Module._extensions..js (module.js:422:10)
    at Module.load (module.js:357:32)
    at Function.Module._load (module.js:314:12)
    at Module.require (module.js:367:17)
    at require (internal/module.js:16:19)
    at Object.<anonymous> (a.js:1:73)
    at Module._compile (module.js:413:34)
    at Object.Module._extensions..js (module.js:422:10)

exportsに修正

exportのところをexportsに修正します。

// b.js
exports.default = () => {
  return 'b!';
};

今度は呼び出しのところで関数じゃないっていうエラーになりました。

$ node a.js
a.js:4
  console.log(`b: ${b()}`);
                    ^

TypeError: b is not a function
    at a.js:4:21
    at Object.<anonymous> (a.js:5:3)
    at Module._compile (module.js:413:34)
    at Object.Module._extensions..js (module.js:422:10)
    at Module.load (module.js:357:32)
    at Function.Module._load (module.js:314:12)
    at Function.Module.runMain (module.js:447:10)
    at startup (node.js:139:18)
    at node.js:999:3

呼び出しをdefaultに修正

requireした時のbの型を見るとobjectになっていました。 そして、babelで変換したソースを見たらexport defaultの場合はdefaultという名前のプロパティを呼び出すようになっていました。 なので、defaultという名前のプロパティを呼び出すように修正します。 export.defaultっていうのは単純にdefaultっていう名前のプロパティに設定しているだけなんですね。 export.funcbだったらfuncbっていう名前のプロパティに設定しているのと同じことだったんですけど、何かdefaultって特別のような気がして気がつきませんでした。

// a.js
const b = require('./b');

(() => {
  console.log(`b: ${b.default()}`);
})();

エラーがなくなりました。

$ node a.js
b: b!

終わり

ECMAScriptを含めた周辺の動きに過熱感がありすぎてバブルのような印象をうけますけど、適切な関わり方をしていくことが大切だと思います。

「最も重要なのは、自分の能力の輪をどれだけ大きくするかではなく、その輪の境界をどこまで厳密に決められるかです」 ウォーレン・バフェット

参考

30 March 2018 追記

次の記事を書きました。