<?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/">
  <channel>
    <title>okiyama.dev</title>
    <link>https://okiyama.dev/</link>
    <description>Recent content on okiyama.dev</description>
    <generator>Hugo -- 0.151.0</generator>
    <language>ja</language>
    <lastBuildDate>Sat, 14 Feb 2026 05:56:39 +0900</lastBuildDate>
    <atom:link href="https://okiyama.dev/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>VoiceOS</title>
      <link>https://okiyama.dev/posts/2026-02-14-dictation/</link>
      <pubDate>Sat, 14 Feb 2026 05:56:39 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2026-02-14-dictation/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://www.voiceos.com/&#34; target=&#34;_blank&#34;&gt;VoiceOS&lt;/a&gt;
 を使い始めた。 &lt;a href=&#34;https://aquavoice.com/&#34; target=&#34;_blank&#34;&gt;AquaVoice&lt;/a&gt;
 も無料の範囲で試したが、 VoiceOS は指示モードを別キーに割り当てられる点が好み。&lt;/p&gt;
&lt;h2 id=&#34;キー割り当て&#34;&gt;キー割り当て&lt;/h2&gt;
&lt;p&gt;最初 F13 キーに割り当てたら、 Apple 純正メモや ChatGPT など一部のアプリで、キーを押しっぱなしにするとビープ音が鳴り続ける現象があった。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://remap-keys.app/&#34; target=&#34;_blank&#34;&gt;REMAP&lt;/a&gt;
 で Ro (International 1) を割り当て、 VoiceOS の設定で割り当てたら解消した。&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://okiyama.dev/images/voiceos-1-remap.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://okiyama.dev/images/voiceos-2-settings.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Unknown&lt;/code&gt; と表示されるが正常に動作する。&lt;/p&gt;
&lt;h2 id=&#34;音声入力の文章&#34;&gt;音声入力の文章&lt;/h2&gt;
&lt;p&gt;音声入力は文章が冗長になりやすい。最近のものはフィラーを取り除いてくれるが、それ以外にも手入力では出にくい余分な表現が増える。 AI チャットにならそのまま送れば良いが、日記やブログ、メッセンジャーで使う場合は入力後に削りたくなる。 AI に整形させてもいいが毎回は面倒。&lt;/p&gt;
&lt;p&gt;VoiceOS の指示モードで「要約して」と最初に指示してから話すとわりといい感じになる。&lt;/p&gt;
&lt;p&gt;以下は「これから話す内容を簡潔に要約して。要約前の文も出力すること」で始めた結果。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;これから話す内容を簡潔に要約して要約前の文も出力することMacやPCではVoiceOSやAquaOSで精度の高い音声入力をすることができるようになったけどスマホというかiOSは標準を使っていて他にもアプリがあるようなのでそれも気になるただモバイルでの音声入力はAndroidのデフォルトのディクテーションがかなり優秀らしいので気になる&lt;/p&gt;
&lt;p&gt;MacやPCではVoiceOSやAquaOSで高精度な音声入力が可能だが、iOSは標準機能のみで他のアプリも気になる。一方、Androidのデフォルトディクテーションは非常に優秀と聞く。&lt;/p&gt;&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;AquaOS となっているのは誤認識ではなく単なる言い間違い&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;要約して、だけだとあまりに短くなりすぎる気もするが、しばらく試してみたい。&lt;/p&gt;
&lt;p&gt;VoiceOS の設定にカスタムプロンプトというものがある。ここに設定して常に有効にすればいいと考えたが、これはアプリごとに設定するもので、全体に適用するのは今のところできないようだ。&lt;/p&gt;
&lt;p&gt;招待コード: &lt;a href=&#34;https://voiceos.com/r?code=f7e38407&#34; target=&#34;_blank&#34;&gt;https://voiceos.com/r?code=f7e38407&lt;/a&gt;
&lt;/p&gt;
&lt;h2 id=&#34;chatgpt-の音声入力&#34;&gt;ChatGPT の音声入力&lt;/h2&gt;
&lt;p&gt;ChatGPT の音声入力も使える。&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://okiyama.dev/images/voiceos-3-chatgpt.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;AquaVoice を使った時は、音声入力ツールに追加でお金を出すのはどうかなと思い、結局やめてしまった。&lt;/p&gt;
&lt;p&gt;ChatGPT は既に契約しているし、 Mac のアプリであればショートカットキーで呼び出せるし、とりあえずこれ使えばいいか&amp;hellip;という。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><a href="https://www.voiceos.com/" target="_blank">VoiceOS</a>
 を使い始めた。 <a href="https://aquavoice.com/" target="_blank">AquaVoice</a>
 も無料の範囲で試したが、 VoiceOS は指示モードを別キーに割り当てられる点が好み。</p>
<h2 id="キー割り当て">キー割り当て</h2>
<p>最初 F13 キーに割り当てたら、 Apple 純正メモや ChatGPT など一部のアプリで、キーを押しっぱなしにするとビープ音が鳴り続ける現象があった。</p>
<p><a href="https://remap-keys.app/" target="_blank">REMAP</a>
 で Ro (International 1) を割り当て、 VoiceOS の設定で割り当てたら解消した。</p>
<p><img loading="lazy" src="/images/voiceos-1-remap.jpg"></p>
<p><img loading="lazy" src="/images/voiceos-2-settings.png"></p>
<p><code>Unknown</code> と表示されるが正常に動作する。</p>
<h2 id="音声入力の文章">音声入力の文章</h2>
<p>音声入力は文章が冗長になりやすい。最近のものはフィラーを取り除いてくれるが、それ以外にも手入力では出にくい余分な表現が増える。 AI チャットにならそのまま送れば良いが、日記やブログ、メッセンジャーで使う場合は入力後に削りたくなる。 AI に整形させてもいいが毎回は面倒。</p>
<p>VoiceOS の指示モードで「要約して」と最初に指示してから話すとわりといい感じになる。</p>
<p>以下は「これから話す内容を簡潔に要約して。要約前の文も出力すること」で始めた結果。</p>
<blockquote>
<p>これから話す内容を簡潔に要約して要約前の文も出力することMacやPCではVoiceOSやAquaOSで精度の高い音声入力をすることができるようになったけどスマホというかiOSは標準を使っていて他にもアプリがあるようなのでそれも気になるただモバイルでの音声入力はAndroidのデフォルトのディクテーションがかなり優秀らしいので気になる</p>
<p>MacやPCではVoiceOSやAquaOSで高精度な音声入力が可能だが、iOSは標準機能のみで他のアプリも気になる。一方、Androidのデフォルトディクテーションは非常に優秀と聞く。</p></blockquote>
<ul>
<li>AquaOS となっているのは誤認識ではなく単なる言い間違い</li>
</ul>
<p>要約して、だけだとあまりに短くなりすぎる気もするが、しばらく試してみたい。</p>
<p>VoiceOS の設定にカスタムプロンプトというものがある。ここに設定して常に有効にすればいいと考えたが、これはアプリごとに設定するもので、全体に適用するのは今のところできないようだ。</p>
<p>招待コード: <a href="https://voiceos.com/r?code=f7e38407" target="_blank">https://voiceos.com/r?code=f7e38407</a>
</p>
<h2 id="chatgpt-の音声入力">ChatGPT の音声入力</h2>
<p>ChatGPT の音声入力も使える。</p>
<p><img loading="lazy" src="/images/voiceos-3-chatgpt.png"></p>
<p>AquaVoice を使った時は、音声入力ツールに追加でお金を出すのはどうかなと思い、結局やめてしまった。</p>
<p>ChatGPT は既に契約しているし、 Mac のアプリであればショートカットキーで呼び出せるし、とりあえずこれ使えばいいか&hellip;という。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Next.js, next-intl のセットアップ</title>
      <link>https://okiyama.dev/posts/2025-04-20-nextjs-setup-with-next-intl/</link>
      <pubDate>Sun, 20 Apr 2025 18:29:36 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2025-04-20-nextjs-setup-with-next-intl/</guid>
      <description>&lt;h2 id=&#34;プロジェクト作成&#34;&gt;プロジェクト作成&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://nextjs.org/docs/app/api-reference/cli/create-next-app&#34; target=&#34;_blank&#34;&gt;create-next-app&lt;/a&gt;
 コマンドで作成する。&lt;/p&gt;
&lt;p&gt;パッケージマネージャに pnpm を使用。選択肢は全てデフォルト。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;$ pnpx create-next-app@latest --use-pnpm example-nextjs
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;✔ Would you like to use TypeScript? … Yes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;✔ Would you like to use ESLint? … Yes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;✔ Would you like to use Tailwind CSS? … Yes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;✔ Would you like your code inside a &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;src/&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt; directory? … No
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;✔ Would you like to use App Router? &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;recommended&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt; … Yes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;✔ Would you like to use Turbopack &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;next dev&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;? … Yes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;✔ Would you like to customize the import alias &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt;@/*&lt;span style=&#34;color:#e6db74&#34;&gt;`&lt;/span&gt; by default&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;? … No
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;.node-version&lt;/code&gt; ファイルを作成する:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="プロジェクト作成">プロジェクト作成</h2>
<p><a href="https://nextjs.org/docs/app/api-reference/cli/create-next-app" target="_blank">create-next-app</a>
 コマンドで作成する。</p>
<p>パッケージマネージャに pnpm を使用。選択肢は全てデフォルト。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>$ pnpx create-next-app@latest --use-pnpm example-nextjs
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>✔ Would you like to use TypeScript? … Yes
</span></span><span style="display:flex;"><span>✔ Would you like to use ESLint? … Yes
</span></span><span style="display:flex;"><span>✔ Would you like to use Tailwind CSS? … Yes
</span></span><span style="display:flex;"><span>✔ Would you like your code inside a <span style="color:#e6db74">`</span>src/<span style="color:#e6db74">`</span> directory? … No
</span></span><span style="display:flex;"><span>✔ Would you like to use App Router? <span style="color:#f92672">(</span>recommended<span style="color:#f92672">)</span> … Yes
</span></span><span style="display:flex;"><span>✔ Would you like to use Turbopack <span style="color:#66d9ef">for</span> <span style="color:#e6db74">`</span>next dev<span style="color:#e6db74">`</span>? … Yes
</span></span><span style="display:flex;"><span>✔ Would you like to customize the import alias <span style="color:#f92672">(</span><span style="color:#e6db74">`</span>@/*<span style="color:#e6db74">`</span> by default<span style="color:#f92672">)</span>? … No
</span></span></code></pre></div><p><code>.node-version</code> ファイルを作成する:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e"># .node-version を作成</span>
</span></span><span style="display:flex;"><span>$ node -v &gt; .node-version
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># もしくは major version のみ記載する場合</span>
</span></span><span style="display:flex;"><span>$ node -p <span style="color:#e6db74">&#39;process.versions.node.split(&#34;.&#34;)[0]&#39;</span> &gt; .node-version
</span></span></code></pre></div><h2 id="依存パッケージを追加">依存パッケージを追加</h2>
<p>パッケージをいくつか追加する。</p>
<ul>
<li>i18n ライブラリである <a href="https://next-intl.dev/" target="_blank">next-intl</a>
</li>
<li>Tailwind CSS とあわせてよく使われる <a href="https://www.tailwind-variants.org/" target="_blank">tailwind-variants</a>
</li>
<li>ESLint と Prettier 関連
<ul>
<li><a href="https://github.com/francoismassart/eslint-plugin-tailwindcss" target="_blank">eslint-plugin-tailwindcss</a>
 は 2025-04-20 時点で Tailwind CSS v4 に対応していないため除外</li>
</ul>
</li>
</ul>
<p><em>2025-11-16 追記: Tailwind CSS v4 サポートを謳う <a href="https://github.com/schoero/eslint-plugin-better-tailwindcss" target="_blank">eslint-plugin-better-tailwindcss</a>
 を追加</em></p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e"># dependencies</span>
</span></span><span style="display:flex;"><span>$ pnpm add tailwind-variants next-intl
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># devDependencies</span>
</span></span><span style="display:flex;"><span>$ pnpm add -D <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>    prettier <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>    eslint-config-prettier <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>    @trivago/prettier-plugin-sort-imports <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>    prettier-plugin-classnames <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>    prettier-plugin-merge <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>    prettier-plugin-tailwindcss <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span><span style="color:#ae81ff"></span>    eslint-plugin-better-tailwindcss
</span></span></code></pre></div><h2 id="eslint-設定">ESLint 設定</h2>
<p>prettier と TypeScript の未使用変数に関する設定を追加。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#a6e22e">+// @ts-check
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> import nextVitals from &#34;eslint-config-next/core-web-vitals&#34;;
</span></span><span style="display:flex;"><span> import nextTs from &#34;eslint-config-next/typescript&#34;;
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+import eslintConfigPrettier from &#34;eslint-config-prettier&#34;;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+import eslintPluginBetterTailwindcss from &#34;eslint-plugin-better-tailwindcss&#34;;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> import { defineConfig, globalIgnores } from &#34;eslint/config&#34;;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> const eslintConfig = defineConfig([
</span></span><span style="display:flex;"><span>   ...nextVitals,
</span></span><span style="display:flex;"><span><span style="color:#75715e">@@ -13,6 +16,30 @@ const eslintConfig = defineConfig([
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>     &#34;build/**&#34;,
</span></span><span style="display:flex;"><span>     &#34;next-env.d.ts&#34;,
</span></span><span style="display:flex;"><span>   ]),
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  eslintConfigPrettier,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    rules: {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      &#34;@typescript-eslint/no-unused-vars&#34;: [
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        &#34;warn&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        { argsIgnorePattern: &#34;^_&#34; },
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      ],
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    },
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  },
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    plugins: {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      &#34;better-tailwindcss&#34;: eslintPluginBetterTailwindcss,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    },
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    settings: {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      &#34;better-tailwindcss&#34;: {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        entryPoint: &#34;app/globals.css&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      },
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    },
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    rules: {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      ...eslintPluginBetterTailwindcss.configs[&#34;recommended-warn&#34;].rules,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      &#34;better-tailwindcss/enforce-consistent-line-wrapping&#34;: &#34;off&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      &#34;better-tailwindcss/enforce-consistent-class-order&#34;: &#34;off&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    },
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  },
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> ]);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> export default eslintConfig;
</span></span></code></pre></div><p><em>2025-11-16 追記: 設定について</em></p>
<ul>
<li><code>no-unused-vars</code> はエラーでなく warn レベルになるようにする
<ul>
<li>コーディング中にエディタ上でエラーが即座に報告されるのが煩わしいため</li>
<li>warn レベルの問題を CI で検出する想定</li>
</ul>
</li>
<li>eslint-plugin-better-tailwindcss
<ul>
<li><code>entryPoint</code> で create-next-app で作られるグローバル CSS ファイルを指定</li>
<li>recommended ルールを warn レベルで取り込む</li>
<li>改行と順序のルールは off にし、 Prettier に任せる</li>
</ul>
</li>
</ul>
<h2 id="prettier-設定">Prettier 設定</h2>
<p>import のソート、 Tailwind CSS のクラス名のソートと改行を行うプラグインを有効化。</p>
<p><code>.prettierrc</code> を作成:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;plugins&#34;</span>: [
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;@trivago/prettier-plugin-sort-imports&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;prettier-plugin-tailwindcss&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;prettier-plugin-classnames&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e6db74">&#34;prettier-plugin-merge&#34;</span>
</span></span><span style="display:flex;"><span>  ],
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;importOrder&#34;</span>: [<span style="color:#e6db74">&#34;&lt;THIRD_PARTY_MODULES&gt;&#34;</span>, <span style="color:#e6db74">&#34;^@/&#34;</span>, <span style="color:#e6db74">&#34;^[./]&#34;</span>],
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;tailwindFunctions&#34;</span>: [<span style="color:#e6db74">&#34;tv&#34;</span>],
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;customFunctions&#34;</span>: [<span style="color:#e6db74">&#34;tv&#34;</span>]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>なお <code>tailwindFunctions</code> は <a href="https://github.com/tailwindlabs/prettier-plugin-tailwindcss" target="_blank">prettier-plugin-tailwindcss</a>
 の設定で、 <code>customFunctions</code> は <a href="https://github.com/ony3000/prettier-plugin-classnames" target="_blank">prettier-plugin-classnames</a>
 の設定。</p>
<p><em>2025-11-16 追記: prettier-plugin-classnames 関連の変更</em></p>
<ul>
<li>以前は <code>&quot;endingPosition&quot;: &quot;absolute-with-indent&quot;</code> を指定していたが v0.8.0 以降で不要になった:
<a href="https://github.com/ony3000/prettier-plugin-classnames?tab=readme-ov-file#ending-position" target="_blank">https://github.com/ony3000/prettier-plugin-classnames?tab=readme-ov-file#ending-position</a>
</li>
<li><code>&quot;experimentalOptimization&quot;: true</code> オプションは削除された:
<a href="https://github.com/ony3000/prettier-plugin-classnames/pull/98" target="_blank">https://github.com/ony3000/prettier-plugin-classnames/pull/98</a>
</li>
</ul>
<p>ignore リストに pnpm の lockfile を入れておく。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>$ echo pnpm-lock.yaml &gt;&gt; .prettierignore
</span></span></code></pre></div><p><code>package.json</code> のスクリプト定義に <code>&quot;format&quot;: &quot;prettier --write .&quot;</code> を追加し、コマンド実行してフォーマットを適用しておく。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>$ pnpm run format
</span></span></code></pre></div><h2 id="next-intl-設定">next-intl 設定</h2>
<p>ドキュメントの通り設定する。
以下は locale-based routing と呼ばれる <code>https://example.com/ja</code> のようにパスに言語コードが入る設定。</p>
<p><a href="https://next-intl.dev/docs/getting-started/app-router/with-i18n-routing" target="_blank">App Router setup with i18n routing – Internationalization (i18n) for Next.js</a>
</p>
<ul>
<li><code>messages/ja.json</code> と <code>messages/en.json</code> を作成</li>
<li><code>next.config.ts</code> を変更</li>
<li><code>i18n/navigation.ts</code> を作成</li>
<li><code>i18n/request.ts</code> を作成</li>
<li><code>middleware.ts</code> を作成</li>
<li><code>i18n/routing.ts</code> を作成</li>
<li><code>app/layout.tsx</code> を <code>app/[locale]/layout.tsx</code> に移動、修正</li>
<li><code>app/page.tsx</code> を <code>app/[locale]/page.tsx</code> に移動、修正</li>
</ul>
<p>追加で TypeScript 型定義の設定を行う。</p>
<p><a href="https://next-intl.dev/docs/workflows/typescript" target="_blank">TypeScript augmentation – Internationalization (i18n) for Next.js</a>
</p>
<p><code>global.d.ts</code> を作成:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">routing</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/i18n/routing&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">messages</span> <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@/messages/ja.json&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">declare</span> <span style="color:#a6e22e">module</span> <span style="color:#e6db74">&#34;next-intl&#34;</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">interface</span> <span style="color:#a6e22e">AppConfig</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Locale</span><span style="color:#f92672">:</span> (<span style="color:#66d9ef">typeof</span> <span style="color:#a6e22e">routing</span>.<span style="color:#a6e22e">locales</span>)[<span style="color:#66d9ef">number</span>];
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">Messages</span>: <span style="color:#66d9ef">typeof</span> <span style="color:#a6e22e">messages</span>;
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>開発サーバを起動し、ブラウザで <code>http://localhost:3000</code> を開く。</p>
<p><code>http://localhost:3000/ja</code> にリダイレクトされてページが表示されれば OK。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Apple AirPods Pro のバージョンの違い</title>
      <link>https://okiyama.dev/posts/2024-12-08-airpods-pro-versions/</link>
      <pubDate>Sun, 08 Dec 2024 19:35:00 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2024-12-08-airpods-pro-versions/</guid>
      <description>&lt;h2 id=&#34;バージョンの違い&#34;&gt;バージョンの違い&lt;/h2&gt;
&lt;p&gt;Apple の AirPods Pro は第1・第2世代があるが、それぞれマイナーアップデートが行われている。&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;発売時期&lt;/th&gt;
          &lt;th&gt;名称&lt;/th&gt;
          &lt;th&gt;コネクタ&lt;/th&gt;
          &lt;th&gt;無線充電&lt;/th&gt;
          &lt;th&gt;イヤホン&lt;/th&gt;
          &lt;th&gt;ケース&lt;/th&gt;
          &lt;th&gt;備考&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;2019年10月&lt;/td&gt;
          &lt;td&gt;&lt;nobr&gt;AirPods Pro&lt;/nobr&gt;&lt;/td&gt;
          &lt;td&gt;Lightning&lt;/td&gt;
          &lt;td&gt;Qi&lt;/td&gt;
          &lt;td&gt;IPX4&lt;/td&gt;
          &lt;td&gt;耐水なし&lt;/td&gt;
          &lt;td&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;2021年10月&lt;/td&gt;
          &lt;td&gt;&lt;nobr&gt;AirPods Pro&lt;/nobr&gt;&lt;/td&gt;
          &lt;td&gt;Lightning&lt;/td&gt;
          &lt;td&gt;MagSafe&lt;/td&gt;
          &lt;td&gt;IPX4&lt;/td&gt;
          &lt;td&gt;耐水なし&lt;/td&gt;
          &lt;td&gt;ケースのみ変更&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;2022年9月&lt;/td&gt;
          &lt;td&gt;AirPods Pro (第2世代)&lt;/td&gt;
          &lt;td&gt;Lightning&lt;/td&gt;
          &lt;td&gt;MagSafe&lt;/td&gt;
          &lt;td&gt;IPX4&lt;/td&gt;
          &lt;td&gt;IPX4&lt;/td&gt;
          &lt;td&gt;&lt;nobr&gt;多くの変更 (スワイプで音量変更、ケースにストラップホール・スピーカーが追加など)&lt;/nobr&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;2023年9月&lt;/td&gt;
          &lt;td&gt;AirPods Pro (第2世代)&lt;/td&gt;
          &lt;td&gt;USB-C&lt;/td&gt;
          &lt;td&gt;MagSafe&lt;/td&gt;
          &lt;td&gt;IP54&lt;/td&gt;
          &lt;td&gt;IP54&lt;/td&gt;
          &lt;td&gt;ケースとイヤホンの変更&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id=&#34;ケースのスピーカーについて&#34;&gt;ケースのスピーカーについて&lt;/h2&gt;
&lt;p&gt;音楽再生などはできず、以下の機能を持つ。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Apple の「探す」でスピーカーを鳴らす&lt;/li&gt;
&lt;li&gt;Qi または MagSafe 充電の開始時にスピーカーが鳴る
&lt;ul&gt;
&lt;li&gt;これにより Qi 充電で置き場所が悪く充電できていないトラブルが減った。初期モデルは LED での判別のみだったのに比べて気付きやすい。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;第2世代のマイナーアップデートでイヤホンは何が変わったのか&#34;&gt;第2世代のマイナーアップデートでイヤホンは何が変わったのか&lt;/h2&gt;
&lt;p&gt;ケースは USB Type-C 端子に変わり、耐水性能が向上。&lt;/p&gt;
&lt;p&gt;一方イヤホンは地味な変更。耐水性能が向上したのと、機能的には今のところ Apple Vision Pro のロスレスオーディオ対応くらい。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="バージョンの違い">バージョンの違い</h2>
<p>Apple の AirPods Pro は第1・第2世代があるが、それぞれマイナーアップデートが行われている。</p>
<table>
  <thead>
      <tr>
          <th>発売時期</th>
          <th>名称</th>
          <th>コネクタ</th>
          <th>無線充電</th>
          <th>イヤホン</th>
          <th>ケース</th>
          <th>備考</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>2019年10月</td>
          <td><nobr>AirPods Pro</nobr></td>
          <td>Lightning</td>
          <td>Qi</td>
          <td>IPX4</td>
          <td>耐水なし</td>
          <td></td>
      </tr>
      <tr>
          <td>2021年10月</td>
          <td><nobr>AirPods Pro</nobr></td>
          <td>Lightning</td>
          <td>MagSafe</td>
          <td>IPX4</td>
          <td>耐水なし</td>
          <td>ケースのみ変更</td>
      </tr>
      <tr>
          <td>2022年9月</td>
          <td>AirPods Pro (第2世代)</td>
          <td>Lightning</td>
          <td>MagSafe</td>
          <td>IPX4</td>
          <td>IPX4</td>
          <td><nobr>多くの変更 (スワイプで音量変更、ケースにストラップホール・スピーカーが追加など)</nobr></td>
      </tr>
      <tr>
          <td>2023年9月</td>
          <td>AirPods Pro (第2世代)</td>
          <td>USB-C</td>
          <td>MagSafe</td>
          <td>IP54</td>
          <td>IP54</td>
          <td>ケースとイヤホンの変更</td>
      </tr>
  </tbody>
</table>
<h2 id="ケースのスピーカーについて">ケースのスピーカーについて</h2>
<p>音楽再生などはできず、以下の機能を持つ。</p>
<ul>
<li>Apple の「探す」でスピーカーを鳴らす</li>
<li>Qi または MagSafe 充電の開始時にスピーカーが鳴る
<ul>
<li>これにより Qi 充電で置き場所が悪く充電できていないトラブルが減った。初期モデルは LED での判別のみだったのに比べて気付きやすい。</li>
</ul>
</li>
</ul>
<h2 id="第2世代のマイナーアップデートでイヤホンは何が変わったのか">第2世代のマイナーアップデートでイヤホンは何が変わったのか</h2>
<p>ケースは USB Type-C 端子に変わり、耐水性能が向上。</p>
<p>一方イヤホンは地味な変更。耐水性能が向上したのと、機能的には今のところ Apple Vision Pro のロスレスオーディオ対応くらい。</p>
<h2 id="参考">参考</h2>
<ul>
<li><a href="https://www.gizmodo.jp/2022/09/airpods-speaker.html" target="_blank">新型AirPodsにはなぜスピーカーがついているのか？ | ギズモード・ジャパン</a>
</li>
<li><a href="https://www.apple.com/jp/newsroom/2023/09/apple-upgrades-airpods-pro-2nd-generation-with-usb-c-charging/" target="_blank">Apple、AirPods Pro（第2世代）をUSB-C充電にアップグレード - Apple (日本)</a>
</li>
<li><a href="https://ascii.jp/elem/000/004/157/4157126/" target="_blank">ASCII.jp：Vision ProとAirPods Proの低遅延ロスレス接続、これはいったい何なのか？ (1/2)</a>
</li>
<li><a href="https://ascii.jp/elem/000/004/164/4164204/" target="_blank">ASCII.jp：【レビュー】新AirPods Pro、USB-C対応で変わること・変わらないこと (1/4)</a>
</li>
<li><a href="https://support.apple.com/ja-jp/105046" target="_blank">AirPods Pro、AirPods 3、AirPods 4（両モデル）の耐汗・耐水性能について - Apple サポート (日本)</a>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>pnpm コマンドのシェル補完</title>
      <link>https://okiyama.dev/posts/2024-10-20-pnpm-completion-for-fish/</link>
      <pubDate>Sun, 20 Oct 2024 16:52:17 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2024-10-20-pnpm-completion-for-fish/</guid>
      <description>&lt;p&gt;fish の場合以下コマンドで設定する。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fish&#34; data-lang=&#34;fish&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;pnpm&lt;/span&gt; completion fish &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; ~/.config/fish/completions/pnpm.fish
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href=&#34;https://pnpm.io/completion&#34; target=&#34;_blank&#34;&gt;Command line tab-completion | pnpm&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;最近まで知らずに設定していなかった。
内容は執筆時点では以下のようになっていて、将来変更された時にコマンド再実行が必要になるかもしれない。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fish&#34; data-lang=&#34;fish&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;###-begin-pnpm-completion-###
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_pnpm_completion&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt; cmd &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;commandline &lt;span style=&#34;color:#a6e22e&#34;&gt;-o&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt; cursor &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;commandline &lt;span style=&#34;color:#a6e22e&#34;&gt;-C&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt; words &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt; $cmd&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt; completions &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;eval env DEBUG&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; \&amp;#34;&amp;#34;&lt;/span&gt; COMP_CWORD&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$words&lt;span style=&#34;color:#e6db74&#34;&gt;\&amp;#34;&amp;#34;&lt;/span&gt; COMP_LINE&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$cmd&lt;span style=&#34;color:#e6db74&#34;&gt; \&amp;#34;&amp;#34;&lt;/span&gt; COMP_POINT&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;\&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$cursor&lt;span style=&#34;color:#e6db74&#34;&gt;\&amp;#34;&amp;#34;&lt;/span&gt; SHELL&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;fish &lt;span style=&#34;color:#a6e22e&#34;&gt;pnpm&lt;/span&gt; completion-server &lt;span style=&#34;color:#a6e22e&#34;&gt;-- &lt;/span&gt;$cmd&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$completions&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;__tabtab_complete_files__&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;-l&lt;/span&gt; matches &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;commandline &lt;span style=&#34;color:#a6e22e&#34;&gt;-ct&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;*
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;n &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$matches&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#a6e22e&#34;&gt;__fish_complete_path&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;commandline &lt;span style=&#34;color:#a6e22e&#34;&gt;-ct&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; completion &lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; $completions
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;echo&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;-e&lt;/span&gt; $completion
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;complete &lt;span style=&#34;color:#a6e22e&#34;&gt;-f&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;-d&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;pnpm&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;-c&lt;/span&gt; pnpm &lt;span style=&#34;color:#a6e22e&#34;&gt;-a&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;(_pnpm_completion)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;###-end-pnpm-completion-###
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
      <content:encoded><![CDATA[<p>fish の場合以下コマンドで設定する。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#a6e22e">pnpm</span> completion fish <span style="color:#f92672">&gt;</span> ~/.config/fish/completions/pnpm.fish
</span></span></code></pre></div><p><a href="https://pnpm.io/completion" target="_blank">Command line tab-completion | pnpm</a>
</p>
<p>最近まで知らずに設定していなかった。
内容は執筆時点では以下のようになっていて、将来変更された時にコマンド再実行が必要になるかもしれない。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#75715e">###-begin-pnpm-completion-###
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">function</span> <span style="color:#a6e22e">_pnpm_completion</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">set</span> cmd <span style="color:#f92672">(</span>commandline <span style="color:#a6e22e">-o</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">set</span> cursor <span style="color:#f92672">(</span>commandline <span style="color:#a6e22e">-C</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">set</span> words <span style="color:#f92672">(</span><span style="color:#a6e22e">count</span> $cmd<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">set</span> completions <span style="color:#f92672">(</span>eval env DEBUG<span style="color:#f92672">=</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">&#34; \&#34;&#34;</span> COMP_CWORD<span style="color:#f92672">=</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">&#34;</span>$words<span style="color:#e6db74">\&#34;&#34;</span> COMP_LINE<span style="color:#f92672">=</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">&#34;</span>$cmd<span style="color:#e6db74"> \&#34;&#34;</span> COMP_POINT<span style="color:#f92672">=</span><span style="color:#ae81ff">\&#34;</span><span style="color:#e6db74">&#34;</span>$cursor<span style="color:#e6db74">\&#34;&#34;</span> SHELL<span style="color:#f92672">=</span>fish <span style="color:#a6e22e">pnpm</span> completion-server <span style="color:#a6e22e">-- </span>$cmd<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> <span style="color:#e6db74">&#34;</span>$completions<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;__tabtab_complete_files__&#34;</span> <span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">set</span> <span style="color:#a6e22e">-l</span> matches <span style="color:#f92672">(</span>commandline <span style="color:#a6e22e">-ct</span><span style="color:#f92672">)</span>*
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">if</span> <span style="color:#f92672">[</span> <span style="color:#f92672">-</span>n <span style="color:#e6db74">&#34;</span>$matches<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">__fish_complete_path</span> <span style="color:#f92672">(</span>commandline <span style="color:#a6e22e">-ct</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">end</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">else</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> completion <span style="color:#66d9ef">in</span> $completions
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">echo</span> <span style="color:#a6e22e">-e</span> $completion
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">end</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">end</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">end</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>complete <span style="color:#a6e22e">-f</span> <span style="color:#a6e22e">-d</span> <span style="color:#e6db74">&#39;pnpm&#39;</span> <span style="color:#a6e22e">-c</span> pnpm <span style="color:#a6e22e">-a</span> <span style="color:#e6db74">&#34;(_pnpm_completion)&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">###-end-pnpm-completion-###
</span></span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>Vite, TypeScript, React のセットアップ</title>
      <link>https://okiyama.dev/posts/2024-04-06-vite-ts-react/</link>
      <pubDate>Sat, 06 Apr 2024 13:45:40 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2024-04-06-vite-ts-react/</guid>
      <description>&lt;p&gt;&lt;em&gt;2024-04-13 Updated: eslint-plugin-tailwindcss の章を追加&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;2024-09-25 Updated: eslint flat config に対応&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Vite で TypeScript の React プロジェクトを作る手順のメモです。&lt;/p&gt;
&lt;p&gt;Tailwind や Redux など常に必要なわけではないライブラリも含まれるのでご注意ください。&lt;/p&gt;
&lt;h1 id=&#34;プロジェクト作成&#34;&gt;プロジェクト作成&lt;/h1&gt;
&lt;p&gt;以前は Create React App というツールが使われていましたが、現在ではメンテナンスされていないようです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://zenn.dev/nekoya/articles/dd0f0e8a2fa35f&#34; target=&#34;_blank&#34;&gt;Create React App は役割を終えました&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://zenn.dev/ishiyama/scraps/a8abf192857f9f&#34; target=&#34;_blank&#34;&gt;Vite にたどり着くまで（Webpack 以降のモジュールバンドラー振り返り）&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;https://react.dev/learn/start-a-new-react-project&#34; target=&#34;_blank&#34;&gt;公式&lt;/a&gt;
 には &lt;a href=&#34;https://nextjs.org/&#34; target=&#34;_blank&#34;&gt;Next.js&lt;/a&gt;
 や &lt;a href=&#34;https://remix.run/&#34; target=&#34;_blank&#34;&gt;Remix&lt;/a&gt;
 が推奨されていますが、フレームワークを使わずに始めたい場合は &lt;a href=&#34;https://vitejs.dev/&#34; target=&#34;_blank&#34;&gt;Vite&lt;/a&gt;
 がよく使われるようです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://ja.vitejs.dev/guide/#%E6%9C%80%E5%88%9D%E3%81%AE-vite-%E3%83%95%E3%82%9A%E3%83%AD%E3%82%B7%E3%82%99%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B&#34; target=&#34;_blank&#34;&gt;はじめに | Vite&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;テンプレートに &lt;a href=&#34;https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts&#34; target=&#34;_blank&#34;&gt;&lt;code&gt;react-ts&lt;/code&gt;&lt;/a&gt;
 を指定して作成します。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pnpm create vite@latest --template react-ts &amp;lt;app-name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;.node-version&lt;/code&gt; ファイルを作成しておきます。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# .node-version を作成&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;node -v &amp;gt; .node-version
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# もしくは major version のみ記載する場合&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;node -p &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;process.versions.node.split(&amp;#34;.&amp;#34;)[0]&amp;#39;&lt;/span&gt; &amp;gt; .node-version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id=&#34;prettier&#34;&gt;Prettier&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://prettier.io/docs/en/install&#34; target=&#34;_blank&#34;&gt;Install · Prettier&lt;/a&gt;
&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><em>2024-04-13 Updated: eslint-plugin-tailwindcss の章を追加</em></p>
<p><em>2024-09-25 Updated: eslint flat config に対応</em></p>
<p>Vite で TypeScript の React プロジェクトを作る手順のメモです。</p>
<p>Tailwind や Redux など常に必要なわけではないライブラリも含まれるのでご注意ください。</p>
<h1 id="プロジェクト作成">プロジェクト作成</h1>
<p>以前は Create React App というツールが使われていましたが、現在ではメンテナンスされていないようです。</p>
<ul>
<li><a href="https://zenn.dev/nekoya/articles/dd0f0e8a2fa35f" target="_blank">Create React App は役割を終えました</a>
</li>
<li><a href="https://zenn.dev/ishiyama/scraps/a8abf192857f9f" target="_blank">Vite にたどり着くまで（Webpack 以降のモジュールバンドラー振り返り）</a>
</li>
</ul>
<p><a href="https://react.dev/learn/start-a-new-react-project" target="_blank">公式</a>
 には <a href="https://nextjs.org/" target="_blank">Next.js</a>
 や <a href="https://remix.run/" target="_blank">Remix</a>
 が推奨されていますが、フレームワークを使わずに始めたい場合は <a href="https://vitejs.dev/" target="_blank">Vite</a>
 がよく使われるようです。</p>
<ul>
<li><a href="https://ja.vitejs.dev/guide/#%E6%9C%80%E5%88%9D%E3%81%AE-vite-%E3%83%95%E3%82%9A%E3%83%AD%E3%82%B7%E3%82%99%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B" target="_blank">はじめに | Vite</a>
</li>
</ul>
<p>テンプレートに <a href="https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts" target="_blank"><code>react-ts</code></a>
 を指定して作成します。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>pnpm create vite@latest --template react-ts &lt;app-name&gt;
</span></span></code></pre></div><p><code>.node-version</code> ファイルを作成しておきます。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e"># .node-version を作成</span>
</span></span><span style="display:flex;"><span>node -v &gt; .node-version
</span></span><span style="display:flex;"><span><span style="color:#75715e"># もしくは major version のみ記載する場合</span>
</span></span><span style="display:flex;"><span>node -p <span style="color:#e6db74">&#39;process.versions.node.split(&#34;.&#34;)[0]&#39;</span> &gt; .node-version
</span></span></code></pre></div><h1 id="prettier">Prettier</h1>
<p><a href="https://prettier.io/docs/en/install" target="_blank">Install · Prettier</a>
</p>
<p>コードフォーマッターです。テンプレートのスタイルに合わせて singleQuote と semi を設定します。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>pnpm install -D prettier eslint-config-prettier
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>echo <span style="color:#e6db74">&#39;{
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  &#34;singleQuote&#34;: true,
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">  &#34;semi&#34;: false
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">}&#39;</span> &gt; .prettierrc
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>echo pnpm-lock.yaml &gt; .prettierignore
</span></span></code></pre></div><p><code>eslint.config.js</code> に追加:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#75715e">@@ -3,6 +3,7 @@ import globals from &#39;globals&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> import reactHooks from &#39;eslint-plugin-react-hooks&#39;
</span></span><span style="display:flex;"><span> import reactRefresh from &#39;eslint-plugin-react-refresh&#39;
</span></span><span style="display:flex;"><span> import tseslint from &#39;typescript-eslint&#39;
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+import eslintConfigPrettier from &#39;eslint-config-prettier&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span> export default tseslint.config(
</span></span><span style="display:flex;"><span>   { ignores: [&#39;dist&#39;] },
</span></span><span style="display:flex;"><span><span style="color:#75715e">@@ -25,4 +26,5 @@ export default tseslint.config(
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>       ],
</span></span><span style="display:flex;"><span>     },
</span></span><span style="display:flex;"><span>   },
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  eslintConfigPrettier,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> )
</span></span></code></pre></div><p><code>package.json</code> の scripts にコマンドを追加:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#75715e">@@ -7,7 +7,8 @@
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>     &#34;dev&#34;: &#34;vite&#34;,
</span></span><span style="display:flex;"><span>     &#34;build&#34;: &#34;tsc &amp;&amp; vite build&#34;,
</span></span><span style="display:flex;"><span>     &#34;lint&#34;: &#34;eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0&#34;,
</span></span><span style="display:flex;"><span><span style="color:#f92672">-    &#34;preview&#34;: &#34;vite preview&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+    &#34;preview&#34;: &#34;vite preview&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    &#34;format&#34;: &#34;prettier --write .&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>   },
</span></span><span style="display:flex;"><span>   &#34;dependencies&#34;: {
</span></span><span style="display:flex;"><span>     &#34;react&#34;: &#34;^18.2.0&#34;,
</span></span></code></pre></div><p>フォーマットを適用します。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>❯ pnpm run format
</span></span></code></pre></div><h2 id="trivagoprettier-plugin-sort-imports">@trivago/prettier-plugin-sort-imports</h2>
<p><a href="https://github.com/trivago/prettier-plugin-sort-imports" target="_blank">https://github.com/trivago/prettier-plugin-sort-imports</a>
</p>
<p>import をソートするプラグインです。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>pnpm install -D @trivago/prettier-plugin-sort-imports
</span></span></code></pre></div><p><code>.pretterrc</code> を修正:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#75715e">@@ -1,4 +1,6 @@
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> {
</span></span><span style="display:flex;"><span>   &#34;singleQuote&#34;: true,
</span></span><span style="display:flex;"><span><span style="color:#f92672">-  &#34;semi&#34;: false
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+  &#34;semi&#34;: false,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  &#34;plugins&#34;: [&#34;@trivago/prettier-plugin-sort-imports&#34;],
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  &#34;importOrder&#34;: [&#34;&lt;THIRD_PARTY_MODULES&gt;&#34;, &#34;^[./]&#34;]
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> }
</span></span></code></pre></div><h1 id="eslint-の追加設定">ESLint の追加設定</h1>
<p>テンプレートの ESLint の設定はいくつかの recommended 設定が最初から有効ですが、 <code>@typescript-eslint/recommended-type-checked</code> も追加します。</p>
<h2 id="optional-ts-check-をオンにする">optional: @ts-check をオンにする</h2>
<p><code>eslint.config.js</code> の先頭に <code>@ts-check</code> を追加します。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#75715e">@@ -1,3 +1,4 @@
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">+// @ts-check
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> import js from &#39;@eslint/js&#39;
</span></span><span style="display:flex;"><span> import eslintConfigPrettier from &#39;eslint-config-prettier&#39;
</span></span><span style="display:flex;"><span> import reactHooks from &#39;eslint-plugin-react-hooks&#39;
</span></span></code></pre></div><p>ただし、これを執筆している時点 (2024-09-25) では <code>@ts-check</code> を追加すると <code>react-hooks</code> と <code>rules</code> の箇所でコンパイルエラーが表示されます。
以下のいずれかの対応を選択することになります。</p>
<ul>
<li><code>@ts-check</code> を追加しない
<ul>
<li>エラーを検出できなくなるが、気にしないという判断。</li>
<li>追加しなくても、無効な設定を書いてしまった場合は ESLint がエラーを出すので気付ける。</li>
<li>追加しなくとも、
<a href="https://typescript-eslint.io/packages/typescript-eslint#config" target="_blank">tseslint.config() の効果</a>

でエディタの TypeScript 補完は効く。</li>
</ul>
</li>
<li>追加した上で、エラーを無視する
<ul>
<li>VSCode などエディタ上でエラーになるだけで、ビルドなどは問題ない。したがってエラーが出る状態にしておき、単に無視する。</li>
<li>将来的にプラグイン側で対応されたら解消するはず。</li>
</ul>
</li>
</ul>
<h2 id="typescript-eslintrecommended-type-checked-をオンにする">@typescript-eslint/recommended-type-checked をオンにする</h2>
<p>通常の <code>@typescript-eslint/recommended</code> のルールに加え、 TypeScript の型情報を使う設定です。
<a href="https://typescript-eslint.io/rules/no-floating-promises/" target="_blank">no-floating-promises</a>
 などのルールが含まれます。</p>
<p><code>eslint.config.js</code> を修正:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#75715e">@@ -9,7 +9,10 @@ import tseslint from &#39;typescript-eslint&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> export default tseslint.config(
</span></span><span style="display:flex;"><span>   { ignores: [&#39;dist&#39;] },
</span></span><span style="display:flex;"><span>   {
</span></span><span style="display:flex;"><span><span style="color:#f92672">-    extends: [js.configs.recommended, ...tseslint.configs.recommended],
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+    extends: [
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      js.configs.recommended,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      ...tseslint.configs.recommendedTypeChecked,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    ],
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>     files: [&#39;**/*.{ts,tsx}&#39;],
</span></span><span style="display:flex;"><span>     languageOptions: {
</span></span><span style="display:flex;"><span>       ecmaVersion: 2020,
</span></span></code></pre></div><h3 id="parseroptionsproject-を追加">parserOptions.project を追加</h3>
<p>以下のエラーが起きるようになります。</p>
<pre tabindex="0"><code>Oops! Something went wrong! :(

ESLint: 9.11.1

Error: Error while loading rule &#39;@typescript-eslint/await-thenable&#39;: You have used a rule which requires parserServices to be generated. You must therefore provide a value for the &#34;parserOptions.project&#34; property for @typescript-eslint/parser.
Parser: typescript-eslint/parser
Occurred while linting (snip)/src/main.tsx
# snip
 ELIFECYCLE  Command failed with exit code 2.
</code></pre><p><code>eslint.config.js</code> に以下の設定を追加すると解消します。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#a6e22e">tseslint</span>.<span style="color:#a6e22e">config</span>(
</span></span><span style="display:flex;"><span>  { <span style="color:#a6e22e">ignores</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#34;dist&#34;</span>] },
</span></span><span style="display:flex;"><span>  {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">/* snip */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">languageOptions</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">ecmaVersion</span>: <span style="color:#66d9ef">2020</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">globals</span>: <span style="color:#66d9ef">globals.browser</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">parserOptions</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// languageOptions の子として追加
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#a6e22e">project</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#34;./tsconfig.app.json&#34;</span>, <span style="color:#e6db74">&#34;./tsconfig.node.json&#34;</span>],
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">/* snip */</span>
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">eslintConfigPrettier</span>
</span></span><span style="display:flex;"><span>);
</span></span></code></pre></div><h2 id="tsconfig-の-nounused-をオフ--eslint-の-typescript-eslintno-unused-vars-を-warn-に">tsconfig の noUnused&hellip; をオフ + ESLint の @typescript-eslint/no-unused-vars を warn に</h2>
<p>noUnusedLocals と noUnusedParameters を無効にし、 ESLint の <a href="https://typescript-eslint.io/rules/no-unused-vars/" target="_blank">@typescript-eslint/no-unused-vars</a>
 を warn にします。</p>
<p><code>tsconfig.app.json</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#75715e">@@ -16,8 +16,8 @@
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span>     /* Linting */
</span></span><span style="display:flex;"><span>     &#34;strict&#34;: true,
</span></span><span style="display:flex;"><span><span style="color:#f92672">-    &#34;noUnusedLocals&#34;: true,
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-    &#34;noUnusedParameters&#34;: true,
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+    &#34;noUnusedLocals&#34;: false,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    &#34;noUnusedParameters&#34;: false,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>     &#34;noFallthroughCasesInSwitch&#34;: true
</span></span><span style="display:flex;"><span>   },
</span></span><span style="display:flex;"><span>   &#34;include&#34;: [&#34;src&#34;]
</span></span></code></pre></div><p><code>eslint.config.js</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#a6e22e">tseslint</span>.<span style="color:#a6e22e">config</span>(
</span></span><span style="display:flex;"><span>  { <span style="color:#a6e22e">ignores</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#34;dist&#34;</span>] },
</span></span><span style="display:flex;"><span>  {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">/* snip */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">rules</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#75715e">/* snip */</span>
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#34;@typescript-eslint/no-unused-vars&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;warn&#34;</span>, <span style="color:#75715e">// rules の子として追加
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    },
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">eslintConfigPrettier</span>
</span></span><span style="display:flex;"><span>);
</span></span></code></pre></div><p>この設定は tsconfig と ESLint で重複するため ESLint に任せることにします。また新しい変数を書いたそばからエラーになるのは邪魔に感じるため、個人的には error でなく warn にしたい。</p>
<p>@typescript-eslint/no-unused-vars は recommended 設定だと error に設定されているため、 warn に変更しています。この場合 CI で warn を許さないようにチェックするとよいでしょう。</p>
<p>例: CI では error レベルにし、かつ <code>_</code> 始まりの変数は許容する設定</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">isCI</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">process</span>.<span style="color:#a6e22e">env</span>.<span style="color:#a6e22e">CI</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#a6e22e">tseslint</span>.<span style="color:#a6e22e">config</span>(
</span></span><span style="display:flex;"><span>  { <span style="color:#a6e22e">ignores</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#34;dist&#34;</span>] },
</span></span><span style="display:flex;"><span>  {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">/* snip */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">rules</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#75715e">/* snip */</span>
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#34;@typescript-eslint/no-unused-vars&#34;</span><span style="color:#f92672">:</span> [
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">isCI</span> <span style="color:#f92672">?</span> <span style="color:#e6db74">&#34;error&#34;</span> <span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;warn&#34;</span>,
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">argsIgnorePattern</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;_&#34;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#a6e22e">varsIgnorePattern</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;^_+$&#34;</span>,
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>      ],
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">eslintConfigPrettier</span>
</span></span><span style="display:flex;"><span>);
</span></span></code></pre></div><h2 id="tsconfig-の-compileoptions-nouncheckedindexedaccess-を有効にする">tsconfig の compileOptions noUncheckedIndexedAccess を有効にする</h2>
<p><a href="https://typescriptbook.jp/reference/tsconfig/nouncheckedindexedaccess" target="_blank">noUncheckedIndexedAccess | TypeScript 入門『サバイバル TypeScript』</a>
</p>
<p><code>&quot;strict&quot;: true</code> で有効にならないオプションですが、配列を安全に扱うのに有用なので設定しておきます。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#75715e">@@ -18,7 +18,8 @@
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>     &#34;strict&#34;: true,
</span></span><span style="display:flex;"><span>     &#34;noUnusedLocals&#34;: false,
</span></span><span style="display:flex;"><span>     &#34;noUnusedParameters&#34;: false,
</span></span><span style="display:flex;"><span><span style="color:#f92672">-    &#34;noFallthroughCasesInSwitch&#34;: true
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+    &#34;noFallthroughCasesInSwitch&#34;: true,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    &#34;noUncheckedIndexedAccess&#34;: true
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>   },
</span></span><span style="display:flex;"><span>   &#34;include&#34;: [&#34;src&#34;]
</span></span><span style="display:flex;"><span> }
</span></span></code></pre></div><h1 id="tailwind-css">Tailwind CSS</h1>
<p><a href="https://tailwindcss.com/docs/guides/vite" target="_blank">Install Tailwind CSS with Vite - Tailwind CSS</a>
</p>
<p>CSS フレームワークです。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>pnpm install -D tailwindcss postcss autoprefixer
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 設定ファイルは TypeScript を選択</span>
</span></span><span style="display:flex;"><span>pnpx tailwindcss init --ts --postcss
</span></span></code></pre></div><p><code>tailwind.config.ts</code> を修正:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> import type { Config } from &#39;tailwindcss&#39;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> export default {
</span></span><span style="display:flex;"><span><span style="color:#f92672">-  content: [],
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+  content: [&#39;./index.html&#39;, &#39;./src/**/*.{js,jsx,ts,tsx}&#39;],
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>   theme: {
</span></span><span style="display:flex;"><span>     extend: {},
</span></span><span style="display:flex;"><span>   },
</span></span><span style="display:flex;"><span>   plugins: [],
</span></span><span style="display:flex;"><span> } satisfies Config
</span></span></code></pre></div><p><code>index.css</code> を修正:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-css" data-lang="css"><span style="display:flex;"><span>@<span style="color:#66d9ef">tailwind</span> <span style="color:#f92672">base</span>;
</span></span><span style="display:flex;"><span>@<span style="color:#66d9ef">tailwind</span> <span style="color:#f92672">components</span>;
</span></span><span style="display:flex;"><span>@<span style="color:#66d9ef">tailwind</span> <span style="color:#f92672">utilities</span>;
</span></span></code></pre></div><h2 id="tsconfignodejson-に-tailwind-ファイルを含める">tsconfig.node.json に tailwind ファイルを含める</h2>
<p>この状態で lint を実行すると以下のエラーが発生します。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>pnpm run lint
</span></span><span style="display:flex;"><span><span style="color:#75715e">#(snip)</span>
</span></span><span style="display:flex;"><span>&lt;snip&gt;/tailwind.config.ts
</span></span><span style="display:flex;"><span>  0:0  error  Parsing error: ESLint was configured to run on <span style="color:#e6db74">`</span>&lt;tsconfigRootDir&gt;/tailwind.config.ts<span style="color:#e6db74">`</span> using <span style="color:#e6db74">`</span>parserOptions.project<span style="color:#e6db74">`</span>:
</span></span><span style="display:flex;"><span>- &lt;snip&gt;/tsconfig.json
</span></span><span style="display:flex;"><span>- &lt;snip&gt;/tsconfig.node.json
</span></span></code></pre></div><p><code>tsconfig.node.json</code> の include に <code>tailwind.config.ts</code> を追加すると解消します。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#75715e">@@ -7,5 +7,5 @@
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>     &#34;allowSyntheticDefaultImports&#34;: true,
</span></span><span style="display:flex;"><span>     &#34;strict&#34;: true
</span></span><span style="display:flex;"><span>   },
</span></span><span style="display:flex;"><span><span style="color:#f92672">-  &#34;include&#34;: [&#34;vite.config.ts&#34;]
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+  &#34;include&#34;: [&#34;vite.config.ts&#34;, &#34;tailwind.config.ts&#34;]
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> }
</span></span></code></pre></div><h2 id="prettier-plugin-tailwindcss">prettier-plugin-tailwindcss</h2>
<p><a href="https://tailwindcss.com/docs/editor-setup#automatic-class-sorting-with-prettier" target="_blank">Editor Setup - Tailwind CSS</a>

