Snowpack で実現する未来のフロントエンド開発

Web アプリケーション開発において TypeScript で型付けをして、 React, Vue, Angular などのフレームワークでビューを作り、 Webpack でバンドルする、という構成が当たり前のようになってから久しいですね。
フロントエンドアプリケーションの設計は洗練され、我々はまあまあ安全かつ開発しやすい環境でアプリケーションを作れるようになりました。

ところでみなさんはその開発環境の構築にどれだけのコストをかけていますか？
新規プロダクトを作るたびに最適な Webpack の設定ファイルを作るために苦心して、気づけばその肉体にゴリゴリのウェブパッ筋を宿していませんか？

賢い人はスターターボイラープレート的なものを自分で用意しているかもしれません。
だけどそれも頻繁な依存ライブラリのアップデートに合わせるために常にメンテナンスが必要となります。カビの生えないボイラープレートを維持するのも容易ではありません。

そんな苦しみを背負ったあなたに、もしかしたら幸せな未来の可能性があるかもしれないよ？という、今日はそんな話をしに来ました。

## Snowpack で脱モジュールバンドラしよう

[Snowpack](https://www.snowpack.dev/) は Webpack のようなモジュールバンドラを使わないフロントエンド開発を実現するためのツールです。
どうやってモジュールバンドラなしで開発をするのか、その技術的根拠は [ES Modules](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/import) にあります。
Snowpack は package.json の dependencies やアプリケーションコードの依存ライブラリを見て、 `/web_modules` ディレクトリに依存ライブラリをそのままブラウザから利用できる形で格納します。そしてアプリケーションから `import React from './web_modules/react.js'` と読むだけで動かせるようになります。

package.json の dependencies に依存ライブラリを定義して `npx snowpack` を叩くと `/web_modules` ディレクトリが生成されます。あとはいつも通りアプリケーションコードを書けば(パスはちょこっとだけ変えて)そのままブラウザで実行できます。
このアプリケーションコードを書き始められるまでの速度が圧倒的で、モダンなフロントエンド開発を誰でもすぐに始められる世界を感じました。

Snowpack の詳しい説明や使い方は、こちらの記事でわかりやすく解説されているので読んでみてください。
[モジュールバンドラを使わないモダン Web 開発を実現する「Snowpack」](https://qiita.com/yukukotani/items/dfe236d29f97b0986b91)

:point_up: の記事を読んでもらえれば Snowpack の概要はよくわかると思います。
ではこの記事ではどんな話をするかと言うと、「Snowpack がリアルワールドでの運用に耐えうるのか」というのを考えていきます。

## リアルワールドでの運用に耐えうるのか

確かに本当に Webpack の設定ファイルがなくなるのであれば嬉しいけどそれって本当にプロダクトで使えるの？と誰しもが考えると思います。
僕の結論としては、「いけないことはない！かな…まあ…うん」です！

その根拠を、僕がプロダクトで採用できるのかどうかで気になったポイントを一つ一つ見ながら書いていきます。

### IE 対応

最初からめちゃめちゃ泥臭い話になってしまって申し訳ないのですが、アプリケーションの性質にもよりますが現在の日本ではB向けのプロダクトなどでは IE を切るという選択はなかなか難しく、 IE に対応できないのであれば採用を見送るという意思決定は往々にしてあります。

でも安心してください。 IE 対応いけます！
`npx snowpack --nomodule src/app.js` という感じに `--nomodule` フラグをつけてアプリケーションのエントリポイントを指定すると、依存をまとめたファイルを `./web_modules/app.nomodule.js` として吐き出してくれるので、それを HTML から指定することで IE を含むレガシーブラウザに対応できます。

結局バンドルしとるやんけ！と思うかもしれません。ただ、 dev 環境での開発においては関係ありませんし、 IE 用本番ビルドで `--nomodule` をつけるだけで対応が終わる話なので、これまでよりずっと楽になっていることに間違いはありません。

### JSX

バンドルのフェーズを挟まないということは、勘の良い方は「ECMAScript の標準ではない JSX は扱えないのでは？」と考えたかもしれませんね。

でも安心してください。使えます！
もちろん、標準ではない JSX を動かすということは、 JSX を変換するためにトランスパイルフェーズを挟むことになってしまいます。まあ標準から外れるというのはそういうことなので、そこは受け入れましょう。
手順を解説します。

まずは `.babelrc` を用意します。

```json:.babelrc
{
  "presets": ["@babel/preset-react"]
}
```

次に必要なライブラリをインストールします。

```bash
npm i -D @babel/cli @babel/core @babel/preset-react
npm i react@npm:@reactesm/react react-dom@npm:@reactesm/react-dom
```

package.json に npm script を追加して npm script を実行します。

```json:package.json
{
  "scripts": {
    "develop": "babel src/ --out-dir lib --watch"
  }
}
```

```bash
npm run develop
```

あとは HTML から `lib/app.js` を読むことで JSX を含む React が実行できます。簡単ですね。

…何かおかしいことに気づきましたか？この不可思議なインストールコマンドに！！

```bash
npm i react@npm:@reactesm/react react-dom@npm:@reactesm/react-dom
```

これは現状残念ながら React が ES Modules をサポートしていないため、 React を ES Modules 化したパッケージの方を使う、ということをしています。
もうこの時点でこれはプロダクション採用できるとは言えないのでは？という話になるかもしれないのですが、一応 [こちらのイシュー](https://github.com/facebook/react/issues/11503)で話は進んでいるし、 ES Modules 対応はいつか必ずなされる(はず！)ので、現状はこちらを使うというワークアラウンドでやっていくという選択もアリではないでしょうか。(アリではないでしょうか…)

### TypeScript

この記事を読んでいるようなフロントエンドエンジニアの多くの方はもはや型なしの生活など考えられないのではないでしょうか。
もう予想はついているとは思いますが、 JSX の時と同じで事前に自分でトランスパイルすることになります。

まずは `.babelrc` を用意します。

```json:.babelrc
{
  "presets": ["@babel/preset-typescript", "@babel/preset-react"],
  "plugins": [
    ["snowpack/assets/babel-plugin.js", {
      "optionalExtensions": true
    }]
  ]
}
```

最低限の `tsconfig.json` も用意します。

```json:tsconfig.json
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "lib": ["ESNext", "dom"],
    "jsx": "react",
    "strict": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true
  }
}
```

次に必要なライブラリをインストールします。

```bash
npm i -D @babel/cli @babel/core @babel/preset-react @babel/preset-typescript 
typescript @types/react @types/react-dom
npm i react@npm:@reactesm/react react-dom@npm:@reactesm/react-dom
```

package.json に npm script を追加して npm script を実行します。

```json:package.json
{
  "scripts": {
    "develop": "babel src/ --extensions '.ts,.tsx' --out-dir lib --watch"
  }
}
```

```bash
npm run develop
```

これで TypeScript を動かすことができます。

`.babelrc` でちょっと設定をいじっている箇所があります。

```json:.babelrc
{
  "presets": ["@babel/preset-typescript", "@babel/preset-react"],
  "plugins": [
    ["snowpack/assets/babel-plugin.js", {
      "optionalExtensions": true
    }]
  ]
}
```

Snowpack を使うと、依存ライブラリの import は `import hoge from './web_modules/hoge.js'` の形式で行わなければいけないので、この場合普通では TypeScript の型を一緒に読むことができません。
なのでアプリケーション側では今まで通り `import hoge from 'hoge'` の形で書きつつ、 Babel を通す際にパスをブラウザから読める形に書き換える必要があります。
それをやってくれるのが `snowpack/assets/babel-plugin.js` です。このプラグインを追加するだけで対応してくれます。

さらに、  `{ "optionalExtensions": true }` のオプションを渡すことで、アプリケーション内の依存ファイル名の解決もしてくれます。
例えば、同階層にある `App.tsx` を読みたい時に `import { App } from './App'` と書きますよね。それを Babel に通すと `import { App } from './App.js'` に変換してくれます。

これで完全に書き味としてはこれまでのアプリケーションと全く同じで、 ES Modules を使用できます。

### プロダクション向けビルド

アプリケーションコードがそのまま読めると言っても、本番環境ではそのファイルは minify など最適化して配信したいですよね。

安心してください。プロダクションビルドいけます！
`snowpack --optimize` と叩くだけで、 Snowpack がよしなに最適化してくれます。ソースマップを伴う minify はもちろん、 pckage.json に書かれた browserslist に合わせてトランスパイルもしてくれます。そして Tree-Shaking も効くので不要なコードは削除してくれます。
とは言え Snowpack はアプリケーションコードには絡んでこないので、そちらの最適化は自分でやる必要があります。

## 一つのファイルにバンドルしないからこその強みがある

ここまで読んでみて、なんだかプロダクトで使えそうな気がふわっとでもしてきませんか？
ここまでは実際の運用にも耐えうる根拠について話しましたが、バンドルせずに ES Modules に乗っかることによるメリットもあります。

メリットとして一番大きいのはキャッシュだと思います。
各ライブラリごとにリクエストを送ることになるので、変更をかけたファイル以外全てキャッシュから取得できるので、 HTTP/2, HTTP/3 の時代において大きなアドバンテージになりそうです。

ただアプリケーションコードで import したファイルがまた別のファイルを import して、といったように import がどんどんネストしていった時にどれだけパフォーマンスが落ちるのか、というのはまだ検証できていないためそこは正直なところわかりません。
これからサンプルレベルではないアプリケーションをがっつり作ってみてそこらへんを検証していきたいなと考えています。

## Snowpack を採用できないパターン

Snowpack はリアルワールドで運用できるんだぞ、と言いたい記事ではあるのですが、もちろん Snowpack を避けるべきパターンというのも存在します。
以下のような場合は Snowpack を使わずこれまで通り Webpack でバンドルする必要があります。

### CSS や画像を JavaScript から直接読みたい

CSS Modules を実現するために、 css-loader や image-loader などの Webpack の loader を多用している場合は、 Snowpack では現状対応できないので、 Webpack を使うことになります。
個人的には Webpack を使っている時も複雑にしたくないので loader の使用は極力避けていたので、僕はここを問題とは感じませんでしたが、現在既存のソースコードで使用している場合は移行は難しいでしょう。

### ES Modules に対応していないライブラリを使用している

例えば React もそうなんですが、ライブラリ側で ES Modules に対応していない場合は Snowpack を通して使用できません。
ただ React の場合は ES Modules 化してパッケージ化してくれている方がいるので一応動かせはするのですが、あまり安定しているとは言い難いですね。

Snowpack を開発している Pika のサイトから ES Modules に対応している npm のパッケージを確認できます。
[Pika - Search npm for fast, modern packages.](https://www.pika.dev/search)

ここにある範囲のライブラリでアプリケーションを作る、という縛りになってしまうことは意識しないといけないです。

## Pika の提示するフロントエンド開発の未来

ここまでリアルワールドで使える可能性をわずかにでも増やそうと可能性を探ってきましたが、実際のところ今すぐ Snowpack をプロダクトに導入して Webpack をやめるというのは難しい選択だと思います。

それでも僕が現状できる対応を考えて試していたのは、 Pika が好きだからというのがあります。
Pika は `make the web 90% faster` というトンデモなミッションを掲げている面白いチームで、リリースするツールは興味を引かれるものばかりなので個人的に応援しています。

とても面白いのでぜひサイトを覗いてみてください。
[Pika](https://www.pika.dev/)

今回の記事を作るために試したサンプルコードは GitHub にあげていますので良ければ参考にしてください。
[nabeliwo/snowpack-ts-react-demo](https://github.com/nabeliwo/snowpack-ts-react-demo)

少しでも Snowpack を試してみようと思ってくれる方が増えることを願っています！終わり。