JavaScript
trans-loader

trans-loader: node/webpack なしではじめるフロントエンドプロジェクト

フロントエンドはツールチェイン多くて大変ですね。何から初めていいかわからないと思います。

というわけで、 babel や webpack, というかもはや node の インストールすら不要でフロントエンドを始めることができるプロジェクトを作りました。

https://github.com/mizchi/frontend-starter

git clone git@github.com:mizchi/frontend-starter.git --depth 1 --single-branch master
cd frontend-starter
ruby -run -e httpd . -p 8000 # 好きな静的サーバー

あとはブラウザで http://localhost:8000 を開くだけ。

デフォルトだとシンプルな React プロジェクトになっています。vue でもなんでも多分大丈夫。

試しに編集して動くか確認してみましょう。

components/App.js
import React from "react";
export default () => <span>This is App component</span>;

This is App を何か書き換えてリロードしてみてください。適用されるはずです。

最後に一つ注意。裏側はとても富豪的に動いているので、本番環境では使わないでください

ここで動くコードはちょっと微調整を加えると webpack でビルドできます。 ご近所の webpack 職人を呼びつけください。

裏側の実装の話。

最近のモダンブラウザでは Native ES Modules が実行可能になっています。基本的にはこれを使っています。なのでモダンブラウザ限定です。

ただし、Native ESM を実用しようとすると、いくつか問題があります。

  • dependecy が深い時に解決が遅い。モジュール解決のN+1問題。
  • node のモジュールシステムとパス解決の解釈が違うので、そのままでは動かない
  • そのままでは JSX の構文が通らない。

といった事情で、現時点でもWebpackでビルドするのが最適だとGoogleも推奨しています。
Introduction  |  Web Fundamentals  |  Google Developers

これらの問題を解決するには、サーバーサイドプッシュなどのいくつかのWeb標準の問題を解決する必要があり、現時点では npm エコシステムのものをビルド無しで解決するのは現実的ではありません。

とはいえ、開発環境用だと割り切れば、色々とやりようがあります。そのための専用のServiceWorkerを作りました。

https://github.com/mizchi/trans-loader

これが何をしているかというと、

  • Service Worker がフルコントロールになるまで読み込みを待機する
  • js読み込み時、専用の Babel プラグインで module path を書き換える
  • https://jspm.io を経由して、 npm の module をESMにコンパイルして解決
  • dev.jspm.io からのリクエストを service-worker でキャッシュ
  • .js の flow/react 拡張を transform
  • .ts の TypeScript transform

つまりは、ServiceWorker Side Transform です。

こんなJSの初期化を

<script src="/main.js"></script>

wget https://raw.githubusercontent.com/mizchi/trans-loader/master/dist/sw.js で trans-loader の sw.js をダウンロードして、こんな風に書き換えると適用できます。

<script type=module>
(async () => {
  const run = () => import('/main.js') // your entry js
  if (navigator.serviceWorker.controller) {
    run()
  } else {
    const reg = await navigator.serviceWorker.register("/sw.js");
    await navigator.serviceWorker.ready;
    navigator.serviceWorker.addEventListener('controllerchange', run)
  }
})()
</script>

このとき、 sw.js は必ずドメインのルート直下に置く必要があります。自分より下位のパスにしかコントロールが持てないという serviec-worker の仕様上の制限です。ホスティングせず、わざわざダウンロードしてるのも、そのため。

要は service-worker を経由して読み込むときに、babel でモジュールパスを書き換えて無理矢理辻褄を合わせています。これによって webpack の特殊なローダー以外はほとんど挙動を再現できます。

結果として、 trans-loader の ServiceWorker が Babel コンパイラと TypeScript コンパイラを同梱することになり、uglifyjs でビルドしても 3MB になりました。一回読み込めばcache に入りますが、ブラウザキャッシュなので初回は必ず jspm.io にアクセスが飛んでしまうことも考えると、プロダクションでの使用は現実的ではありません。たぶん jspm.io に怒られると思います。

未来

webpack 批判も多いですが、IE11が死ぬ未来では本来こういうことができる、という紹介でした。 webpack の高度な loader は再現できませんが、学習環境、テスト環境ではこれぐらいで大丈夫ではないでしょうか。

ついでにフロントエンドの人間がなぜIE11を恨んでいるかなどを察していただければ幸いです