<a href="https://github.com/tailwindlabs/prettier-plugin-tailwindcss" target="_blank">https://github.com/tailwindlabs/prettier-plugin-tailwindcss</a>
</p>
<p><code>className</code> をソートする Tailwind 公式の Prettier プラグインです。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>pnpm install -D prettier-plugin-tailwindcss
</span></span></code></pre></div><p><code>.prettierrc</code> を修正:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#75715e">@@ -1,6 +1,9 @@
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> {
</span></span><span style="display:flex;"><span>   &#34;singleQuote&#34;: true,
</span></span><span style="display:flex;"><span>   &#34;semi&#34;: false,
</span></span><span style="display:flex;"><span><span style="color:#f92672">-  &#34;plugins&#34;: [&#34;@trivago/prettier-plugin-sort-imports&#34;],
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+  &#34;plugins&#34;: [
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    &#34;@trivago/prettier-plugin-sort-imports&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    &#34;prettier-plugin-tailwindcss&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  ],
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>   &#34;importOrder&#34;: [&#34;&lt;THIRD_PARTY_MODULES&gt;&#34;, &#34;^[./]&#34;]
</span></span><span style="display:flex;"><span> }
</span></span></code></pre></div><h2 id="clsx">clsx</h2>
<p><a href="https://github.com/lukeed/clsx" target="_blank">https://github.com/lukeed/clsx</a>
</p>
<p>クラス名を結合する関数を提供するライブラリで、条件付きでクラスを切り替えたりする場合に使います。
同種のツールでより高機能な tailwind-merge が存在しますが、今回は clsx を選択。</p>
<ul>
<li><a href="https://github.com/dcastil/tailwind-merge" target="_blank">https://github.com/dcastil/tailwind-merge</a>
</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>pnpm install clsx
</span></span></code></pre></div><p><code>.pretterrc</code> を修正:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>   &#34;plugins&#34;: [&#34;@trivago/prettier-plugin-sort-imports&#34;, &#34;prettier-plugin-tailwindcss&#34;],
</span></span><span style="display:flex;"><span><span style="color:#f92672">-  &#34;importOrder&#34;: [&#34;&lt;THIRD_PARTY_MODULES&gt;&#34;, &#34;^[./]&#34;]
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+  &#34;importOrder&#34;: [&#34;&lt;THIRD_PARTY_MODULES&gt;&#34;, &#34;^[./]&#34;],
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  &#34;tailwindFunctions&#34;: [&#34;clsx&#34;]
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> }
</span></span></code></pre></div><h2 id="prettier-plugin-classnames">prettier-plugin-classnames</h2>
<p>長いクラス名を改行する Prettier プラグインです。詳細は <a href="https://okiyama.dev/posts/2024-03-17-prettier-plugin-classnames/" target="_blank">prettier-plugin-classnames でクラス名を改行する | okiyama.dev</a>
 を参照。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>pnpm install -D prettier-plugin-classnames prettier-plugin-merge
</span></span></code></pre></div><p><code>.prettierrc</code> を修正:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#75715e">@@ -3,8 +3,11 @@
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>   &#34;semi&#34;: false,
</span></span><span style="display:flex;"><span>   &#34;plugins&#34;: [
</span></span><span style="display:flex;"><span>     &#34;@trivago/prettier-plugin-sort-imports&#34;,
</span></span><span style="display:flex;"><span><span style="color:#f92672">-    &#34;prettier-plugin-tailwindcss&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+    &#34;prettier-plugin-tailwindcss&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    &#34;prettier-plugin-classnames&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    &#34;prettier-plugin-merge&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>   ],
</span></span><span style="display:flex;"><span>   &#34;importOrder&#34;: [&#34;&lt;THIRD_PARTY_MODULES&gt;&#34;, &#34;^[./]&#34;],
</span></span><span style="display:flex;"><span><span style="color:#f92672">-  &#34;tailwindFunctions&#34;: [&#34;clsx&#34;]
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+  &#34;tailwindFunctions&#34;: [&#34;clsx&#34;],
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  &#34;endingPosition&#34;: &#34;absolute-with-indent&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> }
</span></span></code></pre></div><h2 id="eslint-plugin-tailwindcss">eslint-plugin-tailwindcss</h2>
<p><a href="https://www.npmjs.com/package/eslint-plugin-tailwindcss" target="_blank">eslint-plugin-tailwindcss - npm</a>
</p>
<p>ESLint プラグインです。 Tailwind のクラス名以外を検出などのルールがあります。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>pnpm install -D eslint-plugin-tailwindcss
</span></span></code></pre></div><p><code>eslint.config.js</code> を修正:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#75715e">// import 追加
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">tailwind</span> <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;eslint-plugin-tailwindcss&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">/* snip */</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#a6e22e">tseslint</span>.<span style="color:#a6e22e">config</span>(
</span></span><span style="display:flex;"><span>  { <span style="color:#a6e22e">ignores</span><span style="color:#f92672">:</span> [<span style="color:#e6db74">&#34;dist&#34;</span>] },
</span></span><span style="display:flex;"><span>  ...<span style="color:#a6e22e">tailwind</span>.<span style="color:#a6e22e">configs</span>[<span style="color:#e6db74">&#34;flat/recommended&#34;</span>], <span style="color:#75715e">// 追加
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">/* snip */</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">rules</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#75715e">/* snip */</span>
</span></span><span style="display:flex;"><span>      <span style="color:#e6db74">&#34;tailwindcss/classnames-order&#34;</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;off&#34;</span>, <span style="color:#75715e">// rule 追加
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>    },
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">eslintConfigPrettier</span>
</span></span><span style="display:flex;"><span>);
</span></span></code></pre></div><p>rules で <code>tailwindcss/classnames-order</code> を <code>off</code> にしているのは、クラス名のソートは <code>prettier-plugin-tailwindcss</code> に任せるためです。</p>
<h1 id="redux-toolkit-react-redux">Redux Toolkit, react-redux</h1>
<p><a href="https://redux.js.org/introduction/installation" target="_blank">Installation | Redux</a>
</p>
<p>ステート管理ライブラリです。 Redux Toolkit が出てから以前にもまして重量級の雰囲気ですが、 Toolkit の流儀に従っておけばボイラープレートも少なくシンプルに書けるようになっています。</p>
<p><a href="https://www.npmjs.com/package/redux" target="_blank">redux</a>
 は <a href="https://www.npmjs.com/package/@reduxjs/toolkit" target="_blank">@reduxjs/toolkit</a>
 の依存に含まれているため個別にインストールする必要はなく、このふたつだけでよいです。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>pnpm install @reduxjs/toolkit react-redux
</span></span></code></pre></div><p><code>src/redux/store.ts</code> を作成:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">configureStore</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@reduxjs/toolkit&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">rootReducer</span> <span style="color:#f92672">=</span> {};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">setupStore</span> <span style="color:#f92672">=</span> () <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">store</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">configureStore</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">reducer</span>: <span style="color:#66d9ef">rootReducer</span>,
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// TODO: hot reloading の設定
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>  <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">store</span>;
</span></span><span style="display:flex;"><span>};
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">AppStore</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">ReturnType</span>&lt;<span style="color:#f92672">typeof</span> <span style="color:#a6e22e">setupStore</span>&gt;;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">type</span> <span style="color:#a6e22e">AppState</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">ReturnType</span>&lt;<span style="color:#f92672">AppStore</span><span style="color:#960050;background-color:#1e0010">[</span><span style="color:#e6db74">&#34;getState&#34;</span><span style="color:#960050;background-color:#1e0010">]</span>&gt;;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">type</span> <span style="color:#a6e22e">AppDispatch</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">AppStore</span>[<span style="color:#e6db74">&#34;dispatch&#34;</span>];
</span></span></code></pre></div><p><code>src/redux/hooks.ts</code> を作成:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">useDispatch</span>, <span style="color:#a6e22e">useSelector</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;react-redux&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">AppDispatch</span>, <span style="color:#a6e22e">AppState</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;./store&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">useAppDispatch</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">useDispatch</span>.<span style="color:#a6e22e">withTypes</span>&lt;<span style="color:#f92672">AppDispatch</span>&gt;();
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">useAppSelector</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">useSelector</span>.<span style="color:#a6e22e">withTypes</span>&lt;<span style="color:#f92672">AppState</span>&gt;();
</span></span></code></pre></div><p><code>src/main.tsx</code> を修正:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#75715e">@@ -2,9 +2,15 @@ import React from &#39;react&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> import ReactDOM from &#39;react-dom/client&#39;
</span></span><span style="display:flex;"><span> import App from &#39;./App.tsx&#39;
</span></span><span style="display:flex;"><span> import &#39;./index.css&#39;
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+import { Provider } from &#39;react-redux&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+import { setupStore } from &#39;./redux/store.ts&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+const store = setupStore()
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span> ReactDOM.createRoot(document.getElementById(&#39;root&#39;)!).render(
</span></span><span style="display:flex;"><span>   &lt;React.StrictMode&gt;
</span></span><span style="display:flex;"><span><span style="color:#f92672">-    &lt;App /&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+    &lt;Provider store={store}&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      &lt;App /&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    &lt;/Provider&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>   &lt;/React.StrictMode&gt;,
</span></span><span style="display:flex;"><span> )
</span></span></code></pre></div><h2 id="課題-redux-のホットリロード">課題: Redux のホットリロード</h2>
<p>ホットリロードの設定方法が書かれていますが、 Vite だとうまく動作しませんでした。</p>
<p><a href="https://redux.js.org/usage/configuring-your-store#hot-reloading" target="_blank">Configuring Your Store | Redux</a>
</p>
<p>このように設定してみたのですが、ファイル保存時にページ全体がリロードされてしまう。未解決です。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#75715e">// rootReducer を ./reducers.ts に移動したうえで以下を追加
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">if</span> (<span style="color:#66d9ef">import</span>.<span style="color:#a6e22e">meta</span>.<span style="color:#a6e22e">env</span>.<span style="color:#a6e22e">DEV</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#66d9ef">import</span>.<span style="color:#a6e22e">meta</span>.<span style="color:#a6e22e">hot</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">import</span>.<span style="color:#a6e22e">meta</span>.<span style="color:#a6e22e">hot</span>.<span style="color:#a6e22e">accept</span>(<span style="color:#e6db74">&#34;./reducers&#34;</span>, <span style="color:#66d9ef">async</span> () <span style="color:#f92672">=&gt;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">store</span>.<span style="color:#a6e22e">replaceReducer</span>((<span style="color:#66d9ef">await</span> <span style="color:#66d9ef">import</span>(<span style="color:#e6db74">&#34;./reducers&#34;</span>)).<span style="color:#a6e22e">rootReducer</span>)
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>関連するかもしれない Discussion: <a href="https://github.com/reduxjs/redux-toolkit/discussions/4281" target="_blank">https://github.com/reduxjs/redux-toolkit/discussions/4281</a>
</p>
<h1 id="react-router">React Router</h1>
<p><a href="https://reactrouter.com/en/main/start/tutorial#setup" target="_blank">Tutorial v6.22.3 | React Router</a>
</p>
<p>ルーターライブラリです。この例は React Router ですが、今新しく始めるなら <a href="https://tanstack.com/router/latest" target="_blank">TanStack Router</a>
 もよいかもしれません。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>pnpm install react-router-dom
</span></span></code></pre></div><p><code>main.tsx</code> を修正:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> import React from &#39;react&#39;
</span></span><span style="display:flex;"><span> import ReactDOM from &#39;react-dom/client&#39;
</span></span><span style="display:flex;"><span> import { Provider } from &#39;react-redux&#39;
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+import { RouterProvider, createBrowserRouter } from &#39;react-router-dom&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> import App from &#39;./App.tsx&#39;
</span></span><span style="display:flex;"><span> import &#39;./index.css&#39;
</span></span><span style="display:flex;"><span> import { setupStore } from &#39;./redux/store.ts&#39;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> const store = setupStore()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+const router = createBrowserRouter([
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    path: &#39;/&#39;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    element: &lt;App /&gt;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  },
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+])
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> ReactDOM.createRoot(document.getElementById(&#39;root&#39;)!).render(
</span></span><span style="display:flex;"><span>   &lt;React.StrictMode&gt;
</span></span><span style="display:flex;"><span>     &lt;Provider store={store}&gt;
</span></span><span style="display:flex;"><span><span style="color:#f92672">-      &lt;App /&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+      &lt;RouterProvider router={router} /&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>     &lt;/Provider&gt;
</span></span><span style="display:flex;"><span>   &lt;/React.StrictMode&gt;,
</span></span><span style="display:flex;"><span> )
</span></span></code></pre></div><h1 id="vitest">Vitest</h1>
<p><a href="https://vitest.dev/guide/" target="_blank">Getting Started | Guide | Vitest</a>
</p>
<p>テストフレームワークです。 DOM ライブラリである <a href="https://github.com/capricorn86/happy-dom/wiki/Getting-started" target="_blank">happy-dom</a>
 もインストールします。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>pnpm install -D vitest happy-dom
</span></span></code></pre></div><p><code>package.json</code> の scripts にコマンドを追加:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#75715e">@@ -8,7 +8,8 @@
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>     &#34;build&#34;: &#34;tsc -b &amp;&amp; vite build&#34;,
</span></span><span style="display:flex;"><span>     &#34;lint&#34;: &#34;eslint .&#34;,
</span></span><span style="display:flex;"><span>     &#34;preview&#34;: &#34;vite preview&#34;,
</span></span><span style="display:flex;"><span><span style="color:#f92672">-    &#34;format&#34;: &#34;prettier --write .&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+    &#34;format&#34;: &#34;prettier --write .&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    &#34;test&#34;: &#34;vitest&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>   },
</span></span><span style="display:flex;"><span>   &#34;dependencies&#34;: {
</span></span><span style="display:flex;"><span>     &#34;clsx&#34;: &#34;^2.1.1&#34;,
</span></span></code></pre></div><p><code>vitest.config.ts</code> を作成:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">defineConfig</span>, <span style="color:#a6e22e">mergeConfig</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;vitest/config&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">viteConfig</span> <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;./vite.config&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#a6e22e">mergeConfig</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">viteConfig</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">defineConfig</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">test</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">globals</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">environment</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;happy-dom&#34;</span>,
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>);
</span></span></code></pre></div><p><code>globals: true</code> の設定で <code>describe</code>, <code>test</code> などをインポートせずに使えるようになります。この設定を使う場合は tsconfig の設定も必要です。</p>
<p><code>tsconfig.app.json</code> を修正:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#75715e">@@ -19,7 +19,10 @@
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>     &#34;noUnusedLocals&#34;: false,
</span></span><span style="display:flex;"><span>     &#34;noUnusedParameters&#34;: false,
</span></span><span style="display:flex;"><span>     &#34;noFallthroughCasesInSwitch&#34;: true,
</span></span><span style="display:flex;"><span><span style="color:#f92672">-    &#34;noUncheckedIndexedAccess&#34;: true
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+    &#34;noUncheckedIndexedAccess&#34;: true,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    /* Vitest */
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    &#34;types&#34;: [&#34;vitest/globals&#34;]
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>   },
</span></span><span style="display:flex;"><span>   &#34;include&#34;: [&#34;src&#34;]
</span></span><span style="display:flex;"><span> }
</span></span></code></pre></div><p>また、このファイルも <code>tsconfig.node.json</code> の <code>include</code> に追加しておきます。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#75715e">@@ -7,5 +7,5 @@
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>     &#34;allowSyntheticDefaultImports&#34;: true,
</span></span><span style="display:flex;"><span>     &#34;strict&#34;: true
</span></span><span style="display:flex;"><span>   },
</span></span><span style="display:flex;"><span><span style="color:#f92672">-  &#34;include&#34;: [&#34;vite.config.ts&#34;, &#34;tailwind.config.ts&#34;]
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+  &#34;include&#34;: [&#34;vite.config.ts&#34;, &#34;vitest.config.ts&#34;, &#34;tailwind.config.ts&#34;]
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> }
</span></span></code></pre></div><h2 id="react-testing-library-user-event-jest-dom">React Testing Library, user-event, jest-dom</h2>
<ul>
<li><a href="https://testing-library.com/docs/react-testing-library/intro" target="_blank">React Testing Library | Testing Library</a>
</li>
<li><a href="https://testing-library.com/docs/user-event/install" target="_blank">Installation | Testing Library</a>
</li>
<li><a href="https://testing-library.com/docs/ecosystem-jest-dom" target="_blank">jest-dom | Testing Library</a>
</li>
</ul>
<p>テストコードでの DOM 要素の取得やアサーションに使うライブラリです。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>pnpm install -D @testing-library/react @testing-library/user-event @testing-library/jest-dom
</span></span></code></pre></div><p><code>vitest-setup.ts</code> を追加:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#e6db74">&#34;@testing-library/jest-dom/vitest&#34;</span>;
</span></span></code></pre></div><p><code>vitest.config.ts</code> を修正:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#75715e">@@ -7,6 +7,7 @@ export default mergeConfig(
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>     test: {
</span></span><span style="display:flex;"><span>       globals: true,
</span></span><span style="display:flex;"><span>       environment: &#39;happy-dom&#39;,
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      setupFiles: [&#39;./vitest-setup.ts&#39;],
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>     },
</span></span><span style="display:flex;"><span>   }),
</span></span><span style="display:flex;"><span> )
</span></span></code></pre></div><p><code>tsconfig.node.json</code> を修正:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#75715e">@@ -18,5 +18,10 @@
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>     &#34;noUnusedParameters&#34;: true,
</span></span><span style="display:flex;"><span>     &#34;noFallthroughCasesInSwitch&#34;: true
</span></span><span style="display:flex;"><span>   },
</span></span><span style="display:flex;"><span><span style="color:#f92672">-  &#34;include&#34;: [&#34;vite.config.ts&#34;, &#34;vitest.config.ts&#34;, &#34;tailwind.config.ts&#34;]
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+  &#34;include&#34;: [
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    &#34;vite.config.ts&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    &#34;vitest.config.ts&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    &#34;vitest-setup.ts&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    &#34;tailwind.config.ts&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  ]
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> }
</span></span></code></pre></div><h3 id="テストコード">テストコード</h3>
<p>以下のようにテストします。</p>
<p><code>src/App.test.tsx</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-tsx" data-lang="tsx"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">render</span>, <span style="color:#a6e22e">screen</span>, <span style="color:#a6e22e">waitFor</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@testing-library/react&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">userEvent</span> <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;@testing-library/user-event&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">App</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;./App&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">user</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">userEvent</span>.<span style="color:#a6e22e">setup</span>();
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">it</span>(<span style="color:#e6db74">&#34;App&#34;</span>, <span style="color:#66d9ef">async</span> () <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">render</span>(&lt;<span style="color:#f92672">App</span> /&gt;);
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">user</span>.<span style="color:#a6e22e">click</span>(<span style="color:#a6e22e">screen</span>.<span style="color:#a6e22e">getByText</span>(<span style="color:#e6db74">&#34;count is 0&#34;</span>));
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">waitFor</span>(() <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">screen</span>.<span style="color:#a6e22e">getByText</span>(<span style="color:#e6db74">&#34;count is 1&#34;</span>);
</span></span><span style="display:flex;"><span>  });
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><h2 id="msw">MSW</h2>
<p><a href="https://mswjs.io/" target="_blank">Mock Service Worker - API mocking library for browser and Node.js</a>
</p>
<p>テストで HTTP API, GraphQL API をモック化するのに使います。 <a href="https://testing-library.com/docs/react-testing-library/example-intro/" target="_blank">React Testing Library のドキュメント</a>
 でも推奨されていました。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>pnpm install -D msw@latest
