📦

Deno 用のフロントエンド開発ツール packup について

2021/12/19に公開

Deno (ディノ) Advent Calendar 17日目の記事です。

今日は Deno 用のフロントエンド開発ツールの packup を紹介します。

packup is 何

packup は Deno 用の parcel です。HTML ファイルをエントリポイントとして、js / css をバンドルして、シングルページアプリを生成します。

JavaScript のバンドルには WASM 版の esbuild を使っています。

使い方

次のコマンドでインストールできます。

deno run -A https://deno.land/x/packup@v0.1.10/install.ts

インストールが成功すると、packup コマンドが使えるようになります。

$ packup -v
packup v0.1.10

まずは簡単な例から紹介します。

次のような index.html と script.ts を用意してください。(packup はデフォルトで TypeScript をサポートしています。)

index.html
<html>
  <body>
    <script src="./script.ts"></script>
    <h1>Hello from packup!</h1>
  </body>
</html>
script.ts
console.log("Hello in console!");

この状態で packup index.html と実行すると、localhost:1234 に開発サーバーが立ち上がって、上で書いた HTML が表示されます。コンソールには Hello in console! と表示されます。

React を使う

次にもう少し実用的な例として、React を使う例を紹介します。

Packup は Deno と互換な import 文をサポートしています。この機能を使って、Deno 用に npm モジュールを変換してくれる CDN である、skypack を使って、Packup の中で React を使うことができます。

index2.html
<html>
  <head>
    <title>React Packup Example</title>
    <script src="script.tsx"></script>
  </head>
  <body>
    <div id="main"></div>
  </body>
</html>
script.tsx
import React from "https://cdn.skypack.dev/react@17.0.2?dts";
import ReactDOM from "https://cdn.skypack.dev/react-dom@17.0.2?dts";

function App() {
  return (
    <div>
      <h2>Hello from React!</h2>
    </div>
  );
}

function main() {
  ReactDOM.render(<App />, document.querySelector("#main"));
}

addEventListener("DOMContentLoaded", () => {
  main();
});

この状態で、packup index2.html と実行すると、次のように React でレンダリングされた画面が表示されます。

ルータと styled-components を使う

Packup でルータを使う場合は、現状では Wouter が推奨です。Wouter は React Router と似た API を持った、React Router よりも軽量なルータライブラリです。

styled-components は React 界隈で人気のある CSS in JS ライブラリです。

index3.html
<html>
  <head>
    <title>React Packup Example</title>
    <script src="script3.tsx"></script>
  </head>
  <body>
    <div id="main"></div>
  </body>
</html>
script3.tsx
import React from "https://cdn.skypack.dev/react@17.0.2?dts";
import ReactDOM from "https://cdn.skypack.dev/react-dom@17.0.2?dts";
import {
  Link,
  Route,
  Switch,
} from "https://cdn.skypack.dev/wouter@2.7.5?dts";
import styled, {
  createGlobalStyle,
} from "https://cdn.skypack.dev/styled-components@5.3.3?dts";

const GlobalStyle = createGlobalStyle`
  body {
    margin: 0;
  }
`;

function App() {
  return (
    <>
      <GlobalStyle />
      <div>
        <Nav>
          <List>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/about">About</Link>
            </li>
            <li>
              <Link to="/users">Users</Link>
            </li>
          </List>
        </Nav>
        <Switch>
          <Route path="/about">
            <About />
          </Route>
          <Route path="/users">
            <Users />
          </Route>
          <Route path="/">
            <Home />
          </Route>
        </Switch>
      </div>
    </>
  );
}

const List = styled.ul`
  display: flex;
  padding-inline-start: 0;

  li {
    list-style-type: none;
    margin-right: 8px;
  }
`;

const Nav = styled.nav`
  width: 100%;
  padding: 15px;
  border-bottom-width: 1px;
  border-bottom-style: solid;
  border-bottom-color: #eee;
`;

function Home() {
  return (
    <Main>
      <Heading>Home</Heading>
      <p>This is Home page</p>
    </Main>
  );
}

