🏔️

Snowpack に入門してみる

2020/12/13に公開

Snowpack + React + TypeScript に入門してみました。
create-snowpack-app は使わず、全部マニュアルでやっていきます。
Snowpack に入門することがメインなので作成する web ページは簡素なものです。

基本的に公式ドキュメントを参考に進んでいきます。

最終的に作られたものはこちらです↓
https://github.com/succie/learn-snowpack

Snowpack のインストール

project ディレクトリ (今回は learn-snowpack) を作成し、npm init -y を実行して package.json, package-lock.json を作成、
npm i --save-dev snowpack でインストールします。

npm run-scripts を追加

package.json の scripts フィールドに startbuild を追加します

package.json
"scripts": {
  "start": "snowpack dev",
  "build": "snowpack build"
},
  • start はローカルで開発用の dev server を起動します。プロジェクトルート下の index.html を探してそれをエントリーポイントとします。(ルート以外に置く場合は別途 mount 設定が必要です。)
  • build は production 用のファイルを生成します。

snowpack.config.js の作成

先述の通り、プロジェクトルート以外に index.html を置く場合は mount 設定を行う必要があります。今回は public/index.html をパスとするのでその設定を行います。

snowpack.config.js
module.exports = {
  mount: {
    public: { url: '/', static: true }
  }
};

mount[path: string]: string | {url: string, static: boolean, resolve: boolean} の形式で記述します。
ドキュメント

  • Key ([path: string]) には対象とするディレクトリ名を指定
  • Value (string | {url: string, static: boolean, resolve: boolean}) にはマウント先のディレクトリ名を指定するか、オブジェクトの形で指定します
    • url: マウント先ディレクトリ名を指定
    • static (default: false): true の場合は、このディレクトリ下のファイルはビルドされません
    • resolve (default: true): false の場合は、JS、CSS、および HTML ファイル内の JS および CSS のインポートを解決せず、すべてのインポートを書かれたとおりにブラウザに送信します。(正直よくわかってない)

今回は public ディレクトリをルートディレクトリにマウントしたいため url には '/' を指定しています。また、public ディレクトリ下には HTML 等のビルドを必要としないファイルを置くため statictrue にしています。
また、このあと作成する TypeScript や CSS ファイルの置き場所である src ディレクトリの mount 設定もしておきます。

snowpack.config.js
module.exports = {
  mount: {
    public: { url: '/', static: true },
    src: '/dist'
  }
};

src ディレクトリの方は単にマウント先として /dist を設定したいだけなのでこれでOKです。
ちなみに、js ファイルもルートディレクトリにマウントしたい場合は src: '/' と書くことで設定可能です。

public/index.html の作成

React を使うときによくやる HTML をとりあえず作って public/index.html として保存します

public/index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>learn snowpack</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/dist/index.js"></script>
  </body>
</html>

ここで重要なのは <script type="module" src="/dist/index.js"></script> の部分です。
script タグに type="module" を指定しています。
これによりブラウザにこのファイルがモジュールであることを宣言し、 importexport が使えるようになります。

src/index.tsx の作成

まだ reactreact-domtypescript 及び型ファイルをインストールしていなかったのでインストールしておきます。

$ npm i react react-dom
$ npm i --save-dev typescript @types/react @types/react-dom

tsconfig.json も作ります

tsconfig.json
{
  "compilerOptions": {
    "module": "ESNext",
    "target": "ESNext",
    "strict": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "jsx": "preserve",
    "noEmit": true,
    "skipLibCheck": true,
  },
  "include": ["src"]
}

続いて TypeScript のファイルを作っていきます

src/index.tsx
import React from 'react';
import { render } from "react-dom";

const App = () => {
  return <h1>Learn snowpack.</h1>;
};

render(<App />, document.getElementById("root"));

これで準備完了です。
npm start を実行して dev server を起動してみましょう。
自動でブラウザでページ表示され「Learn snowpack.」と表示されます。

スタイルを当てる

CSS も適用していきます。
そういえば CSS ModuleState of CSS 2020 で満足度1位 だったので使っていきましょう。
Snowpack で CSS Module を使用するためには CSS のファイル名を [name].module.css とする必要があります。(ドキュメント)

src/index.module.css を作成します。

src/index.module.css
.text {
  color: red;
}

src/index.tsx から CSS を読み込んでスタイルを当てます。

src/index.tsx
import React from 'react';
import { render } from 'react-dom';

// 追記
import styles from './index.module.css';

const App = () => {
  return (
    <div>
      {/* className={styles.text} を追加 */}
      <h1 className={styles.text}>Learn snowpack.</h1> 
    </div>
  );
};

render(<App />, document.getElementById('root'));

このままだと CSS の import 時に TypeScript がエラーを吐いてくるのでそれを解決します。
このエラーはモジュール index.module.css の型ファイルが見つからないことが原因なので、型ファイルを作ってやります。
types/css.d.ts を作成します。

types/css.d.ts
declare module '*.css' {
  const classNames: { [className: string]: string };
  export default classNames;
}

TypeScript にこの型ファイルを認識させるために tsconfig.json を編集します

tsconfig.json
{
  "compilerOptions": {
    "module": "ESNext",
    "target": "ESNext",
    "strict": true,
    "moduleResolution": "node",
    "esModuleInterop": true,
    "jsx": "preserve",
    "noEmit": true,
    "skipLibCheck": true,
    "typeRoots": ["node_modules/@types", "types"]
  },
  "include": ["src", "types"]
}

compilerOptions.typeRoots の追加と includetypes を追加しました。

これで .css という拡張子がつくモジュールの型宣言ができました。

それでは、npm start で起動している開発環境を見に行くと「Learn snowpack.」の色が赤くなっています。
無事にスタイルを当てることができました。

画像を表示する

ついでに画像も表示してみましょう。
snowpack 公式から配布されているロゴ を使用させていただきます。
snowpack-logo-dark.pngsnowpack-wordmark-black.png をそれぞれ src/images ディレクトリに配置します。
src/index.tsx から画像を読み込んで配置します。

src/index.tsx
import React from 'react';
import { render } from 'react-dom';

import logo from './images/snowpack-logo-dark.png';
import wordmark from './images/snowpack-wordmark-black.png';
import styles from './index.module.css';

const App = () => {
  return (
    <h1 className={styles.text}>
      Learn <img className={styles.logo} src={logo} />
      <img className={styles.wordmark} src={wordmark} />
    </h1>
  );
};

render(<App />, document.getElementById('root'));

CSS のときと同様に、import 時にエラーが出ているので types/image.d.ts を作成します。

types/image.d.ts
declare module '*.png';
declare module '*.jpg';
declare module '*.svg';

そのまま表示すると画像が大きすぎるので適当に大きさを整えます。

src/index.module.css
.text {
  color: red;
}

.logo {
  width: 1em;
}

.wordmark {
  width: 3.2em;
}

開発環境を見ると以下のようになっていると思います。
表示イメージ
表示イメージ

まとめ

Snowpack + React + TypeScript の入門を簡単にやってみました。
Webpack に比べていろんな loader とか babel とか使用せずに使用できるのは良いですね!
今回はただ文字と画像を表示するだけのものだったので Snowpack の持ち味である Unbundled development の恩恵が少なかったり、 HMR + Fast Refresh の設定等はしなかったのですが、大きな web アプリケーションを作成していくとき楽しみです。

Discussion