</span></span></code></pre></div><p><code>src/__mocks__/server.ts</code> を作成:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">http</span>, <span style="color:#a6e22e">HttpResponse</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;msw&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">setupServer</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;msw/node&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">server</span> <span style="color:#f92672">=</span> <span style="color:#a6e22e">setupServer</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">http</span>.<span style="color:#66d9ef">get</span>(<span style="color:#e6db74">&#34;https://example.com/greeting&#34;</span>, () <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">HttpResponse</span>.<span style="color:#a6e22e">json</span>({ <span style="color:#a6e22e">greeting</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;hello there&#34;</span> });
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>);
</span></span></code></pre></div><p><code>src/setupTests.ts</code> を作成:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">server</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;./__mocks__/server&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">beforeAll</span>(() <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">server</span>.<span style="color:#a6e22e">listen</span>());
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">afterEach</span>(() <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">server</span>.<span style="color:#a6e22e">resetHandlers</span>());
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">afterAll</span>(() <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">server</span>.<span style="color:#a6e22e">close</span>());
</span></span></code></pre></div><p><code>vitest-setup.ts</code> に追記:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#75715e">@@ -1 +1,6 @@
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> import &#39;@testing-library/jest-dom/vitest&#39;
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+import { server } from &#39;./src/__mocks__/server&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+beforeAll(() =&gt; server.listen())
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+afterEach(() =&gt; server.resetHandlers())
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+afterAll(() =&gt; server.close())
</span></span></span></code></pre></div><p>以下のようにテストします。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">HttpResponse</span>, <span style="color:#a6e22e">http</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;msw&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">server</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;../__mocks__/server&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">it</span>(<span style="color:#e6db74">&#34;api success&#34;</span>, <span style="color:#66d9ef">async</span> () <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">res</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">fetch</span>(<span style="color:#e6db74">&#34;https://example.com/greeting&#34;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">expect</span>(<span style="color:#66d9ef">await</span> <span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">json</span>()).<span style="color:#a6e22e">toStrictEqual</span>({ <span style="color:#a6e22e">greeting</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;hello there&#34;</span> });
</span></span><span style="display:flex;"><span>});
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">it</span>(<span style="color:#e6db74">&#34;api error&#34;</span>, <span style="color:#66d9ef">async</span> () <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">server</span>.<span style="color:#a6e22e">use</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">http</span>.<span style="color:#66d9ef">get</span>(<span style="color:#e6db74">&#34;https://example.com/greeting&#34;</span>, () <span style="color:#f92672">=&gt;</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">HttpResponse</span>(<span style="color:#66d9ef">null</span>, { <span style="color:#a6e22e">status</span>: <span style="color:#66d9ef">500</span> });
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>  );
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">const</span> <span style="color:#a6e22e">res</span> <span style="color:#f92672">=</span> <span style="color:#66d9ef">await</span> <span style="color:#a6e22e">fetch</span>(<span style="color:#e6db74">&#34;https://example.com/greeting&#34;</span>);
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">expect</span>(<span style="color:#a6e22e">res</span>.<span style="color:#a6e22e">status</span>).<span style="color:#a6e22e">toBe</span>(<span style="color:#ae81ff">500</span>);
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>Jest から Vitest への移行</title>
      <link>https://okiyama.dev/posts/2024-03-31-migrate-to-vitest-from-jest/</link>
      <pubDate>Sun, 31 Mar 2024 10:15:16 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2024-03-31-migrate-to-vitest-from-jest/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://okiyama.dev/posts/2024-03-30-migrate-to-vite-from-create-react-app&#34;&gt;前回の記事&lt;/a&gt;
 では &lt;a href=&#34;https://vitejs.dev/&#34; target=&#34;_blank&#34;&gt;Vite&lt;/a&gt;
 に移行しましたが、テストは &lt;a href=&#34;https://jestjs.io/&#34; target=&#34;_blank&#34;&gt;Jest&lt;/a&gt;
 のままでした。今回は &lt;a href=&#34;https://vitest.dev/&#34; target=&#34;_blank&#34;&gt;Vitest&lt;/a&gt;
 に移行した際のログです。&lt;/p&gt;
&lt;h2 id=&#34;ライブラリのインストール&#34;&gt;ライブラリのインストール&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/capricorn86/happy-dom&#34; target=&#34;_blank&#34;&gt;happy-dom&lt;/a&gt;
 だと通らなくなるテストがいくつかありました。 &lt;code&gt;screen.getByRole&lt;/code&gt; などの取得ができないなどがあり、今回は &lt;a href=&#34;https://github.com/jsdom/jsdom&#34; target=&#34;_blank&#34;&gt;jsdom&lt;/a&gt;
 で進めることにしました。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;yarn add -D vitest jsdom
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt; の scripts を変更しておきます。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     &amp;#34;start&amp;#34;: &amp;#34;vite&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     &amp;#34;build&amp;#34;: &amp;#34;tsc &amp;amp;&amp;amp; vite build&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     &amp;#34;preview&amp;#34;: &amp;#34;vite preview&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-    &amp;#34;test&amp;#34;: &amp;#34;jest&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-    &amp;#34;test-coverage&amp;#34;: &amp;#34;jest --coverage --watchAll=false&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+    &amp;#34;test&amp;#34;: &amp;#34;vitest&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+    &amp;#34;test-coverage&amp;#34;: &amp;#34;vitest --coverage&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;&lt;/span&gt;     &amp;#34;compile&amp;#34;: &amp;#34;tsc&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     &amp;#34;lint&amp;#34;: &amp;#34;eslint src/**/*.ts src/**/*.tsx --quiet &amp;amp;&amp;amp; prettier --check .&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;     &amp;#34;verify-code&amp;#34;: &amp;#34;yarn compile &amp;amp;&amp;amp; yarn lint&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;設定&#34;&gt;設定&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;vitest.config.ts&lt;/code&gt; を作成:&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><a href="/posts/2024-03-30-migrate-to-vite-from-create-react-app">前回の記事</a>
 では <a href="https://vitejs.dev/" target="_blank">Vite</a>
 に移行しましたが、テストは <a href="https://jestjs.io/" target="_blank">Jest</a>
 のままでした。今回は <a href="https://vitest.dev/" target="_blank">Vitest</a>
 に移行した際のログです。</p>
<h2 id="ライブラリのインストール">ライブラリのインストール</h2>
<p><a href="https://github.com/capricorn86/happy-dom" target="_blank">happy-dom</a>
 だと通らなくなるテストがいくつかありました。 <code>screen.getByRole</code> などの取得ができないなどがあり、今回は <a href="https://github.com/jsdom/jsdom" target="_blank">jsdom</a>
 で進めることにしました。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>yarn add -D vitest jsdom
</span></span></code></pre></div><p><code>package.json</code> の scripts を変更しておきます。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>     &#34;start&#34;: &#34;vite&#34;,
</span></span><span style="display:flex;"><span>     &#34;build&#34;: &#34;tsc &amp;&amp; vite build&#34;,
</span></span><span style="display:flex;"><span>     &#34;preview&#34;: &#34;vite preview&#34;,
</span></span><span style="display:flex;"><span><span style="color:#f92672">-    &#34;test&#34;: &#34;jest&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-    &#34;test-coverage&#34;: &#34;jest --coverage --watchAll=false&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+    &#34;test&#34;: &#34;vitest&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    &#34;test-coverage&#34;: &#34;vitest --coverage&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>     &#34;compile&#34;: &#34;tsc&#34;,
</span></span><span style="display:flex;"><span>     &#34;lint&#34;: &#34;eslint src/**/*.ts src/**/*.tsx --quiet &amp;&amp; prettier --check .&#34;,
</span></span><span style="display:flex;"><span>     &#34;verify-code&#34;: &#34;yarn compile &amp;&amp; yarn lint&#34;,
</span></span></code></pre></div><h2 id="設定">設定</h2>
<p><code>vitest.config.ts</code> を作成:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> { <span style="color:#a6e22e">defineConfig</span>, <span style="color:#a6e22e">mergeConfig</span> } <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;vitest/config&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">viteConfig</span> <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;./vite.config&#34;</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#a6e22e">mergeConfig</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">viteConfig</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">defineConfig</span>({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">test</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">globals</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#a6e22e">environment</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;jsdom&#34;</span>,
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>  })
</span></span><span style="display:flex;"><span>);
</span></span></code></pre></div><p><code>globals: true</code> を設定すると、テストコードで <code>describe</code>, <code>it</code>, <code>vi</code> などを import せず使えるようになります。これらの型定義を使えるようにするため tsconfig にも設定が必要です。</p>
<p><code>tsconfig.json</code> を修正:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>     &#34;strict&#34;: true,
</span></span><span style="display:flex;"><span>     //&#34;noUnusedLocals&#34;: true,
</span></span><span style="display:flex;"><span>     //&#34;noUnusedParameters&#34;: true,
</span></span><span style="display:flex;"><span><span style="color:#f92672">-    &#34;noFallthroughCasesInSwitch&#34;: true
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+    &#34;noFallthroughCasesInSwitch&#34;: true,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    /* Vitest */
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    &#34;types&#34;: [&#34;vitest/globals&#34;]
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>   },
</span></span><span style="display:flex;"><span>   &#34;include&#34;: [&#34;src&#34;, &#34;graphql/codegen.ts&#34;],
</span></span><span style="display:flex;"><span>   &#34;references&#34;: [{ &#34;path&#34;: &#34;./tsconfig.node.json&#34; }],
</span></span></code></pre></div><p>また、 <code>vitest.config.ts</code> が型チェックに含まれるようにします。</p>
<p><code>tsconfig.node.json</code> を修正:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>     &#34;moduleResolution&#34;: &#34;bundler&#34;,
</span></span><span style="display:flex;"><span>     &#34;allowSyntheticDefaultImports&#34;: true
</span></span><span style="display:flex;"><span>   },
</span></span><span style="display:flex;"><span><span style="color:#f92672">-  &#34;include&#34;: [&#34;vite.config.ts&#34;, &#34;.eslintrc.cjs&#34;]
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+  &#34;include&#34;: [&#34;vite.config.ts&#34;, &#34;vitest.config.ts&#34;, &#34;.eslintrc.cjs&#34;]
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> }
</span></span></code></pre></div><h3 id="testpathignorepatterns-を-exclude-に移行">testPathIgnorePatterns を exclude に移行</h3>
<p>Jest 使用時は以下のように testPathIgnorePatterns を正規表現で指定して、ファイル名が <code>_</code> で始まるファイルを除外していました。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;testPathIgnorePatterns&#34;</span>: [<span style="color:#e6db74">&#34;__tests__/_(.)+.ts(x)?$&#34;</span>]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Vitest の exclude は glob pattern を指定します。また <code>defaultExclude</code> を含めるようにします。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#f92672">-import { defineConfig, mergeConfig } from &#39;vitest/config&#39;;
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+import { defineConfig, mergeConfig, defaultExclude } from &#39;vitest/config&#39;;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> import viteConfig from &#39;./vite.config&#39;;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> export default mergeConfig(
</span></span><span style="display:flex;"><span>   viteConfig,
</span></span><span style="display:flex;"><span>   defineConfig({
</span></span><span style="display:flex;"><span>     test: {
</span></span><span style="display:flex;"><span>       globals: true,
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      exclude: [...defaultExclude, &#39;**/__tests__/_*.ts&#39;, &#39;**/__tests__/_*.tsx&#39;],
</span></span></span></code></pre></div><h2 id="テストエラー対応">テストエラー対応</h2>
<h3 id="エラー-invalid-chai-property-tohavetextcontent">エラー Invalid Chai property: toHaveTextContent</h3>
<p><code>toHaveTextContent</code> の呼び出しでエラーになりいました。 これは <code>@testing-library/jest-dom</code> の提供するメソッドです。 <code>setupTests.ts</code> に import を追加し、このファイルをロードするよう設定します。</p>
<p><code>src/setupTests.ts</code> を作成:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#e6db74">&#34;@testing-library/jest-dom&#34;</span>;
</span></span></code></pre></div><p><code>vitest.test.config</code> を修正:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>     test: {
</span></span><span style="display:flex;"><span>       globals: true,
</span></span><span style="display:flex;"><span>       exclude: [...defaultExclude, &#39;**/__tests__/_*.ts&#39;, &#39;**/__tests__/_*.tsx&#39;],
</span></span><span style="display:flex;"><span>       environment: &#39;happy-dom&#39;,
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      setupFiles: [&#39;./src/setupTests.ts&#39;],
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>     },
</span></span><span style="display:flex;"><span>   }),
</span></span><span style="display:flex;"><span> );
</span></span></code></pre></div><h3 id="クラス名が-scoped-のものになっていてセレクタで取れずアサーション失敗">クラス名が scoped のものになっていてセレクタで取れずアサーション失敗</h3>
<p>このアプリケーションでは module css (scss) を使用しています。 Jest で実行していた際は元のクラス名で render されていたのが、 scoped なクラス名になっていました。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#f92672">-      &lt;div class=&#34;Info&#34; &gt;
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+      &lt;div class=&#34;_Info_78db8b&#34; &gt;
</span></span></span></code></pre></div><p>これをテストで <code>expect(container.querySelector(&quot;.Info&quot;)).toHavTextContent(...)</code> としていて、取得できずエラーになりました。</p>
<p>Jest を使っていた時は <a href="https://www.npmjs.com/package/identity-obj-proxy" target="_blank">identity-obj-proxy</a>
 を使ってこの問題に対処していました。 Vitest では以下の設定で対処できます。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>       globals: true,
</span></span><span style="display:flex;"><span>       exclude: [...defaultExclude, &#39;**/__tests__/_*.ts&#39;, &#39;**/__tests__/_*.tsx&#39;],
</span></span><span style="display:flex;"><span>       environment: &#39;jsdom&#39;,
</span></span><span style="display:flex;"><span>       setupFiles: [&#39;./src/setupTests.ts&#39;],
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      css: {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        modules: {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+          classNameStrategy: &#39;non-scoped&#39;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+        },
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      },
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>     },
</span></span><span style="display:flex;"><span>   }),
</span></span><span style="display:flex;"><span> );
</span></span></code></pre></div><ul>
<li><a href="https://vitest.dev/config/#css-modules-classnamestrategy" target="_blank">Configuring Vitest | Vitest</a>
</li>
</ul>
<h3 id="jest-global-setupcjs-を移行">jest-global-setup.cjs を移行</h3>
<p><code>jest-global-setup.cjs</code> で dotenv のロードなどをしていたので移行します。 TypeScript ファイルに変更し、 <code>vitest.config.ts</code> でこのファイルをロードするようにします。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>       globals: true,
</span></span><span style="display:flex;"><span>       exclude: [...defaultExclude, &#39;**/__tests__/_*.ts&#39;, &#39;**/__tests__/_*.tsx&#39;],
</span></span><span style="display:flex;"><span>       environment: &#39;jsdom&#39;,
</span></span><span style="display:flex;"><span>       setupFiles: [&#39;./src/setupTests.ts&#39;],
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      globalSetup: [&#39;./src/vitestGlobalSetup.ts&#39;],
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>       css: {
</span></span><span style="display:flex;"><span>         modules: {
</span></span><span style="display:flex;"><span>           classNameStrategy: &#39;non-scoped&#39;,
</span></span></code></pre></div><h3 id="expected-spy-to-not-be-called-at-all-but-actually-been-called-1-times">expected &ldquo;spy&rdquo; to not be called at all, but actually been called 1 times</h3>
<p>特定のケースでモックのアサーションが失敗していました。</p>
<pre tabindex="0"><code>AssertionError: expected &#34;spy&#34; to not be called at all, but actually been called 1 times

Received:
  1st spy call:
    Array []

Number of calls: 1
</code></pre><p>モックのリセットをしていないことが原因でした。</p>
<p><code>vitest.config.ts</code> を修正:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>      globalSetup: [&#39;./src/vitestGlobalSetup.ts&#39;],
</span></span><span style="display:flex;"><span>      css: {
</span></span><span style="display:flex;"><span>        modules: {
</span></span><span style="display:flex;"><span>           classNameStrategy: &#39;non-scoped&#39;,
</span></span><span style="display:flex;"><span>         },
</span></span><span style="display:flex;"><span>       },
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      mockReset: true,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>    },
</span></span><span style="display:flex;"><span>  }),
</span></span><span style="display:flex;"><span>);
</span></span></code></pre></div><h3 id="vitest-テスト実行がハングアップする問題">Vitest テスト実行がハングアップする問題</h3>
<p>テストがハングアップする問題が起きました。具体的には、コマンドを実行して特定のテストで止まり、そのまま応答がなくなるもののプロセスは動き続けています。</p>
<p><code>setupTests.ts</code> にモックを追加していたのですが、この定義方法に問題がありました。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#a6e22e">vi</span>.<span style="color:#a6e22e">mock</span>(<span style="color:#e6db74">&#39;react-i18next&#39;</span>, () <span style="color:#f92672">=&gt;</span> ({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">useTranslation</span><span style="color:#f92672">:</span> () <span style="color:#f92672">=&gt;</span> ({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">t</span><span style="color:#f92672">:</span> (<span style="color:#a6e22e">key</span>: <span style="color:#66d9ef">string</span>) <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">key</span>,
</span></span><span style="display:flex;"><span>  }),
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p><code>useTranslation</code> の戻り値のプロパティ <code>t</code> がコード内で <code>useCallback</code> の依存配列に含まれているとハングアップが発生します。この定義方法だと <code>t</code> が毎回新しい関数オブジェクトになるため、 <code>useCallback</code> が無限ループのような状態になっていたものと思われます。</p>
<p>以下のように <code>t</code> をトップレベルの変数に変更すると解消しました。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">t</span> <span style="color:#f92672">=</span> (<span style="color:#a6e22e">key</span>: <span style="color:#66d9ef">string</span>) <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">key</span>;
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">vi</span>.<span style="color:#a6e22e">mock</span>(<span style="color:#e6db74">&#39;react-i18next&#39;</span>, () <span style="color:#f92672">=&gt;</span> ({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">useTranslation</span><span style="color:#f92672">:</span> () <span style="color:#f92672">=&gt;</span> ({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">t</span>,
</span></span><span style="display:flex;"><span>  }),
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><p>ちなみに <code>vi.mock</code> はファイル上部に移動されます (巻き上げ)。変数定義を <code>vi.mock</code> よりも前にしたい場合 <code>vi.hoisted</code> を使います。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#66d9ef">const</span> <span style="color:#a6e22e">t</span> <span style="color:#f92672">=</span>  <span style="color:#a6e22e">vi</span>.<span style="color:#a6e22e">hoisted</span>((<span style="color:#a6e22e">key</span>: <span style="color:#66d9ef">string</span>) <span style="color:#f92672">=&gt;</span> <span style="color:#a6e22e">key</span>);
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">vi</span>.<span style="color:#a6e22e">mock</span>(<span style="color:#e6db74">&#39;react-i18next&#39;</span>, () <span style="color:#f92672">=&gt;</span> ({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">useTranslation</span><span style="color:#f92672">:</span> () <span style="color:#f92672">=&gt;</span> ({
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">t</span>,
</span></span><span style="display:flex;"><span>  }),
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><ul>
<li><a href="https://zenn.dev/ptna/articles/617b0884f6af0e" target="_blank">ESM の mock 巻き上げ問題と Vitest の vi.hoisted について</a>
</li>
</ul>
<h2 id="テストコード修正">テストコード修正</h2>
<p>jest 関数を置換していきます。</p>
<ul>
<li><code>jest.fn</code> =&gt; <code>vi.fn</code></li>
<li><code>jest.mock</code> =&gt; <code>vi.mock</code></li>
<li><code>jest.requireActual</code> =&gt; <code>await vi.importActual</code></li>
<li><code>jest.spyOn</code> =&gt; <code>vi.spyOn</code></li>
</ul>
<h3 id="viimportactual">vi.importActual</h3>
<p><code>vi.importActual</code> は Promise を返すため以下のような変更が必要でした。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#75715e">@@ -33,8 +33,8 @@ import {
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> const mockedNavigate = vi.fn();
</span></span><span style="display:flex;"><span><span style="color:#f92672">-jest.mock(&#39;react-router-dom&#39;, () =&gt; ({
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">-  ...jest.requireActual(&#39;react-router-dom&#39;),
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+vi.mock(&#39;react-router-dom&#39;, async () =&gt; ({
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  ...(await vi.importActual(&#39;react-router-dom&#39;)),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>   useNavigate: () =&gt; mockedNavigate,
</span></span><span style="display:flex;"><span> }));
</span></span></code></pre></div><p><code>requireActual</code> で複数行にわたるものがなかったため正規表現で一括置換できました。 (ただし外側の関数に async をつけるのは手動)</p>
<ul>
<li>pattern: <code>jest\.requireActual\('(.*?)'\)</code></li>
<li>replace: <code>(await vi.importActual('$1'))</code></li>
</ul>
<h3 id="spyon-mockimplementation">spyOn, mockImplementation</h3>
<p>mockImplementation に何も渡していないコードがあり、 jest ではこの場合元の処理が使われます。つまりアサーションでコール回数を数えるためだけのために spyOn を使っているということです。</p>
<p>今回は単に削除で対応できました。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#f92672">-  const mocked = vi.spyOn(MyClass, &#39;method&#39;).mockImplementation();
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+  const mocked = vi.spyOn(MyClass, &#39;method&#39;);
</span></span></span></code></pre></div><h3 id="型エラー-vifnvoid-">型エラー <code>vi.fn&lt;void, []&gt;()</code></h3>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#f92672">-vi.fn&lt;void, []&gt;()
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+vi.fn&lt;[], []&gt;()
</span></span></span></code></pre></div><h2 id="close-timed-out-after-10000ms">close timed out after 10000ms</h2>
<p>テスト実行の終了後にこのメッセージが表示されて、プロセスが終了しない状態になりました。</p>
<blockquote>
<p>close timed out after 10000ms
Failed to terminate worker while running <code>&lt;snip&gt;.test.tsx</code>.
See <a href="https://vitest.dev/guide/common-errors.html#failed-to-terminate-worker" target="_blank">https://vitest.dev/guide/common-errors.html#failed-to-terminate-worker</a>
 for troubleshooting.
Tests closed successfully but something prevents Vite server from exiting
You can try to identify the cause by enabling &ldquo;hanging-process&rdquo; reporter. See <a href="https://vitest.dev/config/#reporters" target="_blank">https://vitest.dev/config/#reporters</a>
</p></blockquote>
<p>ドキュメントの通り <code>vitest.config.ts</code> に <code>pool: 'forks'</code> を設定して解消しました。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>      css: {
</span></span><span style="display:flex;"><span>        modules: {
</span></span><span style="display:flex;"><span>          classNameStrategy: &#39;non-scoped&#39;,
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>      mockReset: true,
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+     pool: &#39;forks&#39;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>    },
</span></span></code></pre></div><ul>
<li><a href="https://vitest.dev/guide/common-errors.html#failed-to-terminate-worker" target="_blank">Common Errors | Guide | Vitest</a>
</li>
</ul>
<h2 id="移行完了">移行完了</h2>
<p>最後に Jest で使っていた設定ファイル、パッケージを削除して完了です。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>rm babel.config.json
</span></span><span style="display:flex;"><span>yarn remove babel-preset-vite identity-obj-proxy
</span></span></code></pre></div><p>ここまでの修正でほぼ全てのテストが通るようになりました。</p>
<p>Jest を使っていた頃と比べると、テストの実行速度が上がっておよそ半分の時間で実行できるようになりました。 Node v18 から v20 へのアップデートでさらに半分になり、当初の 4 倍速くなりました。</p>
]]></content:encoded>
    </item>
    <item>
      <title>CRA から Vite への移行</title>
      <link>https://okiyama.dev/posts/2024-03-30-migrate-to-vite-from-create-react-app/</link>
      <pubDate>Sat, 30 Mar 2024 14:10:13 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2024-03-30-migrate-to-vite-from-create-react-app/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://create-react-app.dev/&#34; target=&#34;_blank&#34;&gt;CRA&lt;/a&gt;
 と &lt;a href=&#34;https://www.npmjs.com/package/react-scripts&#34; target=&#34;_blank&#34;&gt;react-scripts&lt;/a&gt;
 で構築していた React アプリを &lt;a href=&#34;https://vitejs.dev/&#34; target=&#34;_blank&#34;&gt;Vite&lt;/a&gt;
 に移行した際のログです。&lt;/p&gt;
&lt;h2 id=&#34;ライブラリのインストール&#34;&gt;ライブラリのインストール&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;yarn add -D vite @vitejs/plugin-react
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;indexhtml-をルートフォルダに移動&#34;&gt;index.html をルートフォルダに移動&lt;/h2&gt;
&lt;p&gt;Vite ではここに置く必要があるようです。&lt;/p&gt;
&lt;p&gt;また &lt;code&gt;public/&lt;/code&gt; ディレクトリにあるファイルへは &lt;code&gt;/&lt;/code&gt; でアクセスできるので、以下のような変更をします。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;- &amp;lt;link rel=&amp;#34;icon&amp;#34; href=&amp;#34;%PUBLIC_URL%/favicon.ico&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+ &amp;lt;link rel=&amp;#34;icon&amp;#34; href=&amp;#34;/favicon.ico&amp;#34; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;import-path-を--で絶対参照できるようにする&#34;&gt;import path を &lt;code&gt;~/&lt;/code&gt; で絶対参照できるようにする&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;vite.config.ts&lt;/code&gt; で alias を設定します。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;default&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;defineConfig&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;plugins&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [&lt;span style=&#34;color:#a6e22e&#34;&gt;react&lt;/span&gt;()],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;resolve&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;alias&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;find&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;~&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;replacement&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;path.resolve&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;__dirname&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;./src&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;エラー-sass-の-import-&#34;&gt;エラー: sass の import ~@&lt;/h2&gt;
&lt;p&gt;この状態で起動してブラウザで表示すると以下のエラーが発生しました。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[plugin:vite:css] [sass] Can&amp;#39;t find stylesheet to import.
  ╷
6 │ @import &amp;#39;~@progress/kendo-theme-default/scss/core/_index.scss&amp;#39;;
  │         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ╵
  src/index.scss 6:9  root stylesheet
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&#34;https://github.com/vitejs/vite/issues/5764&#34; title=&#34;https://github.com/vitejs/vite/issues/5764&#34; target=&#34;_blank&#34;&gt;Error: Can&amp;rsquo;t find stylesheet to import. · Issue #5764 · vitejs/vite · GitHub&lt;/a&gt;
&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><a href="https://create-react-app.dev/" target="_blank">CRA</a>
 と <a href="https://www.npmjs.com/package/react-scripts" target="_blank">react-scripts</a>
 で構築していた React アプリを <a href="https://vitejs.dev/" target="_blank">Vite</a>
 に移行した際のログです。</p>
<h2 id="ライブラリのインストール">ライブラリのインストール</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>yarn add -D vite @vitejs/plugin-react
</span></span></code></pre></div><h2 id="indexhtml-をルートフォルダに移動">index.html をルートフォルダに移動</h2>
<p>Vite ではここに置く必要があるようです。</p>
<p>また <code>public/</code> ディレクトリにあるファイルへは <code>/</code> でアクセスできるので、以下のような変更をします。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#f92672">- &lt;link rel=&#34;icon&#34; href=&#34;%PUBLIC_URL%/favicon.ico&#34; /&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ &lt;link rel=&#34;icon&#34; href=&#34;https://okiyama.dev/favicon.ico&#34; /&gt;
</span></span></span></code></pre></div><h2 id="import-path-を--で絶対参照できるようにする">import path を <code>~/</code> で絶対参照できるようにする</h2>
<p><code>vite.config.ts</code> で alias を設定します。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#66d9ef">export</span> <span style="color:#66d9ef">default</span> <span style="color:#a6e22e">defineConfig</span>({
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">plugins</span><span style="color:#f92672">:</span> [<span style="color:#a6e22e">react</span>()],
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">resolve</span><span style="color:#f92672">:</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">alias</span><span style="color:#f92672">:</span> [
</span></span><span style="display:flex;"><span>      {
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">find</span><span style="color:#f92672">:</span> <span style="color:#e6db74">&#34;~&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">replacement</span>: <span style="color:#66d9ef">path.resolve</span>(<span style="color:#a6e22e">__dirname</span>, <span style="color:#e6db74">&#34;./src&#34;</span>),
</span></span><span style="display:flex;"><span>      },
</span></span><span style="display:flex;"><span>    ],
</span></span><span style="display:flex;"><span>  },
</span></span><span style="display:flex;"><span>});
</span></span></code></pre></div><h2 id="エラー-sass-の-import-">エラー: sass の import ~@</h2>
<p>この状態で起動してブラウザで表示すると以下のエラーが発生しました。</p>
<pre tabindex="0"><code>[plugin:vite:css] [sass] Can&#39;t find stylesheet to import.
  ╷
6 │ @import &#39;~@progress/kendo-theme-default/scss/core/_index.scss&#39;;
  │         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  ╵
  src/index.scss 6:9  root stylesheet
</code></pre><p><a href="https://github.com/vitejs/vite/issues/5764" title="https://github.com/vitejs/vite/issues/5764" target="_blank">Error: Can&rsquo;t find stylesheet to import. · Issue #5764 · vitejs/vite · GitHub</a>
</p>
<p><code>vitest.config.ts</code> の alias に以下を追加して解消しました。</p>
<pre tabindex="0"><code>      {
        find: /^~@progress\/kendo-theme-default/,
        replacement: &#39;@progress/kendo-theme-default&#39;,
      },
</code></pre><h2 id="エラー-sass-の-import--1">エラー: sass の import ~/</h2>
<p>起動時に以下のエラーが表示されました。</p>
<pre tabindex="0"><code>[plugin:vite:css] [sass] ENOENT: no such file or directory, open &#39;&lt;snip&gt;/src/themes/mixin&#39;
  ╷
1 │ @import &#39;~/src/themes/mixin&#39;;
  │         ^^^^^^^^^^^^^^^^^^^^
  ╵
</code></pre><p>scss ファイルにある import を以下のように修正して解消しました。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#f92672">-@import &#39;~/src/themes/mixin&#39;;
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+@import &#39;~/themes/mixin&#39;;
</span></span></span></code></pre></div><h2 id="エラー-process-is-not-defined">エラー: process is not defined</h2>
<p>ブラウザ画面が何も表示されなくなり、 devtools の console にエラーが出力されていました:</p>
<pre tabindex="0"><code>Uncaught ReferenceError: process is not defined
    at index.ts:2:24
</code></pre><p>Vite では環境変数の扱いが異なるためです。以下の変更で解消しました。</p>
<ul>
<li><code>process.env</code> を <code>import.meta.env</code> に変更</li>
<li>環境変数の prefix を <code>REACT_APP_</code> から <code>VITE_</code> に変更</li>
<li><code>react-app-env.d.ts</code> を <code>vite-app-env.d.ts</code> にリネーム＆修正</li>
</ul>
<h2 id="エラー-require-is-not-defined">エラー: require is not defined</h2>
<p>一部で require を使っていた箇所があったためエラーになりました。</p>
<pre tabindex="0"><code>Uncaught ReferenceError: require is not defined
</code></pre><p>require を全て import に置き換えて解消しました。なお moment のロケールを条件付きで require していた処理があり、以下のように置き換えました。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#a6e22e">+import &#39;moment/locale/ja&#39;;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+import moment from &#39;moment&#39;;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+moment.locale(&#39;en&#39;);
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> if (i18n.language.startsWith(&#39;ja&#39;)) {
</span></span><span style="display:flex;"><span><span style="color:#f92672">-  require(&#39;moment/locale/ja&#39;);
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+  moment.locale(&#39;ja&#39;);
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> }
</span></span></code></pre></div><ul>
<li><a href="https://azukiazusa.dev/blog/vite-require/" target="_blank">Vite だと require() が使えないよ〜</a>
</li>
</ul>
<h2 id="uncaught-typeerror-momentdurationformat-is-not-a-function">Uncaught TypeError: moment.duration(&hellip;).format is not a function</h2>
<p>ここまでの変更で <code>yarn start</code> で起動してブラウザで動作することを確認できました。</p>
<p>しかし、 devtools の console に以下のエラーが出ています。</p>
<pre tabindex="0"><code>Uncaught TypeError: moment.duration(...).format is not a function
    at setDuration (index.tsx:39:37)
    at setTime (index.tsx:42:53)
    at index.tsx:46:42
</code></pre><p><code>src/index.tsx</code> に以下を追加して解消しました。 ESM になった影響と思われます。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ts" data-lang="ts"><span style="display:flex;"><span><span style="color:#66d9ef">import</span> <span style="color:#a6e22e">momentDurationFormatSetup</span> <span style="color:#66d9ef">from</span> <span style="color:#e6db74">&#34;moment-duration-format&#34;</span>;
</span></span><span style="display:flex;"><span><span style="color:#75715e">// eslint-disable-next-line @typescript-eslint/no-explicit-any
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#a6e22e">momentDurationFormatSetup</span>(<span style="color:#a6e22e">moment</span> <span style="color:#66d9ef">as</span> <span style="color:#66d9ef">any</span>);
</span></span></code></pre></div><p>なお <a href="https://momentjs.com/" target="_blank">Moment.js</a>
 自体が現在は非推奨とされているようです。今回のアプリケーションでは <a href="https://date-fns.org/" target="_blank">date-fns</a>
 へと段階的に移行しています。</p>
<blockquote>
<p>We now generally consider Moment to be a legacy project in maintenance mode. It is not dead, but it is indeed done.</p>
<p><a href="https://momentjs.com/docs/#/-project-status/" target="_blank">https://momentjs.com/docs/#/-project-status/</a>
</p></blockquote>
<h2 id="eslint-エラー-require-of-es-module-eslintrcjs-from-node_moduleseslinteslintrcdisteslintrccjs-not-supported">ESLint エラー: require() of ES Module .eslintrc.js from node_modules/@eslint/eslintrc/dist/eslintrc.cjs not supported.</h2>
<p>公式サイトに以下の説明がありました。 <code>.eslintrc.cjs</code> にリネームして解消しました。</p>
<blockquote>
<p><strong>JavaScript (ESM)</strong> - use <code>.eslintrc.cjs</code> when running ESLint in JavaScript packages that specify <code>&quot;type&quot;:&quot;module&quot;</code> in their <code>package.json</code>. Note that ESLint does not support ESM configuration at this time.</p>
<p><a href="https://eslint.org/docs/latest/use/configure/configuration-files#configuration-file-formats" title="https://eslint.org/docs/latest/use/configure/configuration-files#configuration-file-formats" target="_blank">Configuration Files - ESLint - Pluggable JavaScript Linter</a>
</p></blockquote>
<h2 id="global-is-not-defined">global is not defined</h2>
<p>特定の処理で <code>global is not defined</code> というエラーが発生することがありました。 Webpack ではこの変数がデフォルトで含まれていましたが、 Vite では設定をすることで含まれるようになります。</p>
<p><code>vite.config.ts</code> を修正:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#75715e">@@ -20,4 +20,7 @@ export default defineConfig({
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>       },
</span></span><span style="display:flex;"><span>     ],
</span></span><span style="display:flex;"><span>   },
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  define: {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    global: &#39;window&#39;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  },
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> });
</span></span></code></pre></div><ul>
<li><a href="https://github.com/vitejs/vite/issues/2778#issuecomment-810086159" target="_blank">vite import dragula error: global is not defined · Issue #2778 · vitejs/vite · GitHub</a>
</li>
</ul>
<h2 id="typeerror-cannot-read-properties-of-undefined">TypeError: Cannot read properties of undefined</h2>
<p>class 構文を使っている処理で <code>TypeError: Cannot read properties of undefined</code> が発生することがありました。</p>
<p>TS のコンパイラオプションで <code>useDefineForClassFields</code> というものがあり、<a href="https://github.com/vitejs/vite/blob/e0a6ef2b9e6f1df8c5e71efab6182b7cf662d18d/packages/create-vite/template-react-ts/tsconfig.json#L4" target="_blank">Vite+React のテンプレート</a>
が true だったためそれに倣って設定していましたが、これにより class のトランスパイル結果が変わってエラーになっていました。</p>
<p><code>tsconfig.json</code> を修正:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#75715e">@@ -2,7 +2,7 @@
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>   &#34;extends&#34;: &#34;./tsconfig.paths.json&#34;,
</span></span><span style="display:flex;"><span>   &#34;compilerOptions&#34;: {
</span></span><span style="display:flex;"><span>     &#34;target&#34;: &#34;ES2020&#34;,
</span></span><span style="display:flex;"><span><span style="color:#f92672">-    &#34;useDefineForClassFields&#34;: true,
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+    &#34;useDefineForClassFields&#34;: false,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>     &#34;lib&#34;: [&#34;ESNext&#34;, &#34;DOM&#34;, &#34;DOM.Iterable&#34;],
</span></span><span style="display:flex;"><span>     &#34;module&#34;: &#34;ESNext&#34;,
</span></span><span style="display:flex;"><span>     &#34;skipLibCheck&#34;: true,
</span></span></code></pre></div><p>最近の React では class 構文はほとんど使わなくなっていますが、今回のアプリケーションでは一部にレガシーなコードベースが残っており、その中で以前のトランスパイル結果に依存している箇所があったようです。</p>
<ul>
<li><a href="https://ja.vitejs.dev/guide/features.html#usedefineforclassfields" target="_blank">特徴 | Vite</a>
</li>
<li><a href="https://qiita.com/vvakame/items/60d8d43ded0b160a99cc#%E3%82%88%E3%82%8A%E5%8E%B3%E5%AF%86%E3%81%AAes%E4%BB%95%E6%A7%98%E3%81%B8%E3%81%AE%E8%BF%BD%E5%BE%93%E3%81%A8-usedefineforclassfields-%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%AE%E8%BF%BD%E5%8A%A0" target="_blank">TypeScript v3.7.2 変更点 #TypeScript - Qiita</a>
</li>
</ul>
<h1 id="デプロイ関連のエラー">デプロイ関連のエラー</h1>
<p>ここまでの変更でローカルでは問題なく動作するようになりました。次にデプロイ周りで起きた問題を記載します。</p>
<h2 id="ci-デプロイジョブがエラー-the-user-provided-path-build-does-not-exist">CI デプロイジョブがエラー: The user-provided path ./build does not exist</h2>
<p>ビルド結果の出力先ディレクトリ名が Vite のデフォルトは <code>dist</code> になっています。今までは <code>build</code> だったので、デプロイコマンドを変更しました。</p>
<h2 id="cloudflare-の最適化で-js-ファイルが壊れる">Cloudflare の最適化で JS ファイルが壊れる</h2>
<p>デプロイはできたものの、アクセスしても何も表示されません。 devtools の console には以下のように出力されていました。</p>
<pre tabindex="0"><code>index-SKSNfXQs.js:3
Uncaught SyntaxError: Invalid or unexpected token (at index-SKSNfXQs.js:3:8090)