function About() {
  return (
    <Main>
      <Heading>About</Heading>
      <p>This is About page</p>
    </Main>
  );
}

function Users() {
  return (
    <Main>
      <Heading>Users</Heading>
      <p>This is Users page</p>
    </Main>
  );
}

const Main = styled.main`
  padding: 15px;
`;

const Heading = styled.h2`
  font-size: 24px;
  font-weight: 900;
  color: #500;
`;

function main() {
  ReactDOM.render(<App />, document.querySelector("#main"));
}

addEventListener("DOMContentLoaded", () => {
  main();
});

以上のように Wouter と styled-components を使って、ルーティングとスタイリングを実装することが出来ます。

エディターでの型チェック

Packup 自体は型チェックを行いませんが、Packup 用に書かれた TypeScript を VS Code の Deno 公式 Extension で型チェック、型補完することが出来ます。(VS Code 以外でも、deno lsp ベースのエディタプラグインであれば同様の設定が可能です。)

VS Code のエクステンション検索から、Deno エクステンションを検索してインストールしてください。

その状態で、コマンドパレット (Ctrl + Shift + P / Command + Shift + P) を開いてそこから Deno: Initialize Workspace Configuration コマンドを実行してください。(その際、いくつか設定を訊かれますが、リント -> 有効, unstable API -> 無効 の設定がおすすすめです。)

すると、.vscode/settings.json が以下のように作成されます。

.vscode/settings.json
{
    "deno.enable": true,
    "deno.lint": true,
    "deno.unstable": false
}

ここに "deno.config": "deno.json" という設定を追加して、deno.json を認識するように設定を追加します。

 {
     "deno.enable": true,
     "deno.lint": true,
-    "deno.unstable": false
+    "deno.unstable": false,
+    "deno.config": "deno.json"
 }

deno.json には、以下のように、TypeScript コンパイラ向けに "lib" 設定をしてください。

deno.json
{
  "compilerOptions": {
    "lib": ["dom", "esnext"]
  }
}

この設定により、言語の型 (exnext) と、ブラウザの型 (dom) が認識されて、逆に Deno 独自の型が認識されない状態になります。つまり、フロントエンド向けの環境での型チェックを行うことが出来るようになります。

ここまで設定して、先ほどの React, Wouter, Styled Components の例を VS Code で開くと、全ての API に型がついた状態で認識されます。

JSX 内のアトリビュートなどにもきちんと型が付きます。

ここまでで、VS Code 上で packup 向けコードの型を認識させる方法を説明しました。

なお、コマンドラインから型のチェックだけを行いたい場合は、以下のコマンドで上記と同等の型チェックを行うことが出来ます。

deno cache --config deno.json script3.tsx

静的アセットをビルドする

packup build コマンドを使って、静的アセットを生成することが出来ます。このコマンドで生成されたファイルを GitHub pages や Vercel などにアップロードすることで、開発したサイトをホスティングすることが出来ます。

packup build index.html

生成されたファイルは、デフォルトで dist/ 以下に生成されます。上の React と Wouter を使う例だと、以下のような dist/ ディレクトリが生成されます。

dist
├── index.6948f3f38bb51ebcf417cb6302968fe4.js
└── index.html

上を見ると分かるように、packup は TypeScript ファイルの依存モジュール (react, wouter など) を全て1ファイルにまとめた結果を出力します。

なお、静的アセットを利用したい場合は、static というディレクトリを作成すると、そのディレクトリから自動的に dist/ にコピーされます。(開発サーバー起動時は開発サーバー上から参照できるようになります。)

その他の機能

例では触れられませんでしたが、他に以下のような機能があります。

  • html ファイルの中で、link タグで、<link rel="stylesheet" href="style.css" /> のように参照した css を自動的に解決してくれます。ビルド事は dist ディレクトリに出力されます。
  • 上の記法で拡張子が .scss だった場合は Sass の .scss ファイルとしてビルドします

まとめ

今日は Deno 用のフロントエンド開発ツールの packup を使って、シングルページアプリケーションを開発する方法と、IDE 上で型を認識させる方法などを紹介しました。

Discussion