SlideShare a Scribd company logo
Na#ve ESM への道
∼ 最終章: Babel / TypeScript Modules との闘い ∼
Node 学園 35 時限目 / 2021-02-24
@teppeis
1
自己紹介
• @teppeis / Teppei Sato
• サイボウズから来ました。
• もちろん We are hiring!!!
• プロダクト開発チーム
• フロントエンドエキスパートチーム
2
Node.js の ES Modules 対応って
どうなったの?
3
前回までのあらすじ
2015 年の ES Modules (ESM) 策定からときは流れ、Node.js での ESM
対応は混迷を極めた1
。
しかし 2018 年 10 月、 Node.js Modules Team は ESM 対応計画 2
を
なんとかまとめあげ、ようやく実装が進んでいった。
そして 2020 年末にはその計画が実装完了し、2021 年は Na8ve ESM
時代が到来するかに見えた。
2
Plan for New Modules Implementa5on · nodejs/modules
1
前回スライドを参照: You Don't Know ES Modules, Teppei Sato (2016)
4
Node.js ESM 実装状況
基本機能は Node v12.20+, v14.13+ で フラグ無し で提供済み3
• Na$ve ESM import / export
• CommonJS interoperability (CJS Interop)
• package.json imports / exports mapping
3
ESM import / export と CJS interop は v15 系で stability: stable, v12, v14 系は次の minor リリースで stable になる
予定。imports / exports mapping は v15 系で stable になったが v12, v14 は未定。
5
2021 年は Na've ESM の時代に?
• 前述の通り、Node v12 系以上は ESM を実装済み
• Node v10 は 2021-04-30 に EOL
• つまり、5 月以降は全 Node 環境で Na5ve ESM が利用可能!
• やったー、全部 ESM で書こうぜー
6
h"ps:/
/blog.sindresorhus.com/get-ready-for-esm-aa53530b3f77 7
h"ps:/
/twi"er.com/jus2nfagnani/status/1356012934564958212 8
そんな風に思っていた時期が
私にもありました。
9
鬼門: CJS Interop
10
CJS Interop
CJS から ESM へスムーズな移行を進めるために検討されてきた相
互互換機能
• CJS から ESM をロードできる
• ESM から CJS をロードできる
仕様の複雑さと既存パッケージとの互換性から、ここまで Node
の ESM 対応を遅らせてきた鬼門でもある
11
import ESM from CJS
// esm.mjs
export default "foo";
export const named = "bar";
// cjs.js
const { default: def, named } = await import("./esm.mjs");
//// def: "foo"
//// named: "bar"
• dynamic import を使用
• ESM と同じ使い勝手で、違和感はない
12
import CJS from ESM: named import
// cjs.js
exports.named = "foo";
// esm.mjs
import { named } from "./cjs.js";
//// named: "foo"
• exports のプロパティが named import される
13
import CJS from ESM: default import
// cjs.js
module.exports = "foo";
// esm.mjs
import cjs from "./cjs.js";
//// cjs: "foo"
• module.exports が default import される
14
ところで
Babel と TypeScript はどうだっけ?
15
Babel / TypeScript の ESM → CJS 変換
// Before (ESM in Babel)
export default "foo";
// After (CJS)
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = "foo";
• default export は、exports.default に変換される
• おや?Node.js の CJS Interop と仕様が違うぞ!
16
Babel / TypeScript で CJS にトランスパイルされた
npm パッケージを Na5ve ESM からロードしたいけど
// node_modules/foo/index.ts
export default "foo!";
// main.mjs
import foo from "foo";
//// foo: { default: "foo!" }
default export された値を
期待通りに default import できない!
17
無理矢理書くこともできるが
// node_modules/foo/index.ts
export default "foo!";
// main.mjs
import def from "foo";
const { foo } = def;
//// foo: "foo!"
これなら動くが、import したい全ての npm パッケージについて内
部的に Babel / TS を使っているかを調べて書き分ける必要があり、
書き味も体験も非常に厳しい。
18
Babel Modules ≠ ES Modules
TypeScript Modules ≠ ES Modules
19
ここまでは
Babel / TypeScript ベースの
CJS な npm パッケージを
Na5ve ESM からロードする話
20
TypeScript で
Na-ve ESM を書くのは
もっと厳しい
21
TypeScript で Na-ve ESM を出力したい
公式にはサポートされてないので基本的に厳しい
• .mjs を出力できない
• .mjs を入力できない
• moduleResolution:node が Node の ESM 仕様と異なる
• imports / exports mapping に未対応
22
TypeScript で無理矢理書くこともできるが
// foo.ts
export default "foo!";
// main.ts
import foo from "./foo.js";
import 文で拡張子を省略せず .js を指定し(.ts ではない)、
pacakge.json で type:"module" にすることで、変換後に
Na$ve ESM として無理矢理動かすことは可能。
しかし ts-node など周辺ツールが未対応で jest も動かせない。
23
TypeScript Modules を import 不可能
// node_modules/foo/index.ts
export default "foo!";
// main.ts
import def from "foo";
const { foo } = def;
//// TS2339 Error: Property 'foo' does not exist on type 'String'.
TS が CJS に変換したパッケージに同梱された型定義が Node ESM
仕様と食い違うため、TS Na-ve ESM から default import しようと
すると 型エラーかランタイムエラーのどちらか になる。
24
今後どうなるのか?
25
Babel / TypScript 陣営の動き
• Babel は新オプション importInterop:"node" を検討している4
• TypeScript は最近の動きは見えない
• 2020 年 3 月に新しい moduleResolution フラグを作る方針
というコメント5
があったが、続報なし
5
Design Mee*ng Notes, 3/27/2020 · Issue #37897 · microso=/TypeScript
4
Implement importInterop: "node" op+on for module transforms by nicolo-ribaudo · Pull Request #12838 ·
babel/babel
26
Babel / TypeScript が対応しても
• 既に npm 公開された大量の Babel / TS Modules はトランスパイ
ル済みであり、挙動は変わらない
• 更新のない Babel / TS Modules は Na4ve ESM からは扱いにく
い存在として残り続ける
• npm 界に ESM 対応のアップデートが広がるのを待つしかない
• 時間が解決するのか...
27
Sindre はどうしてるの?
そんなこともあろうかと、既存の TypeScript ベースの npm パッケージにあら
かじめ workaround を仕込んでいた!6
export default got;
module.exports = got;
module.exports.default = got;
module.exports.__esModule = true;
これで TypeScript からも Na-ve ESM からも default import 可能。
ただし dirty hack で副作用もあるのでご利用は計画的に。
6
h$ps:/
/github.com/sindresorhus/got/blob/v11.8.1/source/index.ts#L127-L130
28
ところで、
どうして Na$ve ESM を書きたいんだっけ?
• 皮肉にも、CJS Interop の実装が進んだ結果、CJS でも特に不満
がない状況になりつつある
• ESM のセールスポイントだった tree-shaking も、静的解析の発
展により Webpack や Parcel が CJS にも対応し始めた
• それでも ESM を書くと キマる んだ!
29
まとめ
• Na$ve ESM は Node.js の中だけなら 5 月から自由に使える
• しかし Babel / TypeScript ベースの npm パッケージを default import すると
いろいろハマって厳しい
• この状況は各種ツールと既存 npm パッケージが更新されるまでだいぶ続き
そう
• TypeScript で Na$ve ESM が書けるようになるのはまだまだ先
• しばらくは TypeScript で CJS を書く今の生活が続きそう
30