881 Unchecked runtime.lastError: A listener indicated an asynchronous response by returning true, but the message channel closed before a response was received
</code></pre><p>エラーの発生箇所を見ると、 minify されたコードですが <code>1.toString</code> という箇所でエラーになっていました。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-js" data-lang="js"><span style="display:flex;"><span>...<span style="color:#a6e22e">Lot</span><span style="color:#f92672">=</span>Math.<span style="color:#a6e22e">random</span>(),<span style="color:#a6e22e">Rot</span><span style="color:#f92672">=</span><span style="color:#a6e22e">jot</span>(<span style="color:#ae81ff">1.</span><span style="color:#a6e22e">toString</span>),<span style="color:#a6e22e">OA</span><span style="color:#f92672">=</span><span style="color:#66d9ef">function</span>(<span style="color:#a6e22e">e</span>){<span style="color:#66d9ef">return</span><span style="color:#960050;background-color:#1e0010">&#34;</span><span style="color:#a6e22e">Symbol</span>(...
</span></span><span style="display:flex;"><span>                             <span style="color:#f92672">^^^^^^^^^^</span>
</span></span></code></pre></div><p>原因は Cloudflare の最適化機能である minify によって JavaScript ファイルが変わってしまっていたためでした。</p>
<ul>
<li><a href="https://community.cloudflare.com/t/auto-minify-breaks-javascript-syntax/417399" target="_blank">&ldquo;Auto Minify&rdquo; breaks javascript syntax</a>
</li>
</ul>
<p>このアプリケーションは AWS S3 でホストして Cloudflare で配信する構成になっています。 Cloudflare の設定で minify をオフにすることで解消しました。<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>元のコード<br/>
<code>Rot=jot(1 .toString)</code> のスペースが除去されて<br/>
<code>Rot=jot(1.toString)</code> になって SyntaxError が起きていたようです。</p>
<p>これは <a href="https://esbuild.github.io/" target="_blank">esbuild</a>
 によるビルド特有のようで、 Vite はビルドに esbuild を使用しているためこれが起きるようになりました。</p>
<ul>
<li><a href="https://community.cloudflare.com/t/cloudflare-auto-minify-breaks-esbuild-minified-js/547036" target="_blank">Cloudflare Auto Minify breaks esbuild minified JS</a>
</li>
<li><a href="https://github.com/evanw/esbuild/issues/3116" target="_blank">Cloudflare Minification breaks esbuild&rsquo;s &ldquo;1 .toString&rdquo; · Issue #3116 · evanw/esbuild · GitHub</a>
</li>
</ul>
<p>そもそもビルド時に minify しているので Cloudflare 側でさらに minify する意味はなかったわけですが、たまたまオンにしてしまっていました。</p>
<p>ところで <code>1 .toString</code> というコードは何を意味するのでしょうか。 minify された結果なのではっきりとはわかりませんが、 <a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Number/toString" target="_blank">Number.prototype.toString</a>
 を関数として取り出しているように見えます。書き方としては <code>(1).toString</code> と等価で、 <code>1 .toString</code> の方が 1 文字節約できるため esbuild がこのような出力をしているのだと思われます。</p>
