Hugoでnpmパッケージを利用する

Hugoでnpmパッケージを利用する方法について調べました。今回はreactを利用したjsxで作成したスクリプトを記事の中で利用してみました。

プロジェクトの初期化

まずは以下のコマンドでnpmプロジェクトの初期化を行います。

npm init

次にreactreact-domをプロジェクトにインストールします。

npm install --save react react-dom

ディレクトリ構成について

この記事のマークダウンはcontentディレクトリ以下にあります。

assetsディレクトリ以下にcontentディレクトリから記事のマークダウンまでと同じ階層でディレクトリを作成しました。

このディレクトリにスクリプトを配置するルールにしました。

この記事の場合のディレクトリ構成は以下です。

    • content/posts/blog
      • hugo-use-npm-package
        • index.mdこの記事のマークダウンファイルです
    • assets/posts/blog
      • hugo-use-npm-packageこの記事のアッセットを配置するディレクトリです
        • tsconfig.json
        • index.ts
        • App.tsx

index.tsが読み込まれるスクリプトのエントリーポイントになります。

テンプレートの設定について

次にテンプレートにスクリプトをビルドして記事に読み込むための設定を追加します。

通常の記事ページで利用したいのでsingle.htmlに設定を追加しました。

layouts/_default/single.html
<head>
...
{{- with resources.Get (path.Join .Page.File.Dir "index.ts") -}}
  {{- with . | js.Build (dict "minify" true "target" "es6") | fingerprint -}}
    <script src="{{ .Permalink | relURL }}" defer></script>
  {{- end -}}
{{- end -}}
</head>

リソースディレクトリからresources.Getで記事のマークダウンがあるディレクトリと同じ階層にあるindex.tsを取得します。

ファイルがある場合はjs.Buildでビルドして<script>タグを出力するようにしました。

js.Buildtargetオプションを利用することでビルドして出力されるjavascriptの規格を指定することができます。

ただし、js.Buildが利用しているesbuildは現時点ではes6からes5への変換がまだサポートされていません。そのためtargetにはes6を指定しました。

読み込むスクリプトの作成

それでは記事に読み込むスクリプトを作成していきます。

まずは以下の内容でtypescriptの構成ファイルtsconfig.jsonを作成しました。tsconfig.jsonを作成することでエディタでの編集も快適に行えると思います。

assets/posts/blog/hugo-use-npm-package/tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "module": "ES6",
    "moduleResolution": "Node",
    "target": "ES6",
    "lib": ["ES6", "DOM"],
    "jsx": "react-jsx"
  }
}

続いてreacttsxで以下のスクリプトを作成しました。スライダーで設定した赤、緑、青の色を表示する簡単なプログラムです。

assets/posts/blog/hugo-use-npm-package/App.tsx
import * as React from 'react'
import { createRoot } from 'react-dom/client'

function App() {
  const [r, setR] = React.useState('0')
  const [g, setG] = React.useState('50')
  const [b, setB] = React.useState('100')
  const color = `rgb(${r}, ${g}, ${b})`

  return (
    <React.StrictMode>
      <div style={{ backgroundColor: color }}>
        <input
          type="range"
          max="255"
          value={r}
          onChange={(e) => setR(e.target.value)}
        />
        <input
          type="range"
          max="255"
          value={g}
          onChange={(e) => setG(e.target.value)}
        />
        <input
          type="range"
          max="255"
          value={b}
          onChange={(e) => setB(e.target.value)}
        />
      </div>
      <span>{color}</span>
    </React.StrictMode>
  )
}

export function render(node: HTMLElement) {
  createRoot(node).render(<App />)
}

次にエントリーポイントとなるindex.tsを作成しました。App.tsxを読み込んで記事の中にあるidApp1のHTML要素にマウントするようにしています。

assets/posts/blog/hugo-use-npm-package/index.ts
import { render } from './App'

window.addEventListener('DOMContentLoaded', () => {
  render(document.getElementById('App1'))
})

最後に記事のマークダウンの中にidApp1<div>タグを追加しました。

<div id="App1"></div>

スタイルをあてて少し見た目を調整していますが実際の表示のサンプルは以下です。

お使いのブラウザがInternet Explorerの場合は構文エラーになるため表示されていないと思いますがご了承ください。

おわりに

Hugoでnpmパッケージを利用する方法について調べて、実際に作成したサンプルのスクリプトを記事の中に表示してみました。

esbuildが利用されているのでビルドがかなり早いのでスクリプトの作成をとても快適に行うことができました。記事の中にサンプルを表示したい場合に使えそうです。