More Related Content

Node.js Native ESM への道 〜最終章: Babel / TypeScript Modules との闘い〜

  • 1. Na#ve ESM への道 ∼ 最終章: Babel / TypeScript Modules との闘い ∼ Node 学園 35 時限目 / 2021-02-24 @teppeis 1
  • 2. 自己紹介 • @teppeis / Teppei Sato • サイボウズから来ました。 • もちろん We are hiring!!! • プロダクト開発チーム • フロントエンドエキスパートチーム 2
  • 3. Node.js の ES Modules 対応って どうなったの? 3
  • 4. 前回までのあらすじ 2015 年の ES Modules (ESM) 策定からときは流れ、Node.js での ESM 対応は混迷を極めた1 。 しかし 2018 年 10 月、 Node.js Modules Team は ESM 対応計画 2 を なんとかまとめあげ、ようやく実装が進んでいった。 そして 2020 年末にはその計画が実装完了し、2021 年は Na8ve ESM 時代が到来するかに見えた。 2 Plan for New Modules Implementa5on · nodejs/modules 1 前回スライドを参照: You Don't Know ES Modules, Teppei Sato (2016) 4
  • 5. Node.js ESM 実装状況 基本機能は Node v12.20+, v14.13+ で フラグ無し で提供済み3 • Na$ve ESM import / export • CommonJS interoperability (CJS Interop) • package.json imports / exports mapping 3 ESM import / export と CJS interop は v15 系で stability: stable, v12, v14 系は次の minor リリースで stable になる 予定。imports / exports mapping は v15 系で stable になったが v12, v14 は未定。 5
  • 6. 2021 年は Na've ESM の時代に? • 前述の通り、Node v12 系以上は ESM を実装済み • Node v10 は 2021-04-30 に EOL • つまり、5 月以降は全 Node 環境で Na5ve ESM が利用可能! • やったー、全部 ESM で書こうぜー 6
  • 11. CJS Interop CJS から ESM へスムーズな移行を進めるために検討されてきた相 互互換機能 • CJS から ESM をロードできる • ESM から CJS をロードできる 仕様の複雑さと既存パッケージとの互換性から、ここまで Node の ESM 対応を遅らせてきた鬼門でもある 11
  • 12. import ESM from CJS // esm.mjs export default "foo"; export const named = "bar"; // cjs.js const { default: def, named } = await import("./esm.mjs"); //// def: "foo" //// named: "bar" • dynamic import を使用 • ESM と同じ使い勝手で、違和感はない 12
  • 13. import CJS from ESM: named import // cjs.js exports.named = "foo"; // esm.mjs import { named } from "./cjs.js"; //// named: "foo" • exports のプロパティが named import される 13
  • 14. import CJS from ESM: default import // cjs.js module.exports = "foo"; // esm.mjs import cjs from "./cjs.js"; //// cjs: "foo" • module.exports が default import される 14
  • 15. ところで Babel と TypeScript はどうだっけ? 15
  • 16. Babel / TypeScript の ESM → CJS 変換 // Before (ESM in Babel) export default "foo"; // After (CJS) Object.defineProperty(exports, "__esModule", { value: true }); exports.default = "foo"; • default export は、exports.default に変換される • おや?Node.js の CJS Interop と仕様が違うぞ! 16
  • 17. Babel / TypeScript で CJS にトランスパイルされた npm パッケージを Na5ve ESM からロードしたいけど // node_modules/foo/index.ts export default "foo!"; // main.mjs import foo from "foo"; //// foo: { default: "foo!" } default export された値を 期待通りに default import できない! 17
  • 18. 無理矢理書くこともできるが // node_modules/foo/index.ts export default "foo!"; // main.mjs import def from "foo"; const { foo } = def; //// foo: "foo!" これなら動くが、import したい全ての npm パッケージについて内 部的に Babel / TS を使っているかを調べて書き分ける必要があり、 書き味も体験も非常に厳しい。 18
  • 19. Babel Modules ≠ ES Modules TypeScript Modules ≠ ES Modules 19
  • 20. ここまでは Babel / TypeScript ベースの CJS な npm パッケージを Na5ve ESM からロードする話 20
  • 21. TypeScript で Na-ve ESM を書くのは もっと厳しい 21
  • 22. TypeScript で Na-ve ESM を出力したい 公式にはサポートされてないので基本的に厳しい • .mjs を出力できない • .mjs を入力できない • moduleResolution:node が Node の ESM 仕様と異なる • imports / exports mapping に未対応 22
  • 23. TypeScript で無理矢理書くこともできるが // foo.ts export default "foo!"; // main.ts import foo from "./foo.js"; import 文で拡張子を省略せず .js を指定し(.ts ではない)、 pacakge.json で type:"module" にすることで、変換後に Na$ve ESM として無理矢理動かすことは可能。 しかし ts-node など周辺ツールが未対応で jest も動かせない。 23
  • 24. TypeScript Modules を import 不可能 // node_modules/foo/index.ts export default "foo!"; // main.ts import def from "foo"; const { foo } = def; //// TS2339 Error: Property 'foo' does not exist on type 'String'. TS が CJS に変換したパッケージに同梱された型定義が Node ESM 仕様と食い違うため、TS Na-ve ESM から default import しようと すると 型エラーかランタイムエラーのどちらか になる。 24
  • 26. Babel / TypScript 陣営の動き • Babel は新オプション importInterop:"node" を検討している4 • TypeScript は最近の動きは見えない • 2020 年 3 月に新しい moduleResolution フラグを作る方針 というコメント5 があったが、続報なし 5 Design Mee*ng Notes, 3/27/2020 · Issue #37897 · microso=/TypeScript 4 Implement importInterop: "node" op+on for module transforms by nicolo-ribaudo · Pull Request #12838 · babel/babel 26
  • 27. Babel / TypeScript が対応しても • 既に npm 公開された大量の Babel / TS Modules はトランスパイ ル済みであり、挙動は変わらない • 更新のない Babel / TS Modules は Na4ve ESM からは扱いにく い存在として残り続ける • npm 界に ESM 対応のアップデートが広がるのを待つしかない • 時間が解決するのか... 27
  • 28. Sindre はどうしてるの? そんなこともあろうかと、既存の TypeScript ベースの npm パッケージにあら かじめ workaround を仕込んでいた!6 export default got; module.exports = got; module.exports.default = got; module.exports.__esModule = true; これで TypeScript からも Na-ve ESM からも default import 可能。 ただし dirty hack で副作用もあるのでご利用は計画的に。 6 h$ps:/ /github.com/sindresorhus/got/blob/v11.8.1/source/index.ts#L127-L130 28
  • 29. ところで、 どうして Na$ve ESM を書きたいんだっけ? • 皮肉にも、CJS Interop の実装が進んだ結果、CJS でも特に不満 がない状況になりつつある • ESM のセールスポイントだった tree-shaking も、静的解析の発 展により Webpack や Parcel が CJS にも対応し始めた • それでも ESM を書くと キマる んだ! 29
  • 30. まとめ • Na$ve ESM は Node.js の中だけなら 5 月から自由に使える • しかし Babel / TypeScript ベースの npm パッケージを default import すると いろいろハマって厳しい • この状況は各種ツールと既存 npm パッケージが更新されるまでだいぶ続き そう • TypeScript で Na$ve ESM が書けるようになるのはまだまだ先 • しばらくは TypeScript で CJS を書く今の生活が続きそう 30