<ul>
<li><a href="https://stackoverflow.com/questions/38968598/what-happened-inside-of-1-tostring-and-1-tostring-in-javascript" target="_blank">What happened inside of (1).toString() and 1.toString() in Javascript</a>
</li>
</ul>
<h2 id="browserslist-を-vite-で使用するための設定">Browserslist を Vite で使用するための設定</h2>
<p>Vite では browserslist を使うのに設定が必要です。</p>
<ul>
<li><a href="https://github.com/vitejs/vite/discussions/6849" target="_blank">Browserslist support · vitejs vite · Discussion #6849</a>
</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>yarn add -D browserslist-to-esbuild
</span></span></code></pre></div><p><code>vite.config.ts</code> に以下を追加:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> export default defineConfig({
</span></span><span style="display:flex;"><span>   plugins: [react()],
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  build: {
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    target:
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      process.env.NODE_ENV === &#39;production&#39; &amp;&amp;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+      browserslistToEsbuild([&#39;&gt;0.2%&#39;, &#39;not dead&#39;, &#39;not op_mini all&#39;]),
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  },
</span></span></span></code></pre></div><h3 id="設定変更-ie11-を削除">設定変更: ie11 を削除</h3>
<p>上記の設定では既に削除されていますが、 ie11 があると <code>Maximum call stack size</code> というエラーが発生するため削除しました。 (既に Internet Explorer はサポート対象外だったのですが、 Browserslist 設定に残ってしまっていた)</p>
<p>エラーログ:</p>
<pre tabindex="0"><code>✓ 5956 modules transformed.
✓ built in 6m 29s
error during build:
RangeError: Maximum call stack size exceeded
    at String.substring (&lt;anonymous&gt;)
    at replaceClose (file:///&lt;snip&gt;/node_modules/vite/dist/node/chunks/dep-R0I0XnyH.js:109:21)
    at replaceClose (file:///&lt;snip&gt;/node_modules/vite/dist/node/chunks/dep-R0I0XnyH.js:112:30)
    at replaceClose (file:///&lt;snip&gt;/node_modules/vite/dist/node/chunks/dep-R0I0XnyH.js:112:30)
    at replaceClose (file:///&lt;snip&gt;/node_modules/vite/dist/node/chunks/dep-R0I0XnyH.js:112:30)
    at replaceClose (file:///&lt;snip&gt;/node_modules/vite/dist/node/chunks/dep-R0I0XnyH.js:112:30)
    at replaceClose (file:///&lt;snip&gt;/node_modules/vite/dist/node/chunks/dep-R0I0XnyH.js:112:30)
    at replaceClose (file:///&lt;snip&gt;/node_modules/vite/dist/node/chunks/dep-R0I0XnyH.js:112:30)
    at replaceClose (file:///&lt;snip&gt;/node_modules/vite/dist/node/chunks/dep-R0I0XnyH.js:112:30)
    at replaceClose (file:///&lt;snip&gt;/node_modules/vite/dist/node/chunks/dep-R0I0XnyH.js:112:30)
</code></pre><h1 id="jest-関連の移行">Jest 関連の移行</h1>
<p>のちに <a href="https://vitest.dev/" target="_blank">Vitest</a>
 へと移行するのですが、まずは <a href="https://jestjs.io/" target="_blank">Jest</a>
 のまま動かすことにしました。</p>
<h2 id="jest-をインストールしてみたがうまく動かない">Jest をインストールしてみたがうまく動かない</h2>
<p>インストールして多少の設定をしてみましたが、うまく動きませんでした。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>yarn add -D jest @types/jest
</span></span></code></pre></div><p>テスト実行するとエラー:</p>
<pre tabindex="0"><code>ReferenceError: Jest: Got error running globalSetup - &lt;snip&gt;/jest-global-setup.js, reason: module is not defined in ES module scope
This file is being treated as an ES module because it has a &#39;.js&#39; file extension and &#39;&lt;snip&gt;/package.json&#39; contains &#34;type&#34;: &#34;module&#34;. To treat it as a CommonJS script, rename it to use the &#39;.cjs&#39; file extension.
    at file:///&lt;snip&gt;/jest-global-setup.js:3:1
    at ModuleJob.run (node:internal/modules/esm/module_job:194:25)
</code></pre><p>→ <code>jest-global-setup.cjs</code> にリネーム。ファイル内容も変更が必要で、 require を import に置き換えました。</p>
<p>この状態でテスト実行すると走り始めるようになりましたが、すべてのテストケースが以下のエラーになります。</p>
<blockquote>
<p>Jest encountered an unexpected token</p>
<p>Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.</p>
<p>Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.</p></blockquote>
<p>このアプローチは諦めて、 eject して必要な設定をしていくことにしました。</p>
<h2 id="eject-実行時のエラー">eject 実行時のエラー</h2>
<p>eject コマンドを実行したところエラーになりました。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>❯ yarn run eject
</span></span><span style="display:flex;"><span>NOTE: Create React App 2+ supports TypeScript, Sass, CSS Modules and more without ejecting: https://reactjs.org/blog/2018/10/01/create-react-app-v2.html
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>✔ Are you sure you want to eject? This action is permanent. … yes
</span></span><span style="display:flex;"><span>Ejecting...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Out of the box, Create React App only supports overriding these Jest options:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  • clearMocks
</span></span><span style="display:flex;"><span>  • collectCoverageFrom
</span></span><span style="display:flex;"><span>  • coveragePathIgnorePatterns
</span></span><span style="display:flex;"><span>  • coverageReporters
</span></span><span style="display:flex;"><span>  • coverageThreshold
</span></span><span style="display:flex;"><span>  • displayName
</span></span><span style="display:flex;"><span>  • extraGlobals
</span></span><span style="display:flex;"><span>  • globalSetup
</span></span><span style="display:flex;"><span>  • globalTeardown
</span></span><span style="display:flex;"><span>  • moduleNameMapper
</span></span><span style="display:flex;"><span>  • resetMocks
</span></span><span style="display:flex;"><span>  • resetModules
</span></span><span style="display:flex;"><span>  • restoreMocks
</span></span><span style="display:flex;"><span>  • snapshotSerializers
</span></span><span style="display:flex;"><span>  • testMatch
</span></span><span style="display:flex;"><span>  • transform
</span></span><span style="display:flex;"><span>  • transformIgnorePatterns
</span></span><span style="display:flex;"><span>  • watchPathIgnorePatterns.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>These options in your package.json Jest configuration are not currently supported by Create React App:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  • testPathIgnorePatterns
</span></span><span style="display:flex;"><span>  • collectCoverage
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>If you wish to override other Jest options, you need to eject from the default setup. You can <span style="color:#66d9ef">do</span> so by running npm run eject but remember that this is a one-way operation. You may also file an issue with Create React App to discuss supporting more options out of the box.
</span></span></code></pre></div><p>Jest の config から <code>testPathIgnorePatterns</code> と <code>collectCoverage</code> を削除して再実行すると eject できました。</p>
<h2 id="eject-した設定から必要なものを残す">eject した設定から必要なものを残す</h2>
<p>その後はこちらの記事の <a href="https://zenn.dev/akineko/articles/765f8388e84c06#jest-%E3%81%AE-cra-%E4%BE%9D%E5%AD%98%E3%82%92%E5%A4%96%E3%81%99" target="_blank">Jest の CRA 依存を外す</a>
 に書いてあるとおり修正を行い、正常にテスト実行ができるようになりました。内容そのままなので詳細は記事に譲ります。</p>
<ul>
<li><a href="https://zenn.dev/akineko/articles/765f8388e84c06" target="_blank">create-react-app から Vite への移行</a>
</li>
</ul>
<h2 id="vitest-移行">Vitest 移行</h2>
<p>まずは Jest 環境を維持したまま移行したのですが、その後 Vitest に移行しました。次の記事を参照してください。</p>
<ul>
<li><a href="/posts/2024-03-31-migrate-to-vitest-from-jest">Jest から Vitest への移行</a>
</li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>正確には Cloudflare の設定変更後、再度デプロイすると解消しました。&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>prettier-plugin-classnames でクラス名を改行する</title>
      <link>https://okiyama.dev/posts/2024-03-17-prettier-plugin-classnames/</link>
      <pubDate>Sun, 17 Mar 2024 16:49:36 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2024-03-17-prettier-plugin-classnames/</guid>
      <description>&lt;p&gt;Tailwind CSS はクラス名の記述が長くなりがちという問題がある。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-jsx&#34; data-lang=&#34;jsx&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;className&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;transform bg-red-500 text-center text-xl text-red-900 duration-500 ease-in hover:bg-blue-500 hover:text-blue-900&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;改行を入れることで多少改善するが、 Prettier にやって欲しいところ。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-jsx&#34; data-lang=&#34;jsx&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;className&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;transform bg-red-500 text-center text-xl text-red-900
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    duration-500 ease-in hover:bg-blue-500 hover:text-blue-900&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#f92672&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;公式の Prettier プラグインでも検討はされているが実装されていない&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;。&lt;/p&gt;
&lt;p&gt;非公式であるが &lt;code&gt;prettier-plugin-classnames&lt;/code&gt; というプラグインがクラス名を改行する機能を提供している。 (&lt;a href=&#34;https://github.com/tailwindlabs/tailwindcss/discussions/7763#discussioncomment-7904679&#34; target=&#34;_blank&#34;&gt;Discussion のコメント&lt;/a&gt;
 より)&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.npmjs.com/package/prettier-plugin-classnames&#34; target=&#34;_blank&#34;&gt;prettier-plugin-classnames - npm&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;公式のプラグインと併用するには &lt;code&gt;prettier-plugin-merge&lt;/code&gt; も必要。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.npmjs.com/package/prettier-plugin-merge&#34; target=&#34;_blank&#34;&gt;prettier-plugin-merge - npm&lt;/a&gt;
&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pnpm install -D prettier-plugin-classnames prettier-plugin-merge
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;.prettierrc&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff&#34; data-lang=&#34;diff&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;#34;singleQuote&amp;#34;: true,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &amp;#34;semi&amp;#34;: false,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-  &amp;#34;plugins&amp;#34;: [&amp;#34;prettier-plugin-tailwindcss&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+  &amp;#34;plugins&amp;#34;: [
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+    &amp;#34;prettier-plugin-tailwindcss&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+    &amp;#34;prettier-plugin-classnames&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+    &amp;#34;prettier-plugin-merge&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+  ],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;+  &amp;#34;endingPosition&amp;#34;: &amp;#34;absolute-with-indent&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;&lt;/span&gt;   &amp;#34;tailwindFunctions&amp;#34;: [&amp;#34;clsx&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href=&#34;https://github.com/rokiyama/example-vite-react-app/blob/507b5f91df552016413fa53217f8f6d27c861c47/.prettierrc&#34; target=&#34;_blank&#34;&gt;https://github.com/rokiyama/example-vite-react-app/blob/507b5f91df552016413fa53217f8f6d27c861c47/.prettierrc&lt;/a&gt;
&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Tailwind CSS はクラス名の記述が長くなりがちという問題がある。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span>&lt;<span style="color:#f92672">div</span> <span style="color:#a6e22e">className</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;transform bg-red-500 text-center text-xl text-red-900 duration-500 ease-in hover:bg-blue-500 hover:text-blue-900&#34;</span>&gt;
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">div</span>&gt;
</span></span></code></pre></div><p>改行を入れることで多少改善するが、 Prettier にやって欲しいところ。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-jsx" data-lang="jsx"><span style="display:flex;"><span>&lt;<span style="color:#f92672">div</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a6e22e">className</span><span style="color:#f92672">=</span><span style="color:#e6db74">&#34;transform bg-red-500 text-center text-xl text-red-900
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">    duration-500 ease-in hover:bg-blue-500 hover:text-blue-900&#34;</span>
</span></span><span style="display:flex;"><span>&gt;
</span></span><span style="display:flex;"><span>  ...
</span></span><span style="display:flex;"><span>&lt;/<span style="color:#f92672">div</span>&gt;
</span></span></code></pre></div><p>公式の Prettier プラグインでも検討はされているが実装されていない<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>。</p>
<p>非公式であるが <code>prettier-plugin-classnames</code> というプラグインがクラス名を改行する機能を提供している。 (<a href="https://github.com/tailwindlabs/tailwindcss/discussions/7763#discussioncomment-7904679" target="_blank">Discussion のコメント</a>
 より)</p>
<p><a href="https://www.npmjs.com/package/prettier-plugin-classnames" target="_blank">prettier-plugin-classnames - npm</a>
</p>
<p>公式のプラグインと併用するには <code>prettier-plugin-merge</code> も必要。</p>
<p><a href="https://www.npmjs.com/package/prettier-plugin-merge" target="_blank">prettier-plugin-merge - npm</a>
</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>pnpm install -D prettier-plugin-classnames prettier-plugin-merge
</span></span></code></pre></div><p><code>.prettierrc</code>:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> {
</span></span><span style="display:flex;"><span>   &#34;singleQuote&#34;: true,
</span></span><span style="display:flex;"><span>   &#34;semi&#34;: false,
</span></span><span style="display:flex;"><span><span style="color:#f92672">-  &#34;plugins&#34;: [&#34;prettier-plugin-tailwindcss&#34;]
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+  &#34;plugins&#34;: [
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    &#34;prettier-plugin-tailwindcss&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    &#34;prettier-plugin-classnames&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+    &#34;prettier-plugin-merge&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  ],
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+  &#34;endingPosition&#34;: &#34;absolute-with-indent&#34;,
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>   &#34;tailwindFunctions&#34;: [&#34;clsx&#34;]
</span></span><span style="display:flex;"><span> }
</span></span></code></pre></div><p><a href="https://github.com/rokiyama/example-vite-react-app/blob/507b5f91df552016413fa53217f8f6d27c861c47/.prettierrc" target="_blank">https://github.com/rokiyama/example-vite-react-app/blob/507b5f91df552016413fa53217f8f6d27c861c47/.prettierrc</a>
</p>
<p><code>endingPosition</code> は改行位置を決めるもの。 <code>relative</code>, <code>absolute</code>, <code>absolute-with-indent</code> のいずれかを設定する。
デフォルトは <code>relative</code> であるが、 <code>absolute-with-indent</code> が自然な挙動に思えたので設定。</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>一度実装されたが revert されたらしい: <a href="https://zenn.dev/makotot/articles/781b09850b4e6c#%E3%83%90%E3%83%AA%E3%82%A8%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E6%AF%8E%E3%81%AB%E5%88%86%E9%A1%9E%E3%81%99%E3%82%8B" target="_blank">Tailwind CSS のクラス属性長くなりがちな問題について</a>
&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>レガシーな Redux コードの移行</title>
      <link>https://okiyama.dev/posts/2024-03-16-migrating-to-modern-redux/</link>
      <pubDate>Sat, 16 Mar 2024 09:04:28 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2024-03-16-migrating-to-modern-redux/</guid>
      <description>&lt;p&gt;レガシーな Redux コードを移行する機会があったのでメモ&lt;/p&gt;
&lt;p&gt;基本的に &lt;a href=&#34;https://redux.js.org/usage/migrating-to-modern-redux&#34; target=&#34;_blank&#34;&gt;Migrating to Modern Redux&lt;/a&gt;
 という公式のガイドの通り。
比較的スムーズに移行できたポイントとして、古い書き方と現代的な書き方を共存したまま進めることができるのが大きかった。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Many users are working on older Redux codebases that have been around since before these &amp;ldquo;modern Redux&amp;rdquo; patterns existed. Migrating those codebases to today&amp;rsquo;s recommended modern Redux patterns will result in codebases that are much smaller and easier to maintain.&lt;/p&gt;
&lt;p&gt;The good news is that &lt;strong&gt;you can migrate your code to modern Redux incrementally, piece by piece, with old and new Redux code coexisting and working together!&lt;/strong&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>レガシーな Redux コードを移行する機会があったのでメモ</p>
<p>基本的に <a href="https://redux.js.org/usage/migrating-to-modern-redux" target="_blank">Migrating to Modern Redux</a>
 という公式のガイドの通り。
比較的スムーズに移行できたポイントとして、古い書き方と現代的な書き方を共存したまま進めることができるのが大きかった。</p>
<blockquote>
<p>Many users are working on older Redux codebases that have been around since before these &ldquo;modern Redux&rdquo; patterns existed. Migrating those codebases to today&rsquo;s recommended modern Redux patterns will result in codebases that are much smaller and easier to maintain.</p>
<p>The good news is that <strong>you can migrate your code to modern Redux incrementally, piece by piece, with old and new Redux code coexisting and working together!</strong></p>
<p><a href="https://redux.js.org/usage/migrating-to-modern-redux#overview" target="_blank">https://redux.js.org/usage/migrating-to-modern-redux#overview</a>
</p></blockquote>
<h2 id="移行作業の概要">移行作業の概要</h2>
<p>大まかな流れは以下の通り:</p>
<ol>
<li><code>redux</code>, <code>react-redux</code> のバージョン最新化と <code>@reduxjs/toolkit</code> のインストール</li>
<li><code>createStore</code> から <code>configureStore</code> への移行</li>
<li><code>connect</code> から Hooks API への移行</li>
<li><code>action</code>, <code>actionCreator</code>, <code>reducer</code> の構成から <code>createSlice</code> または RTK Query への移行</li>
</ol>
<p>1, 2 は npm の依存関係とアプリケーションの初期化プロセスの変更で、主に一箇所を書き換えるだけの変更。 3, 4 は機能ごとに書き換えの作業が必要だった。</p>
<p>すべての機能を移行できたわけではなく、 3, 4 の変更は段階的に進めていてレガシーな部分も残っている。
移行が終わったコンポーネントはテストも書きやすくなったが、古い構成の Redux コードに依存しているコンポーネントはテストコードも以前のままである。</p>
<p>ユニットテストについては <a href="https://enzymejs.github.io/enzyme" target="_blank">Enzyme</a>
 から <a href="https://testing-library.com/" target="_blank">React Testing Library</a>
 と <a href="https://mswjs.io/" target="_blank">MSW</a>
 の導入も必要だった。</p>
<h2 id="その他の公式ドキュメント">その他の公式ドキュメント</h2>
<ul>
<li><a href="https://redux.js.org/usage/writing-tests" target="_blank">Writing Tests | Redux</a>

<ul>
<li>ユニットテストで <code>preloadedState</code> を渡せる <code>configureStore</code> の書き方</li>
</ul>
</li>
<li><a href="https://redux.js.org/usage/usage-with-typescript" target="_blank">Usage With TypeScript | Redux</a>

<ul>
<li><a href="https://redux.js.org/usage/usage-with-typescript#usage-with-react-redux" target="_blank">Define Typed Hooks</a>
 に Hooks API にアプリケーション固有の <code>useAppSelector</code>, <code>useAppDispatcher</code> 型を作る例</li>
</ul>
</li>
<li><a href="https://redux-toolkit.js.org/tutorials/overview" target="_blank">Tutorials Overview | Redux Toolkit</a>

<ul>
<li>Redux とは別のリポジトリに Redux Toolkit のドキュメントがある。 <code>createSlice</code> や RTK Query の詳しい使い方はこちらを参照</li>
</ul>
</li>
<li><a href="https://redux-toolkit.js.org/usage/immer-reducers" target="_blank">Writing Reducers with Immer | Redux Toolkit</a>

<ul>
<li>reducer に渡されるステートが Immer のオブジェクトになっている</li>
</ul>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>ドメイン名を Google Domains から Vercel に移管した</title>
      <link>https://okiyama.dev/posts/2024-02-18-migrate-from-google-domains-to-vercel/</link>
      <pubDate>Sun, 18 Feb 2024 16:02:41 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2024-02-18-migrate-from-google-domains-to-vercel/</guid>
      <description>&lt;p&gt;このサイトのドメイン名 &lt;code&gt;okiyama.dev&lt;/code&gt; を Google Domains から Vercel に移管しました。&lt;/p&gt;
&lt;p&gt;もともと Google Domains で購入したドメイン名でしたが、 Google Domains のサービス終了がアナウンスされ Squarespace に移管される予定になっていました。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://support.google.com/domains/answer/13689670?hl=ja&#34; target=&#34;_blank&#34;&gt;Squarespace への Google Domains のドメイン登録の譲渡について - Google Domains ヘルプ&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;そのまま Squarespace に移行しても構わなかったのですが、ホスティングを Vercel で行っている関係でドメインの管理も同じサービスに任せることにしました。&lt;/p&gt;
&lt;p&gt;以下の手順で移管を行いました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Google Domains
&lt;ul&gt;
&lt;li&gt;ドメインのロックを解除&lt;/li&gt;
&lt;li&gt;認証コードを取得&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Vercel
&lt;ul&gt;
&lt;li&gt;クレジットカードを登録しておく (設定画面 → Payment method)&lt;/li&gt;
&lt;li&gt;Domains から Transfer In よりドメイン名と認証コードを入力&lt;/li&gt;
&lt;li&gt;元のレジストラの確認待ちと、カスタム DNS ゾーンの設定移行を促すダイアログが表示される
&lt;ul&gt;
&lt;li&gt;今回はゾーンの設定は不要なので、移行が完了するまで待つ&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Google Domains
&lt;ul&gt;
&lt;li&gt;移管の承認・キャンセルのメールが届くので、リンクから承認する&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Vercel
&lt;ul&gt;
&lt;li&gt;ドメイン移管完了のメールが届く&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;今回は Google Domains から承認確認のメールが届くまで 10 分程度、 Vercel から移管完了のメールが届くまで 1 時間程度かかりました。&lt;/p&gt;
&lt;p&gt;参考&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://support.google.com/domains/answer/3251178?sjid=1035049167829836067-AP&#34; target=&#34;_blank&#34;&gt;Google Domains から別の登録事業者に移管する - Google Domains ヘルプ&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://vercel.com/docs/projects/domains/transfer-your-domain#transfer-a-domain-to-vercel&#34; target=&#34;_blank&#34;&gt;Transferring Domains to Another Team or Project&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <content:encoded><![CDATA[<p>このサイトのドメイン名 <code>okiyama.dev</code> を Google Domains から Vercel に移管しました。</p>
<p>もともと Google Domains で購入したドメイン名でしたが、 Google Domains のサービス終了がアナウンスされ Squarespace に移管される予定になっていました。</p>
<p><a href="https://support.google.com/domains/answer/13689670?hl=ja" target="_blank">Squarespace への Google Domains のドメイン登録の譲渡について - Google Domains ヘルプ</a>
</p>
<p>そのまま Squarespace に移行しても構わなかったのですが、ホスティングを Vercel で行っている関係でドメインの管理も同じサービスに任せることにしました。</p>
<p>以下の手順で移管を行いました。</p>
<ul>
<li>Google Domains
<ul>
<li>ドメインのロックを解除</li>
<li>認証コードを取得</li>
</ul>
</li>
<li>Vercel
<ul>
<li>クレジットカードを登録しておく (設定画面 → Payment method)</li>
<li>Domains から Transfer In よりドメイン名と認証コードを入力</li>
<li>元のレジストラの確認待ちと、カスタム DNS ゾーンの設定移行を促すダイアログが表示される
<ul>
<li>今回はゾーンの設定は不要なので、移行が完了するまで待つ</li>
</ul>
</li>
</ul>
</li>
<li>Google Domains
<ul>
<li>移管の承認・キャンセルのメールが届くので、リンクから承認する</li>
</ul>
</li>
<li>Vercel
<ul>
<li>ドメイン移管完了のメールが届く</li>
</ul>
</li>
</ul>
<p>今回は Google Domains から承認確認のメールが届くまで 10 分程度、 Vercel から移管完了のメールが届くまで 1 時間程度かかりました。</p>
<p>参考</p>
<ul>
<li><a href="https://support.google.com/domains/answer/3251178?sjid=1035049167829836067-AP" target="_blank">Google Domains から別の登録事業者に移管する - Google Domains ヘルプ</a>
</li>
<li><a href="https://vercel.com/docs/projects/domains/transfer-your-domain#transfer-a-domain-to-vercel" target="_blank">Transferring Domains to Another Team or Project</a>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>expo install --check で TypeScript をアップデートしたら ESLint が動かなくなったのでダウングレードした</title>
      <link>https://okiyama.dev/posts/2023-09-16-expo-typescript-eslint-versions/</link>
      <pubDate>Sat, 16 Sep 2023 10:30:42 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2023-09-16-expo-typescript-eslint-versions/</guid>
      <description>&lt;h2 id=&#34;tldr&#34;&gt;TL;DR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;expo install --check&lt;/code&gt; で TypeScript のバージョンを上げたら、 eslint が動かなくなった&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@typescript-eslint/eslint-plugin&lt;/code&gt; を v6 に上げる必要があるが &lt;code&gt;@react-native/eslint-config&lt;/code&gt; が v5 系に依存しているためアップデートできない&lt;/li&gt;
&lt;li&gt;TypeScript のバージョンを 5.0.x に落としたら ESLint が実行できるようになった&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;expo-install-check-とは&#34;&gt;expo install &amp;ndash;check とは&lt;/h2&gt;
&lt;p&gt;Expo CLI に用意されている &lt;code&gt;expo install --check&lt;/code&gt; は、バージョンを検証・修正するコマンドです。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://docs.expo.dev/more/expo-cli/#version-validation&#34; target=&#34;_blank&#34;&gt;https://docs.expo.dev/more/expo-cli/#version-validation&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;ローカルで実行すると対話式で修正でき、 CI ではチェック結果に応じてエラーになります。&lt;/p&gt;
&lt;p&gt;最近、 Expo SDK のアップデートに合わせて実行したところ、 TypeScript のバージョンが上がりました。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;❯ npx expo install --check
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Some dependencies are incompatible with the installed expo version:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  typescript@5.0.4 - expected version: ^5.1.3
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Your project may not work correctly &lt;span style=&#34;color:#66d9ef&#34;&gt;until&lt;/span&gt; you install the correct versions of the packages.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Fix with: npx expo install --fix
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;✔ Fix dependencies? … yes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;› Installing &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; SDK 49.0.0 compatible native module using npm
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;gt; npm install
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# snip&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;changed &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; package, and audited &lt;span style=&#34;color:#ae81ff&#34;&gt;1600&lt;/span&gt; packages in 9s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(実際には SDK アップデートと同時に行ったため、もっとログがありました。上記は後日再実行した際のログです)&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="tldr">TL;DR</h2>
<ul>
<li><code>expo install --check</code> で TypeScript のバージョンを上げたら、 eslint が動かなくなった</li>
<li><code>@typescript-eslint/eslint-plugin</code> を v6 に上げる必要があるが <code>@react-native/eslint-config</code> が v5 系に依存しているためアップデートできない</li>
<li>TypeScript のバージョンを 5.0.x に落としたら ESLint が実行できるようになった</li>
</ul>
<h2 id="expo-install-check-とは">expo install &ndash;check とは</h2>
<p>Expo CLI に用意されている <code>expo install --check</code> は、バージョンを検証・修正するコマンドです。</p>
<p><a href="https://docs.expo.dev/more/expo-cli/#version-validation" target="_blank">https://docs.expo.dev/more/expo-cli/#version-validation</a>
</p>
<p>ローカルで実行すると対話式で修正でき、 CI ではチェック結果に応じてエラーになります。</p>
<p>最近、 Expo SDK のアップデートに合わせて実行したところ、 TypeScript のバージョンが上がりました。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>❯ npx expo install --check
</span></span><span style="display:flex;"><span>Some dependencies are incompatible with the installed expo version:
</span></span><span style="display:flex;"><span>  typescript@5.0.4 - expected version: ^5.1.3
</span></span><span style="display:flex;"><span>Your project may not work correctly <span style="color:#66d9ef">until</span> you install the correct versions of the packages.
</span></span><span style="display:flex;"><span>Fix with: npx expo install --fix
</span></span><span style="display:flex;"><span>✔ Fix dependencies? … yes
</span></span><span style="display:flex;"><span>› Installing <span style="color:#ae81ff">1</span> SDK 49.0.0 compatible native module using npm
</span></span><span style="display:flex;"><span>&gt; npm install
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># snip</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>changed <span style="color:#ae81ff">1</span> package, and audited <span style="color:#ae81ff">1600</span> packages in 9s
</span></span></code></pre></div><p>(実際には SDK アップデートと同時に行ったため、もっとログがありました。上記は後日再実行した際のログです)</p>
<p>上記の通り TypeScript のバージョンが上がりましたが、これにより ESLint が動かなくなりました。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>❯ npm run lint
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&gt; eslint ./src
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">=============</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>You may find that it works just fine, or you may not.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>SUPPORTED TYPESCRIPT VERSIONS: &gt;<span style="color:#f92672">=</span>3.3.1 &lt;5.1.0
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>YOUR TYPESCRIPT VERSION: 5.2.2
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Please only submit bug reports when using the officially supported version.
</span></span></code></pre></div><p>確認すると、 <code>@typescript-eslint/eslint-plugin</code> のバージョンが古く、最新の v6 系ではなく v5 系を使っていました。</p>
<p>v6 系にアップデートしようとしましたが、 <code>@react-native-community/eslint-config@3.2.0</code> が v5 系に依存しておりアップデートできません。そもそもこのパッケージ自体が古く、 React Native 0.72 で名称が変更されています。</p>
<p><a href="https://reactnative.dev/blog/2023/06/21/0.72-metro-package-exports-symlinks#package-renames" target="_blank">https://reactnative.dev/blog/2023/06/21/0.72-metro-package-exports-symlinks#package-renames</a>
</p>
<ul>
<li>OLD: <code>@react-native-community/eslint-config</code></li>
<li>NEW: <code>@react-native/eslint-config</code></li>
</ul>
<p>というわけで <code>@react-native/eslint-config</code> の最新をインストールしましたが、こちらも v5 系に依存していました。リポジトリを見たところ以下のようになっています。</p>
<table>
  <thead>
      <tr>
          <th>revision</th>
          <th>directory</th>
          <th>package name</th>
          <th>version</th>
          <th>依存している <code>@typescript-eslint/eslint-plugin</code> のバージョン</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><a href="https://github.com/facebook/react-native/blob/61861d21ff71a9451019e0f98e0c0414cf12c153/packages/eslint-config-react-native/package.json#L26" target="_blank">main</a>
</td>
          <td><code>packages/eslint-config-react-native</code></td>
          <td><code>@react-native/eslint-config</code></td>
          <td><code>0.73.0</code></td>
          <td><code>^5.57.1</code></td>
      </tr>
      <tr>
          <td><a href="https://github.com/facebook/react-native/blob/v0.72.4/packages/eslint-config-react-native-community/package.json#L17" target="_blank">v0.72.4</a>
</td>
          <td><code>packages/eslint-config-react-native-community</code></td>
          <td><code>@react-native/eslint-config</code></td>
          <td><code>0.72.2</code></td>
          <td><code>^5.30.5</code></td>
      </tr>
  </tbody>
</table>
<p>ディレクトリ名が異なる理由は不明ですが、パッケージ名は一致しています。 <a href="https://www.npmjs.com/package/@react-native/eslint-config?activeTab=versions" target="_blank">npm.js</a>
 によると 0.72.2 が最新で、 0.73.0 は nightly となっています。いずれにせよ、どちらも <code>@typescript-eslint/eslint-plugin</code> の v5 系に依存しています。</p>
<p>依存関係を無視して <code>@typescript-eslint/eslint-plugin</code> を最新化することも考えられますが、今回は手っ取り早く対応するため TypeScript のバージョンを下げます。 ESLint のエラーメッセージによると <code>&lt;5.1.0</code> が必要なので、 v5.0.x 系にします。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>❯ npm install -D typescript@~5.0.0
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>changed <span style="color:#ae81ff">1</span> package, and audited <span style="color:#ae81ff">1600</span> packages in 2s
</span></span></code></pre></div><p>ESLint が実行できるようになりました。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>❯ npm run lint
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>&gt; expo-chat-command-gpt@1.0.0 lint
</span></span><span style="display:flex;"><span>&gt; eslint ./src
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Warning: React version not specified in eslint-plugin-react settings. See https://github.com/jsx-eslint/eslint-plugin-react#configuration .
</span></span></code></pre></div><h2 id="実際のコミット">実際のコミット</h2>
<p><a href="https://github.com/rokiyama/gpt-prompter-frontend/commit/2c877c25f13ac8de4bc38d1139bc8591b6ca65cd" target="_blank">https://github.com/rokiyama/gpt-prompter-frontend/commit/2c877c25f13ac8de4bc38d1139bc8591b6ca65cd</a>
</p>
]]></content:encoded>
    </item>
    <item>
      <title>Xcode 15 beta をインストールすると gopls がエラー</title>
      <link>https://okiyama.dev/posts/2023-08-26-xcode15-beta-gopls-error/</link>
      <pubDate>Sat, 26 Aug 2023 11:16:55 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2023-08-26-xcode15-beta-gopls-error/</guid>
      <description>&lt;p&gt;要約:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;しばらく Go に触れていなかった&lt;/li&gt;
&lt;li&gt;最近 Xcode 15 beta 6 をインストールした&lt;/li&gt;
&lt;li&gt;VSCode で Go のプロジェクトを開くと gopls のエラーが出た&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/golang/go/issues/61190#issuecomment-1663426102&#34; target=&#34;_blank&#34;&gt;issue のコメント&lt;/a&gt;
に従って gopls を一度削除して Homebrew で入れ直したら解消した&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;VSCode で Go のプロジェクトを開くとエラーが２つ通知されました。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;The gopls server failed to initialize.
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;gopls client: couldn&amp;#39;t create connection to server.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;コマンドラインで &lt;code&gt;gopls&lt;/code&gt; を実行してみるとエラーでした。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;❯ gopls version
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;R PAKAYAH LI LETTER PHAfish: Job 1, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;gopls version&amp;#39;&lt;/span&gt; terminated by signal SIGSEGV &lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;Address boundary error&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Issue が上がっていました。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/golang/vscode-go/issues/2909&#34; target=&#34;_blank&#34;&gt;Language server fails to start · Issue #2909 · golang/vscode-go · GitHub&lt;/a&gt;
&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>要約:</p>
<ul>
<li>しばらく Go に触れていなかった</li>
<li>最近 Xcode 15 beta 6 をインストールした</li>
<li>VSCode で Go のプロジェクトを開くと gopls のエラーが出た</li>
<li><a href="https://github.com/golang/go/issues/61190#issuecomment-1663426102" target="_blank">issue のコメント</a>
に従って gopls を一度削除して Homebrew で入れ直したら解消した</li>
</ul>
<p>VSCode で Go のプロジェクトを開くとエラーが２つ通知されました。</p>
<pre tabindex="0"><code>The gopls server failed to initialize.
</code></pre><pre tabindex="0"><code>gopls client: couldn&#39;t create connection to server.
</code></pre><p>コマンドラインで <code>gopls</code> を実行してみるとエラーでした。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>❯ gopls version
</span></span><span style="display:flex;"><span>R PAKAYAH LI LETTER PHAfish: Job 1, <span style="color:#e6db74">&#39;gopls version&#39;</span> terminated by signal SIGSEGV <span style="color:#f92672">(</span>Address boundary error<span style="color:#f92672">)</span>
</span></span></code></pre></div><p>Issue が上がっていました。</p>
<p><a href="https://github.com/golang/vscode-go/issues/2909" target="_blank">Language server fails to start · Issue #2909 · golang/vscode-go · GitHub</a>
</p>
<p><a href="https://github.com/golang/go/issues/61190#issuecomment-1663426102" target="_blank">runtime: gopls -v crashes immediately when linked with Xcode 15 beta · Issue #61190 · golang/go · GitHub</a>
</p>
<p>一度 Go modules でインストールされたバイナリを削除して、 Homebrew のバージョンをインストールし直すと解消するようです。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e"># 削除</span>
</span></span><span style="display:flex;"><span>❯ rm ~/go/bin/gopls
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Homebrew でインストール</span>
</span></span><span style="display:flex;"><span>❯ brew install gopls
</span></span><span style="display:flex;"><span><span style="color:#75715e"># (snip)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">==</span>&gt; Fetching gopls
</span></span><span style="display:flex;"><span><span style="color:#f92672">==</span>&gt; Downloading https://ghcr.io/v2/homebrew/core/gopls/manifests/0.13.2
</span></span><span style="display:flex;"><span><span style="color:#75715e">######################################################################################################################################################### 100.0%</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">==</span>&gt; Downloading https://ghcr.io/v2/homebrew/core/gopls/blobs/sha256:a70553eebb2218b4062c6b452eb7a5168e33224eaa396e847c45abb1825fbf5e
</span></span><span style="display:flex;"><span><span style="color:#75715e">######################################################################################################################################################### 100.0%</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">==</span>&gt; Pouring gopls--0.13.2.arm64_ventura.bottle.tar.gz
</span></span><span style="display:flex;"><span>🍺  /opt/homebrew/Cellar/gopls/0.13.2: <span style="color:#ae81ff">5</span> files, 25.9MB
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># 正常になった</span>
</span></span><span style="display:flex;"><span>❯ gopls version
</span></span><span style="display:flex;"><span>golang.org/x/tools/gopls v0.13.2
</span></span><span style="display:flex;"><span>    golang.org/x/tools/gopls@<span style="color:#f92672">(</span>devel<span style="color:#f92672">)</span>
</span></span></code></pre></div><p>この後、 VSCode で <code>Reload Window</code> を実行すると正常になりました。</p>
]]></content:encoded>
    </item>
    <item>
      <title>eslint-config-prettier と eslint-plugin-prettier</title>
      <link>https://okiyama.dev/posts/2023-08-09-eslint-config-prettier-vs-eslint-plugin-prettier/</link>
      <pubDate>Wed, 09 Aug 2023 08:34:43 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2023-08-09-eslint-config-prettier-vs-eslint-plugin-prettier/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://prettier.io/docs/en/integrating-with-linters&#34; target=&#34;_blank&#34;&gt;Prettier 公式サイトの説明&lt;/a&gt;
 によると、 eslint-config-prettier が紹介される一方、 eslint-plugin-prettier は非推奨とされています。&lt;/p&gt;
&lt;p&gt;それぞれがどのような機能のパッケージなのか調べてみました。&lt;/p&gt;
&lt;h2 id=&#34;eslint-config-prettier-prettier-と競合する-eslint-ルールを無効にする&#34;&gt;eslint-config-prettier: Prettier と競合する ESLint ルールを無効にする&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/prettier/eslint-config-prettier&#34; target=&#34;_blank&#34;&gt;https://github.com/prettier/eslint-config-prettier&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;ESLint には、不具合の原因となるコードを検出するためのルールのほか、フォーマットに関するルールも存在します。 Prettier を使っている場合、フォーマットのルールは不要なだけでなく、 Prettier の設定とコンフリクトする場合もあります。&lt;/p&gt;
&lt;p&gt;eslint-config-prettier はそのようなルールを無効にします。&lt;/p&gt;
&lt;h2 id=&#34;eslint-plugin-prettier-eslint-実行時に-prettier-によるフォーマットを実行する&#34;&gt;eslint-plugin-prettier: ESLint 実行時に Prettier によるフォーマットを実行する&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/prettier/eslint-plugin-prettier&#34; target=&#34;_blank&#34;&gt;https://github.com/prettier/eslint-plugin-prettier&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;eslint-plugin-prettier は ESLint 実行時に Prettier によるフォーマットを実行する ESLint プラグインです。フォーマットが適用されていないファイルを検出する機能もあります。&lt;/p&gt;
&lt;p&gt;Prettier が登場した当初は、エディタとの統合はまだ存在していませんでした。一方で ESLint (などの Linter ツール) は以前から存在していたため、多くのエディタがサポートしていました。このような時代に、 ESLint 経由で実行できる eslint-plugin-prettier は有用だったようです。&lt;/p&gt;
&lt;p&gt;しかし現在は多くのエディタが Prettier をサポートしています。そして、 ESLint のプラグインとして Prettier を実行することにはデメリットがあり (不要な警告が出る・パフォーマンスが悪いなど) 、現在では eslint-plugin-prettier を使う理由はありません。&lt;/p&gt;
&lt;h2 id=&#34;eslint-設定の-extends-に-pluginprettierrecommended-を書くとどうなるのか&#34;&gt;ESLint 設定の extends に plugin:prettier/recommended を書くとどうなるのか&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/prettier/eslint-plugin-prettier/tree/910aeb60a7456beb6193c634bb8dec1b7181312d#recommended-configuration&#34; target=&#34;_blank&#34;&gt;README&lt;/a&gt;
 によると、これ一つで eslint-config-prettier と eslint-plugin-prettier の両方が有効になります。以下のように展開されます。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><a href="https://prettier.io/docs/en/integrating-with-linters" target="_blank">Prettier 公式サイトの説明</a>
 によると、 eslint-config-prettier が紹介される一方、 eslint-plugin-prettier は非推奨とされています。</p>
<p>それぞれがどのような機能のパッケージなのか調べてみました。</p>
<h2 id="eslint-config-prettier-prettier-と競合する-eslint-ルールを無効にする">eslint-config-prettier: Prettier と競合する ESLint ルールを無効にする</h2>
<p><a href="https://github.com/prettier/eslint-config-prettier" target="_blank">https://github.com/prettier/eslint-config-prettier</a>
</p>
<p>ESLint には、不具合の原因となるコードを検出するためのルールのほか、フォーマットに関するルールも存在します。 Prettier を使っている場合、フォーマットのルールは不要なだけでなく、 Prettier の設定とコンフリクトする場合もあります。</p>
<p>eslint-config-prettier はそのようなルールを無効にします。</p>
<h2 id="eslint-plugin-prettier-eslint-実行時に-prettier-によるフォーマットを実行する">eslint-plugin-prettier: ESLint 実行時に Prettier によるフォーマットを実行する</h2>
<p><a href="https://github.com/prettier/eslint-plugin-prettier" target="_blank">https://github.com/prettier/eslint-plugin-prettier</a>
</p>
<p>eslint-plugin-prettier は ESLint 実行時に Prettier によるフォーマットを実行する ESLint プラグインです。フォーマットが適用されていないファイルを検出する機能もあります。</p>
<p>Prettier が登場した当初は、エディタとの統合はまだ存在していませんでした。一方で ESLint (などの Linter ツール) は以前から存在していたため、多くのエディタがサポートしていました。このような時代に、 ESLint 経由で実行できる eslint-plugin-prettier は有用だったようです。</p>
<p>しかし現在は多くのエディタが Prettier をサポートしています。そして、 ESLint のプラグインとして Prettier を実行することにはデメリットがあり (不要な警告が出る・パフォーマンスが悪いなど) 、現在では eslint-plugin-prettier を使う理由はありません。</p>
<h2 id="eslint-設定の-extends-に-pluginprettierrecommended-を書くとどうなるのか">ESLint 設定の extends に plugin:prettier/recommended を書くとどうなるのか</h2>
<p><a href="https://github.com/prettier/eslint-plugin-prettier/tree/910aeb60a7456beb6193c634bb8dec1b7181312d#recommended-configuration" target="_blank">README</a>
 によると、これ一つで eslint-config-prettier と eslint-plugin-prettier の両方が有効になります。以下のように展開されます。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;extends&#34;</span>: [<span style="color:#e6db74">&#34;prettier&#34;</span>],
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;plugins&#34;</span>: [<span style="color:#e6db74">&#34;prettier&#34;</span>],
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;rules&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;prettier/prettier&#34;</span>: <span style="color:#e6db74">&#34;error&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;arrow-body-style&#34;</span>: <span style="color:#e6db74">&#34;off&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;prefer-arrow-callback&#34;</span>: <span style="color:#e6db74">&#34;off&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>現在は eslint-config-prettier だけを有効にすればよいので、 Prettier 関連で eslintrc に必要な設定はこれだけです。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;extends&#34;</span>: [<span style="color:#e6db74">&#34;prettier&#34;</span>]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>なお、以前は <code>prettier/react</code> や <code>prettier/vue</code> のような他のプラグインと共存するための設定も存在したようですが、 <a href="https://github.com/prettier/eslint-config-prettier/blob/19826807f2d668a05bb9c29a5f6f6a6e6e3287e4/CHANGELOG.md#version-800-2021-02-21" target="_blank">eslint-config-prettier の 8.0.0</a>
 から不要になったようです。</p>
<h2 id="ci-で-eslint-と同時に-prettier-のチェックも実行する">CI で ESLint と同時に Prettier のチェックも実行する</h2>
<p>CI で ESLint を実行しているなら、 <code>prettier --check</code> コマンドも同時に実行するとよいかもしれません。フォーマットが適用されていないファイルを検出できます。</p>
<p>例として CI で <code>npm run lint</code> を実行している場合、以下のように設定します。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;scripts&#34;</span>: {
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">&#34;lint&#34;</span>: <span style="color:#e6db74">&#34;eslint ./src &amp;&amp; prettier --check .&#34;</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="参考文献">参考文献</h2>
<ul>
<li><a href="https://prettier.io/docs/en/integrating-with-linters" target="_blank">Integrating with Linters · Prettier</a>
</li>
<li><a href="https://knote.dev/post/2020-08-29/duprecated-eslint-plugin-prettier/" target="_blank">いつのまにか eslint-plugin-prettier が推奨されないものになってた | K note.dev</a>
</li>
<li><a href="https://www.sunapro.com/eslint-with-prettier/" target="_blank">eslint と prettier を併用する時の設定 - すな.dev</a>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Expo でのモバイルアプリ開発、ローカル環境からストア公開まで</title>
      <link>https://okiyama.dev/posts/2023-07-16-expo-development-react-native-app/</link>
      <pubDate>Sun, 16 Jul 2023 16:52:54 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2023-07-16-expo-development-react-native-app/</guid>
      <description>&lt;p&gt;Expo で作ったアプリを App Store に公開しました。 (今のところ iOS のみ)&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://apps.apple.com/jp/app/ai-prompt-editor/id6448696132&#34; target=&#34;_blank&#34;&gt;https://apps.apple.com/jp/app/ai-prompt-editor/id6448696132&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;モバイルで OpenAI API の GPT を使うためのアプリです。テンプレートを穴埋め形式で入力することで、モバイルでも長文のプロンプトを作成しやすくします。&lt;/p&gt;
&lt;p&gt;システム構成は以下のようになっています。&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://okiyama.dev/images/expo-app.drawio.svg&#34;&gt;&lt;/p&gt;
&lt;p&gt;フロントエンドは TypeScript で書かれていて、 Expo を使った React Native アプリになっています。&lt;/p&gt;
&lt;p&gt;バックエンドは Go 言語で、 AWS Lambda で動いています。 OpenAI API は Server Sent Events に対応しているので、それをアプリに WebSocket で送ります。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://vercel.com/&#34; target=&#34;_blank&#34;&gt;Vercel&lt;/a&gt;
 はテンプレート文言を JSON ファイルとしてホストするために使用しています。&lt;/p&gt;
&lt;p&gt;デプロイは、アプリについては手動 (ローカルマシンから EAS Build をトリガー) で、バックエンドについては GitHub Actions で CDK を実行しています。&lt;/p&gt;
&lt;p&gt;リポジトリ:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/rokiyama/gpt-prompter-frontend&#34; target=&#34;_blank&#34;&gt;https://github.com/rokiyama/gpt-prompter-frontend&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/rokiyama/gpt-prompter-backend&#34; target=&#34;_blank&#34;&gt;https://github.com/rokiyama/gpt-prompter-backend&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/rokiyama/gpt-chat-misc&#34; target=&#34;_blank&#34;&gt;https://github.com/rokiyama/gpt-chat-misc&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;expo-での開発について&#34;&gt;Expo での開発について&lt;/h2&gt;
&lt;p&gt;アプリで使用しているライブラリのうち主なものは以下の通りです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://expo.dev/&#34; target=&#34;_blank&#34;&gt;Expo&lt;/a&gt;
 / &lt;a href=&#34;https://reactnative.dev/&#34; target=&#34;_blank&#34;&gt;React Native&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://redux-toolkit.js.org/&#34; target=&#34;_blank&#34;&gt;Redux Toolkit&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://tailwindcss.com/&#34; target=&#34;_blank&#34;&gt;Tailwind CSS&lt;/a&gt;
 / &lt;a href=&#34;https://github.com/vadimdemedes/tailwind-rn&#34; target=&#34;_blank&#34;&gt;tailwind-rn&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/FaridSafi/react-native-gifted-chat&#34; target=&#34;_blank&#34;&gt;Gifted Chat&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Expo を使うことで、ビルド環境をローカルに用意することなく開発からストア公開まで行うことができました。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Expo で作ったアプリを App Store に公開しました。 (今のところ iOS のみ)</p>
<p><a href="https://apps.apple.com/jp/app/ai-prompt-editor/id6448696132" target="_blank">https://apps.apple.com/jp/app/ai-prompt-editor/id6448696132</a>
</p>
<p>モバイルで OpenAI API の GPT を使うためのアプリです。テンプレートを穴埋め形式で入力することで、モバイルでも長文のプロンプトを作成しやすくします。</p>
<p>システム構成は以下のようになっています。</p>
<p><img loading="lazy" src="/images/expo-app.drawio.svg"></p>
<p>フロントエンドは TypeScript で書かれていて、 Expo を使った React Native アプリになっています。</p>
<p>バックエンドは Go 言語で、 AWS Lambda で動いています。 OpenAI API は Server Sent Events に対応しているので、それをアプリに WebSocket で送ります。</p>
<p><a href="https://vercel.com/" target="_blank">Vercel</a>
 はテンプレート文言を JSON ファイルとしてホストするために使用しています。</p>
<p>デプロイは、アプリについては手動 (ローカルマシンから EAS Build をトリガー) で、バックエンドについては GitHub Actions で CDK を実行しています。</p>
<p>リポジトリ:</p>
<ul>
<li><a href="https://github.com/rokiyama/gpt-prompter-frontend" target="_blank">https://github.com/rokiyama/gpt-prompter-frontend</a>
</li>
<li><a href="https://github.com/rokiyama/gpt-prompter-backend" target="_blank">https://github.com/rokiyama/gpt-prompter-backend</a>
</li>
<li><a href="https://github.com/rokiyama/gpt-chat-misc" target="_blank">https://github.com/rokiyama/gpt-chat-misc</a>
</li>
</ul>
<h2 id="expo-での開発について">Expo での開発について</h2>
<p>アプリで使用しているライブラリのうち主なものは以下の通りです。</p>
<ul>
<li><a href="https://expo.dev/" target="_blank">Expo</a>
 / <a href="https://reactnative.dev/" target="_blank">React Native</a>
</li>
<li><a href="https://redux-toolkit.js.org/" target="_blank">Redux Toolkit</a>
</li>
<li><a href="https://tailwindcss.com/" target="_blank">Tailwind CSS</a>
 / <a href="https://github.com/vadimdemedes/tailwind-rn" target="_blank">tailwind-rn</a>
</li>
<li><a href="https://github.com/FaridSafi/react-native-gifted-chat" target="_blank">Gifted Chat</a>
</li>
</ul>
<p>Expo を使うことで、ビルド環境をローカルに用意することなく開発からストア公開まで行うことができました。</p>
<p>Expo はライブラリや CLI ツールをはじめとした統合的なエコシステムになっていて、プロジェクトの生成を行う <code>create-expo-app</code> のほか、以下のようなアプリ・サービスを提供しています。</p>
<h3 id="expo-go">Expo Go</h3>
<p>開発中のプレビューは Expo Go というモバイルアプリを使用します。このアプリは開発用のクライアントで、 <code>npm run start</code> で起動したローカルのサーバに接続してアプリを動かせます。</p>
<pre class="mermaid">flowchart LR
  subgraph PC
    node[Expo dev server]

    subgraph Simulator
      expoGoS[Expo Go]
    end
  end

  subgraph Phone
    expoGo[Expo Go]
  end

  expoGo --> node
  expoGoS --> node
</pre>

<p>このアプリはよくできていて、普通の React アプリを Web ブラウザでプレビューしながら開発するのとそんなに変わらない体験です。 Expo Go を使っている限りは、 JS のライブラリ関連でエラーに悩まされることはありますが、ネイティブ関連のビルドエラーとは無縁です。</p>
<p>シミュレータだけでなく実機でも動作し、 Expo ユーザとしてログインしていれば LAN 内の自分のサーバを自動で見つけてくれるなど、非常に便利です。</p>
<p>ただし Expo Go でできることは用意された Expo SDK の範囲でできることに限られ、それを逸脱する実装をしたい場合はカスタムのネイティブコードを含めてビルドする必要があります。このような場合の対応方法も用意されており、 <a href="https://docs.expo.dev/develop/development-builds/introduction/" target="_blank">Development builds</a>
 という自分専用にカスタムされた Expo Go アプリのようなものをビルドする方法があります。</p>
<p>今回のアプリではカスタムビルドが必要になることはなかったのですが、一度試したところ後述の EAS Build を使えば特に難しいところもなくカスタムビルドを行うことができました。</p>
<h3 id="expo-application-services-eas">Expo Application Services (EAS)</h3>
<p>Expo 社が提供するクラウドサービスです。このサービスにネイティブアプリのビルドと、 App Store 申請に必要な証明書の管理などを任せることができ、ストア公開まで比較的簡単にできました。</p>
<p><a href="https://expo.dev/eas" target="_blank">https://expo.dev/eas</a>
</p>
<p>一部、 Apple のサイトで手動による設定が必要なところもありますが、ドキュメントに従っていれば特に大きなトラブルもなく進められました。</p>
<p>無料で使い始めることができ、無料枠だとビルド回数が制限される、キューの待ちが発生する、インスタンスの性能も低いなどの制限があるようです。今回の開発では無料枠のまま使えていますが、頻繁にアップデートしたいなどの場合は有料プランが必要になりそうです。</p>
<p><a href="https://expo.dev/pricing" target="_blank">https://expo.dev/pricing</a>
</p>
<p>ちょっと困ったのは、アプリバージョンとビルド番号の管理です。既に提出済みのビルド番号を再提出しようとするとエラーになるので、番号をインクリメントしてからビルドをトリガーする必要があるのですが、エラーになるのはビルドが完了した後の Apple に提出するタイミングです。なので、インクリメントを忘れるともう一度ビルドからやり直す必要が生じます。運用でカバーする方法を考えたいところです。</p>
<h2 id="参考にしたもの">参考にしたもの</h2>
<p>Udemy の講座が参考になりました。</p>
<ul>
<li><a href="https://www.udemy.com/course/react-native-expo-firebase/" target="_blank">https://www.udemy.com/course/react-native-expo-firebase/</a>
</li>
<li><a href="https://www.udemy.com/course/react-native-first-step/" target="_blank">https://www.udemy.com/course/react-native-first-step/</a>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>QMK Firmware でキーマップの変更とコンパイル、自作キーボードへの書き込みを行う</title>
      <link>https://okiyama.dev/posts/2023-02-23-qmk-firmware/</link>
      <pubDate>Thu, 23 Feb 2023 13:26:33 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2023-02-23-qmk-firmware/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://qmk.fm/ja/&#34; target=&#34;_blank&#34;&gt;QMK Firmware&lt;/a&gt;
 でキーマップの変更とコンパイル、自作キーボードへの書き込みを行う手順のメモです。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://shop.yushakobo.jp/products/834?variant=37665283571873&#34; target=&#34;_blank&#34;&gt;meishi2&lt;/a&gt;
 という自作キーボード初心者向けのキットで実施しました。&lt;/p&gt;
&lt;h2 id=&#34;インストールセットアップ&#34;&gt;インストール・セットアップ&lt;/h2&gt;
&lt;p&gt;macOS の場合、 Homebrew でインストールします。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;brew install qmk/qmk/qmk
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;インストール後にセットアップコマンドを実行します。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;qmk setup
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;セットアップ中に &lt;a href=&#34;https://github.com/qmk/qmk_firmware/&#34; target=&#34;_blank&#34;&gt;公式リポジトリ&lt;/a&gt;
 の git clone が行われます。事前に任意の場所に clone しておき、そのパスを指定することもできます。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;qmk setup -H ~/ghq/github.com/qmk/qmk_firmware
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;ファイル作成&#34;&gt;ファイル作成&lt;/h2&gt;
&lt;p&gt;QMK のリポジトリ &lt;code&gt;qmk_firmware&lt;/code&gt; のディレクトリに移動します。このリポジトリの &lt;code&gt;keyboards/&lt;/code&gt; ディレクトリに、様々なキーボードのキーマップ設定が収められているようです。&lt;/p&gt;
&lt;p&gt;meishi2 の場合は以下のディレクトリです。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;keyboards/biacco42/meishi2/keymaps/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;以下のコマンドを実行すると、このディレクトリにある &lt;code&gt;default&lt;/code&gt; をコピーして、新たなディレクトリが作成されます。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;qmk new-keymap -kb biacco42/meishi2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;実行すると、名前の入力を求めるプロンプトになります。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Keymap Name:
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;適当に &lt;code&gt;my_meishi2&lt;/code&gt; と名付けることにして、プロンプトに入力して enter を入力します。すると、次のように表示されます。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Ψ my_meishi2 keymap directory created in: /Users/okiyama/ghq/github.com/qmk/qmk_firmware/keyboards/biacco42/meishi2/keymaps/my_meishi2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Ψ Compile a firmware with your new keymap by typing:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        qmk compile -kb biacco42/meishi2 -km my_meishi2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;メッセージの通り、 &lt;code&gt;my_meishi2&lt;/code&gt; という名前のディレクトリが作成されます。作成されたファイルを編集し、任意のキーマップに変更します。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><a href="https://qmk.fm/ja/" target="_blank">QMK Firmware</a>
 でキーマップの変更とコンパイル、自作キーボードへの書き込みを行う手順のメモです。</p>
<p><a href="https://shop.yushakobo.jp/products/834?variant=37665283571873" target="_blank">meishi2</a>
 という自作キーボード初心者向けのキットで実施しました。</p>
<h2 id="インストールセットアップ">インストール・セットアップ</h2>
<p>macOS の場合、 Homebrew でインストールします。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>brew install qmk/qmk/qmk
</span></span></code></pre></div><p>インストール後にセットアップコマンドを実行します。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>qmk setup
</span></span></code></pre></div><p>セットアップ中に <a href="https://github.com/qmk/qmk_firmware/" target="_blank">公式リポジトリ</a>
 の git clone が行われます。事前に任意の場所に clone しておき、そのパスを指定することもできます。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>qmk setup -H ~/ghq/github.com/qmk/qmk_firmware
</span></span></code></pre></div><h2 id="ファイル作成">ファイル作成</h2>
<p>QMK のリポジトリ <code>qmk_firmware</code> のディレクトリに移動します。このリポジトリの <code>keyboards/</code> ディレクトリに、様々なキーボードのキーマップ設定が収められているようです。</p>
<p>meishi2 の場合は以下のディレクトリです。</p>
<p><code>keyboards/biacco42/meishi2/keymaps/</code></p>
<p>以下のコマンドを実行すると、このディレクトリにある <code>default</code> をコピーして、新たなディレクトリが作成されます。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>qmk new-keymap -kb biacco42/meishi2
</span></span></code></pre></div><p>実行すると、名前の入力を求めるプロンプトになります。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>Keymap Name:
</span></span></code></pre></div><p>適当に <code>my_meishi2</code> と名付けることにして、プロンプトに入力して enter を入力します。すると、次のように表示されます。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>Ψ my_meishi2 keymap directory created in: /Users/okiyama/ghq/github.com/qmk/qmk_firmware/keyboards/biacco42/meishi2/keymaps/my_meishi2
</span></span><span style="display:flex;"><span>Ψ Compile a firmware with your new keymap by typing:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        qmk compile -kb biacco42/meishi2 -km my_meishi2
</span></span></code></pre></div><p>メッセージの通り、 <code>my_meishi2</code> という名前のディレクトリが作成されます。作成されたファイルを編集し、任意のキーマップに変更します。</p>
<h2 id="コンパイル書き込み">コンパイル・書き込み</h2>
<p>以下のコマンドを実行すると、指定したキーマップでファームウェアがコンパイルされます。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>qmk compile -kb biacco42/meishi2 -km my_meishi2
</span></span></code></pre></div><p>コンパイルのログが表示されます。以下は成功時のログです。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>Ψ Compiling keymap with gmake --jobs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> biacco42/meishi2:my_meishi2
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>QMK Firmware 0.19.12
</span></span><span style="display:flex;"><span>Making biacco42/meishi2 with keymap my_meishi2
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>avr-gcc <span style="color:#f92672">(</span>Homebrew AVR GCC 8.5.0<span style="color:#f92672">)</span> 8.5.0
</span></span><span style="display:flex;"><span>Copyright <span style="color:#f92672">(</span>C<span style="color:#f92672">)</span> <span style="color:#ae81ff">2018</span> Free Software Foundation, Inc.
</span></span><span style="display:flex;"><span>This is free software; see the source <span style="color:#66d9ef">for</span> copying conditions.  There is NO
</span></span><span style="display:flex;"><span>warranty; not even <span style="color:#66d9ef">for</span> MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Size before:
</span></span><span style="display:flex;"><span>   text    data     bss     dec     hex filename
</span></span><span style="display:flex;"><span>      <span style="color:#ae81ff">0</span>   <span style="color:#ae81ff">20354</span>       <span style="color:#ae81ff">0</span>   <span style="color:#ae81ff">20354</span>    4f82 biacco42_meishi2_my_meishi2.hex
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Compiling: quantum/keymap_introspection.c                                                           <span style="color:#f92672">[</span>OK<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>Compiling: quantum/command.c                                                                        <span style="color:#f92672">[</span>OK<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>Linking: .build/biacco42_meishi2_my_meishi2.elf                                                     <span style="color:#f92672">[</span>OK<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>Creating load file <span style="color:#66d9ef">for</span> flashing: .build/biacco42_meishi2_my_meishi2.hex                             <span style="color:#f92672">[</span>OK<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>Copying biacco42_meishi2_my_meishi2.hex to qmk_firmware folder                                      <span style="color:#f92672">[</span>OK<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span>Checking file size of biacco42_meishi2_my_meishi2.hex                                               <span style="color:#f92672">[</span>OK<span style="color:#f92672">]</span>
</span></span><span style="display:flex;"><span> * The firmware size is fine - 20354/28672 <span style="color:#f92672">(</span>70%, <span style="color:#ae81ff">8318</span> bytes free<span style="color:#f92672">)</span>
</span></span></code></pre></div><p>コンパイルに成功したので、キーボードに書き込みます。以下のコマンドを実行します。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>qmk flash -kb biacco42/meishi2 -km my_meishi2
</span></span></code></pre></div><p>実行すると (このとき再びコンパイルが行われるようです)、以下のようにキーボードのリセット待ち状態になります。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>Ψ Compiling keymap with gmake --jobs<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> biacco42/meishi2:my_meishi2:flash
</span></span><span style="display:flex;"><span><span style="color:#75715e"># snip</span>
</span></span><span style="display:flex;"><span> * The firmware size is fine - 20354/28672 <span style="color:#f92672">(</span>70%, <span style="color:#ae81ff">8318</span> bytes free<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>Flashing <span style="color:#66d9ef">for</span> bootloader: caterina
</span></span><span style="display:flex;"><span>Waiting <span style="color:#66d9ef">for</span> USB serial port - reset your controller now <span style="color:#f92672">(</span>Ctrl+C to cancel<span style="color:#f92672">)</span>......
</span></span></code></pre></div><p>ここでまだキーボードを接続していなければ接続し、リセットスイッチを押します。するとキーボードが認識され、書き込みが行われる様子がログで流れます。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>Device /dev/tty.usbmodem101 has appeared; assuming it is the controller.
</span></span><span style="display:flex;"><span>Waiting <span style="color:#66d9ef">for</span> /dev/tty.usbmodem101 to become writable.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Connecting to programmer: .
</span></span><span style="display:flex;"><span>Found programmer: Id <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;CATERIN&#34;</span>; type <span style="color:#f92672">=</span> S
</span></span><span style="display:flex;"><span>    Software Version <span style="color:#f92672">=</span> 1.0; No Hardware Version given.
</span></span><span style="display:flex;"><span>Programmer supports auto addr increment.
</span></span><span style="display:flex;"><span>Programmer supports buffered memory access with buffersize<span style="color:#f92672">=</span><span style="color:#ae81ff">128</span> bytes.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Programmer supports the following devices:
</span></span><span style="display:flex;"><span>    Device code: 0x44
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>avrdude: AVR device initialized and ready to accept instructions
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Reading | <span style="color:#75715e">################################################## | 100% 0.00s</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>avrdude: Device signature <span style="color:#f92672">=</span> 0x1e9587 <span style="color:#f92672">(</span>probably m32u4<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>avrdude: NOTE: <span style="color:#e6db74">&#34;flash&#34;</span> memory has been specified, an erase cycle will be performed
</span></span><span style="display:flex;"><span>         To disable this feature, specify the -D option.
</span></span><span style="display:flex;"><span>avrdude: erasing chip
</span></span><span style="display:flex;"><span>avrdude: reading input file <span style="color:#e6db74">&#34;.build/biacco42_meishi2_my_meishi2.hex&#34;</span>
</span></span><span style="display:flex;"><span>avrdude: input file .build/biacco42_meishi2_my_meishi2.hex auto detected as Intel Hex
</span></span><span style="display:flex;"><span>avrdude: writing flash <span style="color:#f92672">(</span><span style="color:#ae81ff">20354</span> bytes<span style="color:#f92672">)</span>:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Writing | <span style="color:#75715e">################################################## | 100% 1.57s</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>avrdude: <span style="color:#ae81ff">20354</span> bytes of flash written
</span></span><span style="display:flex;"><span>avrdude: verifying flash memory against .build/biacco42_meishi2_my_meishi2.hex:
</span></span><span style="display:flex;"><span>avrdude: input file .build/biacco42_meishi2_my_meishi2.hex auto detected as Intel Hex
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Reading | <span style="color:#75715e">################################################## | 100% 0.17s</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>avrdude: <span style="color:#ae81ff">20354</span> bytes of flash verified
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>avrdude <span style="color:#66d9ef">done</span>.  Thank you.
</span></span></code></pre></div><p>上記のように <code>avrdude done</code> と表示されれば完了で、キーボードとして使える状態になります。</p>
<h2 id="補足">補足</h2>
<p>QMK Firmware を使ったファームウェア書き込みは最も基本的な方法の一つですが、他にもいくつかツールがあり、 2023 年現在は <a href="https://remap-keys.app/" target="_blank">Remap</a>
 が人気のようです。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Chrome 拡張 &#34;Bitbucket Canonical Url&#34; を作った</title>
      <link>https://okiyama.dev/posts/2022-11-28-chrome-extension-bitbucket-canonical-url/</link>
      <pubDate>Mon, 28 Nov 2022 18:09:15 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2022-11-28-chrome-extension-bitbucket-canonical-url/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://chrome.google.com/webstore/detail/bitbucket-canonical-url/kfckcleaglfgjkobhcbclfflhcllmnmm&#34; target=&#34;_blank&#34;&gt;Bitbucket canonical URL&lt;/a&gt;
 という Chrome 拡張を作りました。 Chrome ウェブストアでインストールできます。&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://okiyama.dev/images/bitbucket-canonical-url-usage.gif&#34;&gt;&lt;/p&gt;
&lt;p&gt;Bitbucket の PR を開いて URL が変に長いときに、アイコンをクリックすると短い URL に置き換わるという拡張です。&lt;/p&gt;
&lt;p&gt;ソースコード: &lt;a href=&#34;https://github.com/rokiyama/bitbucket-canonical-url&#34; target=&#34;_blank&#34;&gt;https://github.com/rokiyama/bitbucket-canonical-url&lt;/a&gt;
&lt;/p&gt;
&lt;h2 id=&#34;概要&#34;&gt;概要&lt;/h2&gt;
&lt;p&gt;Bitbucket の PR を使っていると URL が妙に長くなることがあります。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# 長い
https://bitbucket.org/foo/%7B840d6a13-5d55-4c1a-a86b-3372c3ceeef1%7D/pull-requests/123/branch-name#comment-123456789

↓

# 短くできる
https://bitbucket.org/foo/bar/pull-requests/123#comment-123456789
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;UUID のようなものが埋め込まれていて、リポジトリ名が判別できません。無駄にブランチ名も入っています。
どうやら JIRA から PR に飛んだ時などに発生するようです。&lt;/p&gt;
&lt;p&gt;この拡張をインストールしてツールバーのアイコンをクリックすると、短い URL に書き換えられます。&lt;/p&gt;
&lt;p&gt;Bitbucket と JIRA を併用していて、かつ URL の見映えを気にする人がターゲットユーザーでしょうか。&lt;/p&gt;
&lt;h2 id=&#34;クリックしたら書き換える方式にした理由&#34;&gt;クリックしたら書き換える方式にした理由&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://chrome.google.com/webstore/detail/amazon-url-shortener/bonkcfmjkpdnieejahndognlbogaikdg&#34; target=&#34;_blank&#34;&gt;Amazon URL Shortener&lt;/a&gt;

という拡張があり、これを参考に開発を始めました。&lt;/p&gt;
&lt;p&gt;最初はこれと同様、ページを開いた瞬間に URL を書き換えるようにしたかったのですが、
PR 内でコメントにジャンプしたりした際にも URL が長くなる現象が起きることがあったため、
任意のタイミングで短縮処理が行えるようにしています。&lt;/p&gt;
&lt;h2 id=&#34;backgroundts-と-contentts&#34;&gt;background.ts と content.ts&lt;/h2&gt;
&lt;p&gt;クリック時に行う処理は
&lt;a href=&#34;https://github.com/rokiyama/bitbucket-canonical-url/blob/main/src/background.ts&#34; target=&#34;_blank&#34;&gt;background.ts&lt;/a&gt;

で行っており、これは service worker として動作します。&lt;/p&gt;
&lt;p&gt;また URL が
&lt;code&gt;https://bitbucket.org/foo/%7B840d6a13-5d55-4c1a-a86b-3372c3ceeef1%7D/pull-requests/...&lt;/code&gt;
のような場合、リポジトリ名をどこかから取ってくる必要があり、これは DOM から取得する必要がありました。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><a href="https://chrome.google.com/webstore/detail/bitbucket-canonical-url/kfckcleaglfgjkobhcbclfflhcllmnmm" target="_blank">Bitbucket canonical URL</a>
 という Chrome 拡張を作りました。 Chrome ウェブストアでインストールできます。</p>
<p><img loading="lazy" src="/images/bitbucket-canonical-url-usage.gif"></p>
<p>Bitbucket の PR を開いて URL が変に長いときに、アイコンをクリックすると短い URL に置き換わるという拡張です。</p>
<p>ソースコード: <a href="https://github.com/rokiyama/bitbucket-canonical-url" target="_blank">https://github.com/rokiyama/bitbucket-canonical-url</a>
</p>
<h2 id="概要">概要</h2>
<p>Bitbucket の PR を使っていると URL が妙に長くなることがあります。</p>
<pre tabindex="0"><code># 長い
https://bitbucket.org/foo/%7B840d6a13-5d55-4c1a-a86b-3372c3ceeef1%7D/pull-requests/123/branch-name#comment-123456789

↓

# 短くできる
https://bitbucket.org/foo/bar/pull-requests/123#comment-123456789
</code></pre><p>UUID のようなものが埋め込まれていて、リポジトリ名が判別できません。無駄にブランチ名も入っています。
どうやら JIRA から PR に飛んだ時などに発生するようです。</p>
<p>この拡張をインストールしてツールバーのアイコンをクリックすると、短い URL に書き換えられます。</p>
<p>Bitbucket と JIRA を併用していて、かつ URL の見映えを気にする人がターゲットユーザーでしょうか。</p>
<h2 id="クリックしたら書き換える方式にした理由">クリックしたら書き換える方式にした理由</h2>
<p><a href="https://chrome.google.com/webstore/detail/amazon-url-shortener/bonkcfmjkpdnieejahndognlbogaikdg" target="_blank">Amazon URL Shortener</a>

という拡張があり、これを参考に開発を始めました。</p>
<p>最初はこれと同様、ページを開いた瞬間に URL を書き換えるようにしたかったのですが、
PR 内でコメントにジャンプしたりした際にも URL が長くなる現象が起きることがあったため、
任意のタイミングで短縮処理が行えるようにしています。</p>
<h2 id="backgroundts-と-contentts">background.ts と content.ts</h2>
<p>クリック時に行う処理は
<a href="https://github.com/rokiyama/bitbucket-canonical-url/blob/main/src/background.ts" target="_blank">background.ts</a>

で行っており、これは service worker として動作します。</p>
<p>また URL が
<code>https://bitbucket.org/foo/%7B840d6a13-5d55-4c1a-a86b-3372c3ceeef1%7D/pull-requests/...</code>
のような場合、リポジトリ名をどこかから取ってくる必要があり、これは DOM から取得する必要がありました。</p>
<p>DOM の取得は service worker では行えず、 content scripts で行う必要があります。
この処理は
<a href="https://github.com/rokiyama/bitbucket-canonical-url/blob/main/src/content.ts" target="_blank">content.ts</a>

で行っています。</p>
<p>それぞれのファイルの役割は <a href="https://github.com/rokiyama/bitbucket-canonical-url/blob/main/vite.config.ts" target="_blank">マニフェスト</a>
 で指定するようになっています。</p>
<h2 id="ストアでの公開">ストアでの公開</h2>
<p>開発者登録料として $5 支払いました。一度払えばそれ以降の支払いは不要なようです。</p>
<p>11/19(土) に審査を申請して、 11/21(月) に公開されました。
Eメールで通知などはしてくれないようで、いつの間にか公開されていたという感じでした。</p>
<h2 id="今後の課題">今後の課題</h2>
<ul>
<li>バージョン管理</li>
<li>GitHub Actions でテスト、リリース</li>
</ul>
<h2 id="参考にした記事">参考にした記事</h2>
<p>拡張の作り方はこちらの記事と、この筆者の方の作られた拡張を参考にさせていただきました。</p>
<ul>
<li><a href="https://r7kamura.com/articles/2022-05-18-learn-chrome-extention-in-y-minutes" target="_blank">Chrome拡張をつくるチュートリアル</a>
</li>
<li><a href="https://chrome.google.com/webstore/detail/amazon-url-shortener/bonkcfmjkpdnieejahndognlbogaikdg" target="_blank">Amazon URL Shortener - Chrome ウェブストア</a>
</li>
</ul>
<p>こちらも参考にさせていただきました。ストア公開の手順までが簡潔にまとめられていてわかりやすかったです。</p>
<ul>
<li><a href="https://zenn.dev/fjsh/articles/chrome-extension-with-vite" target="_blank">Chrome拡張をVite+TypeScript+Reactで作る</a>
</li>
</ul>
<p>こちらは開発に必要な概念が詳しく説明されていて参考になりました。内容はマニフェスト V2 に基づいていますが、基本的な考え方は V3 でも大きくは変わらないようです。</p>
<ul>
<li><a href="https://qiita.com/sakaimo/items/416f36db1aa982d8d00c" target="_blank">Chrome Extension の作り方 (その1: 3つの世界) - Qiita</a>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>tmux で文字列をコピーした後にコピーモードを抜けないようにする</title>
      <link>https://okiyama.dev/posts/2022-11-09-tmux-copy-text-without-leaving-copy-mode/</link>
      <pubDate>Wed, 09 Nov 2022 08:06:00 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2022-11-09-tmux-copy-text-without-leaving-copy-mode/</guid>
      <description>&lt;p&gt;&lt;code&gt;copy-pipe&lt;/code&gt; を使うようキーバインドを設定します。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-properties&#34; data-lang=&#34;properties&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;bind-key&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;-T copy-mode-vi Enter send-keys -X copy-pipe&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;プラグイン
&lt;a href=&#34;https://github.com/tmux-plugins/tmux-yank&#34; target=&#34;_blank&#34;&gt;tmux-yank&lt;/a&gt;

を使用している場合は以下を設定します。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-properties&#34; data-lang=&#34;properties&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;set&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;-g @yank_action &amp;#39;copy-pipe&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;tmux 3.3a, tmux-yank 2.3.0 で確認しました。&lt;/p&gt;
&lt;h2 id=&#34;キーバインドを確認する&#34;&gt;キーバインドを確認する&lt;/h2&gt;
&lt;p&gt;コマンド &lt;code&gt;tmux list-key&lt;/code&gt; で、現在のキーバインドが確認できます。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# デフォルト状態の tmux キーバインド一覧&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;❯ tmux -f /dev/null list-key | rg &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;copy-pipe&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bind-key    -T copy-mode    C-k                  send-keys -X copy-pipe-end-of-line-and-cancel
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bind-key    -T copy-mode    C-w                  send-keys -X copy-pipe-and-cancel
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bind-key    -T copy-mode    MouseDragEnd1Pane    send-keys -X copy-pipe-and-cancel
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bind-key    -T copy-mode    DoubleClick1Pane     &lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt;-pane &lt;span style=&#34;color:#ae81ff&#34;&gt;\;&lt;/span&gt; send-keys -X &lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt;-word &lt;span style=&#34;color:#ae81ff&#34;&gt;\;&lt;/span&gt; run-shell -d 0.3 &lt;span style=&#34;color:#ae81ff&#34;&gt;\;&lt;/span&gt; send-keys -X copy-pipe-and-cancel
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bind-key    -T copy-mode    TripleClick1Pane     &lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt;-pane &lt;span style=&#34;color:#ae81ff&#34;&gt;\;&lt;/span&gt; send-keys -X &lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt;-line &lt;span style=&#34;color:#ae81ff&#34;&gt;\;&lt;/span&gt; run-shell -d 0.3 &lt;span style=&#34;color:#ae81ff&#34;&gt;\;&lt;/span&gt; send-keys -X copy-pipe-and-cancel
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bind-key    -T copy-mode    M-w                  send-keys -X copy-pipe-and-cancel
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bind-key    -T copy-mode-vi C-j                  send-keys -X copy-pipe-and-cancel
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bind-key    -T copy-mode-vi Enter                send-keys -X copy-pipe-and-cancel
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bind-key    -T copy-mode-vi D                    send-keys -X copy-pipe-end-of-line-and-cancel
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bind-key    -T copy-mode-vi MouseDragEnd1Pane    send-keys -X copy-pipe-and-cancel
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bind-key    -T copy-mode-vi DoubleClick1Pane     &lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt;-pane &lt;span style=&#34;color:#ae81ff&#34;&gt;\;&lt;/span&gt; send-keys -X &lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt;-word &lt;span style=&#34;color:#ae81ff&#34;&gt;\;&lt;/span&gt; run-shell -d 0.3 &lt;span style=&#34;color:#ae81ff&#34;&gt;\;&lt;/span&gt; send-keys -X copy-pipe-and-cancel
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bind-key    -T copy-mode-vi TripleClick1Pane     &lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt;-pane &lt;span style=&#34;color:#ae81ff&#34;&gt;\;&lt;/span&gt; send-keys -X &lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt;-line &lt;span style=&#34;color:#ae81ff&#34;&gt;\;&lt;/span&gt; run-shell -d 0.3 &lt;span style=&#34;color:#ae81ff&#34;&gt;\;&lt;/span&gt; send-keys -X copy-pipe-and-cancel
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bind-key    -T root         DoubleClick1Pane     &lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt;-pane -t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;-shell -F &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;#{||:#{pane_in_mode},#{mouse_any_flag}}&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; send-keys -M &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; copy-mode -H ; send-keys -X &lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt;-word ; run-shell -d 0.3 ; send-keys -X copy-pipe-and-cancel &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bind-key    -T root         TripleClick1Pane     &lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt;-pane -t &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;-shell -F &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;#{||:#{pane_in_mode},#{mouse_any_flag}}&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; send-keys -M &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt; copy-mode -H ; send-keys -X &lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt;-line ; run-shell -d 0.3 ; send-keys -X copy-pipe-and-cancel &lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;vi モードのデフォルトでは Enter にコピー機能が割り当てられていて、コマンドは &lt;code&gt;copy-pipe-and-cancel&lt;/code&gt; となっています。
キーバインドを上書きして &lt;code&gt;copy-pipe&lt;/code&gt; に変更すれば、コピーモードから抜けないようになります。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><code>copy-pipe</code> を使うようキーバインドを設定します。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-properties" data-lang="properties"><span style="display:flex;"><span><span style="color:#a6e22e">bind-key</span> <span style="color:#e6db74">-T copy-mode-vi Enter send-keys -X copy-pipe</span>
</span></span></code></pre></div><p>プラグイン
<a href="https://github.com/tmux-plugins/tmux-yank" target="_blank">tmux-yank</a>

を使用している場合は以下を設定します。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-properties" data-lang="properties"><span style="display:flex;"><span><span style="color:#a6e22e">set</span> <span style="color:#e6db74">-g @yank_action &#39;copy-pipe&#39;</span>
</span></span></code></pre></div><p>tmux 3.3a, tmux-yank 2.3.0 で確認しました。</p>
<h2 id="キーバインドを確認する">キーバインドを確認する</h2>
<p>コマンド <code>tmux list-key</code> で、現在のキーバインドが確認できます。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span><span style="color:#75715e"># デフォルト状態の tmux キーバインド一覧</span>
</span></span><span style="display:flex;"><span>❯ tmux -f /dev/null list-key | rg <span style="color:#e6db74">&#39;copy-pipe&#39;</span>
</span></span><span style="display:flex;"><span>bind-key    -T copy-mode    C-k                  send-keys -X copy-pipe-end-of-line-and-cancel
</span></span><span style="display:flex;"><span>bind-key    -T copy-mode    C-w                  send-keys -X copy-pipe-and-cancel
</span></span><span style="display:flex;"><span>bind-key    -T copy-mode    MouseDragEnd1Pane    send-keys -X copy-pipe-and-cancel
</span></span><span style="display:flex;"><span>bind-key    -T copy-mode    DoubleClick1Pane     <span style="color:#66d9ef">select</span>-pane <span style="color:#ae81ff">\;</span> send-keys -X <span style="color:#66d9ef">select</span>-word <span style="color:#ae81ff">\;</span> run-shell -d 0.3 <span style="color:#ae81ff">\;</span> send-keys -X copy-pipe-and-cancel
</span></span><span style="display:flex;"><span>bind-key    -T copy-mode    TripleClick1Pane     <span style="color:#66d9ef">select</span>-pane <span style="color:#ae81ff">\;</span> send-keys -X <span style="color:#66d9ef">select</span>-line <span style="color:#ae81ff">\;</span> run-shell -d 0.3 <span style="color:#ae81ff">\;</span> send-keys -X copy-pipe-and-cancel
</span></span><span style="display:flex;"><span>bind-key    -T copy-mode    M-w                  send-keys -X copy-pipe-and-cancel
</span></span><span style="display:flex;"><span>bind-key    -T copy-mode-vi C-j                  send-keys -X copy-pipe-and-cancel
</span></span><span style="display:flex;"><span>bind-key    -T copy-mode-vi Enter                send-keys -X copy-pipe-and-cancel
</span></span><span style="display:flex;"><span>bind-key    -T copy-mode-vi D                    send-keys -X copy-pipe-end-of-line-and-cancel
</span></span><span style="display:flex;"><span>bind-key    -T copy-mode-vi MouseDragEnd1Pane    send-keys -X copy-pipe-and-cancel
</span></span><span style="display:flex;"><span>bind-key    -T copy-mode-vi DoubleClick1Pane     <span style="color:#66d9ef">select</span>-pane <span style="color:#ae81ff">\;</span> send-keys -X <span style="color:#66d9ef">select</span>-word <span style="color:#ae81ff">\;</span> run-shell -d 0.3 <span style="color:#ae81ff">\;</span> send-keys -X copy-pipe-and-cancel
</span></span><span style="display:flex;"><span>bind-key    -T copy-mode-vi TripleClick1Pane     <span style="color:#66d9ef">select</span>-pane <span style="color:#ae81ff">\;</span> send-keys -X <span style="color:#66d9ef">select</span>-line <span style="color:#ae81ff">\;</span> run-shell -d 0.3 <span style="color:#ae81ff">\;</span> send-keys -X copy-pipe-and-cancel
</span></span><span style="display:flex;"><span>bind-key    -T root         DoubleClick1Pane     <span style="color:#66d9ef">select</span>-pane -t <span style="color:#f92672">=</span> <span style="color:#ae81ff">\;</span> <span style="color:#66d9ef">if</span>-shell -F <span style="color:#e6db74">&#34;#{||:#{pane_in_mode},#{mouse_any_flag}}&#34;</span> <span style="color:#f92672">{</span> send-keys -M <span style="color:#f92672">}</span> <span style="color:#f92672">{</span> copy-mode -H ; send-keys -X <span style="color:#66d9ef">select</span>-word ; run-shell -d 0.3 ; send-keys -X copy-pipe-and-cancel <span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>bind-key    -T root         TripleClick1Pane     <span style="color:#66d9ef">select</span>-pane -t <span style="color:#f92672">=</span> <span style="color:#ae81ff">\;</span> <span style="color:#66d9ef">if</span>-shell -F <span style="color:#e6db74">&#34;#{||:#{pane_in_mode},#{mouse_any_flag}}&#34;</span> <span style="color:#f92672">{</span> send-keys -M <span style="color:#f92672">}</span> <span style="color:#f92672">{</span> copy-mode -H ; send-keys -X <span style="color:#66d9ef">select</span>-line ; run-shell -d 0.3 ; send-keys -X copy-pipe-and-cancel <span style="color:#f92672">}</span>
</span></span></code></pre></div><p>vi モードのデフォルトでは Enter にコピー機能が割り当てられていて、コマンドは <code>copy-pipe-and-cancel</code> となっています。
キーバインドを上書きして <code>copy-pipe</code> に変更すれば、コピーモードから抜けないようになります。</p>
<h2 id="プラグイン-tmux-yank-について">プラグイン tmux-yank について</h2>
<p>コピー機能は OS 特有の対応が必要な場合があります。</p>
<p>現在は不要になりましたが、 tmux 2.6 以前は macOS では <code>reattach-to-user-namespace</code> が必要で、
これを使うようなキーバインドを設定する必要がありました。
Linux では <code>xsel</code> や <code>xclip</code> を使う、 WSL では <code>clip.exe</code> を使うなど OS によって異なります。</p>
<p><a href="https://github.com/tmux-plugins/tmux-yank" target="_blank">tmux-yank</a>

はこの問題にまとめて対処するプラグインです。
インストールすると、使用している環境に合わせたコマンドでいくつかのキーバインドが追加されます。</p>
<p>このプラグインにも <code>copy-pipe</code> に変更する方法が用意されていて、記事冒頭に記載の方法で変更できます。</p>
<h2 id="参考にした記事">参考にした記事</h2>
<ul>
<li><a href="https://udomomo.hatenablog.com/entry/2020/01/12/235955" target="_blank">tmux 3.0でコピーモードの設定を行う - りんごとバナナとエンジニア</a>
</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>golang の標準パッケージ testing/quick は非推奨？</title>
      <link>https://okiyama.dev/posts/2022-10-19-golang-package-testing-quick/</link>
      <pubDate>Wed, 19 Oct 2022 19:29:17 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2022-10-19-golang-package-testing-quick/</guid>
      <description>&lt;p&gt;結論: 非推奨ではありませんが今後積極的に使うものではなく、
gopter, rapid など別のライブラリを検討するのが良さそうです。&lt;/p&gt;
&lt;h2 id=&#34;frozen-だが-deprecated-とは書かれていない&#34;&gt;frozen だが deprecated とは書かれていない&lt;/h2&gt;
&lt;p&gt;2016 年の時点で「このパッケージはフリーズし、今後修正や機能追加はしない」と宣言されています。&lt;/p&gt;
&lt;p&gt;ドキュメントには以下のように書かれていて&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The testing/quick package is frozen and is not accepting new features.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://pkg.go.dev/testing/quick&#34; target=&#34;_blank&#34;&gt;https://pkg.go.dev/testing/quick&lt;/a&gt;
&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/golang/go/blame/19309779ac5e2f5a2fd3cbb34421dafb2855ac21/src/testing/quick/quick.go&#34; target=&#34;_blank&#34;&gt;ソースの blame&lt;/a&gt;

を見ると 2016/10 のコミットで追加されたようです。関連 issue は &lt;a href=&#34;https://github.com/golang/go/issues/15557&#34; target=&#34;_blank&#34;&gt;golang/go#15557&lt;/a&gt;
 です。&lt;/p&gt;
&lt;p&gt;その後 2017 年に出された &lt;a href=&#34;https://github.com/golang/go/issues/23135&#34; target=&#34;_blank&#34;&gt;機能追加プロポーザルの issue&lt;/a&gt;
 を読むと、
サードパーティのライブラリの利用が示唆されるなど、開発の方針がわかります。&lt;/p&gt;
&lt;p&gt;比較のため他のパッケージを見ると、 2020 年に廃止となった &lt;a href=&#34;https://pkg.go.dev/golang.org/x/lint&#34; target=&#34;_blank&#34;&gt;x/lint&lt;/a&gt;
 は
&lt;em&gt;deprecated and frozen&lt;/em&gt; と書かれています&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;。&lt;/p&gt;
&lt;h2 id=&#34;サードパーティのライブラリはどんなものがある&#34;&gt;サードパーティのライブラリはどんなものがある？&lt;/h2&gt;
&lt;p&gt;testing/quick のようなランダム値によるテスト手法は Property based testing と呼ばれています。&lt;/p&gt;
&lt;p&gt;golang で Property based testing の実践をサポートするライブラリとしては
&lt;a href=&#34;https://github.com/leanovate/gopter&#34; target=&#34;_blank&#34;&gt;leanovate/gopter&lt;/a&gt;
 と
&lt;a href=&#34;https://github.com/flyingmutant/rapid&#34; target=&#34;_blank&#34;&gt;flyingmutant/rapid&lt;/a&gt;
 が有名なようです。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://star-history.com/#leanovate/gopter&amp;amp;flyingmutant/rapid&amp;amp;Date&#34; target=&#34;_blank&#34;&gt;&lt;img alt=&#34;GitHub Star History&#34; loading=&#34;lazy&#34; src=&#34;https://okiyama.dev/images/star-history-20221019.png&#34;&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;h2 id=&#34;参考記事&#34;&gt;参考記事&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://qiita.com/rerorero/items/568e227da3939dbf9532&#34; target=&#34;_blank&#34;&gt;gopterを使ってGoでProperty Based Testingする - Qiita&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://kazchimo.com/2021/03/30/go-pbt-testing/&#34; target=&#34;_blank&#34;&gt;Goにproperty based testingを布教したい - These Walls&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;ただし x 配下のパッケージは法則が異なるかもしれない&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>結論: 非推奨ではありませんが今後積極的に使うものではなく、
gopter, rapid など別のライブラリを検討するのが良さそうです。</p>
<h2 id="frozen-だが-deprecated-とは書かれていない">frozen だが deprecated とは書かれていない</h2>
<p>2016 年の時点で「このパッケージはフリーズし、今後修正や機能追加はしない」と宣言されています。</p>
<p>ドキュメントには以下のように書かれていて</p>
<blockquote>
<p>The testing/quick package is frozen and is not accepting new features.</p>
<p><a href="https://pkg.go.dev/testing/quick" target="_blank">https://pkg.go.dev/testing/quick</a>
</p></blockquote>
<p><a href="https://github.com/golang/go/blame/19309779ac5e2f5a2fd3cbb34421dafb2855ac21/src/testing/quick/quick.go" target="_blank">ソースの blame</a>

を見ると 2016/10 のコミットで追加されたようです。関連 issue は <a href="https://github.com/golang/go/issues/15557" target="_blank">golang/go#15557</a>
 です。</p>
<p>その後 2017 年に出された <a href="https://github.com/golang/go/issues/23135" target="_blank">機能追加プロポーザルの issue</a>
 を読むと、
サードパーティのライブラリの利用が示唆されるなど、開発の方針がわかります。</p>
<p>比較のため他のパッケージを見ると、 2020 年に廃止となった <a href="https://pkg.go.dev/golang.org/x/lint" target="_blank">x/lint</a>
 は
<em>deprecated and frozen</em> と書かれています<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>。</p>
<h2 id="サードパーティのライブラリはどんなものがある">サードパーティのライブラリはどんなものがある？</h2>
<p>testing/quick のようなランダム値によるテスト手法は Property based testing と呼ばれています。</p>
<p>golang で Property based testing の実践をサポートするライブラリとしては
<a href="https://github.com/leanovate/gopter" target="_blank">leanovate/gopter</a>
 と
<a href="https://github.com/flyingmutant/rapid" target="_blank">flyingmutant/rapid</a>
 が有名なようです。</p>
<p><a href="https://star-history.com/#leanovate/gopter&amp;flyingmutant/rapid&amp;Date" target="_blank"><img alt="GitHub Star History" loading="lazy" src="/images/star-history-20221019.png"></a>
</p>
<h2 id="参考記事">参考記事</h2>
<ul>
<li><a href="https://qiita.com/rerorero/items/568e227da3939dbf9532" target="_blank">gopterを使ってGoでProperty Based Testingする - Qiita</a>
</li>
<li><a href="https://kazchimo.com/2021/03/30/go-pbt-testing/" target="_blank">Goにproperty based testingを布教したい - These Walls</a>
</li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>ただし x 配下のパッケージは法則が異なるかもしれない&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>Node.js ワンライナーの基本</title>
      <link>https://okiyama.dev/posts/2022-08-29-one-liner-nodejs/</link>
      <pubDate>Mon, 29 Aug 2022 14:29:34 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2022-08-29-one-liner-nodejs/</guid>
      <description>&lt;p&gt;node コマンドでワンライナーを書くための基本的な知識をまとめました。&lt;/p&gt;
&lt;p&gt;参照: &lt;a href=&#34;https://nodejs.org/dist/latest-v16.x/docs/api/cli.html&#34; target=&#34;_blank&#34;&gt;https://nodejs.org/dist/latest-v16.x/docs/api/cli.html&lt;/a&gt;
&lt;/p&gt;
&lt;h2 id=&#34;node-コマンドのオプション&#34;&gt;node コマンドのオプション&lt;/h2&gt;
&lt;p&gt;使うオプションは基本的にこの 3 つです。おおむね &lt;code&gt;-p&lt;/code&gt; と &lt;code&gt;-r&lt;/code&gt; でなんとかなりそうです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-e, --eval &amp;quot;script”&lt;/code&gt; &amp;hellip; スクリプトを実行&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-p, --print &amp;quot;script&amp;quot;&lt;/code&gt; &amp;hellip; スクリプトを実行し、結果を標準出力に渡す&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-r, --require module&lt;/code&gt; &amp;hellip; モジュールをロード&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;例-標準入力を文字列置換する&#34;&gt;例: 標準入力を文字列置換する&lt;/h2&gt;
&lt;p&gt;標準入力を受け取り、先頭の空白を除き、標準出力に渡す例です。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cat FILE.txt | node -r fs -p &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;fs.readFileSync(0, &amp;#34;utf-8&amp;#34;).replaceAll(/^\s+/gm, &amp;#34;&amp;#34;)&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;標準入力だけでなく、ファイル名も指定できます。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;node -r fs -p &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;fs.readFileSync(&amp;#34;FILE.txt&amp;#34;, &amp;#34;utf-8&amp;#34;).replaceAll(/^\s+/gm, &amp;#34;&amp;#34;)&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ポイントは以下の通りです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-r fs&lt;/code&gt; &amp;hellip; 標準入力を読むのに fs モジュールを使います。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fs.readFileSync(0, &amp;quot;utf-8&amp;quot;)&lt;/code&gt; &amp;hellip; 第一引数に &lt;code&gt;0&lt;/code&gt; を渡すと標準入力を読むことができます&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;。第二引数に encoding を指定すると &lt;code&gt;string&lt;/code&gt; が得られます (指定しないと &lt;code&gt;Buffer&lt;/code&gt; 型になります)。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;replaceAll&lt;/code&gt; に正規表現を指定する場合は &lt;code&gt;g&lt;/code&gt; フラグが必須です&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;。&lt;/li&gt;
&lt;li&gt;正規表現フラグ &lt;code&gt;m&lt;/code&gt; で各行にマッチするようになります。ここでは &lt;code&gt;^\s+&lt;/code&gt; を各行の行頭にマッチさせるため指定しています&lt;sup id=&#34;fnref:3&#34;&gt;&lt;a href=&#34;#fn:3&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;3&lt;/a&gt;&lt;/sup&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;注意-オプションの順序&#34;&gt;注意: オプションの順序&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;node -r fs -p &amp;quot;script&amp;quot;&lt;/code&gt; OK&lt;/li&gt;
&lt;li&gt;&lt;code&gt;node -p &amp;quot;script&amp;quot; -r fs&lt;/code&gt; OK&lt;/li&gt;
&lt;li&gt;&lt;code&gt;node -p -r fs &amp;quot;script&amp;quot;&lt;/code&gt; これは失敗します。 &lt;code&gt;&amp;quot;script&amp;quot;&lt;/code&gt; が &lt;code&gt;-p&lt;/code&gt; オプションの引数であるためだと思われます。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;&lt;code&gt;0&lt;/code&gt; はファイルディスクリプタで、標準入力を表します。参照: &lt;a href=&#34;https://nodejs.org/dist/latest-v16.x/docs/api/fs.html#fsreadfilesyncpath-options&#34; target=&#34;_blank&#34;&gt;fs.readFileSync(path[, options])&lt;/a&gt;
&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>node コマンドでワンライナーを書くための基本的な知識をまとめました。</p>
<p>参照: <a href="https://nodejs.org/dist/latest-v16.x/docs/api/cli.html" target="_blank">https://nodejs.org/dist/latest-v16.x/docs/api/cli.html</a>
</p>
<h2 id="node-コマンドのオプション">node コマンドのオプション</h2>
<p>使うオプションは基本的にこの 3 つです。おおむね <code>-p</code> と <code>-r</code> でなんとかなりそうです。</p>
<ul>
<li><code>-e, --eval &quot;script”</code> &hellip; スクリプトを実行</li>
<li><code>-p, --print &quot;script&quot;</code> &hellip; スクリプトを実行し、結果を標準出力に渡す</li>
<li><code>-r, --require module</code> &hellip; モジュールをロード</li>
</ul>
<h2 id="例-標準入力を文字列置換する">例: 標準入力を文字列置換する</h2>
<p>標準入力を受け取り、先頭の空白を除き、標準出力に渡す例です。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>cat FILE.txt | node -r fs -p <span style="color:#e6db74">&#39;fs.readFileSync(0, &#34;utf-8&#34;).replaceAll(/^\s+/gm, &#34;&#34;)&#39;</span>
</span></span></code></pre></div><p>標準入力だけでなく、ファイル名も指定できます。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>node -r fs -p <span style="color:#e6db74">&#39;fs.readFileSync(&#34;FILE.txt&#34;, &#34;utf-8&#34;).replaceAll(/^\s+/gm, &#34;&#34;)&#39;</span>
</span></span></code></pre></div><p>ポイントは以下の通りです。</p>
<ul>
<li><code>-r fs</code> &hellip; 標準入力を読むのに fs モジュールを使います。</li>
<li><code>fs.readFileSync(0, &quot;utf-8&quot;)</code> &hellip; 第一引数に <code>0</code> を渡すと標準入力を読むことができます<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>。第二引数に encoding を指定すると <code>string</code> が得られます (指定しないと <code>Buffer</code> 型になります)。</li>
<li><code>replaceAll</code> に正規表現を指定する場合は <code>g</code> フラグが必須です<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>。</li>
<li>正規表現フラグ <code>m</code> で各行にマッチするようになります。ここでは <code>^\s+</code> を各行の行頭にマッチさせるため指定しています<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>。</li>
</ul>
<h2 id="注意-オプションの順序">注意: オプションの順序</h2>
<ul>
<li><code>node -r fs -p &quot;script&quot;</code> OK</li>
<li><code>node -p &quot;script&quot; -r fs</code> OK</li>
<li><code>node -p -r fs &quot;script&quot;</code> これは失敗します。 <code>&quot;script&quot;</code> が <code>-p</code> オプションの引数であるためだと思われます。</li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><code>0</code> はファイルディスクリプタで、標準入力を表します。参照: <a href="https://nodejs.org/dist/latest-v16.x/docs/api/fs.html#fsreadfilesyncpath-options" target="_blank">fs.readFileSync(path[, options])</a>
&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll" target="_blank">String.prototype.replaceAll()
</a>
&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions#%E3%83%95%E3%83%A9%E3%82%B0%E3%82%92%E7%94%A8%E3%81%84%E3%81%9F%E9%AB%98%E5%BA%A6%E3%81%AA%E6%A4%9C%E7%B4%A2" target="_blank">フラグを用いた高度な検索 - 正規表現 - JavaScript | MDN</a>
&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>ショートカットアプリで「現在地」を if の条件にする</title>
      <link>https://okiyama.dev/posts/2022-08-27-shortcuts-app-gps-condition/</link>
      <pubDate>Sat, 27 Aug 2022 15:28:53 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2022-08-27-shortcuts-app-gps-condition/</guid>
      <description>&lt;p&gt;iOS や macOS のショートカットアプリ (Shortcuts.app) で現在地を取得し、住所から文字列を検索して if の条件にしたい場合、「一致するテキスト」を使う。&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://okiyama.dev/images/text-match.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;これを使って、上から順に「現在地取得」→「一致するテキスト」→「if」と並べ、条件は「任意の値」にする。&lt;/p&gt;
&lt;p&gt;以下は、現在地が特定の住所ならば &lt;a href=&#34;https://jp.candyhouse.co/&#34; target=&#34;_blank&#34;&gt;Sesame&lt;/a&gt;
 を実行、そうでなければ確認ダイアログを出すという例。&lt;/p&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://okiyama.dev/images/shortcuts-gps.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;条件の「任意の値」とは &lt;code&gt;not empty&lt;/code&gt; という意味らしい。擬似コードで表すとこのようなイメージ:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;position &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;getPosition&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;matched &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; position.&lt;span style=&#34;color:#a6e22e&#34;&gt;find&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;東京都千代田区千代田1-1&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (matched &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 以下略
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
      <content:encoded><![CDATA[<p>iOS や macOS のショートカットアプリ (Shortcuts.app) で現在地を取得し、住所から文字列を検索して if の条件にしたい場合、「一致するテキスト」を使う。</p>
<p><img loading="lazy" src="/images/text-match.png"></p>
<p>これを使って、上から順に「現在地取得」→「一致するテキスト」→「if」と並べ、条件は「任意の値」にする。</p>
<p>以下は、現在地が特定の住所ならば <a href="https://jp.candyhouse.co/" target="_blank">Sesame</a>
 を実行、そうでなければ確認ダイアログを出すという例。</p>
<p><img loading="lazy" src="/images/shortcuts-gps.png"></p>
<p>条件の「任意の値」とは <code>not empty</code> という意味らしい。擬似コードで表すとこのようなイメージ:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-c" data-lang="c"><span style="display:flex;"><span>position <span style="color:#f92672">=</span> <span style="color:#a6e22e">getPosition</span>()
</span></span><span style="display:flex;"><span>matched <span style="color:#f92672">=</span> position.<span style="color:#a6e22e">find</span>(<span style="color:#e6db74">&#34;東京都千代田区千代田1-1&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> (matched <span style="color:#f92672">!=</span> <span style="color:#e6db74">&#34;&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#75715e">// 以下略
</span></span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>Chrome のタブグループを活用するキーボードショートカットのカスタマイズ (macOS 向け)</title>
      <link>https://okiyama.dev/posts/2022-08-27-chrome-tabgroup-keyboard/</link>
      <pubDate>Sat, 27 Aug 2022 15:04:55 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2022-08-27-chrome-tabgroup-keyboard/</guid>
      <description>&lt;p&gt;以下のように操作できるようにする。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Cmd + Option + G&lt;/code&gt; &amp;hellip; タブグループ化 / タブグループ解除&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Cmd + T&lt;/code&gt; &amp;hellip; タブを右隣に開く
&lt;ul&gt;
&lt;li&gt;本来の「新規タブ」と異なり、現在タブがグループの場合はグループにタブが追加される。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Cmd + Option + T&lt;/code&gt; &amp;hellip; 本来の「新規タブ」&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;macOS では、システム設定でキーボードショートカットを任意のメニュー項目に割り当てることができる。以下のように設定することで上記の操作が実現可能&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img alt=&#34;macOS のシステム設定 → キーボードショートカットの設定&#34; loading=&#34;lazy&#34; src=&#34;https://okiyama.dev/images/chrome-tabgroup-keyboard.png&#34;&gt;&lt;/p&gt;
&lt;p&gt;タブグループとは関係ないが、このスクショでは以下の設定もある。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Cmd + D&lt;/code&gt; &amp;hellip; タブ複製&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Cmd + Option + D&lt;/code&gt; &amp;hellip; ブックマークする&lt;/li&gt;
&lt;li&gt;All Applications: ウィンドウ最小化をデフォルトの &lt;code&gt;Cmd + M&lt;/code&gt; から &lt;code&gt;Cmd + Option + M&lt;/code&gt; に変更&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Windows の場合、このようなカスタマイズは &lt;a href=&#34;https://appletllc.com/%e3%82%bd%e3%83%95%e3%83%88%e3%82%a6%e3%82%a7%e3%82%a2/&#34; target=&#34;_blank&#34;&gt;のどか&lt;/a&gt;
 や &lt;a href=&#34;https://www.autohotkey.com/&#34; target=&#34;_blank&#34;&gt;AutoHotKey&lt;/a&gt;
 のようなツールで実現できると思われる。 (未検証)&lt;/p&gt;
&lt;div class=&#34;footnotes&#34; role=&#34;doc-endnotes&#34;&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id=&#34;fn:1&#34;&gt;
&lt;p&gt;この例では OS を英語設定にしてあるためメニュー名も英語となっている。 OS が日本語設定の場合は日本語でメニュー名を指定する必要がある。&amp;#160;&lt;a href=&#34;#fnref:1&#34; class=&#34;footnote-backref&#34; role=&#34;doc-backlink&#34;&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description>
      <content:encoded><![CDATA[<p>以下のように操作できるようにする。</p>
<ul>
<li><code>Cmd + Option + G</code> &hellip; タブグループ化 / タブグループ解除</li>
<li><code>Cmd + T</code> &hellip; タブを右隣に開く
<ul>
<li>本来の「新規タブ」と異なり、現在タブがグループの場合はグループにタブが追加される。</li>
</ul>
</li>
<li><code>Cmd + Option + T</code> &hellip; 本来の「新規タブ」</li>
</ul>
<p>macOS では、システム設定でキーボードショートカットを任意のメニュー項目に割り当てることができる。以下のように設定することで上記の操作が実現可能<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>。</p>
<p><img alt="macOS のシステム設定 → キーボードショートカットの設定" loading="lazy" src="/images/chrome-tabgroup-keyboard.png"></p>
<p>タブグループとは関係ないが、このスクショでは以下の設定もある。</p>
<ul>
<li><code>Cmd + D</code> &hellip; タブ複製</li>
<li><code>Cmd + Option + D</code> &hellip; ブックマークする</li>
<li>All Applications: ウィンドウ最小化をデフォルトの <code>Cmd + M</code> から <code>Cmd + Option + M</code> に変更</li>
</ul>
<p>Windows の場合、このようなカスタマイズは <a href="https://appletllc.com/%e3%82%bd%e3%83%95%e3%83%88%e3%82%a6%e3%82%a7%e3%82%a2/" target="_blank">のどか</a>
 や <a href="https://www.autohotkey.com/" target="_blank">AutoHotKey</a>
 のようなツールで実現できると思われる。 (未検証)</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>この例では OS を英語設定にしてあるためメニュー名も英語となっている。 OS が日本語設定の場合は日本語でメニュー名を指定する必要がある。&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>macOS Monterey (M1 Mac) にしたら fish shell で ls がカラー表示されない</title>
      <link>https://okiyama.dev/posts/2021-11-12-no-colors-in-fish-ls-on-macos/</link>
      <pubDate>Fri, 12 Nov 2021 10:22:02 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2021-11-12-no-colors-in-fish-ls-on-macos/</guid>
      <description>&lt;h2 id=&#34;対処法&#34;&gt;対処法&lt;/h2&gt;
&lt;p&gt;環境変数を追加する。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fish&#34; data-lang=&#34;fish&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;-Ux&lt;/span&gt; COLORTERM &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;もしこれで直らなければシェルを再起動するか、以下 2 つのグローバル変数を消去する。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fish&#34; data-lang=&#34;fish&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;-e&lt;/span&gt; __fish_ls_command
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;set&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;-e&lt;/span&gt; __fish_ls_color_opt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;あるいは &lt;a href=&#34;https://github.com/athityakumar/colorls&#34; target=&#34;_blank&#34;&gt;athityakumar/colorls&lt;/a&gt;
 をインストールするとそちらが使われるようになる。&lt;/p&gt;
&lt;p&gt;また、試していないが coreutils をインストールすることでも解消すると思われる。&lt;/p&gt;
&lt;h2 id=&#34;補足&#34;&gt;補足&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;type ls&lt;/code&gt; で確認するとわかるが、 fish では &lt;code&gt;ls&lt;/code&gt; コマンドは標準の関数でラップされており、
カラー化するオプションを追加してくれている。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/fish-shell/fish-shell/blob/c16e30931b44628bccf2abc3082ddeb53e08971e/share/functions/ls.fish#L20-L47&#34; target=&#34;_blank&#34;&gt;https://github.com/fish-shell/fish-shell/blob/c16e30931b44628bccf2abc3082ddeb53e08971e/share/functions/ls.fish#L20-L47&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;分岐を見ると、特定の条件を満たした場合&lt;sup id=&#34;fnref:1&#34;&gt;&lt;a href=&#34;#fn:1&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;1&lt;/a&gt;&lt;/sup&gt;はカラー化オプションを決定するために&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ls --color=auto&lt;/code&gt; (GNU 系で有効)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ls -G&lt;/code&gt; (macOS, BSD 系で有効)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ls --color&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ls -F&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;の順で試行し、エラーにならなければそのオプションを採用するようだ。&lt;/p&gt;
&lt;p&gt;しかし macOS Monterey でコマンドを実行してみると、 &lt;code&gt;ls --color=auto&lt;/code&gt; はエラーにならないしカラーにもならない。&lt;/p&gt;
&lt;p&gt;macOS Monterey の &lt;code&gt;ls --color=auto&lt;/code&gt; は、 stdout が tty でありかつ &lt;code&gt;COLORTERM&lt;/code&gt; 環境変数が設定されている場合のみカラー表示を行うためだ&lt;sup id=&#34;fnref:2&#34;&gt;&lt;a href=&#34;#fn:2&#34; class=&#34;footnote-ref&#34; role=&#34;doc-noteref&#34;&gt;2&lt;/a&gt;&lt;/sup&gt;。&lt;/p&gt;
&lt;p&gt;つまり環境変数 &lt;code&gt;COLORTERM&lt;/code&gt; を定義しておけばカラー表示されるようになる。値はなんでも良いようだ。&lt;/p&gt;
&lt;h2 id=&#34;さらに補足&#34;&gt;さらに補足&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ls --color=auto&lt;/code&gt; は以前の macOS ではエラーになっていた。 (おそらく Big Sur まで？)&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="対処法">対処法</h2>
<p>環境変数を追加する。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#66d9ef">set</span> <span style="color:#a6e22e">-Ux</span> COLORTERM <span style="color:#ae81ff">1</span>
</span></span></code></pre></div><p>もしこれで直らなければシェルを再起動するか、以下 2 つのグローバル変数を消去する。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#66d9ef">set</span> <span style="color:#a6e22e">-e</span> __fish_ls_command
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">set</span> <span style="color:#a6e22e">-e</span> __fish_ls_color_opt
</span></span></code></pre></div><p>あるいは <a href="https://github.com/athityakumar/colorls" target="_blank">athityakumar/colorls</a>
 をインストールするとそちらが使われるようになる。</p>
<p>また、試していないが coreutils をインストールすることでも解消すると思われる。</p>
<h2 id="補足">補足</h2>
<p><code>type ls</code> で確認するとわかるが、 fish では <code>ls</code> コマンドは標準の関数でラップされており、
カラー化するオプションを追加してくれている。</p>
<p><a href="https://github.com/fish-shell/fish-shell/blob/c16e30931b44628bccf2abc3082ddeb53e08971e/share/functions/ls.fish#L20-L47" target="_blank">https://github.com/fish-shell/fish-shell/blob/c16e30931b44628bccf2abc3082ddeb53e08971e/share/functions/ls.fish#L20-L47</a>
</p>
<p>分岐を見ると、特定の条件を満たした場合<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>はカラー化オプションを決定するために</p>
<ul>
<li><code>ls --color=auto</code> (GNU 系で有効)</li>
<li><code>ls -G</code> (macOS, BSD 系で有効)</li>
<li><code>ls --color</code></li>
<li><code>ls -F</code></li>
</ul>
<p>の順で試行し、エラーにならなければそのオプションを採用するようだ。</p>
<p>しかし macOS Monterey でコマンドを実行してみると、 <code>ls --color=auto</code> はエラーにならないしカラーにもならない。</p>
<p>macOS Monterey の <code>ls --color=auto</code> は、 stdout が tty でありかつ <code>COLORTERM</code> 環境変数が設定されている場合のみカラー表示を行うためだ<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>。</p>
<p>つまり環境変数 <code>COLORTERM</code> を定義しておけばカラー表示されるようになる。値はなんでも良いようだ。</p>
<h2 id="さらに補足">さらに補足</h2>
<p><code>ls --color=auto</code> は以前の macOS ではエラーになっていた。 (おそらく Big Sur まで？)</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span>❯ ls <span style="color:#a6e22e">--color</span><span style="color:#f92672">=</span>auto
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">ls</span>: illegal option <span style="color:#a6e22e">-- -</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">usage</span>: ls <span style="color:#f92672">[-</span>@ABCFGHLOPRSTUWabcdefghiklmnopqrstuwx1%<span style="color:#f92672">]</span> <span style="color:#f92672">[</span>file <span style="color:#f92672">..</span>.<span style="color:#f92672">]</span>
</span></span></code></pre></div><p>このため以前までは、前述のラッパー関数の判定で <code>ls -G</code> が実際に実行されるコマンドになっていた。</p>
<p>man を見るとこのような違いがある。 BSD の実装から独自実装に変わったのだろうか。これについては検索しても情報が見つけられなかった。</p>
<p>Big Sur:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-man" data-lang="man"><span style="display:flex;"><span>LS(1)                     BSD General Commands Manual                    LS(1)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>NAME
</span></span><span style="display:flex;"><span>     ls -- list directory contents
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>SYNOPSIS
</span></span><span style="display:flex;"><span>     ls [-ABCFGHLOPRSTUW@abcdefghiklmnopqrstuwx1%] [file ...]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>.<span style="color:#e6db74">..</span> <span style="color:#e6db74">(略)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>BSD                              May 19, 2002                              BSD
</span></span></code></pre></div><p>Monterey:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-man" data-lang="man"><span style="display:flex;"><span>LS(1)                            General Commands Manual                            LS(1)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>NAME
</span></span><span style="display:flex;"><span>     ls – list directory contents
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>SYNOPSIS
</span></span><span style="display:flex;"><span>     ls [-@ABCFGHILOPRSTUWabcdefghiklmnopqrstuvwxy1%,] [--color=when] [-D format]
</span></span><span style="display:flex;"><span>        [file ...]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>.<span style="color:#e6db74">..</span> <span style="color:#e6db74">(略)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>macOS 12.0                           August 31, 2020                           macOS 12.0
</span></span></code></pre></div><p><ins>追記: Big Sur は Intel Mac で、 Monterey は M1 Mac で使用していた。</ins></p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>変数 <code>__fish_ls_color_opt</code> が未設定で <code>colorls</code> コマンドが存在しない場合&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>なお <code>-G</code> オプションは、 <code>COLORTERM</code> を設定した上で <code>--color=auto</code> を指定するのと同等&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>fish shell は `Alt &#43; ↑` で引数の履歴を補完してくれる</title>
      <link>https://okiyama.dev/posts/2021-07-20-fish-argument-history-insertion/</link>
      <pubDate>Tue, 20 Jul 2021 13:38:13 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2021-07-20-fish-argument-history-insertion/</guid>
      <description>&lt;p&gt;fish shell では他のシェルと同じように &lt;code&gt;↑&lt;/code&gt;, &lt;code&gt;↓&lt;/code&gt; や &lt;code&gt;Ctrl + n&lt;/code&gt;, &lt;code&gt;Ctrl + p&lt;/code&gt; で行ごとに履歴をたどることができる。これに加えて &lt;code&gt;Alt + ↑&lt;/code&gt;, &lt;code&gt;Alt + ↓&lt;/code&gt; で単語ごとの履歴をたどって補完することができる。&lt;/p&gt;
&lt;p&gt;例えば &lt;code&gt;git status&lt;/code&gt; で変更のあるファイルを一覧し、特定のファイルだけをコミットしたいとする。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fish&#34; data-lang=&#34;fish&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;git&lt;/span&gt; status &lt;span style=&#34;color:#a6e22e&#34;&gt;-sb&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# ## master...origin/master
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#  M dotconfig/karabiner/karabiner.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#  M init.sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# ?? dotconfig/flutter/
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;いくつか変更があるが &lt;code&gt;init.sh&lt;/code&gt; だけをコミットしたい。 &lt;code&gt;git diff&lt;/code&gt; で差分確認してから &lt;code&gt;git add&lt;/code&gt; を行う。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fish&#34; data-lang=&#34;fish&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;git&lt;/span&gt; diff init.sh
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# diff --git a/init.sh b/init.sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# (snip)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;git&lt;/span&gt; add init.sh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ここで &lt;code&gt;git add&lt;/code&gt; を入力してから &lt;code&gt;Alt + ↑&lt;/code&gt; を押すと &lt;code&gt;init.sh&lt;/code&gt; が補完される。 (もう一度押すと &lt;code&gt;diff&lt;/code&gt; に変わる)&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>fish shell では他のシェルと同じように <code>↑</code>, <code>↓</code> や <code>Ctrl + n</code>, <code>Ctrl + p</code> で行ごとに履歴をたどることができる。これに加えて <code>Alt + ↑</code>, <code>Alt + ↓</code> で単語ごとの履歴をたどって補完することができる。</p>
<p>例えば <code>git status</code> で変更のあるファイルを一覧し、特定のファイルだけをコミットしたいとする。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#a6e22e">git</span> status <span style="color:#a6e22e">-sb</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># ## master...origin/master
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#  M dotconfig/karabiner/karabiner.json
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">#  M init.sh
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># ?? dotconfig/flutter/
</span></span></span></code></pre></div><p>いくつか変更があるが <code>init.sh</code> だけをコミットしたい。 <code>git diff</code> で差分確認してから <code>git add</code> を行う。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#a6e22e">git</span> diff init.sh
</span></span><span style="display:flex;"><span><span style="color:#75715e"># diff --git a/init.sh b/init.sh
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"># (snip)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">git</span> add init.sh
</span></span></code></pre></div><p>ここで <code>git add</code> を入力してから <code>Alt + ↑</code> を押すと <code>init.sh</code> が補完される。 (もう一度押すと <code>diff</code> に変わる)</p>
<p>この例だとファイル名が短いのであまりメリットはないが、長いパスを指定する時などに便利。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Flutter 入門中のトラブルシューティング</title>
      <link>https://okiyama.dev/posts/2021-07-09-getting-started-with-flutter/</link>
      <pubDate>Fri, 09 Jul 2021 14:22:50 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2021-07-09-getting-started-with-flutter/</guid>
      <description>&lt;h2 id=&#34;android-ライセンスでエラー&#34;&gt;android ライセンスでエラー&lt;/h2&gt;
&lt;p&gt;セットアップの手順に &lt;code&gt;flutter doctor --android-licenses&lt;/code&gt; で正しく出力されることを確認する項目があるが、ここでエラーが発生した。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-fish&#34; data-lang=&#34;fish&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;❯ flutter doctor &lt;span style=&#34;color:#a6e22e&#34;&gt;--android-licenses&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Exception&lt;/span&gt; in thread &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;main&amp;#34;&lt;/span&gt; java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlSchema
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;at&lt;/span&gt; com.android.repository.api.SchemaModule$SchemaModuleVersion.&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;init&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;(&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;SchemaModule&lt;/span&gt;.java:&lt;span style=&#34;color:#ae81ff&#34;&gt;156&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;at&lt;/span&gt; com.android.repository.api.SchemaModule.&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;init&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;(&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;SchemaModule&lt;/span&gt;.java:&lt;span style=&#34;color:#ae81ff&#34;&gt;75&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;at&lt;/span&gt; com.android.sdklib.repository.AndroidSdkHandler.&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;clinit&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;(&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;AndroidSdkHandler&lt;/span&gt;.java:&lt;span style=&#34;color:#ae81ff&#34;&gt;81&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;at&lt;/span&gt; com.android.sdklib.tool.sdkmanager.SdkManagerCli.main&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;SdkManagerCli&lt;/span&gt;.java:&lt;span style=&#34;color:#ae81ff&#34;&gt;73&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;at&lt;/span&gt; com.android.sdklib.tool.sdkmanager.SdkManagerCli.main&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;SdkManagerCli&lt;/span&gt;.java:&lt;span style=&#34;color:#ae81ff&#34;&gt;48&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Caused&lt;/span&gt; by: java.lang.ClassNotFoundException: javax.xml.bind.annotation.XmlSchema
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;at&lt;/span&gt; java.base/jdk.internal.loader.BuiltinClassLoader.loadClass&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;BuiltinClassLoader&lt;/span&gt;.java:&lt;span style=&#34;color:#ae81ff&#34;&gt;581&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;at&lt;/span&gt; java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ClassLoaders&lt;/span&gt;.java:&lt;span style=&#34;color:#ae81ff&#34;&gt;178&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;at&lt;/span&gt; java.base/java.lang.ClassLoader.loadClass&lt;span style=&#34;color:#f92672&#34;&gt;(&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ClassLoader&lt;/span&gt;.java:&lt;span style=&#34;color:#ae81ff&#34;&gt;522&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ... &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt; more
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Android Studio で設定を行うと解消した。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Android Studio で設定を開き &lt;code&gt;Android SDK&lt;/code&gt; を選択&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SDK Tools&lt;/code&gt; タブを選択&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Android SDK Command-line tools&lt;/code&gt; をチェックし Apply を実行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;参考: &lt;a href=&#34;https://stackoverflow.com/questions/61993738/flutter-doctor-android-licenses-gives-a-java-error/66363044&#34; target=&#34;_blank&#34;&gt;https://stackoverflow.com/questions/61993738/flutter-doctor-android-licenses-gives-a-java-error/66363044&lt;/a&gt;
&lt;/p&gt;
&lt;h2 id=&#34;エミュレータの選択でエラー&#34;&gt;エミュレータの選択でエラー&lt;/h2&gt;
&lt;p&gt;Chrome でのデバッグはできるが、デバイスから &lt;code&gt;Create Android emulator&lt;/code&gt; を選ぶと &lt;code&gt;No device definitions are available&lt;/code&gt; というエラーが発生するが、そもそも create する必要はない。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="android-ライセンスでエラー">android ライセンスでエラー</h2>
<p>セットアップの手順に <code>flutter doctor --android-licenses</code> で正しく出力されることを確認する項目があるが、ここでエラーが発生した。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span>❯ flutter doctor <span style="color:#a6e22e">--android-licenses</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Exception</span> in thread <span style="color:#e6db74">&#34;main&#34;</span> java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlSchema
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">at</span> com.android.repository.api.SchemaModule$SchemaModuleVersion.<span style="color:#f92672">&lt;</span>init<span style="color:#f92672">&gt;(</span><span style="color:#a6e22e">SchemaModule</span>.java:<span style="color:#ae81ff">156</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">at</span> com.android.repository.api.SchemaModule.<span style="color:#f92672">&lt;</span>init<span style="color:#f92672">&gt;(</span><span style="color:#a6e22e">SchemaModule</span>.java:<span style="color:#ae81ff">75</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">at</span> com.android.sdklib.repository.AndroidSdkHandler.<span style="color:#f92672">&lt;</span>clinit<span style="color:#f92672">&gt;(</span><span style="color:#a6e22e">AndroidSdkHandler</span>.java:<span style="color:#ae81ff">81</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">at</span> com.android.sdklib.tool.sdkmanager.SdkManagerCli.main<span style="color:#f92672">(</span><span style="color:#a6e22e">SdkManagerCli</span>.java:<span style="color:#ae81ff">73</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">at</span> com.android.sdklib.tool.sdkmanager.SdkManagerCli.main<span style="color:#f92672">(</span><span style="color:#a6e22e">SdkManagerCli</span>.java:<span style="color:#ae81ff">48</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Caused</span> by: java.lang.ClassNotFoundException: javax.xml.bind.annotation.XmlSchema
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">at</span> java.base/jdk.internal.loader.BuiltinClassLoader.loadClass<span style="color:#f92672">(</span><span style="color:#a6e22e">BuiltinClassLoader</span>.java:<span style="color:#ae81ff">581</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">at</span> java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass<span style="color:#f92672">(</span><span style="color:#a6e22e">ClassLoaders</span>.java:<span style="color:#ae81ff">178</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#a6e22e">at</span> java.base/java.lang.ClassLoader.loadClass<span style="color:#f92672">(</span><span style="color:#a6e22e">ClassLoader</span>.java:<span style="color:#ae81ff">522</span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>        ... <span style="color:#ae81ff">5</span> more
</span></span></code></pre></div><p>Android Studio で設定を行うと解消した。</p>
<ul>
<li>Android Studio で設定を開き <code>Android SDK</code> を選択</li>
<li><code>SDK Tools</code> タブを選択</li>
<li><code>Android SDK Command-line tools</code> をチェックし Apply を実行</li>
</ul>
<p>参考: <a href="https://stackoverflow.com/questions/61993738/flutter-doctor-android-licenses-gives-a-java-error/66363044" target="_blank">https://stackoverflow.com/questions/61993738/flutter-doctor-android-licenses-gives-a-java-error/66363044</a>
</p>
<h2 id="エミュレータの選択でエラー">エミュレータの選択でエラー</h2>
<p>Chrome でのデバッグはできるが、デバイスから <code>Create Android emulator</code> を選ぶと <code>No device definitions are available</code> というエラーが発生するが、そもそも create する必要はない。</p>
<p>リストに既に <code>Pixel_3a</code> というエミュレータがあり、それを使えば良い。</p>
<h2 id="セキュリティソフトのファイアウォール機能を切っておかないと-emulator-が動作しない">セキュリティソフトのファイアウォール機能を切っておかないと emulator が動作しない</h2>
<p>セキュリティソフトの種類にもよるが、自分の環境ではオフにする必要があった。</p>
<h2 id="flutter_driver-を追加した際に-incompatible-エラーが発生する">flutter_driver を追加した際に <code>incompatible</code> エラーが発生する</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#a6e22e">ERR</span> : Because every version of flutter_driver from sdk depends on args <span style="color:#ae81ff">1</span>.<span style="color:#ae81ff">6</span>.<span style="color:#ae81ff">0</span> and <span style="color:#a6e22e">flutter_launcher_icons</span> <span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">9</span>.<span style="color:#ae81ff">0</span> depends on args <span style="color:#ae81ff">2</span>.<span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">0</span>, flutter_driver from sdk is incompatible with flutter_launcher_icons <span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">9</span>.<span style="color:#ae81ff">0</span>.
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">And</span> because no versions of flutter_launcher_icons match <span style="color:#f92672">&gt;</span><span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">9</span>.<span style="color:#ae81ff">0</span> <span style="color:#f92672">&lt;</span><span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">10</span>.<span style="color:#ae81ff">0</span>, flutter_driver from sdk is incompatible with flutter_launcher_icons <span style="color:#f92672">^</span><span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">9</span>.<span style="color:#ae81ff">0</span>.
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">So</span>, because hello_world depends on both flutter_launcher_icons <span style="color:#f92672">^</span><span style="color:#ae81ff">0</span>.<span style="color:#ae81ff">9</span>.<span style="color:#ae81ff">0</span> and <span style="color:#a6e22e">flutter_driver</span> any from sdk, version solving failed.
</span></span></code></pre></div><p>pubspec.yaml で flutter_launcher_icons を古いバージョンに変更して解消した。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#f92672">- flutter_launcher_icons: ^0.9.0
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ flutter_launcher_icons: ^0.8.1
</span></span></span></code></pre></div><p>参考: <a href="https://github.com/fluttercommunity/flutter_launcher_icons/issues/241" target="_blank">https://github.com/fluttercommunity/flutter_launcher_icons/issues/241</a>
</p>
<h2 id="エラー-unexpected-text-late-が発生する">エラー <code>Unexpected text 'late'</code> が発生する</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#a6e22e">Unexpected</span> text <span style="color:#e6db74">&#39;late&#39;</span>.
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Try</span> removing the text.dart<span style="color:#f92672">(</span><span style="color:#a6e22e">unexpected_token</span><span style="color:#f92672">)</span>
</span></span></code></pre></div><p>dart sdk のバージョンが古いことが原因。 <code>late</code> キーワードは dart 2.12.0 以降で使える。 pubspec.yaml でバージョン指定を変更すれば良い。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> environment:
</span></span><span style="display:flex;"><span><span style="color:#f92672">-  sdk: &#34;&gt;=2.7.0 &lt;3.0.0&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+  sdk: &#34;&gt;=2.12.0 &lt;3.0.0&#34;
</span></span></span></code></pre></div><p>参考: <a href="https://stackoverflow.com/questions/67113942/dart-unexpected-text-late" target="_blank">https://stackoverflow.com/questions/67113942/dart-unexpected-text-late</a>
</p>
<p>これを行うと以下のエラーが出るようになるが、これは null-safe ではないコードが残っているため。</p>
<p>エラー:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#a6e22e">The</span> non-nullable local variable <span style="color:#e6db74">&#39;driver&#39;</span> must be assigned before it can be used.
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">Try</span> giving it an initializer expression, or <span style="color:#a6e22e">ensure</span> that it&#39;s assigned on every execution path.dart<span style="color:#f92672">(</span><span style="color:#a6e22e">not_assigned_potentially_non_nullable_local_variable</span><span style="color:#f92672">)</span>
</span></span></code></pre></div><p>以下のように、宣言時に初期化していないメンバー変数がある場合に発生する。これは <code>late</code> を追加すると解消する。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#f92672">-    FlutterDriver driver;
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+    late FlutterDriver driver;
</span></span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>tpm (Tmux Plugin Manager) を導入</title>
      <link>https://okiyama.dev/posts/2021-05-01-tpm-tmux-plugin-manager/</link>
      <pubDate>Sat, 01 May 2021 01:17:52 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2021-05-01-tpm-tmux-plugin-manager/</guid>
      <description>&lt;p&gt;tpm (Tmux Plugin Manager) を導入した。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/tmux-plugins/tpm&#34; target=&#34;_blank&#34;&gt;https://github.com/tmux-plugins/tpm&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;tpm の導入後、 &lt;code&gt;.tmux.conf&lt;/code&gt; に使いたいプラグインを記述して &lt;code&gt;prefix&lt;/code&gt; + &lt;code&gt;I&lt;/code&gt; を押すとプラグインをインストール・ロードしてくれる。&lt;/p&gt;
&lt;p&gt;プラグインのリストもあり、いくつかインストールしてみた。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/tmux-plugins/list&#34; target=&#34;_blank&#34;&gt;https://github.com/tmux-plugins/list&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;私の設定ファイルは &lt;a href=&#34;https://github.com/rokiyama/dotfiles/blob/2f3092b79928bd7ac2979339a042fef9c3cc87f3/.tmux.conf&#34; target=&#34;_blank&#34;&gt;GitHub&lt;/a&gt;
 に置いてある。&lt;/p&gt;
&lt;h2 id=&#34;tmux-sensible-tmux-yank&#34;&gt;tmux-sensible, tmux-yank&lt;/h2&gt;
&lt;p&gt;自分は &lt;code&gt;.tmux.conf&lt;/code&gt; をほとんどカスタムしておらず、基本的な設定とクリップボード連携の設定をしていたくらいだったが、プラグインを導入することでその記述すら不要になった。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tmux-sensible&lt;/code&gt; はいくつかの基本的な設定に、多くのユーザにとって快適になるようなデフォルト値を提供してくれる (ユーザが変更している場合はそれを上書きしないよう配慮されている) 。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/tmux-plugins/tmux-sensible&#34; target=&#34;_blank&#34;&gt;https://github.com/tmux-plugins/tmux-sensible&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tmux-yank&lt;/code&gt; はクリップボード連携の設定をしてくれる。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/tmux-plugins/tmux-yank&#34; target=&#34;_blank&#34;&gt;https://github.com/tmux-plugins/tmux-yank&lt;/a&gt;
&lt;/p&gt;
&lt;h2 id=&#34;tmux-prefix-highlight&#34;&gt;tmux-prefix-highlight&lt;/h2&gt;
&lt;p&gt;ステータスラインに &lt;code&gt;#{prefix_highlight}&lt;/code&gt; と記述すると prefix キーの状態が表示されるようになる。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/tmux-plugins/tmux-prefix-highlight&#34; target=&#34;_blank&#34;&gt;https://github.com/tmux-plugins/tmux-prefix-highlight&lt;/a&gt;
&lt;/p&gt;
&lt;h2 id=&#34;tmux-urlview&#34;&gt;tmux-urlview&lt;/h2&gt;
&lt;p&gt;現在の画面から URL を探し、ブラウザで開くプラグイン。 &lt;code&gt;prefix&lt;/code&gt; + &lt;code&gt;u&lt;/code&gt; で URL リストが開き、選ぶとブラウザが立ち上がる。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/tmux-plugins/tmux-urlview&#34; target=&#34;_blank&#34;&gt;https://github.com/tmux-plugins/tmux-urlview&lt;/a&gt;
&lt;/p&gt;
&lt;h2 id=&#34;extrakto&#34;&gt;extrakto&lt;/h2&gt;
&lt;p&gt;現在の画面から単語を探し、コマンドラインにペーストできるプラグイン。 &lt;code&gt;prefix&lt;/code&gt; + &lt;code&gt;Tab&lt;/code&gt; で単語リストのポップアップが開き、リストから fzf で絞り込んで選択する。&lt;/p&gt;
&lt;p&gt;mac で使う場合は最新の bash を入れておく必要がある。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/laktak/extrakto&#34; target=&#34;_blank&#34;&gt;https://github.com/laktak/extrakto&lt;/a&gt;
&lt;/p&gt;
&lt;h2 id=&#34;vim-プラグイン-tmuxlinevim&#34;&gt;vim プラグイン: tmuxline.vim&lt;/h2&gt;
&lt;p&gt;tmux ではなく vim プラグインだが、実行すると vim のステータスラインを元に tmux のステータスライン設定を生成してくれる。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>tpm (Tmux Plugin Manager) を導入した。</p>
<p><a href="https://github.com/tmux-plugins/tpm" target="_blank">https://github.com/tmux-plugins/tpm</a>
</p>
<p>tpm の導入後、 <code>.tmux.conf</code> に使いたいプラグインを記述して <code>prefix</code> + <code>I</code> を押すとプラグインをインストール・ロードしてくれる。</p>
<p>プラグインのリストもあり、いくつかインストールしてみた。</p>
<p><a href="https://github.com/tmux-plugins/list" target="_blank">https://github.com/tmux-plugins/list</a>
</p>
<p>私の設定ファイルは <a href="https://github.com/rokiyama/dotfiles/blob/2f3092b79928bd7ac2979339a042fef9c3cc87f3/.tmux.conf" target="_blank">GitHub</a>
 に置いてある。</p>
<h2 id="tmux-sensible-tmux-yank">tmux-sensible, tmux-yank</h2>
<p>自分は <code>.tmux.conf</code> をほとんどカスタムしておらず、基本的な設定とクリップボード連携の設定をしていたくらいだったが、プラグインを導入することでその記述すら不要になった。</p>
<p><code>tmux-sensible</code> はいくつかの基本的な設定に、多くのユーザにとって快適になるようなデフォルト値を提供してくれる (ユーザが変更している場合はそれを上書きしないよう配慮されている) 。</p>
<p><a href="https://github.com/tmux-plugins/tmux-sensible" target="_blank">https://github.com/tmux-plugins/tmux-sensible</a>
</p>
<p><code>tmux-yank</code> はクリップボード連携の設定をしてくれる。</p>
<p><a href="https://github.com/tmux-plugins/tmux-yank" target="_blank">https://github.com/tmux-plugins/tmux-yank</a>
</p>
<h2 id="tmux-prefix-highlight">tmux-prefix-highlight</h2>
<p>ステータスラインに <code>#{prefix_highlight}</code> と記述すると prefix キーの状態が表示されるようになる。</p>
<p><a href="https://github.com/tmux-plugins/tmux-prefix-highlight" target="_blank">https://github.com/tmux-plugins/tmux-prefix-highlight</a>
</p>
<h2 id="tmux-urlview">tmux-urlview</h2>
<p>現在の画面から URL を探し、ブラウザで開くプラグイン。 <code>prefix</code> + <code>u</code> で URL リストが開き、選ぶとブラウザが立ち上がる。</p>
<p><a href="https://github.com/tmux-plugins/tmux-urlview" target="_blank">https://github.com/tmux-plugins/tmux-urlview</a>
</p>
<h2 id="extrakto">extrakto</h2>
<p>現在の画面から単語を探し、コマンドラインにペーストできるプラグイン。 <code>prefix</code> + <code>Tab</code> で単語リストのポップアップが開き、リストから fzf で絞り込んで選択する。</p>
<p>mac で使う場合は最新の bash を入れておく必要がある。</p>
<p><a href="https://github.com/laktak/extrakto" target="_blank">https://github.com/laktak/extrakto</a>
</p>
<h2 id="vim-プラグイン-tmuxlinevim">vim プラグイン: tmuxline.vim</h2>
<p>tmux ではなく vim プラグインだが、実行すると vim のステータスラインを元に tmux のステータスライン設定を生成してくれる。</p>
<p><a href="https://github.com/edkolev/tmuxline.vim" target="_blank">https://github.com/edkolev/tmuxline.vim</a>
</p>
<h2 id="プラグインにより用意されるキーバインド">プラグインにより用意されるキーバインド</h2>
<p>以上のプラグインが用意してくれるキーバインドをリストしておく。</p>
<ul>
<li>tpm
<ul>
<li><code>prefix</code> + <code>I</code>
<ul>
<li>Installs new plugins from GitHub or any other git repository</li>
<li>Refreshes TMUX environment</li>
</ul>
</li>
<li><code>prefix</code> + <code>U</code> &hellip; updates plugin(s)</li>
<li><code>prefix</code> + <code>M-u</code> &hellip; remove/uninstall plugins not on the plugin list</li>
</ul>
</li>
<li>urlview
<ul>
<li><code>prefix</code> + <code>u</code> &hellip; listing all urls on bottom pane</li>
</ul>
</li>
<li>extrakto
<ul>
<li><code>prefix</code> + <code>Tab</code> &hellip; start extrakto</li>
</ul>
</li>
</ul>
<h2 id="プラグインが有効にならない場合は">プラグインが有効にならない場合は</h2>
<p>プラグインをインストールしたのに有効にならない時は、エラーになっていないかを確認する。</p>
<p>プラグインファイルは実行ファイルになっており、以下のように実行するとエラーメッセージを確認できる。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>❯ ~/.tmux/plugins/extrakto/extrakto.tmux
</span></span><span style="display:flex;"><span>/Users/p563/.tmux/plugins/extrakto/extrakto.tmux: line 10: <span style="color:#e6db74">${</span>extrakto_key,,<span style="color:#e6db74">}</span>: bad substitution
</span></span></code></pre></div><p>上記のエラーは mac の古い bash を使っているために発生しているエラーで、 homebrew で bash をインストールすると解消する</p>
<h2 id="参考">参考</h2>
<p>tmux プラグインの仕組みについてはこちらの記事が参考になった。</p>
<p><a href="https://dev.classmethod.jp/articles/mastering-tmux-with-tpm-plugin/" target="_blank">tmuxを使いこなす / プラグイン開発で機能を拡張 | DevelopersIO</a>
</p>
]]></content:encoded>
    </item>
    <item>
      <title>VSCode Vim で explorer ペインのキーバインドが使えなくなった時の対処法</title>
      <link>https://okiyama.dev/posts/2021-04-19-vscode-vim-keybind-in-the-explorer-pane-doesnt-working/</link>
      <pubDate>Mon, 19 Apr 2021 11:52:22 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2021-04-19-vscode-vim-keybind-in-the-explorer-pane-doesnt-working/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;http://aka.ms/vscodevim&#34; target=&#34;_blank&#34;&gt;VSCode Vim&lt;/a&gt;
 というエクステンションを入れると vim キーバインドが使える。エディタだけでなく、 explorer ペインでも以下のようなキーバインドが使えるようになる。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;j&lt;/code&gt;, &lt;code&gt;k&lt;/code&gt; で選択&lt;/li&gt;
&lt;li&gt;&lt;code&gt;l&lt;/code&gt;, &lt;code&gt;h&lt;/code&gt; でツリーの開閉&lt;/li&gt;
&lt;li&gt;&lt;code&gt;l&lt;/code&gt; でファイルオープン&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Space&lt;/code&gt; でファイルプレビュー (これは標準機能かも)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ある時これが使えなくなったので調べたところ、以下の二つを設定することで解消した。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;workbench.list.keyboardNavigation&amp;quot;: &amp;quot;simple&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;quot;workbench.list.automaticKeyboardNavigation&amp;quot;: false&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;参考: &lt;a href=&#34;https://github.com/VSCodeVim/Vim/issues/3760&#34; target=&#34;_blank&#34;&gt;Navigation in the explorer pane vim way (j , k) doesn&amp;rsquo;t work after window reload&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;各設定の意味は以下の通り。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;workbench.list.keyboardNavigation&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Controls the keyboard navigation style for lists and trees in the workbench. Can be simple, highlight and filter.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;simple: Simple keyboard navigation focuses elements which match the keyboard input. Matching is done only on prefixes.&lt;/li&gt;
&lt;li&gt;highlight: Highlight keyboard navigation highlights elements which match the keyboard input. Further up and down navigation will traverse only the highlighted elements.&lt;/li&gt;
&lt;li&gt;filter: Filter keyboard navigation will filter out and hide all the elements which do not match the keyboard input.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;workbench.list.automaticKeyboardNavigation&lt;/code&gt;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><a href="http://aka.ms/vscodevim" target="_blank">VSCode Vim</a>
 というエクステンションを入れると vim キーバインドが使える。エディタだけでなく、 explorer ペインでも以下のようなキーバインドが使えるようになる。</p>
<ul>
<li><code>j</code>, <code>k</code> で選択</li>
<li><code>l</code>, <code>h</code> でツリーの開閉</li>
<li><code>l</code> でファイルオープン</li>
<li><code>Space</code> でファイルプレビュー (これは標準機能かも)</li>
</ul>
<p>ある時これが使えなくなったので調べたところ、以下の二つを設定することで解消した。</p>
<ul>
<li><code>&quot;workbench.list.keyboardNavigation&quot;: &quot;simple&quot;</code></li>
<li><code>&quot;workbench.list.automaticKeyboardNavigation&quot;: false</code></li>
</ul>
<p>参考: <a href="https://github.com/VSCodeVim/Vim/issues/3760" target="_blank">Navigation in the explorer pane vim way (j , k) doesn&rsquo;t work after window reload</a>
</p>
<p>各設定の意味は以下の通り。</p>
<blockquote>
<p><code>workbench.list.keyboardNavigation</code></p>
<p>Controls the keyboard navigation style for lists and trees in the workbench. Can be simple, highlight and filter.</p>
<ul>
<li>simple: Simple keyboard navigation focuses elements which match the keyboard input. Matching is done only on prefixes.</li>
<li>highlight: Highlight keyboard navigation highlights elements which match the keyboard input. Further up and down navigation will traverse only the highlighted elements.</li>
<li>filter: Filter keyboard navigation will filter out and hide all the elements which do not match the keyboard input.</li>
</ul>
<p><code>workbench.list.automaticKeyboardNavigation</code></p>
<p>Controls whether keyboard navigation in lists and trees is automatically triggered simply by typing. If set to <code>false</code>, keyboard navigation is only triggered when executing the <code>list.toggleKeyboardNavigation</code> command, for which you can assign a keyboard shortcut.</p>
<p><a href="https://code.visualstudio.com/docs/getstarted/settings" target="_blank">https://code.visualstudio.com/docs/getstarted/settings</a>
</p></blockquote>
]]></content:encoded>
    </item>
    <item>
      <title>Google Nest Wifi ルーターで 2.4GHz のみ対応の IoT デバイスをセットアップする方法</title>
      <link>https://okiyama.dev/posts/2021-04-18-google-nest-wifi-and-smart-light/</link>
      <pubDate>Sun, 18 Apr 2021 19:44:12 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2021-04-18-google-nest-wifi-and-smart-light/</guid>
      <description>&lt;p&gt;Google Nest Wifi ルーターと拡張ポイントを買った。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://store.google.com/jp/product/nest_wifi&#34; target=&#34;_blank&#34;&gt;https://store.google.com/jp/product/nest_wifi&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;スマホのアプリが良く、セットアップがとても簡単だった。 Google Home アプリまたは Google Wifi アプリから設定するのだが、最初に繋ぐときはアプリが勝手にやってくれるため SSID などの入力は必要ない (Bluetooth あるいはスマホを Wifi AP にして接続するらしい)。&lt;/p&gt;
&lt;p&gt;電源の抜き差しを何度か行った際にインターネット接続がなかなか復帰しないことがあったが、それ以外は特に問題なく使えている。 LAN 側の Ethernet ポートが一個しかない点は少し不便。&lt;/p&gt;
&lt;p&gt;つまづいたのが、 2.4GHz と 5GHz が同じ SSID なので 2.4GHz のみ対応のスマート照明に 5GHz で繋がっているスマホから接続できず、セットアップを行えないという問題。なお照明は &lt;a href=&#34;https://amzn.to/3mVS1UR&#34; target=&#34;_blank&#34;&gt;+Style というメーカーの製品&lt;/a&gt;
 である。&lt;/p&gt;
&lt;p&gt;2.4GHz と 5GHz の SSID を分けるのは Nest Wifi ではできないらしい。スマホが 2.4GHz だけに繋がれば良いのだが、今持っているものはそのように設定できない。 2.4GHz のみ対応の古い Fire HD タブレットがあったが、これは Amazon のカスタム OS が入っており +Style のアプリを入れるのがやや面倒だ。&lt;/p&gt;
&lt;p&gt;+Style のアプリにはスマホを一時的に Wifi AP にしてセットアップする互換モードという方式が用意されている。今回はこれを使うことでうまくいったが、このような機能がない製品の場合はいくつか対応手段がある。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;2.4GHz のみ対応の古いスマホを買う&lt;/li&gt;
&lt;li&gt;2.4GHz で接続される程度に家から離れてからセットアップする&lt;/li&gt;
&lt;li&gt;Nest Wifi を停止した状態でスマホを Wifi AP にし、 Nest Wifi で使う SSID/WPA キーと同じものにしてデバイスを接続・セットアップする&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;2 の家から離れるというのは、 5GHz は遠くまで届かないが 2.4GHz は低周波で遠くまで届く特性があり、両方の周波数に対応しているスマホは 5GHz に優先して接続するが、ルーターから離れて電波が減衰すると 2.4GHz にフォールバックするのでその状態で IoT デバイスのセットアップをする、ということらしい。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>Google Nest Wifi ルーターと拡張ポイントを買った。</p>
<p><a href="https://store.google.com/jp/product/nest_wifi" target="_blank">https://store.google.com/jp/product/nest_wifi</a>
</p>
<p>スマホのアプリが良く、セットアップがとても簡単だった。 Google Home アプリまたは Google Wifi アプリから設定するのだが、最初に繋ぐときはアプリが勝手にやってくれるため SSID などの入力は必要ない (Bluetooth あるいはスマホを Wifi AP にして接続するらしい)。</p>
<p>電源の抜き差しを何度か行った際にインターネット接続がなかなか復帰しないことがあったが、それ以外は特に問題なく使えている。 LAN 側の Ethernet ポートが一個しかない点は少し不便。</p>
<p>つまづいたのが、 2.4GHz と 5GHz が同じ SSID なので 2.4GHz のみ対応のスマート照明に 5GHz で繋がっているスマホから接続できず、セットアップを行えないという問題。なお照明は <a href="https://amzn.to/3mVS1UR" target="_blank">+Style というメーカーの製品</a>
 である。</p>
<p>2.4GHz と 5GHz の SSID を分けるのは Nest Wifi ではできないらしい。スマホが 2.4GHz だけに繋がれば良いのだが、今持っているものはそのように設定できない。 2.4GHz のみ対応の古い Fire HD タブレットがあったが、これは Amazon のカスタム OS が入っており +Style のアプリを入れるのがやや面倒だ。</p>
<p>+Style のアプリにはスマホを一時的に Wifi AP にしてセットアップする互換モードという方式が用意されている。今回はこれを使うことでうまくいったが、このような機能がない製品の場合はいくつか対応手段がある。</p>
<ol>
<li>2.4GHz のみ対応の古いスマホを買う</li>
<li>2.4GHz で接続される程度に家から離れてからセットアップする</li>
<li>Nest Wifi を停止した状態でスマホを Wifi AP にし、 Nest Wifi で使う SSID/WPA キーと同じものにしてデバイスを接続・セットアップする</li>
</ol>
<p>2 の家から離れるというのは、 5GHz は遠くまで届かないが 2.4GHz は低周波で遠くまで届く特性があり、両方の周波数に対応しているスマホは 5GHz に優先して接続するが、ルーターから離れて電波が減衰すると 2.4GHz にフォールバックするのでその状態で IoT デバイスのセットアップをする、ということらしい。</p>
<p>参考: <a href="https://support.google.com/googlenest/thread/611640" target="_blank">How do force Google Wifi to 2.4 Ghz only? - Google Nest Community</a>
</p>
]]></content:encoded>
    </item>
    <item>
      <title>fish を使っているなら pushd より cdh が便利</title>
      <link>https://okiyama.dev/posts/2021-03-23-fish-cdh/</link>
      <pubDate>Tue, 23 Mar 2021 20:21:54 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2021-03-23-fish-cdh/</guid>
      <description>&lt;p&gt;fish にはディレクトリ履歴を辿る方法が用意されている。&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://fishshell.com/docs/current/cmds/dirh.html#cmd-dirh&#34; target=&#34;_blank&#34;&gt;dirh&lt;/a&gt;
 履歴を表示&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://fishshell.com/docs/current/cmds/cdh.html#cmd-cdh&#34; target=&#34;_blank&#34;&gt;cdh&lt;/a&gt;
 履歴を素早く操作するためのプロンプトを表示&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://fishshell.com/docs/current/cmds/prevd.html#cmd-prevd&#34; target=&#34;_blank&#34;&gt;prevd&lt;/a&gt;
 履歴を戻る。 &lt;code&gt;Alt&lt;/code&gt;+&lt;code&gt;←&lt;/code&gt; に対応&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://fishshell.com/docs/current/cmds/nextd.html#cmd-nextd&#34; target=&#34;_blank&#34;&gt;nextd&lt;/a&gt;
 履歴を進む。 &lt;code&gt;Alt&lt;/code&gt;+&lt;code&gt;→&lt;/code&gt; に対応&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&#34;https://fishshell.com/docs/current/index.html#id34&#34; target=&#34;_blank&#34;&gt;Introduction — fish-shell 3.2.1 documentation&lt;/a&gt;
&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;実際によく使うのは履歴を戻る &lt;code&gt;Alt&lt;/code&gt;+&lt;code&gt;←&lt;/code&gt; と、 &lt;code&gt;cdh&amp;lt;Space&amp;gt;&amp;lt;Tab&amp;gt;&lt;/code&gt; で補完候補から戻り先を選ぶというもの。&lt;/p&gt;
&lt;h2 id=&#34;補足-pushd-について&#34;&gt;補足: &lt;code&gt;pushd&lt;/code&gt; について&lt;/h2&gt;
&lt;p&gt;どのシェルにもあるビルトインコマンドとして &lt;code&gt;pushd&lt;/code&gt;, &lt;code&gt;popd&lt;/code&gt; があり、履歴を残して &lt;code&gt;cd&lt;/code&gt; するコマンドである。 &lt;code&gt;pushd &amp;lt;path/to/dir&amp;gt;&lt;/code&gt; で現在のパスがスタックに積まれた上で移動し、 &lt;code&gt;popd&lt;/code&gt; でスタックからパスが取り出されてそこに移動する。&lt;/p&gt;
&lt;p&gt;zsh では &lt;code&gt;AUTO_PUSHD&lt;/code&gt; を設定すると &lt;code&gt;cd&lt;/code&gt; した時に自動でパスがスタックに積まれるようになる。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>fish にはディレクトリ履歴を辿る方法が用意されている。</p>
<blockquote>
<ul>
<li><a href="https://fishshell.com/docs/current/cmds/dirh.html#cmd-dirh" target="_blank">dirh</a>
 履歴を表示</li>
<li><a href="https://fishshell.com/docs/current/cmds/cdh.html#cmd-cdh" target="_blank">cdh</a>
 履歴を素早く操作するためのプロンプトを表示</li>
<li><a href="https://fishshell.com/docs/current/cmds/prevd.html#cmd-prevd" target="_blank">prevd</a>
 履歴を戻る。 <code>Alt</code>+<code>←</code> に対応</li>
<li><a href="https://fishshell.com/docs/current/cmds/nextd.html#cmd-nextd" target="_blank">nextd</a>
 履歴を進む。 <code>Alt</code>+<code>→</code> に対応</li>
</ul>
<p><a href="https://fishshell.com/docs/current/index.html#id34" target="_blank">Introduction — fish-shell 3.2.1 documentation</a>
</p></blockquote>
<p>実際によく使うのは履歴を戻る <code>Alt</code>+<code>←</code> と、 <code>cdh&lt;Space&gt;&lt;Tab&gt;</code> で補完候補から戻り先を選ぶというもの。</p>
<h2 id="補足-pushd-について">補足: <code>pushd</code> について</h2>
<p>どのシェルにもあるビルトインコマンドとして <code>pushd</code>, <code>popd</code> があり、履歴を残して <code>cd</code> するコマンドである。 <code>pushd &lt;path/to/dir&gt;</code> で現在のパスがスタックに積まれた上で移動し、 <code>popd</code> でスタックからパスが取り出されてそこに移動する。</p>
<p>zsh では <code>AUTO_PUSHD</code> を設定すると <code>cd</code> した時に自動でパスがスタックに積まれるようになる。</p>
]]></content:encoded>
    </item>
    <item>
      <title>envsubst: シェルでちょっとしたテンプレート処理をする</title>
      <link>https://okiyama.dev/posts/2021-03-11-envsubst/</link>
      <pubDate>Thu, 11 Mar 2021 20:47:40 +0900</pubDate>
      <guid>https://okiyama.dev/posts/2021-03-11-envsubst/</guid>
      <description>&lt;p&gt;&lt;code&gt;envsubst&lt;/code&gt; というコマンドを知った。テキストファイルに環境変数を埋め込んでくれるツール。&lt;/p&gt;
&lt;p&gt;置換前&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// $ cat config.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;TargetCapacity&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Type&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;request&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;TagSpecifications&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;ResourceType&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;spot-fleet-request&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Tags&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Key&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Name&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Value&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;$EC2_INSTANCE_NAME&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;環境変数を設定し、テキストを envsubst に渡すと置換される。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// $ export EC2_INSTANCE_NAME=my-instance
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// $ cat config.json | envsubst
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;TargetCapacity&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Type&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;request&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;TagSpecifications&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;ResourceType&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;spot-fleet-request&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Tags&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Key&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Name&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;Value&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;my-instance&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;GNU gettext というパッケージに含まれており、割と多くのディストリで標準で使えるようだ。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><code>envsubst</code> というコマンドを知った。テキストファイルに環境変数を埋め込んでくれるツール。</p>
<p>置換前</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#75715e">// $ cat config.json
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;TargetCapacity&#34;</span>: <span style="color:#ae81ff">1</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;Type&#34;</span>: <span style="color:#e6db74">&#34;request&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;TagSpecifications&#34;</span>: [
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;ResourceType&#34;</span>: <span style="color:#e6db74">&#34;spot-fleet-request&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;Tags&#34;</span>: [
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">&#34;Key&#34;</span>: <span style="color:#e6db74">&#34;Name&#34;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">&#34;Value&#34;</span>: <span style="color:#e6db74">&#34;$EC2_INSTANCE_NAME&#34;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>      ]
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  ],
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// ...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><p>環境変数を設定し、テキストを envsubst に渡すと置換される。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span><span style="color:#75715e">// $ export EC2_INSTANCE_NAME=my-instance
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// $ cat config.json | envsubst
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;TargetCapacity&#34;</span>: <span style="color:#ae81ff">1</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;Type&#34;</span>: <span style="color:#e6db74">&#34;request&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;TagSpecifications&#34;</span>: [
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;ResourceType&#34;</span>: <span style="color:#e6db74">&#34;spot-fleet-request&#34;</span>,
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">&#34;Tags&#34;</span>: [
</span></span><span style="display:flex;"><span>        {
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">&#34;Key&#34;</span>: <span style="color:#e6db74">&#34;Name&#34;</span>,
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">&#34;Value&#34;</span>: <span style="color:#e6db74">&#34;my-instance&#34;</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>      ]
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>  ],
</span></span><span style="display:flex;"><span>  <span style="color:#75715e">// ...
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span></code></pre></div><p>GNU gettext というパッケージに含まれており、割と多くのディストリで標準で使えるようだ。</p>
<p>以前は sed で置換するとか、 JSON ファイルなら jq でセットするなどしていたが、単純な処理ならこちらの方が簡単だ。</p>
]]></content:encoded>
    </item>
    <item>
      <title>Golang の go-cmp で protobuf の struct を Diff する時、 cannot handle unexported field のような panic になる場合は protocmp.Transform() を試してみると良い</title>
      <link>https://okiyama.dev/posts/2020-09-10-gocmp-proto/</link>
      <pubDate>Fri, 11 Sep 2020 00:00:00 +0000</pubDate>
      <guid>https://okiyama.dev/posts/2020-09-10-gocmp-proto/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://pkg.go.dev/google.golang.org/protobuf/testing/protocmp&#34; target=&#34;_blank&#34;&gt;https://pkg.go.dev/google.golang.org/protobuf/testing/protocmp&lt;/a&gt;
 より:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The primary feature is the Transform option, which transform proto.Message types into a Message map that is suitable for cmp to introspect upon. All other options in this package must be used in conjunction with Transform.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;before&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;	if diff := cmp.Diff(want, got); diff != &amp;#34;&amp;#34; {
		t.Errorf(&amp;#34;mismatch (-want +got):\n%s&amp;#34;, diff)
	}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;after&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;	if diff := cmp.Diff(want, got, protocmp.Transform()); diff != &amp;#34;&amp;#34; {
		t.Errorf(&amp;#34;mismatch (-want +got):\n%s&amp;#34;, diff)
	}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;サンプルコードは &lt;a href=&#34;https://pkg.go.dev/github.com/google/go-cmp/cmp?tab=doc#example-Diff-Testing&#34; target=&#34;_blank&#34;&gt;https://pkg.go.dev/github.com/google/go-cmp/cmp?tab=doc#example-Diff-Testing&lt;/a&gt;
 を参照。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p><a href="https://pkg.go.dev/google.golang.org/protobuf/testing/protocmp" target="_blank">https://pkg.go.dev/google.golang.org/protobuf/testing/protocmp</a>
 より:</p>
<blockquote>
<p>The primary feature is the Transform option, which transform proto.Message types into a Message map that is suitable for cmp to introspect upon. All other options in this package must be used in conjunction with Transform.</p></blockquote>
<p>before</p>
<pre tabindex="0"><code>	if diff := cmp.Diff(want, got); diff != &#34;&#34; {
		t.Errorf(&#34;mismatch (-want +got):\n%s&#34;, diff)
	}
</code></pre><p>after</p>
<pre tabindex="0"><code>	if diff := cmp.Diff(want, got, protocmp.Transform()); diff != &#34;&#34; {
		t.Errorf(&#34;mismatch (-want +got):\n%s&#34;, diff)
	}
</code></pre><p>サンプルコードは <a href="https://pkg.go.dev/github.com/google/go-cmp/cmp?tab=doc#example-Diff-Testing" target="_blank">https://pkg.go.dev/github.com/google/go-cmp/cmp?tab=doc#example-Diff-Testing</a>
 を参照。</p>
]]></content:encoded>
    </item>
    <item>
      <title>CRA &#43; TypeScript プロジェクト作成時のメモ</title>
      <link>https://okiyama.dev/posts/2020-06-04-cra-typescript-memo/</link>
      <pubDate>Thu, 04 Jun 2020 00:00:00 +0000</pubDate>
      <guid>https://okiyama.dev/posts/2020-06-04-cra-typescript-memo/</guid>
      <description>&lt;p&gt;CRA で作り始める時の手順メモ。入門中なのでおかしい所があるかも。&lt;/p&gt;
&lt;h2 id=&#34;プロジェクト作成&#34;&gt;プロジェクト作成&lt;/h2&gt;
&lt;p&gt;参考: &lt;a href=&#34;https://create-react-app.dev/docs/getting-started#creating-a-typescript-app&#34; target=&#34;_blank&#34;&gt;https://create-react-app.dev/docs/getting-started#creating-a-typescript-app&lt;/a&gt;
&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;npx create-react-app myapp --template typescript
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;prettier-追加&#34;&gt;prettier 追加&lt;/h2&gt;
&lt;p&gt;prettier と eslint の設定を追加する。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;yarn add -D eslint-config-prettier eslint-plugin-prettier prettier
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;eslint-config-prettier は、一部ルールが prettier と eslint で重複するため、片方をオフにするためのパッケージ。&lt;/p&gt;
&lt;h2 id=&#34;eslintconfig-修正&#34;&gt;eslintConfig 修正&lt;/h2&gt;
&lt;p&gt;元の &lt;code&gt;react-app&lt;/code&gt; に &lt;code&gt;prettier/recommended&lt;/code&gt; を追加。&lt;/p&gt;
&lt;p&gt;デフォルト値が &lt;a href=&#34;https://prettier.io/docs/en/options.html&#34; target=&#34;_blank&#34;&gt;https://prettier.io/docs/en/options.html&lt;/a&gt;
 に記載されているので、変更したいものは設定する。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff:package.json&#34; data-lang=&#34;diff:package.json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;eslintConfig&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;extends&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;react-app&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;+&lt;/span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;extends&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;+&lt;/span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;react-app&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;+&lt;/span&gt;      &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;plugin:prettier/recommended&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;+&lt;/span&gt;    ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;+&lt;/span&gt;  }&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;+&lt;/span&gt;  &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;prettier&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;+&lt;/span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;trailingComma&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;all&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;+&lt;/span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;semi&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;+&lt;/span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;singleQuote&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;+&lt;/span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;&amp;#34;jsxSingleQuote&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   }&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;script-追加&#34;&gt;script 追加&lt;/h2&gt;
&lt;p&gt;lint (書式チェック) と format (書式修正) を追加する。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-diff:package.json&#34; data-lang=&#34;diff:package.json&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;-&lt;/span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;eject&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;react-scripts eject&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;+&lt;/span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;eject&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;react-scripts eject&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;+&lt;/span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;lint&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;eslint --ext .js,.jsx,.ts,.tsx ./src --color&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;+&lt;/span&gt;    &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;format&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;:&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;prettier --write ./src&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
      <content:encoded><![CDATA[<p>CRA で作り始める時の手順メモ。入門中なのでおかしい所があるかも。</p>
<h2 id="プロジェクト作成">プロジェクト作成</h2>
<p>参考: <a href="https://create-react-app.dev/docs/getting-started#creating-a-typescript-app" target="_blank">https://create-react-app.dev/docs/getting-started#creating-a-typescript-app</a>
</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>npx create-react-app myapp --template typescript
</span></span></code></pre></div><h2 id="prettier-追加">prettier 追加</h2>
<p>prettier と eslint の設定を追加する。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>yarn add -D eslint-config-prettier eslint-plugin-prettier prettier
</span></span></code></pre></div><p>eslint-config-prettier は、一部ルールが prettier と eslint で重複するため、片方をオフにするためのパッケージ。</p>
<h2 id="eslintconfig-修正">eslintConfig 修正</h2>
<p>元の <code>react-app</code> に <code>prettier/recommended</code> を追加。</p>
<p>デフォルト値が <a href="https://prettier.io/docs/en/options.html" target="_blank">https://prettier.io/docs/en/options.html</a>
 に記載されているので、変更したいものは設定する。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff:package.json" data-lang="diff:package.json"><span style="display:flex;"><span>   <span style="color:#e6db74">&#34;eslintConfig&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> {
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">-</span>    <span style="color:#f92672">&#34;extends&#34;</span>: <span style="color:#e6db74">&#34;react-app&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">+</span>    <span style="color:#e6db74">&#34;extends&#34;</span>: [
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">+</span>      <span style="color:#e6db74">&#34;react-app&#34;</span>,
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">+</span>      <span style="color:#e6db74">&#34;plugin:prettier/recommended&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">+</span>    ]
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">+</span>  }<span style="color:#960050;background-color:#1e0010">,</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">+</span>  <span style="color:#e6db74">&#34;prettier&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> {
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">+</span>    <span style="color:#f92672">&#34;trailingComma&#34;</span>: <span style="color:#e6db74">&#34;all&#34;</span>,
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">+</span>    <span style="color:#f92672">&#34;semi&#34;</span>: <span style="color:#66d9ef">false</span>,
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">+</span>    <span style="color:#f92672">&#34;singleQuote&#34;</span>: <span style="color:#66d9ef">true</span>,
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">+</span>    <span style="color:#f92672">&#34;jsxSingleQuote&#34;</span>: <span style="color:#66d9ef">true</span>
</span></span><span style="display:flex;"><span>   }<span style="color:#960050;background-color:#1e0010">,</span>
</span></span></code></pre></div><h2 id="script-追加">script 追加</h2>
<p>lint (書式チェック) と format (書式修正) を追加する。</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff:package.json" data-lang="diff:package.json"><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">-</span>    <span style="color:#e6db74">&#34;eject&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;react-scripts eject&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">+</span>    <span style="color:#e6db74">&#34;eject&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;react-scripts eject&#34;</span><span style="color:#960050;background-color:#1e0010">,</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">+</span>    <span style="color:#e6db74">&#34;lint&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;eslint --ext .js,.jsx,.ts,.tsx ./src --color&#34;</span><span style="color:#960050;background-color:#1e0010">,</span>
</span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">+</span>    <span style="color:#e6db74">&#34;format&#34;</span><span style="color:#960050;background-color:#1e0010">:</span> <span style="color:#e6db74">&#34;prettier --write ./src&#34;</span>
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>mac node インストールメモ</title>
      <link>https://okiyama.dev/posts/2019-10-03-mac-node-install-memo/</link>
      <pubDate>Thu, 03 Oct 2019 00:00:00 +0000</pubDate>
      <guid>https://okiyama.dev/posts/2019-10-03-mac-node-install-memo/</guid>
      <description>&lt;h2 id=&#34;nvm-は-git-clone-で入れる&#34;&gt;nvm は git clone で入れる&lt;/h2&gt;
&lt;p&gt;brew ではインストールしないこと。 (参照: &lt;a href=&#34;https://github.com/nvm-sh/nvm/blob/master/README.md&#34; target=&#34;_blank&#34;&gt;nvm/README.md at master · nvm-sh/nvm&lt;/a&gt;
)&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git clone https://github.com/nvm-sh/nvm.git .nvm
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;nvm-で-node-をインストールする&#34;&gt;nvm で node をインストールする&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;nvm install node &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; nvm alias default node
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;yarn-は-brew-install-で入れる-npm-で入れる&#34;&gt;yarn は &lt;del&gt;brew install で入れる&lt;/del&gt; npm で入れる&lt;/h2&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#brew install yarn --ignore-dependencies&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;npm i -g yarn
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;fish-shell-の場合&#34;&gt;fish shell の場合&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/FabioAntunes/fish-nvm&#34; target=&#34;_blank&#34;&gt;FabioAntunes/fish-nvm: nvm wrapper for fish-shell&lt;/a&gt;
 をインストールする。&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="nvm-は-git-clone-で入れる">nvm は git clone で入れる</h2>
<p>brew ではインストールしないこと。 (参照: <a href="https://github.com/nvm-sh/nvm/blob/master/README.md" target="_blank">nvm/README.md at master · nvm-sh/nvm</a>
)</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git clone https://github.com/nvm-sh/nvm.git .nvm
</span></span></code></pre></div><h2 id="nvm-で-node-をインストールする">nvm で node をインストールする</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>nvm install node <span style="color:#f92672">&amp;&amp;</span> nvm alias default node
</span></span></code></pre></div><h2 id="yarn-は-brew-install-で入れる-npm-で入れる">yarn は <del>brew install で入れる</del> npm で入れる</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e">#brew install yarn --ignore-dependencies</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>npm i -g yarn
</span></span></code></pre></div><h2 id="fish-shell-の場合">fish shell の場合</h2>
<p><a href="https://github.com/FabioAntunes/fish-nvm" target="_blank">FabioAntunes/fish-nvm: nvm wrapper for fish-shell</a>
 をインストールする。</p>
]]></content:encoded>
    </item>
    <item>
      <title>vue-apollo メモ</title>
      <link>https://okiyama.dev/posts/2019-10-02-vue-apollo-memo/</link>
      <pubDate>Wed, 02 Oct 2019 00:00:00 +0000</pubDate>
      <guid>https://okiyama.dev/posts/2019-10-02-vue-apollo-memo/</guid>
      <description>&lt;h2 id=&#34;vue-apollo-の-result-関数が呼ばれるタイミング&#34;&gt;vue-apollo の &lt;code&gt;result&lt;/code&gt; 関数が呼ばれるタイミング&lt;/h2&gt;
&lt;p&gt;2 回ある。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;reactive variables が変更され、ロードが始まったタイミング
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;result.loading === true&lt;/code&gt; になる。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ロードが終わったタイミング
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;result.loading === false&lt;/code&gt; になる。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;apollo-結果がキャッシュされている時にリアクティブなプロパティを変更しても反映されない&#34;&gt;apollo 結果がキャッシュされている時に、リアクティブなプロパティを変更しても反映されない&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;fetchPolicy&lt;/code&gt; を変更すると動作する。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fetchPolicy: &amp;quot;cache-and-network&amp;quot;&lt;/code&gt; &amp;hellip; OK&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fetchPolicy: &amp;quot;network-only&amp;quot;&lt;/code&gt; &amp;hellip; OK&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fetchPolicy: &amp;quot;cache-first&amp;quot;&lt;/code&gt; &amp;hellip; NG&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;fetchPolicy&lt;/code&gt; のデフォルト値は &lt;code&gt;cache-first&lt;/code&gt; である。 (参考: &lt;a href=&#34;https://github.com/apollographql/apollo-client/blob/7a2067e33f748372aa6342ef0a097679e3239d29/packages/apollo-client/src/core/watchQueryOptions.ts#L11&#34; target=&#34;_blank&#34;&gt;apollo-client/watchQueryOptions.ts at 7a2067e33f748372aa6342ef0a097679e3239d29 · apollographql/apollo-client&lt;/a&gt;
)&lt;/p&gt;
&lt;p&gt;参考: &lt;a href=&#34;https://github.com/vuejs/vue-apollo/issues/138&#34; target=&#34;_blank&#34;&gt;Reactive Variables with &amp;lsquo;cache-first&amp;rsquo; not working in new version · Issue #138 · vuejs/vue-apollo&lt;/a&gt;
&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="vue-apollo-の-result-関数が呼ばれるタイミング">vue-apollo の <code>result</code> 関数が呼ばれるタイミング</h2>
<p>2 回ある。</p>
<ol>
<li>reactive variables が変更され、ロードが始まったタイミング
<ul>
<li><code>result.loading === true</code> になる。</li>
</ul>
</li>
<li>ロードが終わったタイミング
<ul>
<li><code>result.loading === false</code> になる。</li>
</ul>
</li>
</ol>
<h2 id="apollo-結果がキャッシュされている時にリアクティブなプロパティを変更しても反映されない">apollo 結果がキャッシュされている時に、リアクティブなプロパティを変更しても反映されない</h2>
<p><code>fetchPolicy</code> を変更すると動作する。</p>
<ul>
<li><code>fetchPolicy: &quot;cache-and-network&quot;</code> &hellip; OK</li>
<li><code>fetchPolicy: &quot;network-only&quot;</code> &hellip; OK</li>
<li><code>fetchPolicy: &quot;cache-first&quot;</code> &hellip; NG</li>
</ul>
<p><code>fetchPolicy</code> のデフォルト値は <code>cache-first</code> である。 (参考: <a href="https://github.com/apollographql/apollo-client/blob/7a2067e33f748372aa6342ef0a097679e3239d29/packages/apollo-client/src/core/watchQueryOptions.ts#L11" target="_blank">apollo-client/watchQueryOptions.ts at 7a2067e33f748372aa6342ef0a097679e3239d29 · apollographql/apollo-client</a>
)</p>
<p>参考: <a href="https://github.com/vuejs/vue-apollo/issues/138" target="_blank">Reactive Variables with &lsquo;cache-first&rsquo; not working in new version · Issue #138 · vuejs/vue-apollo</a>
</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
