<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>Frontend · 仁才徳</title>
    <link>https://jared.lynskey.co.nz/ja/tags/frontend/</link>
    <description>ソフトウェアエンジニアリングと開発組織のリーダーシップについて。評価、採用とチームの拡大、CI/CD、DevOps モニタリング、エージェンティック AI。ソウルより。</description>
    <generator>Hugo</generator>
    <language>ja</language>
    <copyright>© 2026 仁才徳</copyright>
    <lastBuildDate>Wed, 28 Oct 2026 00:00:00 +1300</lastBuildDate>
    <atom:link href="https://jared.lynskey.co.nz/ja/tags/frontend/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Orvalの使い方：OpenAPIスペックから型付きAPIクライアントとモックを自動生成する</title>
      <link>https://jared.lynskey.co.nz/ja/posts/2024/2024-04-10-openapitools/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/ja/posts/2024/2024-04-10-openapitools/</guid>
      <pubDate>Wed, 10 Apr 2024 00:30:03 +0000</pubDate>
      <dc:creator>Jared Lynskey</dc:creator>
      <category>API Development</category>
      <category>Orval</category>
      <category>OpenAPI</category>
      <category>TypeScript</category>
      <category>React Query</category>
      <category>MSW</category>
      <category>Frontend</category>
      <category>Backend</category>
      <category>Teamwork</category>
      <category>Efficiency</category>
      <description>OrvalはOpenAPIスペックを読み込んで、型付きのTypeScriptクライアント、React Queryのフック、MSWのモックを生成してくれるので、APIレイヤーを手書きする人はいなくなります。セットアップの手順、モックの仕組み、openapi-generator・openapi-typescript・Hey APIとの比較をまとめました。</description>
      <content:encoded><![CDATA[<p>Orvalは、OpenAPI（Swagger）のスペックを読み込んで、フロントエンドのAPIレイヤーを代わりに書いてくれるコードジェネレーターです。エンドポイントごとの型付き関数かReact Queryのフック、コントラクトに正確に一致するTypeScriptのモデル、そして指定すれば、Fakerのデータが入ったMSWのモックハンドラーまで生成します。設定ファイルを1つコミットして<code>orval</code>を実行し、スペックが変わったらまた実行する。それだけです。</p>
<p>このシリーズの最初の記事で、「コードを書く前にOpenAPIのコントラクトに合意しよう」という話をしました。今回はそのコントラクトを実際に「元が取れるもの」にしてくれるツールの話です。</p>
<p>フロントエンドのAPIレイヤーを手書きする作業は、楽しいと公言する人がいない類の仕事です。バックエンドのドキュメントから型を書き写し、40回目の同じリクエストラッパーを書き、3週間後に誰かがフィールド名を変えて、呼び出しの半分が静かに壊れる。Orvalはこの仕事そのものを消してくれます。OpenAPIスペックを指定すれば、クライアントコードを生成してくれるのです。全エンドポイント分の型付き関数、コントラクトに正確に一致するモデル、そしてバックエンドが存在する前から開発に使えるモック。</p>

<h2 class="relative group">フロントエンドチームが気に入る理由
    <div id="フロントエンドチームが気に入る理由" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%95%e3%83%ad%e3%83%b3%e3%83%88%e3%82%a8%e3%83%b3%e3%83%89%e3%83%81%e3%83%bc%e3%83%a0%e3%81%8c%e6%b0%97%e3%81%ab%e5%85%a5%e3%82%8b%e7%90%86%e7%94%b1" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>過小評価されがちなのがモック生成です。スペックに合意した時点で、フロントエンドは偽のサーバーを立てて、UI全体をそれに向かって作り込めます。永遠に「もうすぐできます」のエンドポイントを待つ必要はありません。本物のバックエンドができたら、クライアントの向き先を変えるだけで、だいたいそのまま動きます。両側が同じドキュメントから作られているからです。</p>
<p>もうひとつの利点は、退屈なコードが「構造的に正しい」状態になることです。手書きのリクエストボディでフィールド名を打ち間違える人はいなくなります。そもそも手書きのリクエストボディを書く人がいなくなるので。</p>

<h2 class="relative group">バックエンドチームにも嬉しい理由
    <div id="バックエンドチームにも嬉しい理由" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%90%e3%83%83%e3%82%af%e3%82%a8%e3%83%b3%e3%83%89%e3%83%81%e3%83%bc%e3%83%a0%e3%81%ab%e3%82%82%e5%ac%89%e3%81%97%e3%81%84%e7%90%86%e7%94%b1" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>スペックが唯一の情報源になると、日々の会話が変わります。「このエンドポイントって何を返すんでしたっけ？」というチャットのスレッドの代わりに、フロントエンドは生成された型を読めばいい。バックエンド開発者は割り込まれる回数が減り、フィードバックは早く届くようになります。フロントエンドがモック相手に作ってみて「このAPI、使いにくいな」と感じたら、それが分かるのは本番投入後ではなく、まだ変更が安上がりなうちです。</p>

<h2 class="relative group">ワークフローへの組み込み方
    <div id="ワークフローへの組み込み方" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%af%e3%83%bc%e3%82%af%e3%83%95%e3%83%ad%e3%83%bc%e3%81%b8%e3%81%ae%e7%b5%84%e3%81%bf%e8%be%bc%e3%81%bf%e6%96%b9" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>けっして凝ったことはしていません。バックエンドがスペックを提案し、両チームでレビューし、フロントエンドはOrvalでクライアントとモックを生成して作り始め、バックエンドは同じコントラクトに沿って本物を実装する。スペックが変わったら両側で再生成すれば、何が壊れたかはコンパイラが正確に教えてくれます。この最後の部分こそが本質です。フロントエンドとバックエンドのズレが、本番のバグではなくコンパイルエラーになるのです。</p>

<h2 class="relative group">セットアップ
    <div id="セットアップ" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%bb%e3%83%83%e3%83%88%e3%82%a2%e3%83%83%e3%83%97" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>まずは開発用の依存関係としてインストールします。生成されたコードがimportするパッケージも一緒に入れておきましょう。React Queryのフックとモックを使うなら、TanStack Query、MSW、Fakerです。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">npm install --save-dev orval msw @faker-js/faker
</span></span><span class="line"><span class="cl">npm install @tanstack/react-query</span></span></code></pre></div></div>
<p>次に、プロジェクトのルートに<code>orval.config.ts</code>を置きます。Orval公式ドキュメントの例とほぼ同じものです。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ts" data-lang="ts"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;orval&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="k">default</span> <span class="nx">defineConfig</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">  <span class="nx">petstore</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">input</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">target</span><span class="o">:</span> <span class="s1">&#39;./openapi.yaml&#39;</span><span class="p">,</span> <span class="c1">// a path or a URL
</span></span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">output</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">mode</span><span class="o">:</span> <span class="s1">&#39;single&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">target</span><span class="o">:</span> <span class="s1">&#39;./src/api/petstore.ts&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">schemas</span><span class="o">:</span> <span class="s1">&#39;./src/api/model&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">client</span><span class="o">:</span> <span class="s1">&#39;react-query&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">httpClient</span><span class="o">:</span> <span class="s1">&#39;fetch&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nx">mock</span>: <span class="kt">true</span><span class="p">,</span> <span class="c1">// MSW handlers plus Faker data
</span></span></span><span class="line"><span class="cl">      <span class="nx">override</span><span class="o">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nx">fetch</span><span class="o">:</span> <span class="p">{</span> <span class="nx">includeHttpResponseReturnType</span>: <span class="kt">false</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div>
<p>何が生成されるかは<code>client</code>で決まります。素の<code>fetch</code>や<code>axios</code>の関数、React Query・Vue Query・Svelte Query・SWR用のフック、Angularのサービス、Zodのスキーマなどです。<code>includeHttpResponseReturnType: false</code>の行は、fetchクライアントが<code>{ data, status }</code>ではなくレスポンスボディそのものを返すようにする設定で、コンポーネント側が欲しいのはたいていこちらです。</p>
<p>コマンドを誰も覚えなくて済むように、scriptsに登録しておきます。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;scripts&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;api&#34;</span><span class="p">:</span> <span class="s2">&#34;orval&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p><code>npm run api</code>でフックとモデルが書き出されます。あとはコンポーネントから普通のReact Queryのフックと同じように使うだけで、型は最初から埋まっています。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-tsx" data-lang="tsx"><span class="line"><span class="cl"><span class="kr">import</span> <span class="p">{</span> <span class="nx">useListPets</span> <span class="p">}</span> <span class="kr">from</span> <span class="s1">&#39;./api/petstore&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kr">export</span> <span class="kd">function</span> <span class="nx">PetList() {</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="p">{</span> <span class="nx">data</span>: <span class="kt">pets</span><span class="p">,</span> <span class="nx">isLoading</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useListPets</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="nx">isLoading</span><span class="p">)</span> <span class="k">return</span> <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span><span class="nx">Loading</span><span class="err">…</span><span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;;</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span><span class="nx">pets</span><span class="o">?</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">pet</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">&lt;</span><span class="nt">li</span> <span class="na">key</span><span class="o">=</span><span class="p">{</span><span class="nx">pet</span><span class="p">.</span><span class="nx">id</span><span class="p">}&gt;{</span><span class="nx">pet</span><span class="p">.</span><span class="nx">name</span><span class="p">}&lt;/</span><span class="nt">li</span><span class="p">&gt;)}</span>
</span></span><span class="line"><span class="cl">    <span class="p">&lt;/</span><span class="nt">ul</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>スペックでフィールド名を変えて<code>npm run api</code>をもう一度実行すれば、<code>pet.name</code>を使っている箇所が全部赤くなります。上で書いたズレのチェックがこれで、実行コストはゼロです。</p>

<h2 class="relative group">モックを相手に開発する
    <div id="モックを相手に開発する" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%83%a2%e3%83%83%e3%82%af%e3%82%92%e7%9b%b8%e6%89%8b%e3%81%ab%e9%96%8b%e7%99%ba%e3%81%99%e3%82%8b" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p><code>mock: true</code>にすると、Orvalはエンドポイントごとに、レスポンスのスキーマどおりの形をしたFakerのデータを返すMSWのリクエストハンドラーと、それらをまとめて返す関数も書き出します。そのリストを、ブラウザならMSWの<code>setupWorker</code>に、テストなら<code>setupServer</code>に渡せば、アプリは最初からコントラクトに一致した偽のバックエンドを相手に動きます。各ハンドラーは上書き用の値も受け取れるので、テストで特定のペイロードや空のリスト、エラー状態を返したいときも、誰かがフィクスチャを手書きする必要はありません。</p>
<p>フロントエンドが初日から作業を始められるのは、この部分のおかげです。モックは誰かが別途メンテナンスしなければならない成果物ではありません。クライアントと同じスペックから毎回再生成されるので、スペックに置いていかれようがないのです。</p>

<h2 class="relative group">Orvalとopenapi-generator、openapi-typescript、Hey APIの比較
    <div id="orvalとopenapi-generatoropenapi-typescripthey-apiの比較" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#orval%e3%81%a8openapi-generatoropenapi-typescripthey-api%e3%81%ae%e6%af%94%e8%bc%83" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>どれもOpenAPIスペックから出発しますが、どこまで生成するかが違います。</p>
<table>
	<thead>
			<tr>
					<th></th>
					<th>生成するもの</th>
					<th>向いているケース</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><strong>Orval</strong></td>
					<td>型付きのクライアント関数やフレームワーク用のフック（React Query、SWR、Vue、Svelte、Angular）、モデル、MSWのモック、Zodのスキーマ</td>
					<td>1つの設定ファイルからフックとモックをまとめて得たいTypeScriptのフロントエンド</td>
			</tr>
			<tr>
					<td><strong>openapi-generator</strong></td>
					<td>数十の言語のクライアントとサーバースタブ（TypeScriptも含む）</td>
					<td>同じスペックからJava、Kotlin、Swift、TypeScriptを生成しなければならないチーム</td>
			</tr>
			<tr>
					<td><strong>openapi-typescript</strong></td>
					<td>型のみ。小さな型付きfetchラッパーの<code>openapi-fetch</code>と組み合わせて使う</td>
					<td>型だけが欲しくて、ランタイムのコードはほとんど要らない場合</td>
			</tr>
			<tr>
					<td><strong>Hey API</strong>（<code>@hey-api/openapi-ts</code>）</td>
					<td>型付きのSDK。TanStack QueryやZodなどのプラグインあり</td>
					<td>Orvalと守備範囲が近く、プラグインの仕組みを中心に作られている</td>
			</tr>
	</tbody>
</table>
<p>型だけで足りるなら、openapi-typescriptが一番軽くて、文句のつけようがありません。openapi-generatorが真価を発揮するのは、1つのスペックから複数の言語向けにコードを生成しなければならないときです。TypeScriptの出力もちゃんと動きますが、読んでいると、まずJava向けに書かれたものだという感触があります。OrvalとHey APIのどちらにするかは、ほぼ好みと、どちらの出力コードを読みたいか次第です。私がOrvalを使い続けているのは、フックと同じ1回の実行でモックも出てくるからで、2つのチームの働き方を変えたのはまさにこの機能でした。</p>
<p>派手なツールではありません。ただ、チーム間の言い争いをひとつのカテゴリごと静かに消してくれる。それと引き換えなら、私はいつでも喜んで使います。</p>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2024/2024-04-10-openapitools/featured.jpg" medium="image" type="image/jpeg" />
    </item>
    <item>
      <title>APIファースト開発をOpenAPIで：フロントエンドとバックエンドを並行して進める</title>
      <link>https://jared.lynskey.co.nz/ja/posts/2023/2023-10-08-open-api/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/ja/posts/2023/2023-10-08-open-api/</guid>
      <pubDate>Sun, 08 Oct 2023 00:30:03 +0000</pubDate>
      <dc:creator>Jared Lynskey</dc:creator>
      <category>OpenAPI</category>
      <category>backend</category>
      <category>frontend</category>
      <category>API design</category>
      <category>software development</category>
      <category>team collaboration</category>
      <description>APIファースト開発の実践編です。先にOpenAPIでコントラクトに合意し、モックサーバーを立てれば、フロントエンドはバックエンドを待たずに済みます。</description>
      <content:encoded><![CDATA[<p>これまで見てきた中で一番確実にスケジュールを殺すのは、難しいエンジニアリングではありません。「もうすぐできる」エンドポイントを、フロントエンドがひたすら待っている状態です。職場で落ち着いた解決策は地味ですが効きます。コードを書く前に、まずOpenAPIでAPIコントラクトに合意することです。</p>

<h3 class="relative group">待ちのゲーム
    <div id="待ちのゲーム" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e5%be%85%e3%81%a1%e3%81%ae%e3%82%b2%e3%83%bc%e3%83%a0" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>従来のフローは直列です。バックエンドがAPIを設計・実装し、エンドポイントが実際に動くようになってから、フロントエンドが統合を始める。バックエンドの遅れはすべてフロントエンドのカレンダーに直撃しますし、リリース日が載っているのはたいていフロントエンドのカレンダーです。</p>

<h3 class="relative group">コードより先にコントラクト
    <div id="コードより先にコントラクト" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%b3%e3%83%bc%e3%83%89%e3%82%88%e3%82%8a%e5%85%88%e3%81%ab%e3%82%b3%e3%83%b3%e3%83%88%e3%83%a9%e3%82%af%e3%83%88" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>スキーマファーストのフローでは、両者がほぼ同時に走り出せます。コードが1行も存在しない段階で、APIの構造をOpenAPIスキーマに書き出します。すべてのエンドポイント、リクエストとレスポンスのモデル、認証方式、エラーコード。このドキュメントが、フロントエンドとバックエンドの間の契約書になります。</p>
<p>見返りはすぐに現れます。作る前に、全員が同じ形で機能を理解できる。バックエンドが実装中でも、フロントエンドはUIを作り始められる。合意済みの唯一の情報源を更新すればいいので、変更も速く回る。さらにスキーマから直接モックサーバーを生成できるので、本物のエンドポイントがひとつもない段階からフロントエンドのテストが始められます。</p>

<h3 class="relative group">実際の回し方
    <div id="実際の回し方" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e5%ae%9f%e9%9a%9b%e3%81%ae%e5%9b%9e%e3%81%97%e6%96%b9" aria-label="アンカー">#</a>
    </span>
    
</h3>
<ol>
<li>要件を集める。これは何を作るときでも同じです。</li>
<li>スキーマを設計する。エンドポイント、リクエスト／レスポンスモデル、認証、エラーコード。</li>
<li>フロントとバックの両方でレビューする。コントラクトが本領を発揮するのはここです。</li>
<li>モックサーバーを生成し（SwaggerでもPostmanでも可能です）、フロントエンドはそれに対して統合を進める。</li>
<li>バックエンドは同じスキーマに沿って本物のエンドポイントを実装する。</li>
<li>出来上がったものからモックを本物に差し替え、契約が守られているか継続的にテストする。</li>
<li>会話を続ける。スキーマは生きたドキュメントであって、石版ではありません。</li>
</ol>

<h3 class="relative group">引っかかりどころ
    <div id="引っかかりどころ" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e5%bc%95%e3%81%a3%e3%81%8b%e3%81%8b%e3%82%8a%e3%81%a9%e3%81%93%e3%82%8d" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>タダではありません。しっかりしたスキーマを前もって書くには、それなりの時間がかかります。要件が変われば先にスキーマを直す必要があり、その波紋は両チームに及びます。そして全員が、OpenAPIと周辺ツールについて最低限の土地勘を持っている必要があります。</p>
<p>それでも、この先行投資が、防いでくれる統合の混乱に負けたところを見たことがありません。フロントエンドは初日から動き出せますし、議論はビルドが壊れてからではなく、ドキュメントの上で起きるようになります。</p>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2023/2023-10-08-open-api/featured.jpg" medium="image" type="image/jpeg" />
    </item>
    <item>
      <title>Zdog：疑似3DのJavaScriptライブラリで光復節の太極旗を掲げる</title>
      <link>https://jared.lynskey.co.nz/ja/posts/2019/2019-08-08-gwangbokjeol/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/ja/posts/2019/2019-08-08-gwangbokjeol/</guid>
      <pubDate>Thu, 08 Aug 2019 00:00:00 +0000</pubDate>
      <dc:creator>Jared Lynskey</dc:creator>
      <category>Zdog</category>
      <category>JavaScript</category>
      <category>Frontend</category>
      <category>Canvas</category>
      <category>Korea</category>
      <description>Zdogはcanvas・SVG向けの小さな疑似3Dエンジンです。図形は3D空間にあるのに、描画はフラット。最小限のコード例と、光復節に合わせて太極旗を掲げたCodePenを載せています。</description>
      <content:encoded><![CDATA[<p>来週は韓国の光復節です。この日は昔から、家やアパートの窓の外に国旗を掲げる習わしがあります。せっかくなので、この日の記念とJavaScriptの腕磨きを兼ねて、気になっていた疑似3Dライブラリを試してみることにしました。</p>
<p>光復節（8月15日）は、1945年に日本の植民地支配が終わったことを記念する韓国の祝日です。Zdogは、<code>&lt;canvas&gt;</code>やSVGに3Dっぽいイラストを描くための小さなJavaScriptライブラリです。図形は実際に3D空間に置かれていて3Dの物体のように回転しますが、描かれるのは平面の図形なので、3Dレンダリングというより、回して見られるフラットなイラストに近い見た目になります。旗のようなシンプルなものにはちょうどいい道具です。</p>

<h2 class="relative group">Zdogの最小限の例
    <div id="zdogの最小限の例" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#zdog%e3%81%ae%e6%9c%80%e5%b0%8f%e9%99%90%e3%81%ae%e4%be%8b" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>図形2つ、ドラッグで回転、そしてアニメーションのループです。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">canvas</span> <span class="na">class</span><span class="o">=</span><span class="s">&#34;zdog-canvas&#34;</span> <span class="na">width</span><span class="o">=</span><span class="s">&#34;240&#34;</span> <span class="na">height</span><span class="o">=</span><span class="s">&#34;240&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">canvas</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span> <span class="na">src</span><span class="o">=</span><span class="s">&#34;https://unpkg.com/zdog@1/dist/zdog.dist.min.js&#34;</span><span class="p">&gt;&lt;/</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">script</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="kr">const</span> <span class="nx">illo</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Zdog</span><span class="p">.</span><span class="nx">Illustration</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">element</span><span class="o">:</span> <span class="s1">&#39;.zdog-canvas&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">dragRotate</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="c1">// drag to turn the scene
</span></span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// a square, pushed back
</span></span></span><span class="line"><span class="cl">  <span class="k">new</span> <span class="nx">Zdog</span><span class="p">.</span><span class="nx">Rect</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">addTo</span><span class="o">:</span> <span class="nx">illo</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">width</span><span class="o">:</span> <span class="mi">80</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">height</span><span class="o">:</span> <span class="mi">80</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">translate</span><span class="o">:</span> <span class="p">{</span> <span class="nx">z</span><span class="o">:</span> <span class="o">-</span><span class="mi">30</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">stroke</span><span class="o">:</span> <span class="mi">8</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">color</span><span class="o">:</span> <span class="s1">&#39;#0047a0&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1">// a filled circle, pulled forward
</span></span></span><span class="line"><span class="cl">  <span class="k">new</span> <span class="nx">Zdog</span><span class="p">.</span><span class="nx">Ellipse</span><span class="p">({</span>
</span></span><span class="line"><span class="cl">    <span class="nx">addTo</span><span class="o">:</span> <span class="nx">illo</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">diameter</span><span class="o">:</span> <span class="mi">60</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">translate</span><span class="o">:</span> <span class="p">{</span> <span class="nx">z</span><span class="o">:</span> <span class="mi">30</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nx">stroke</span><span class="o">:</span> <span class="mi">8</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">color</span><span class="o">:</span> <span class="s1">&#39;#cd2e3a&#39;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nx">fill</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kd">function</span> <span class="nx">animate</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">illo</span><span class="p">.</span><span class="nx">rotate</span><span class="p">.</span><span class="nx">y</span> <span class="o">+=</span> <span class="mf">0.02</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="nx">illo</span><span class="p">.</span><span class="nx">updateRenderGraph</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="nx">requestAnimationFrame</span><span class="p">(</span><span class="nx">animate</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="nx">animate</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;/</span><span class="nt">script</span><span class="p">&gt;</span></span></span></code></pre></div></div>
<p>図形は<code>addTo</code>でシーンに追加します。3Dらしさを生んでいるのは<code>translate.z</code>で、シーンを回すと円が四角形の前に回り込んできます。<code>updateRenderGraph()</code>が全体を更新して描き直すので、ループの中で呼んでいます。</p>

<h2 class="relative group">公式ドキュメント
    <div id="公式ドキュメント" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e5%85%ac%e5%bc%8f%e3%83%89%e3%82%ad%e3%83%a5%e3%83%a1%e3%83%b3%e3%83%88" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p><a href="https://zzz.dog/"  target="_blank" rel="noreferrer">https://zzz.dog/</a></p>
<p>使ったコードはCodePenで見られます。</p>

<h2 class="relative group">作ってみたもの
    <div id="作ってみたもの" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e4%bd%9c%e3%81%a3%e3%81%a6%e3%81%bf%e3%81%9f%e3%82%82%e3%81%ae" aria-label="アンカー">#</a>
    </span>
    
</h2>
<div class="codepen" data-height="700" data-theme-id="light" data-default-tab="result" data-user="jared-lynskey" data-slug-hash="voRGqp" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="광복절">
  <span>See the Pen <a href="https://codepen.io/jared-lynskey/pen/voRGqp/">
  광복절</a> by Jared Lynskey (<a href="https://codepen.io/jared-lynskey">@jared-lynskey</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</div>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2019/2019-08-08-gwangbokjeol/featured.jpeg" medium="image" type="image/jpeg" />
    </item>
    <item>
      <title>HugoでSCSSを使う方法：Hugo Pipesでコンパイルする手順</title>
      <link>https://jared.lynskey.co.nz/ja/posts/2019/2019-08-06-understandingsass/</link>
      <guid isPermaLink="true">https://jared.lynskey.co.nz/ja/posts/2019/2019-08-06-understandingsass/</guid>
      <pubDate>Tue, 06 Aug 2019 00:00:00 +0000</pubDate>
      <dc:creator>Jared Lynskey</dc:creator>
      <category>css</category>
      <category>frontend</category>
      <category>webdev</category>
      <category>gohugo</category>
      <description>HugoでSCSS（Sass）を使う方法です。このサイトにHugo PipesでSCSSのコンパイルを導入した手順を、短い4ステップでまとめました。</description>
      <content:encoded><![CDATA[<p>このサイトのCSSが、いつの間にか長くて繰り返しだらけの1ファイルに育ってしまったので、ようやくSASSに移行しました。GoHugoでサイトを運用しているなら、15分の作業で、その後スタイルシートを触るたびに元が取れるタイプの改善です。</p>
<p>セットアップの手順をまとめておきます。</p>

<h2 class="relative group">そもそもなぜSASSなのか
    <div id="そもそもなぜsassなのか" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%81%9d%e3%82%82%e3%81%9d%e3%82%82%e3%81%aa%e3%81%9csass%e3%81%aa%e3%81%ae%e3%81%8b" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>素のCSSは、ページ数が少し増えただけで散らかり始めます。SASSなら変数・ネスト・ミックスイン・関数が使えるので、色や共通のレイアウト定義をあちこちにコピペせず、一箇所にまとめられます。そしてHugoユーザーにとって一番うれしいのは、Hugo PipesがSCSSをそのままコンパイルしてくれること。別のビルドツールもウォッチャースクリプトも要りません。</p>
<p>作業中に開いておくと便利なページはこの2つです。</p>
<ul>
<li><a href="https://sass-lang.com/"  target="_blank" rel="noreferrer">SASSのドキュメント</a></li>
<li>Hugoの<a href="https://gohugo.io/functions/css/sass/"  target="_blank" rel="noreferrer">SCSS/SASSサポートのドキュメント</a></li>
</ul>

<h2 class="relative group">セットアップ
    <div id="セットアップ" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%bb%e3%83%83%e3%83%88%e3%82%a2%e3%83%83%e3%83%97" aria-label="アンカー">#</a>
    </span>
    
</h2>

<h3 class="relative group">1. assetsディレクトリを用意する
    <div id="1-assetsディレクトリを用意する" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#1-assets%e3%83%87%e3%82%a3%e3%83%ac%e3%82%af%e3%83%88%e3%83%aa%e3%82%92%e7%94%a8%e6%84%8f%e3%81%99%e3%82%8b" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p>Hugoはプロジェクトのルートにある<code>assets</code>フォルダを見に行くので、そこに作成（または移動）します。<code>resources</code>フォルダの中に置きたい場合は、<code>config.toml</code>でHugoに場所を教えてあげます。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="nx">assetDir</span> <span class="p">=</span> <span class="s2">&#34;resources/assets&#34;</span></span></span></code></pre></div></div>

<h3 class="relative group">2. <code>sass</code>フォルダを作る
    <div id="2-sassフォルダを作る" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#2-sass%e3%83%95%e3%82%a9%e3%83%ab%e3%83%80%e3%82%92%e4%bd%9c%e3%82%8b" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p><code>assets</code>の中に<code>sass</code>というフォルダを作ります。SCSSファイルはここに置きます。</p>

<h3 class="relative group">3. エントリーポイントを追加する
    <div id="3-エントリーポイントを追加する" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#3-%e3%82%a8%e3%83%b3%e3%83%88%e3%83%aa%e3%83%bc%e3%83%9d%e3%82%a4%e3%83%b3%e3%83%88%e3%82%92%e8%bf%bd%e5%8a%a0%e3%81%99%e3%82%8b" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p><code>sass</code>フォルダの中に<code>main.scss</code>を作成します。Hugoがコンパイルするのはこのファイルなので、動作確認を兼ねて少しSCSSを書いておきましょう。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-scss" data-lang="scss"><span class="line"><span class="cl"><span class="c1">// assets/sass/main.scss
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// 変数
</span></span></span><span class="line"><span class="cl"><span class="nv">$primary-color</span><span class="o">:</span> <span class="mh">#3498db</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// ミックスイン
</span></span></span><span class="line"><span class="cl"><span class="k">@mixin</span><span class="nf"> center</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="na">display</span><span class="o">:</span> <span class="ni">flex</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="na">justify-content</span><span class="o">:</span> <span class="ni">center</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="na">align-items</span><span class="o">:</span> <span class="ni">center</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// スタイル
</span></span></span><span class="line"><span class="cl"><span class="nt">body</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="na">font-family</span><span class="o">:</span> <span class="n">Arial</span><span class="o">,</span> <span class="ni">sans-serif</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="na">background-color</span><span class="o">:</span> <span class="nv">$primary-color</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">h1</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">@include</span><span class="nd"> center</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="na">color</span><span class="o">:</span> <span class="ni">white</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>

<h3 class="relative group">4. テンプレートに組み込む
    <div id="4-テンプレートに組み込む" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#4-%e3%83%86%e3%83%b3%e3%83%97%e3%83%ac%e3%83%bc%e3%83%88%e3%81%ab%e7%b5%84%e3%81%bf%e8%be%bc%e3%82%80" aria-label="アンカー">#</a>
    </span>
    
</h3>
<p><code>head.html</code>パーシャルで、コンパイル済みスタイルシートを読み込んでminifyします。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="p">{{</span><span class="w"> </span><span class="err">$</span><span class="nx">style</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">resources</span><span class="p">.</span><span class="nx">Get</span><span class="w"> </span><span class="s">&#34;sass/main.scss&#34;</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nx">resources</span><span class="p">.</span><span class="nx">ToCSS</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nx">resources</span><span class="p">.</span><span class="nx">Minify</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nx">link</span><span class="w"> </span><span class="nx">rel</span><span class="p">=</span><span class="s">&#34;stylesheet&#34;</span><span class="w"> </span><span class="nx">href</span><span class="p">=</span><span class="s">&#34;{{ $style.Permalink }}&#34;</span><span class="p">&gt;</span></span></span></code></pre></div></div>
<p>ここのファイル名が、実際のエントリーポイント（<code>main.scss</code>）と一致しているかだけ確認してください。</p>

<h2 class="relative group">スタイルの分割
    <div id="スタイルの分割" class="anchor"></div>
    
    <span
        class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none">
        <a class="text-primary-300 dark:text-neutral-700 !no-underline" href="#%e3%82%b9%e3%82%bf%e3%82%a4%e3%83%ab%e3%81%ae%e5%88%86%e5%89%b2" aria-label="アンカー">#</a>
    </span>
    
</h2>
<p>サイトが育ってきたら、SCSSをパーシャルに分割して<code>main.scss</code>からインポートします。</p>
<div class="highlight-wrapper"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-scss" data-lang="scss"><span class="line"><span class="cl"><span class="c1">// assets/sass/_variables.scss
</span></span></span><span class="line"><span class="cl"><span class="nv">$primary-color</span><span class="o">:</span> <span class="mh">#3498db</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// assets/sass/_mixins.scss
</span></span></span><span class="line"><span class="cl"><span class="k">@mixin</span><span class="nf"> center</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="na">display</span><span class="o">:</span> <span class="ni">flex</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="na">justify-content</span><span class="o">:</span> <span class="ni">center</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="na">align-items</span><span class="o">:</span> <span class="ni">center</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// assets/sass/main.scss
</span></span></span><span class="line"><span class="cl"><span class="k">@import</span> <span class="s2">&#34;variables&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">@import</span> <span class="s2">&#34;mixins&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">body</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="na">background-color</span><span class="o">:</span> <span class="nv">$primary-color</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nt">h1</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">@include</span><span class="nd"> center</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="na">color</span><span class="o">:</span> <span class="ni">white</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>本当にこれだけです。4ステップで、あとはビルドのたびにHugoがスタイルを再コンパイルしてくれます。アセットパイプラインと格闘する覚悟で臨んだのに、拍子抜けするほどあっさり動きました。</p>
]]></content:encoded>
      <media:content url="https://jared.lynskey.co.nz/en/posts/2019/2019-08-06-understandingsass/featured.jpeg" medium="image" type="image/jpeg" />
    </item>
  </channel>
</rss>
