【Deno】静的サイトジェネレータpagicを使ってお手軽サイト生成

## はじめに

この記事では[pagic](https://github.com/xcatliu/pagic)というDenoモジュールを使って静的サイトを作成する方法を紹介します。

## pagicとは

DenoとReact及びmarkdownを使って静的サイトを生成するツールです
https://github.com/uki00a/blog にて簡単なブログを作成してみたため、興味があればご覧ください。

## 注意点

pagicはまだメジャーリリースされていません。
そのため、この記事で紹介した方法は、将来的に動作しなくなる可能性があります。

## 前提

この記事は以下のツールを使用して動作を確認しています。

|ツール|バージョン|
|:---:|:---:|
|Deno|v1.2.0|
|pagic|v0.7.30|

## セットアップ

```shell
$ deno install --unstable --allow-read --allow-write --allow-net --name pagic -f https://deno.land/x/pagic@v0.7.30/mod.ts
```

## 基本的な使い方

以下のようなディレクトリ構成を想定して話を進めていきたいと思います。

```
src/
├── _deps.tsx
├── _layout.tsx
└── index.md
```


各依存モジュールを`src/_deps.tsx`で管理します。

```typescript:src/_deps.tsx
// @deno-types="https://deno.land/x/types/react/v16.13.1/react.d.ts"
import React from "https://dev.jspm.io/react@16.13.1";
export { PagicLayout } from "https://deno.land/x/pagic@v0.7.30/mod.ts";

export { React };
```

次に`src/_layout.tsx`でページのレイアウトを定義します。
このファイルでdefault exportされたコンポーネントがレイアウトとして使用されます。

```tsx:src/_layout.tsx
import { React, PagicLayout } from "./_deps.tsx";

const Layout: PagicLayout = ({ title, content }) => (
  <html>
    <head>
      <title>{title}</title>
      <meta charSet="utf-8" />
    </head>
    <body>{content}</body>
  </html>
);

export default Layout;
```

最後に`src/index.md`を用意します。
このように、pagicでは記事をmarkdownで記述できます。

```md:src/index.md
# Hello Pagic!

Hello
```

それではビルドしてみます。ビルドする際は、`pagic build`コマンドを使います。`--serve`オプションを付与すると、ビルドの完了後、`http://127.0.0.1:8000`のURLからページを確認することができます。

```shell
$ pagic build  --serve
```

ビルドが完了すると、`public`ディレクトリが作成されます。

```
public/
├── assets/
|   ├── index.css
|   ├── reset.css
|   └── variables.css
├── _layout.js
├── _deps.tsx
├── favicon.ico
├── index.html
├── index.js
├── index_props.js
├── index.md
└── pagic.config.js
```

ブラウザから`http://127.0.0.1:8000`にアクセスして内容を確認してみましょう。

以下のように表示されれば成功です。

![pagic-sample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240412/621dad14-5b48-35fe-a6cc-63fdc7a34583.png)


## ページを追加する

次にページを追加してみます。

まず、以下のコマンドを実行しましょう。`--watch`オプションを付与することで、ソースに変更があった際に自動でリビルドしてくれるため、作業効率が上がります。

```shell
$ pagic build --watch --serve
```

次に以下のようなmarkdownファイルを用意してみます。

```markdown:src/sample-1.md
# Sample 1

テスト
```

ページにヘッダをつけてみます。

pagicでは画面のパーツをReactコンポーネントとして定義できます。
`src/_header.tsx`というファイルを作成し、ヘッダを用意します。

```tsx:src/_header.tsx
import { React } from "./_deps.tsx";

const Header = () => (
  <header className="header">
    <div>
      <a href="/">TOP</a>
    </div>
  </header>
);

export default Header;
```

CSSファイルも用意してみます。

`src/assets`ディレクトリを作成し、その中に`index.css`を用意します。

```css:src/assets/index.css
.header {
  z-index: 100;
  background-color: lightgray;
}
```

作成したCSSファイルとヘッダを読み込むよう`_layout.tsx`を変更しましょう。

```tsx:src/_layout.tsx
import { React, PagicLayout } from "./_deps.tsx";
import Header from "./_header.tsx";

const Layout: PagicLayout = ({ title, content }) => (
  <html>
    <head>
      <title>{title}</title>
      <meta charSet="utf-8" />
      <link rel="stylesheet" href="assets/index.css" />
    </head>
    <body>
      <Header />
      <main>
      {content}
      </main>
    </body>
  </html>
);

export default Layout;
```

それではブラウザから`http://localhost:8000/sample-1.html`を開いて内容を確認してみましょう。(マークダウンのファイル名を元にパスが決定されます)


![pagic-example2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240412/67401c74-a57f-7aee-3c51-ca155eed03f9.png)

もしコンパイルエラー等が発生し、サーバが停止していてたら、それらのファイルを修正してみてください。その後、`pagic build --serve`を実行すればうまく表示されると思います。

## 記事に作成日等のメタデータを定義したい

記事にメタデータを設定したいときは、markdownファイルの先頭で定義します([front matter](https://github.com/jxson/front-matter)というそうです)

```markdown
---
publishedAt: 2020-07-18
updatedAt: 2020-07-19
---

# Sample 1

テスト
```

このように定義しておくと、`Layout`コンポーネントにpropsとしてそれらのメタデータが渡されるようになります。

```tsx:src/_layout.tsx
const Layout: PagicLayout = ({ title, content, publishedAt, updatedAt }) => (
  <html>
    <head>
      <title>{title}</title>
      <meta charSet="utf-8" />
      <link rel="stylesheet" href="assets/index.css" />
    </head>
    <body>
      <Header />
      <main>
      {content}
      </main>
      <div>作成日: {publishedAt ? publishedAt.toString() : ""}</div>
      <div>更新日: {updatedAt ? updatedAt.toString() : ""}</div>
    </body>
  </html>
);
```

## 設定

`pagic.config.ts`ファイルを作成することで、pagicの挙動をカスタマイズできます。
pagicのデフォルトの設定は下記のようになっています。
プロジェクトルートに`pagic.config.ts`を作成しておくと、デフォルトの設定にマージされます。

```typescript
// pagicのREADME.mdより引用
/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2017-present, xcatliu
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
 * associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 
 * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 
 * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
export default {
  srcDir: '.',
  outDir: 'dist',
  include: undefined,
  exclude: [
    // Dot files
    '{,**/}.*',
    // Node common files
    '{,**/}package.json',
    '{,**/}package-lock.json',
    '{,**/}node_modules',
    // pagic.config.ts and pagic.config.tsx
    'pagic.config.{ts,tsx}',
    // https://docs.npmjs.com/using-npm/developers.html#keeping-files-out-of-your-package
    '{,**/}config.gypi',
    '{,**/}CVS',
    '{,**/}npm-debug.log'

    // ${config.outDir} will be added later
  ],
  root: '/',
  theme: 'default',
  plugins: ['clean', 'init', 'md', 'tsx', 'script', 'layout', 'out'],
  watch: false,
  serve: false,
  port: 8000
};
```

## GitHub Pagesにページを公開する

pagicで作成されたページをGitHub Pagesで公開してみます。

まず、GitHubでGitリポジトリを用意します。
[こちら](https://github.com/uki00a/blog)にリポジトリを用意してみたのでそちらも参考にしてみてください。

次にGitHub Actionsの設定を行います。
リポジトリに`.github/workflows/build.yml`を作成します。

```yaml:.github/workflows/build.yml
name: build
on:
  push:
    branches:
      - master
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@master
    - uses: denolib/setup-deno@master
      with:
        deno-version: 1.2.0
    - name: Build
      run: |
        deno run --unstable --allow-read --allow-write --allow-net https://deno.land/x/pagic@v0.7.30/mod.ts build
    - name: Deploy
      uses: peaceiris/actions-gh-pages@v3
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        publish_dir: ./public
```

これでmasterブランチへコミットするたびにpagicによるビルド及び[peaceiris/actions-gh-pagesアクション](https://github.com/peaceiris/actions-gh-pages)によるgh-pagesブランチへの公開が行われるようになります。

公開されたサイトは `https://<ユーザ名>.github.io/<リポジトリ名>/`で確認できます。
(私の場合、https://uki00a.github.io/blog/ でした)

#### GitHub Pagesへの公開がうまくいかない場合

[こちらのページ](https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-first-deployment-with-github_token)の`First Deployment with GITHUB_TOKEN`に記載されているとおり、`GITHUB_TOKEN`を使ったデプロイにはどうも制限があるようです。

私も以下の手順を踏むまではページの公開がうまく行きませんでした :sweat_smile: 

1. GitHub Actionsのセットアップ後、masterブランチへpushする
2. リポジトリの「Settings」ページを開き、GitHub Pagesの設定欄から`Source`ブランチを`gh-pages`から`master`へ変更する
3. 再度masterブランチへpushする
4. GitHub Actionsの実行完了を待つ
5. 再度、リポジトリの「Settings」ページを開き、GitHub Pagesの設定欄から`Source`ブランチを`master`から`gh-pages`へ変更する
6. 再度masterブランチへpushする

## この記事で紹介していない機能

この記事では紹介していませんが、pagicには他にも以下のような機能があります。

- プラグインシステム
- Reactコンポーネントで記事を書く

## おわりに

以上、pagicとマークダウンを使ってサイトを生成する方法について紹介しました。
pagicには将来的にテーマ機能等も実装される予定のようです。
興味があれば、ぜひ試してみてください!

## 参考

- [pagic](https://github.com/xcatliu/pagic)
- [peaceiris/actions-gh-pages](https://github.com/peaceiris/actions-gh-pages)