[{"content":"VoiceOS を使い始めた。 AquaVoice も無料の範囲で試したが、 VoiceOS は指示モードを別キーに割り当てられる点が好み。\nキー割り当て 最初 F13 キーに割り当てたら、 Apple 純正メモや ChatGPT など一部のアプリで、キーを押しっぱなしにするとビープ音が鳴り続ける現象があった。\nREMAP で Ro (International 1) を割り当て、 VoiceOS の設定で割り当てたら解消した。\nUnknown と表示されるが正常に動作する。\n音声入力の文章 音声入力は文章が冗長になりやすい。最近のものはフィラーを取り除いてくれるが、それ以外にも手入力では出にくい余分な表現が増える。 AI チャットにならそのまま送れば良いが、日記やブログ、メッセンジャーで使う場合は入力後に削りたくなる。 AI に整形させてもいいが毎回は面倒。\nVoiceOS の指示モードで「要約して」と最初に指示してから話すとわりといい感じになる。\n以下は「これから話す内容を簡潔に要約して。要約前の文も出力すること」で始めた結果。\nこれから話す内容を簡潔に要約して要約前の文も出力することMacやPCではVoiceOSやAquaOSで精度の高い音声入力をすることができるようになったけどスマホというかiOSは標準を使っていて他にもアプリがあるようなのでそれも気になるただモバイルでの音声入力はAndroidのデフォルトのディクテーションがかなり優秀らしいので気になる\nMacやPCではVoiceOSやAquaOSで高精度な音声入力が可能だが、iOSは標準機能のみで他のアプリも気になる。一方、Androidのデフォルトディクテーションは非常に優秀と聞く。\nAquaOS となっているのは誤認識ではなく単なる言い間違い 要約して、だけだとあまりに短くなりすぎる気もするが、しばらく試してみたい。\nVoiceOS の設定にカスタムプロンプトというものがある。ここに設定して常に有効にすればいいと考えたが、これはアプリごとに設定するもので、全体に適用するのは今のところできないようだ。\n招待コード: https://voiceos.com/r?code=f7e38407 ChatGPT の音声入力 ChatGPT の音声入力も使える。\nAquaVoice を使った時は、音声入力ツールに追加でお金を出すのはどうかなと思い、結局やめてしまった。\nChatGPT は既に契約しているし、 Mac のアプリであればショートカットキーで呼び出せるし、とりあえずこれ使えばいいか\u0026hellip;という。\n","permalink":"https://okiyama.dev/posts/2026-02-14-dictation/","summary":"\u003cp\u003e\u003ca href=\"https://www.voiceos.com/\" target=\"_blank\"\u003eVoiceOS\u003c/a\u003e\n を使い始めた。 \u003ca href=\"https://aquavoice.com/\" target=\"_blank\"\u003eAquaVoice\u003c/a\u003e\n も無料の範囲で試したが、 VoiceOS は指示モードを別キーに割り当てられる点が好み。\u003c/p\u003e\n\u003ch2 id=\"キー割り当て\"\u003eキー割り当て\u003c/h2\u003e\n\u003cp\u003e最初 F13 キーに割り当てたら、 Apple 純正メモや ChatGPT など一部のアプリで、キーを押しっぱなしにするとビープ音が鳴り続ける現象があった。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://remap-keys.app/\" target=\"_blank\"\u003eREMAP\u003c/a\u003e\n で Ro (International 1) を割り当て、 VoiceOS の設定で割り当てたら解消した。\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/images/voiceos-1-remap.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/images/voiceos-2-settings.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eUnknown\u003c/code\u003e と表示されるが正常に動作する。\u003c/p\u003e\n\u003ch2 id=\"音声入力の文章\"\u003e音声入力の文章\u003c/h2\u003e\n\u003cp\u003e音声入力は文章が冗長になりやすい。最近のものはフィラーを取り除いてくれるが、それ以外にも手入力では出にくい余分な表現が増える。 AI チャットにならそのまま送れば良いが、日記やブログ、メッセンジャーで使う場合は入力後に削りたくなる。 AI に整形させてもいいが毎回は面倒。\u003c/p\u003e\n\u003cp\u003eVoiceOS の指示モードで「要約して」と最初に指示してから話すとわりといい感じになる。\u003c/p\u003e\n\u003cp\u003e以下は「これから話す内容を簡潔に要約して。要約前の文も出力すること」で始めた結果。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eこれから話す内容を簡潔に要約して要約前の文も出力することMacやPCではVoiceOSやAquaOSで精度の高い音声入力をすることができるようになったけどスマホというかiOSは標準を使っていて他にもアプリがあるようなのでそれも気になるただモバイルでの音声入力はAndroidのデフォルトのディクテーションがかなり優秀らしいので気になる\u003c/p\u003e\n\u003cp\u003eMacやPCではVoiceOSやAquaOSで高精度な音声入力が可能だが、iOSは標準機能のみで他のアプリも気になる。一方、Androidのデフォルトディクテーションは非常に優秀と聞く。\u003c/p\u003e\u003c/blockquote\u003e\n\u003cul\u003e\n\u003cli\u003eAquaOS となっているのは誤認識ではなく単なる言い間違い\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e要約して、だけだとあまりに短くなりすぎる気もするが、しばらく試してみたい。\u003c/p\u003e\n\u003cp\u003eVoiceOS の設定にカスタムプロンプトというものがある。ここに設定して常に有効にすればいいと考えたが、これはアプリごとに設定するもので、全体に適用するのは今のところできないようだ。\u003c/p\u003e\n\u003cp\u003e招待コード: \u003ca href=\"https://voiceos.com/r?code=f7e38407\" target=\"_blank\"\u003ehttps://voiceos.com/r?code=f7e38407\u003c/a\u003e\n\u003c/p\u003e\n\u003ch2 id=\"chatgpt-の音声入力\"\u003eChatGPT の音声入力\u003c/h2\u003e\n\u003cp\u003eChatGPT の音声入力も使える。\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/images/voiceos-3-chatgpt.png\"\u003e\u003c/p\u003e\n\u003cp\u003eAquaVoice を使った時は、音声入力ツールに追加でお金を出すのはどうかなと思い、結局やめてしまった。\u003c/p\u003e\n\u003cp\u003eChatGPT は既に契約しているし、 Mac のアプリであればショートカットキーで呼び出せるし、とりあえずこれ使えばいいか\u0026hellip;という。\u003c/p\u003e","title":"VoiceOS"},{"content":"プロジェクト作成 create-next-app コマンドで作成する。\nパッケージマネージャに pnpm を使用。選択肢は全てデフォルト。\n$ pnpx create-next-app@latest --use-pnpm example-nextjs ✔ Would you like to use TypeScript? … Yes ✔ Would you like to use ESLint? … Yes ✔ Would you like to use Tailwind CSS? … Yes ✔ Would you like your code inside a `src/` directory? … No ✔ Would you like to use App Router? (recommended) … Yes ✔ Would you like to use Turbopack for `next dev`? … Yes ✔ Would you like to customize the import alias (`@/*` by default)? … No .node-version ファイルを作成する:\n# .node-version を作成 $ node -v \u0026gt; .node-version # もしくは major version のみ記載する場合 $ node -p \u0026#39;process.versions.node.split(\u0026#34;.\u0026#34;)[0]\u0026#39; \u0026gt; .node-version 依存パッケージを追加 パッケージをいくつか追加する。\ni18n ライブラリである next-intl Tailwind CSS とあわせてよく使われる tailwind-variants ESLint と Prettier 関連 eslint-plugin-tailwindcss は 2025-04-20 時点で Tailwind CSS v4 に対応していないため除外 2025-11-16 追記: Tailwind CSS v4 サポートを謳う eslint-plugin-better-tailwindcss を追加\n# dependencies $ pnpm add tailwind-variants next-intl # devDependencies $ pnpm add -D \\ prettier \\ eslint-config-prettier \\ @trivago/prettier-plugin-sort-imports \\ prettier-plugin-classnames \\ prettier-plugin-merge \\ prettier-plugin-tailwindcss \\ eslint-plugin-better-tailwindcss ESLint 設定 prettier と TypeScript の未使用変数に関する設定を追加。\n+// @ts-check import nextVitals from \u0026#34;eslint-config-next/core-web-vitals\u0026#34;; import nextTs from \u0026#34;eslint-config-next/typescript\u0026#34;; +import eslintConfigPrettier from \u0026#34;eslint-config-prettier\u0026#34;; +import eslintPluginBetterTailwindcss from \u0026#34;eslint-plugin-better-tailwindcss\u0026#34;; import { defineConfig, globalIgnores } from \u0026#34;eslint/config\u0026#34;; const eslintConfig = defineConfig([ ...nextVitals, @@ -13,6 +16,30 @@ const eslintConfig = defineConfig([ \u0026#34;build/**\u0026#34;, \u0026#34;next-env.d.ts\u0026#34;, ]), + eslintConfigPrettier, + { + rules: { + \u0026#34;@typescript-eslint/no-unused-vars\u0026#34;: [ + \u0026#34;warn\u0026#34;, + { argsIgnorePattern: \u0026#34;^_\u0026#34; }, + ], + }, + }, + { + plugins: { + \u0026#34;better-tailwindcss\u0026#34;: eslintPluginBetterTailwindcss, + }, + settings: { + \u0026#34;better-tailwindcss\u0026#34;: { + entryPoint: \u0026#34;app/globals.css\u0026#34;, + }, + }, + rules: { + ...eslintPluginBetterTailwindcss.configs[\u0026#34;recommended-warn\u0026#34;].rules, + \u0026#34;better-tailwindcss/enforce-consistent-line-wrapping\u0026#34;: \u0026#34;off\u0026#34;, + \u0026#34;better-tailwindcss/enforce-consistent-class-order\u0026#34;: \u0026#34;off\u0026#34;, + }, + }, ]); export default eslintConfig; 2025-11-16 追記: 設定について\nno-unused-vars はエラーでなく warn レベルになるようにする コーディング中にエディタ上でエラーが即座に報告されるのが煩わしいため warn レベルの問題を CI で検出する想定 eslint-plugin-better-tailwindcss entryPoint で create-next-app で作られるグローバル CSS ファイルを指定 recommended ルールを warn レベルで取り込む 改行と順序のルールは off にし、 Prettier に任せる Prettier 設定 import のソート、 Tailwind CSS のクラス名のソートと改行を行うプラグインを有効化。\n.prettierrc を作成:\n{ \u0026#34;plugins\u0026#34;: [ \u0026#34;@trivago/prettier-plugin-sort-imports\u0026#34;, \u0026#34;prettier-plugin-tailwindcss\u0026#34;, \u0026#34;prettier-plugin-classnames\u0026#34;, \u0026#34;prettier-plugin-merge\u0026#34; ], \u0026#34;importOrder\u0026#34;: [\u0026#34;\u0026lt;THIRD_PARTY_MODULES\u0026gt;\u0026#34;, \u0026#34;^@/\u0026#34;, \u0026#34;^[./]\u0026#34;], \u0026#34;tailwindFunctions\u0026#34;: [\u0026#34;tv\u0026#34;], \u0026#34;customFunctions\u0026#34;: [\u0026#34;tv\u0026#34;] } なお tailwindFunctions は prettier-plugin-tailwindcss の設定で、 customFunctions は prettier-plugin-classnames の設定。\n2025-11-16 追記: prettier-plugin-classnames 関連の変更\n以前は \u0026quot;endingPosition\u0026quot;: \u0026quot;absolute-with-indent\u0026quot; を指定していたが v0.8.0 以降で不要になった: https://github.com/ony3000/prettier-plugin-classnames?tab=readme-ov-file#ending-position \u0026quot;experimentalOptimization\u0026quot;: true オプションは削除された: https://github.com/ony3000/prettier-plugin-classnames/pull/98 ignore リストに pnpm の lockfile を入れておく。\n$ echo pnpm-lock.yaml \u0026gt;\u0026gt; .prettierignore package.json のスクリプト定義に \u0026quot;format\u0026quot;: \u0026quot;prettier --write .\u0026quot; を追加し、コマンド実行してフォーマットを適用しておく。\n$ pnpm run format next-intl 設定 ドキュメントの通り設定する。 以下は locale-based routing と呼ばれる https://example.com/ja のようにパスに言語コードが入る設定。\nApp Router setup with i18n routing – Internationalization (i18n) for Next.js messages/ja.json と messages/en.json を作成 next.config.ts を変更 i18n/navigation.ts を作成 i18n/request.ts を作成 middleware.ts を作成 i18n/routing.ts を作成 app/layout.tsx を app/[locale]/layout.tsx に移動、修正 app/page.tsx を app/[locale]/page.tsx に移動、修正 追加で TypeScript 型定義の設定を行う。\nTypeScript augmentation – Internationalization (i18n) for Next.js global.d.ts を作成:\nimport { routing } from \u0026#34;@/i18n/routing\u0026#34;; import messages from \u0026#34;@/messages/ja.json\u0026#34;; declare module \u0026#34;next-intl\u0026#34; { interface AppConfig { Locale: (typeof routing.locales)[number]; Messages: typeof messages; } } 開発サーバを起動し、ブラウザで http://localhost:3000 を開く。\nhttp://localhost:3000/ja にリダイレクトされてページが表示されれば OK。\n","permalink":"https://okiyama.dev/posts/2025-04-20-nextjs-setup-with-next-intl/","summary":"\u003ch2 id=\"プロジェクト作成\"\u003eプロジェクト作成\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://nextjs.org/docs/app/api-reference/cli/create-next-app\" target=\"_blank\"\u003ecreate-next-app\u003c/a\u003e\n コマンドで作成する。\u003c/p\u003e\n\u003cp\u003eパッケージマネージャに pnpm を使用。選択肢は全てデフォルト。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e$ pnpx create-next-app@latest --use-pnpm example-nextjs\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e✔ Would you like to use TypeScript? … Yes\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e✔ Would you like to use ESLint? … Yes\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e✔ Would you like to use Tailwind CSS? … Yes\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e✔ Would you like your code inside a \u003cspan style=\"color:#e6db74\"\u003e`\u003c/span\u003esrc/\u003cspan style=\"color:#e6db74\"\u003e`\u003c/span\u003e directory? … No\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e✔ Would you like to use App Router? \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003erecommended\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e … Yes\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e✔ Would you like to use Turbopack \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e`\u003c/span\u003enext dev\u003cspan style=\"color:#e6db74\"\u003e`\u003c/span\u003e? … Yes\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e✔ Would you like to customize the import alias \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e`\u003c/span\u003e@/*\u003cspan style=\"color:#e6db74\"\u003e`\u003c/span\u003e by default\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e? … No\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003e.node-version\u003c/code\u003e ファイルを作成する:\u003c/p\u003e","title":"Next.js, next-intl のセットアップ"},{"content":"バージョンの違い Apple の AirPods Pro は第1・第2世代があるが、それぞれマイナーアップデートが行われている。\n発売時期 名称 コネクタ 無線充電 イヤホン ケース 備考 2019年10月 AirPods Pro Lightning Qi IPX4 耐水なし 2021年10月 AirPods Pro Lightning MagSafe IPX4 耐水なし ケースのみ変更 2022年9月 AirPods Pro (第2世代) Lightning MagSafe IPX4 IPX4 多くの変更 (スワイプで音量変更、ケースにストラップホール・スピーカーが追加など) 2023年9月 AirPods Pro (第2世代) USB-C MagSafe IP54 IP54 ケースとイヤホンの変更 ケースのスピーカーについて 音楽再生などはできず、以下の機能を持つ。\nApple の「探す」でスピーカーを鳴らす Qi または MagSafe 充電の開始時にスピーカーが鳴る これにより Qi 充電で置き場所が悪く充電できていないトラブルが減った。初期モデルは LED での判別のみだったのに比べて気付きやすい。 第2世代のマイナーアップデートでイヤホンは何が変わったのか ケースは USB Type-C 端子に変わり、耐水性能が向上。\n一方イヤホンは地味な変更。耐水性能が向上したのと、機能的には今のところ Apple Vision Pro のロスレスオーディオ対応くらい。\n参考 新型AirPodsにはなぜスピーカーがついているのか？ | ギズモード・ジャパン Apple、AirPods Pro（第2世代）をUSB-C充電にアップグレード - Apple (日本) ASCII.jp：Vision ProとAirPods Proの低遅延ロスレス接続、これはいったい何なのか？ (1/2) ASCII.jp：【レビュー】新AirPods Pro、USB-C対応で変わること・変わらないこと (1/4) AirPods Pro、AirPods 3、AirPods 4（両モデル）の耐汗・耐水性能について - Apple サポート (日本) ","permalink":"https://okiyama.dev/posts/2024-12-08-airpods-pro-versions/","summary":"\u003ch2 id=\"バージョンの違い\"\u003eバージョンの違い\u003c/h2\u003e\n\u003cp\u003eApple の AirPods Pro は第1・第2世代があるが、それぞれマイナーアップデートが行われている。\u003c/p\u003e\n\u003ctable\u003e\n  \u003cthead\u003e\n      \u003ctr\u003e\n          \u003cth\u003e発売時期\u003c/th\u003e\n          \u003cth\u003e名称\u003c/th\u003e\n          \u003cth\u003eコネクタ\u003c/th\u003e\n          \u003cth\u003e無線充電\u003c/th\u003e\n          \u003cth\u003eイヤホン\u003c/th\u003e\n          \u003cth\u003eケース\u003c/th\u003e\n          \u003cth\u003e備考\u003c/th\u003e\n      \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e2019年10月\u003c/td\u003e\n          \u003ctd\u003e\u003cnobr\u003eAirPods Pro\u003c/nobr\u003e\u003c/td\u003e\n          \u003ctd\u003eLightning\u003c/td\u003e\n          \u003ctd\u003eQi\u003c/td\u003e\n          \u003ctd\u003eIPX4\u003c/td\u003e\n          \u003ctd\u003e耐水なし\u003c/td\u003e\n          \u003ctd\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e2021年10月\u003c/td\u003e\n          \u003ctd\u003e\u003cnobr\u003eAirPods Pro\u003c/nobr\u003e\u003c/td\u003e\n          \u003ctd\u003eLightning\u003c/td\u003e\n          \u003ctd\u003eMagSafe\u003c/td\u003e\n          \u003ctd\u003eIPX4\u003c/td\u003e\n          \u003ctd\u003e耐水なし\u003c/td\u003e\n          \u003ctd\u003eケースのみ変更\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e2022年9月\u003c/td\u003e\n          \u003ctd\u003eAirPods Pro (第2世代)\u003c/td\u003e\n          \u003ctd\u003eLightning\u003c/td\u003e\n          \u003ctd\u003eMagSafe\u003c/td\u003e\n          \u003ctd\u003eIPX4\u003c/td\u003e\n          \u003ctd\u003eIPX4\u003c/td\u003e\n          \u003ctd\u003e\u003cnobr\u003e多くの変更 (スワイプで音量変更、ケースにストラップホール・スピーカーが追加など)\u003c/nobr\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n          \u003ctd\u003e2023年9月\u003c/td\u003e\n          \u003ctd\u003eAirPods Pro (第2世代)\u003c/td\u003e\n          \u003ctd\u003eUSB-C\u003c/td\u003e\n          \u003ctd\u003eMagSafe\u003c/td\u003e\n          \u003ctd\u003eIP54\u003c/td\u003e\n          \u003ctd\u003eIP54\u003c/td\u003e\n          \u003ctd\u003eケースとイヤホンの変更\u003c/td\u003e\n      \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"ケースのスピーカーについて\"\u003eケースのスピーカーについて\u003c/h2\u003e\n\u003cp\u003e音楽再生などはできず、以下の機能を持つ。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eApple の「探す」でスピーカーを鳴らす\u003c/li\u003e\n\u003cli\u003eQi または MagSafe 充電の開始時にスピーカーが鳴る\n\u003cul\u003e\n\u003cli\u003eこれにより Qi 充電で置き場所が悪く充電できていないトラブルが減った。初期モデルは LED での判別のみだったのに比べて気付きやすい。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"第2世代のマイナーアップデートでイヤホンは何が変わったのか\"\u003e第2世代のマイナーアップデートでイヤホンは何が変わったのか\u003c/h2\u003e\n\u003cp\u003eケースは USB Type-C 端子に変わり、耐水性能が向上。\u003c/p\u003e\n\u003cp\u003e一方イヤホンは地味な変更。耐水性能が向上したのと、機能的には今のところ Apple Vision Pro のロスレスオーディオ対応くらい。\u003c/p\u003e","title":"Apple AirPods Pro のバージョンの違い"},{"content":"fish の場合以下コマンドで設定する。\npnpm completion fish \u0026gt; ~/.config/fish/completions/pnpm.fish Command line tab-completion | pnpm 最近まで知らずに設定していなかった。 内容は執筆時点では以下のようになっていて、将来変更された時にコマンド再実行が必要になるかもしれない。\n###-begin-pnpm-completion-### function _pnpm_completion set cmd (commandline -o) set cursor (commandline -C) set words (count $cmd) set completions (eval env DEBUG=\\\u0026#34;\u0026#34; \\\u0026#34;\u0026#34; COMP_CWORD=\\\u0026#34;\u0026#34;$words\\\u0026#34;\u0026#34; COMP_LINE=\\\u0026#34;\u0026#34;$cmd \\\u0026#34;\u0026#34; COMP_POINT=\\\u0026#34;\u0026#34;$cursor\\\u0026#34;\u0026#34; SHELL=fish pnpm completion-server -- $cmd) if [ \u0026#34;$completions\u0026#34; = \u0026#34;__tabtab_complete_files__\u0026#34; ] set -l matches (commandline -ct)* if [ -n \u0026#34;$matches\u0026#34; ] __fish_complete_path (commandline -ct) end else for completion in $completions echo -e $completion end end end complete -f -d \u0026#39;pnpm\u0026#39; -c pnpm -a \u0026#34;(_pnpm_completion)\u0026#34; ###-end-pnpm-completion-### ","permalink":"https://okiyama.dev/posts/2024-10-20-pnpm-completion-for-fish/","summary":"\u003cp\u003efish の場合以下コマンドで設定する。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fish\" data-lang=\"fish\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003epnpm\u003c/span\u003e completion fish \u003cspan style=\"color:#f92672\"\u003e\u0026gt;\u003c/span\u003e ~/.config/fish/completions/pnpm.fish\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ca href=\"https://pnpm.io/completion\" target=\"_blank\"\u003eCommand line tab-completion | pnpm\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003e最近まで知らずに設定していなかった。\n内容は執筆時点では以下のようになっていて、将来変更された時にコマンド再実行が必要になるかもしれない。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fish\" data-lang=\"fish\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e###-begin-pnpm-completion-###\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\u003cspan style=\"color:#66d9ef\"\u003efunction\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e_pnpm_completion\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eset\u003c/span\u003e cmd \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ecommandline \u003cspan style=\"color:#a6e22e\"\u003e-o\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eset\u003c/span\u003e cursor \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ecommandline \u003cspan style=\"color:#a6e22e\"\u003e-C\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eset\u003c/span\u003e words \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003ecount\u003c/span\u003e $cmd\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eset\u003c/span\u003e completions \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eeval env DEBUG\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34; \\\u0026#34;\u0026#34;\u003c/span\u003e COMP_CWORD\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$words\u003cspan style=\"color:#e6db74\"\u003e\\\u0026#34;\u0026#34;\u003c/span\u003e COMP_LINE\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$cmd\u003cspan style=\"color:#e6db74\"\u003e \\\u0026#34;\u0026#34;\u003c/span\u003e COMP_POINT\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#ae81ff\"\u003e\\\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$cursor\u003cspan style=\"color:#e6db74\"\u003e\\\u0026#34;\u0026#34;\u003c/span\u003e SHELL\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003efish \u003cspan style=\"color:#a6e22e\"\u003epnpm\u003c/span\u003e completion-server \u003cspan style=\"color:#a6e22e\"\u003e-- \u003c/span\u003e$cmd\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$completions\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;__tabtab_complete_files__\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eset\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e-l\u003c/span\u003e matches \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ecommandline \u003cspan style=\"color:#a6e22e\"\u003e-ct\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e*\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e[\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e-\u003c/span\u003en \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e$matches\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#a6e22e\"\u003e__fish_complete_path\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003ecommandline \u003cspan style=\"color:#a6e22e\"\u003e-ct\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eelse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003efor\u003c/span\u003e completion \u003cspan style=\"color:#66d9ef\"\u003ein\u003c/span\u003e $completions\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#66d9ef\"\u003eecho\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e-e\u003c/span\u003e $completion\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#66d9ef\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#66d9ef\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eend\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecomplete \u003cspan style=\"color:#a6e22e\"\u003e-f\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e-d\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;pnpm\u0026#39;\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e-c\u003c/span\u003e pnpm \u003cspan style=\"color:#a6e22e\"\u003e-a\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;(_pnpm_completion)\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e###-end-pnpm-completion-###\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"pnpm コマンドのシェル補完"},{"content":"2024-04-13 Updated: eslint-plugin-tailwindcss の章を追加\n2024-09-25 Updated: eslint flat config に対応\nVite で TypeScript の React プロジェクトを作る手順のメモです。\nTailwind や Redux など常に必要なわけではないライブラリも含まれるのでご注意ください。\nプロジェクト作成 以前は Create React App というツールが使われていましたが、現在ではメンテナンスされていないようです。\nCreate React App は役割を終えました Vite にたどり着くまで（Webpack 以降のモジュールバンドラー振り返り） 公式 には Next.js や Remix が推奨されていますが、フレームワークを使わずに始めたい場合は Vite がよく使われるようです。\nはじめに | Vite テンプレートに react-ts を指定して作成します。\npnpm create vite@latest --template react-ts \u0026lt;app-name\u0026gt; .node-version ファイルを作成しておきます。\n# .node-version を作成 node -v \u0026gt; .node-version # もしくは major version のみ記載する場合 node -p \u0026#39;process.versions.node.split(\u0026#34;.\u0026#34;)[0]\u0026#39; \u0026gt; .node-version Prettier Install · Prettier コードフォーマッターです。テンプレートのスタイルに合わせて singleQuote と semi を設定します。\npnpm install -D prettier eslint-config-prettier echo \u0026#39;{ \u0026#34;singleQuote\u0026#34;: true, \u0026#34;semi\u0026#34;: false }\u0026#39; \u0026gt; .prettierrc echo pnpm-lock.yaml \u0026gt; .prettierignore eslint.config.js に追加:\n@@ -3,6 +3,7 @@ import globals from \u0026#39;globals\u0026#39; import reactHooks from \u0026#39;eslint-plugin-react-hooks\u0026#39; import reactRefresh from \u0026#39;eslint-plugin-react-refresh\u0026#39; import tseslint from \u0026#39;typescript-eslint\u0026#39; +import eslintConfigPrettier from \u0026#39;eslint-config-prettier\u0026#39; export default tseslint.config( { ignores: [\u0026#39;dist\u0026#39;] }, @@ -25,4 +26,5 @@ export default tseslint.config( ], }, }, + eslintConfigPrettier, ) package.json の scripts にコマンドを追加:\n@@ -7,7 +7,8 @@ \u0026#34;dev\u0026#34;: \u0026#34;vite\u0026#34;, \u0026#34;build\u0026#34;: \u0026#34;tsc \u0026amp;\u0026amp; vite build\u0026#34;, \u0026#34;lint\u0026#34;: \u0026#34;eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0\u0026#34;, - \u0026#34;preview\u0026#34;: \u0026#34;vite preview\u0026#34; + \u0026#34;preview\u0026#34;: \u0026#34;vite preview\u0026#34;, + \u0026#34;format\u0026#34;: \u0026#34;prettier --write .\u0026#34; }, \u0026#34;dependencies\u0026#34;: { \u0026#34;react\u0026#34;: \u0026#34;^18.2.0\u0026#34;, フォーマットを適用します。\n❯ pnpm run format @trivago/prettier-plugin-sort-imports https://github.com/trivago/prettier-plugin-sort-imports import をソートするプラグインです。\npnpm install -D @trivago/prettier-plugin-sort-imports .pretterrc を修正:\n@@ -1,4 +1,6 @@ { \u0026#34;singleQuote\u0026#34;: true, - \u0026#34;semi\u0026#34;: false + \u0026#34;semi\u0026#34;: false, + \u0026#34;plugins\u0026#34;: [\u0026#34;@trivago/prettier-plugin-sort-imports\u0026#34;], + \u0026#34;importOrder\u0026#34;: [\u0026#34;\u0026lt;THIRD_PARTY_MODULES\u0026gt;\u0026#34;, \u0026#34;^[./]\u0026#34;] } ESLint の追加設定 テンプレートの ESLint の設定はいくつかの recommended 設定が最初から有効ですが、 @typescript-eslint/recommended-type-checked も追加します。\noptional: @ts-check をオンにする eslint.config.js の先頭に @ts-check を追加します。\n@@ -1,3 +1,4 @@ +// @ts-check import js from \u0026#39;@eslint/js\u0026#39; import eslintConfigPrettier from \u0026#39;eslint-config-prettier\u0026#39; import reactHooks from \u0026#39;eslint-plugin-react-hooks\u0026#39; ただし、これを執筆している時点 (2024-09-25) では @ts-check を追加すると react-hooks と rules の箇所でコンパイルエラーが表示されます。 以下のいずれかの対応を選択することになります。\n@ts-check を追加しない エラーを検出できなくなるが、気にしないという判断。 追加しなくても、無効な設定を書いてしまった場合は ESLint がエラーを出すので気付ける。 追加しなくとも、 tseslint.config() の効果 でエディタの TypeScript 補完は効く。 追加した上で、エラーを無視する VSCode などエディタ上でエラーになるだけで、ビルドなどは問題ない。したがってエラーが出る状態にしておき、単に無視する。 将来的にプラグイン側で対応されたら解消するはず。 @typescript-eslint/recommended-type-checked をオンにする 通常の @typescript-eslint/recommended のルールに加え、 TypeScript の型情報を使う設定です。 no-floating-promises などのルールが含まれます。\neslint.config.js を修正:\n@@ -9,7 +9,10 @@ import tseslint from \u0026#39;typescript-eslint\u0026#39; export default tseslint.config( { ignores: [\u0026#39;dist\u0026#39;] }, { - extends: [js.configs.recommended, ...tseslint.configs.recommended], + extends: [ + js.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + ], files: [\u0026#39;**/*.{ts,tsx}\u0026#39;], languageOptions: { ecmaVersion: 2020, parserOptions.project を追加 以下のエラーが起きるようになります。\nOops! Something went wrong! :( ESLint: 9.11.1 Error: Error while loading rule \u0026#39;@typescript-eslint/await-thenable\u0026#39;: You have used a rule which requires parserServices to be generated. You must therefore provide a value for the \u0026#34;parserOptions.project\u0026#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. eslint.config.js に以下の設定を追加すると解消します。\nexport default tseslint.config( { ignores: [\u0026#34;dist\u0026#34;] }, { /* snip */ languageOptions: { ecmaVersion: 2020, globals: globals.browser, parserOptions: { // languageOptions の子として追加 project: [\u0026#34;./tsconfig.app.json\u0026#34;, \u0026#34;./tsconfig.node.json\u0026#34;], }, }, /* snip */ }, eslintConfigPrettier ); tsconfig の noUnused\u0026hellip; をオフ + ESLint の @typescript-eslint/no-unused-vars を warn に noUnusedLocals と noUnusedParameters を無効にし、 ESLint の @typescript-eslint/no-unused-vars を warn にします。\ntsconfig.app.json:\n@@ -16,8 +16,8 @@ /* Linting */ \u0026#34;strict\u0026#34;: true, - \u0026#34;noUnusedLocals\u0026#34;: true, - \u0026#34;noUnusedParameters\u0026#34;: true, + \u0026#34;noUnusedLocals\u0026#34;: false, + \u0026#34;noUnusedParameters\u0026#34;: false, \u0026#34;noFallthroughCasesInSwitch\u0026#34;: true }, \u0026#34;include\u0026#34;: [\u0026#34;src\u0026#34;] eslint.config.js:\nexport default tseslint.config( { ignores: [\u0026#34;dist\u0026#34;] }, { /* snip */ rules: { /* snip */ \u0026#34;@typescript-eslint/no-unused-vars\u0026#34;: \u0026#34;warn\u0026#34;, // rules の子として追加 }, }, eslintConfigPrettier ); この設定は tsconfig と ESLint で重複するため ESLint に任せることにします。また新しい変数を書いたそばからエラーになるのは邪魔に感じるため、個人的には error でなく warn にしたい。\n@typescript-eslint/no-unused-vars は recommended 設定だと error に設定されているため、 warn に変更しています。この場合 CI で warn を許さないようにチェックするとよいでしょう。\n例: CI では error レベルにし、かつ _ 始まりの変数は許容する設定\nconst isCI = process.env.CI; export default tseslint.config( { ignores: [\u0026#34;dist\u0026#34;] }, { /* snip */ rules: { /* snip */ \u0026#34;@typescript-eslint/no-unused-vars\u0026#34;: [ isCI ? \u0026#34;error\u0026#34; : \u0026#34;warn\u0026#34;, { argsIgnorePattern: \u0026#34;_\u0026#34;, varsIgnorePattern: \u0026#34;^_+$\u0026#34;, }, ], }, }, eslintConfigPrettier ); tsconfig の compileOptions noUncheckedIndexedAccess を有効にする noUncheckedIndexedAccess | TypeScript 入門『サバイバル TypeScript』 \u0026quot;strict\u0026quot;: true で有効にならないオプションですが、配列を安全に扱うのに有用なので設定しておきます。\n@@ -18,7 +18,8 @@ \u0026#34;strict\u0026#34;: true, \u0026#34;noUnusedLocals\u0026#34;: false, \u0026#34;noUnusedParameters\u0026#34;: false, - \u0026#34;noFallthroughCasesInSwitch\u0026#34;: true + \u0026#34;noFallthroughCasesInSwitch\u0026#34;: true, + \u0026#34;noUncheckedIndexedAccess\u0026#34;: true }, \u0026#34;include\u0026#34;: [\u0026#34;src\u0026#34;] } Tailwind CSS Install Tailwind CSS with Vite - Tailwind CSS CSS フレームワークです。\npnpm install -D tailwindcss postcss autoprefixer # 設定ファイルは TypeScript を選択 pnpx tailwindcss init --ts --postcss tailwind.config.ts を修正:\nimport type { Config } from \u0026#39;tailwindcss\u0026#39; export default { - content: [], + content: [\u0026#39;./index.html\u0026#39;, \u0026#39;./src/**/*.{js,jsx,ts,tsx}\u0026#39;], theme: { extend: {}, }, plugins: [], } satisfies Config index.css を修正:\n@tailwind base; @tailwind components; @tailwind utilities; tsconfig.node.json に tailwind ファイルを含める この状態で lint を実行すると以下のエラーが発生します。\npnpm run lint #(snip) \u0026lt;snip\u0026gt;/tailwind.config.ts 0:0 error Parsing error: ESLint was configured to run on `\u0026lt;tsconfigRootDir\u0026gt;/tailwind.config.ts` using `parserOptions.project`: - \u0026lt;snip\u0026gt;/tsconfig.json - \u0026lt;snip\u0026gt;/tsconfig.node.json tsconfig.node.json の include に tailwind.config.ts を追加すると解消します。\n@@ -7,5 +7,5 @@ \u0026#34;allowSyntheticDefaultImports\u0026#34;: true, \u0026#34;strict\u0026#34;: true }, - \u0026#34;include\u0026#34;: [\u0026#34;vite.config.ts\u0026#34;] + \u0026#34;include\u0026#34;: [\u0026#34;vite.config.ts\u0026#34;, \u0026#34;tailwind.config.ts\u0026#34;] } prettier-plugin-tailwindcss Editor Setup - Tailwind CSS https://github.com/tailwindlabs/prettier-plugin-tailwindcss className をソートする Tailwind 公式の Prettier プラグインです。\npnpm install -D prettier-plugin-tailwindcss .prettierrc を修正:\n@@ -1,6 +1,9 @@ { \u0026#34;singleQuote\u0026#34;: true, \u0026#34;semi\u0026#34;: false, - \u0026#34;plugins\u0026#34;: [\u0026#34;@trivago/prettier-plugin-sort-imports\u0026#34;], + \u0026#34;plugins\u0026#34;: [ + \u0026#34;@trivago/prettier-plugin-sort-imports\u0026#34;, + \u0026#34;prettier-plugin-tailwindcss\u0026#34; + ], \u0026#34;importOrder\u0026#34;: [\u0026#34;\u0026lt;THIRD_PARTY_MODULES\u0026gt;\u0026#34;, \u0026#34;^[./]\u0026#34;] } clsx https://github.com/lukeed/clsx クラス名を結合する関数を提供するライブラリで、条件付きでクラスを切り替えたりする場合に使います。 同種のツールでより高機能な tailwind-merge が存在しますが、今回は clsx を選択。\nhttps://github.com/dcastil/tailwind-merge pnpm install clsx .pretterrc を修正:\n\u0026#34;plugins\u0026#34;: [\u0026#34;@trivago/prettier-plugin-sort-imports\u0026#34;, \u0026#34;prettier-plugin-tailwindcss\u0026#34;], - \u0026#34;importOrder\u0026#34;: [\u0026#34;\u0026lt;THIRD_PARTY_MODULES\u0026gt;\u0026#34;, \u0026#34;^[./]\u0026#34;] + \u0026#34;importOrder\u0026#34;: [\u0026#34;\u0026lt;THIRD_PARTY_MODULES\u0026gt;\u0026#34;, \u0026#34;^[./]\u0026#34;], + \u0026#34;tailwindFunctions\u0026#34;: [\u0026#34;clsx\u0026#34;] } prettier-plugin-classnames 長いクラス名を改行する Prettier プラグインです。詳細は prettier-plugin-classnames でクラス名を改行する | okiyama.dev を参照。\npnpm install -D prettier-plugin-classnames prettier-plugin-merge .prettierrc を修正:\n@@ -3,8 +3,11 @@ \u0026#34;semi\u0026#34;: false, \u0026#34;plugins\u0026#34;: [ \u0026#34;@trivago/prettier-plugin-sort-imports\u0026#34;, - \u0026#34;prettier-plugin-tailwindcss\u0026#34; + \u0026#34;prettier-plugin-tailwindcss\u0026#34;, + \u0026#34;prettier-plugin-classnames\u0026#34;, + \u0026#34;prettier-plugin-merge\u0026#34; ], \u0026#34;importOrder\u0026#34;: [\u0026#34;\u0026lt;THIRD_PARTY_MODULES\u0026gt;\u0026#34;, \u0026#34;^[./]\u0026#34;], - \u0026#34;tailwindFunctions\u0026#34;: [\u0026#34;clsx\u0026#34;] + \u0026#34;tailwindFunctions\u0026#34;: [\u0026#34;clsx\u0026#34;], + \u0026#34;endingPosition\u0026#34;: \u0026#34;absolute-with-indent\u0026#34; } eslint-plugin-tailwindcss eslint-plugin-tailwindcss - npm ESLint プラグインです。 Tailwind のクラス名以外を検出などのルールがあります。\npnpm install -D eslint-plugin-tailwindcss eslint.config.js を修正:\n// import 追加 import tailwind from \u0026#34;eslint-plugin-tailwindcss\u0026#34;; /* snip */ export default tseslint.config( { ignores: [\u0026#34;dist\u0026#34;] }, ...tailwind.configs[\u0026#34;flat/recommended\u0026#34;], // 追加 { /* snip */ rules: { /* snip */ \u0026#34;tailwindcss/classnames-order\u0026#34;: \u0026#34;off\u0026#34;, // rule 追加 }, }, eslintConfigPrettier ); rules で tailwindcss/classnames-order を off にしているのは、クラス名のソートは prettier-plugin-tailwindcss に任せるためです。\nRedux Toolkit, react-redux Installation | Redux ステート管理ライブラリです。 Redux Toolkit が出てから以前にもまして重量級の雰囲気ですが、 Toolkit の流儀に従っておけばボイラープレートも少なくシンプルに書けるようになっています。\nredux は @reduxjs/toolkit の依存に含まれているため個別にインストールする必要はなく、このふたつだけでよいです。\npnpm install @reduxjs/toolkit react-redux src/redux/store.ts を作成:\nimport { configureStore } from \u0026#34;@reduxjs/toolkit\u0026#34;; export const rootReducer = {}; export const setupStore = () =\u0026gt; { const store = configureStore({ reducer: rootReducer, }); // TODO: hot reloading の設定 return store; }; type AppStore = ReturnType\u0026lt;typeof setupStore\u0026gt;; export type AppState = ReturnType\u0026lt;AppStore[\u0026#34;getState\u0026#34;]\u0026gt;; export type AppDispatch = AppStore[\u0026#34;dispatch\u0026#34;]; src/redux/hooks.ts を作成:\nimport { useDispatch, useSelector } from \u0026#34;react-redux\u0026#34;; import { AppDispatch, AppState } from \u0026#34;./store\u0026#34;; export const useAppDispatch = useDispatch.withTypes\u0026lt;AppDispatch\u0026gt;(); export const useAppSelector = useSelector.withTypes\u0026lt;AppState\u0026gt;(); src/main.tsx を修正:\n@@ -2,9 +2,15 @@ import React from \u0026#39;react\u0026#39; import ReactDOM from \u0026#39;react-dom/client\u0026#39; import App from \u0026#39;./App.tsx\u0026#39; import \u0026#39;./index.css\u0026#39; +import { Provider } from \u0026#39;react-redux\u0026#39; +import { setupStore } from \u0026#39;./redux/store.ts\u0026#39; + +const store = setupStore() ReactDOM.createRoot(document.getElementById(\u0026#39;root\u0026#39;)!).render( \u0026lt;React.StrictMode\u0026gt; - \u0026lt;App /\u0026gt; + \u0026lt;Provider store={store}\u0026gt; + \u0026lt;App /\u0026gt; + \u0026lt;/Provider\u0026gt; \u0026lt;/React.StrictMode\u0026gt;, ) 課題: Redux のホットリロード ホットリロードの設定方法が書かれていますが、 Vite だとうまく動作しませんでした。\nConfiguring Your Store | Redux このように設定してみたのですが、ファイル保存時にページ全体がリロードされてしまう。未解決です。\n// rootReducer を ./reducers.ts に移動したうえで以下を追加 if (import.meta.env.DEV \u0026amp;\u0026amp; import.meta.hot) { import.meta.hot.accept(\u0026#34;./reducers\u0026#34;, async () =\u0026gt; store.replaceReducer((await import(\u0026#34;./reducers\u0026#34;)).rootReducer) ); } 関連するかもしれない Discussion: https://github.com/reduxjs/redux-toolkit/discussions/4281 React Router Tutorial v6.22.3 | React Router ルーターライブラリです。この例は React Router ですが、今新しく始めるなら TanStack Router もよいかもしれません。\npnpm install react-router-dom main.tsx を修正:\nimport React from \u0026#39;react\u0026#39; import ReactDOM from \u0026#39;react-dom/client\u0026#39; import { Provider } from \u0026#39;react-redux\u0026#39; +import { RouterProvider, createBrowserRouter } from \u0026#39;react-router-dom\u0026#39; import App from \u0026#39;./App.tsx\u0026#39; import \u0026#39;./index.css\u0026#39; import { setupStore } from \u0026#39;./redux/store.ts\u0026#39; const store = setupStore() +const router = createBrowserRouter([ + { + path: \u0026#39;/\u0026#39;, + element: \u0026lt;App /\u0026gt;, + }, +]) + ReactDOM.createRoot(document.getElementById(\u0026#39;root\u0026#39;)!).render( \u0026lt;React.StrictMode\u0026gt; \u0026lt;Provider store={store}\u0026gt; - \u0026lt;App /\u0026gt; + \u0026lt;RouterProvider router={router} /\u0026gt; \u0026lt;/Provider\u0026gt; \u0026lt;/React.StrictMode\u0026gt;, ) Vitest Getting Started | Guide | Vitest テストフレームワークです。 DOM ライブラリである happy-dom もインストールします。\npnpm install -D vitest happy-dom package.json の scripts にコマンドを追加:\n@@ -8,7 +8,8 @@ \u0026#34;build\u0026#34;: \u0026#34;tsc -b \u0026amp;\u0026amp; vite build\u0026#34;, \u0026#34;lint\u0026#34;: \u0026#34;eslint .\u0026#34;, \u0026#34;preview\u0026#34;: \u0026#34;vite preview\u0026#34;, - \u0026#34;format\u0026#34;: \u0026#34;prettier --write .\u0026#34; + \u0026#34;format\u0026#34;: \u0026#34;prettier --write .\u0026#34;, + \u0026#34;test\u0026#34;: \u0026#34;vitest\u0026#34; }, \u0026#34;dependencies\u0026#34;: { \u0026#34;clsx\u0026#34;: \u0026#34;^2.1.1\u0026#34;, vitest.config.ts を作成:\nimport { defineConfig, mergeConfig } from \u0026#34;vitest/config\u0026#34;; import viteConfig from \u0026#34;./vite.config\u0026#34;; export default mergeConfig( viteConfig, defineConfig({ test: { globals: true, environment: \u0026#34;happy-dom\u0026#34;, }, }) ); globals: true の設定で describe, test などをインポートせずに使えるようになります。この設定を使う場合は tsconfig の設定も必要です。\ntsconfig.app.json を修正:\n@@ -19,7 +19,10 @@ \u0026#34;noUnusedLocals\u0026#34;: false, \u0026#34;noUnusedParameters\u0026#34;: false, \u0026#34;noFallthroughCasesInSwitch\u0026#34;: true, - \u0026#34;noUncheckedIndexedAccess\u0026#34;: true + \u0026#34;noUncheckedIndexedAccess\u0026#34;: true, + + /* Vitest */ + \u0026#34;types\u0026#34;: [\u0026#34;vitest/globals\u0026#34;] }, \u0026#34;include\u0026#34;: [\u0026#34;src\u0026#34;] } また、このファイルも tsconfig.node.json の include に追加しておきます。\n@@ -7,5 +7,5 @@ \u0026#34;allowSyntheticDefaultImports\u0026#34;: true, \u0026#34;strict\u0026#34;: true }, - \u0026#34;include\u0026#34;: [\u0026#34;vite.config.ts\u0026#34;, \u0026#34;tailwind.config.ts\u0026#34;] + \u0026#34;include\u0026#34;: [\u0026#34;vite.config.ts\u0026#34;, \u0026#34;vitest.config.ts\u0026#34;, \u0026#34;tailwind.config.ts\u0026#34;] } React Testing Library, user-event, jest-dom React Testing Library | Testing Library Installation | Testing Library jest-dom | Testing Library テストコードでの DOM 要素の取得やアサーションに使うライブラリです。\npnpm install -D @testing-library/react @testing-library/user-event @testing-library/jest-dom vitest-setup.ts を追加:\nimport \u0026#34;@testing-library/jest-dom/vitest\u0026#34;; vitest.config.ts を修正:\n@@ -7,6 +7,7 @@ export default mergeConfig( test: { globals: true, environment: \u0026#39;happy-dom\u0026#39;, + setupFiles: [\u0026#39;./vitest-setup.ts\u0026#39;], }, }), ) tsconfig.node.json を修正:\n@@ -18,5 +18,10 @@ \u0026#34;noUnusedParameters\u0026#34;: true, \u0026#34;noFallthroughCasesInSwitch\u0026#34;: true }, - \u0026#34;include\u0026#34;: [\u0026#34;vite.config.ts\u0026#34;, \u0026#34;vitest.config.ts\u0026#34;, \u0026#34;tailwind.config.ts\u0026#34;] + \u0026#34;include\u0026#34;: [ + \u0026#34;vite.config.ts\u0026#34;, + \u0026#34;vitest.config.ts\u0026#34;, + \u0026#34;vitest-setup.ts\u0026#34;, + \u0026#34;tailwind.config.ts\u0026#34; + ] } テストコード 以下のようにテストします。\nsrc/App.test.tsx:\nimport { render, screen, waitFor } from \u0026#34;@testing-library/react\u0026#34;; import userEvent from \u0026#34;@testing-library/user-event\u0026#34;; import { App } from \u0026#34;./App\u0026#34;; const user = userEvent.setup(); it(\u0026#34;App\u0026#34;, async () =\u0026gt; { render(\u0026lt;App /\u0026gt;); await user.click(screen.getByText(\u0026#34;count is 0\u0026#34;)); await waitFor(() =\u0026gt; { screen.getByText(\u0026#34;count is 1\u0026#34;); }); }); MSW Mock Service Worker - API mocking library for browser and Node.js テストで HTTP API, GraphQL API をモック化するのに使います。 React Testing Library のドキュメント でも推奨されていました。\npnpm install -D msw@latest src/__mocks__/server.ts を作成:\nimport { http, HttpResponse } from \u0026#34;msw\u0026#34;; import { setupServer } from \u0026#34;msw/node\u0026#34;; export const server = setupServer( http.get(\u0026#34;https://example.com/greeting\u0026#34;, () =\u0026gt; { return HttpResponse.json({ greeting: \u0026#34;hello there\u0026#34; }); }) ); src/setupTests.ts を作成:\nimport { server } from \u0026#34;./__mocks__/server\u0026#34;; beforeAll(() =\u0026gt; server.listen()); afterEach(() =\u0026gt; server.resetHandlers()); afterAll(() =\u0026gt; server.close()); vitest-setup.ts に追記:\n@@ -1 +1,6 @@ import \u0026#39;@testing-library/jest-dom/vitest\u0026#39; +import { server } from \u0026#39;./src/__mocks__/server\u0026#39; + +beforeAll(() =\u0026gt; server.listen()) +afterEach(() =\u0026gt; server.resetHandlers()) +afterAll(() =\u0026gt; server.close()) 以下のようにテストします。\nimport { HttpResponse, http } from \u0026#34;msw\u0026#34;; import { server } from \u0026#34;../__mocks__/server\u0026#34;; it(\u0026#34;api success\u0026#34;, async () =\u0026gt; { const res = await fetch(\u0026#34;https://example.com/greeting\u0026#34;); expect(await res.json()).toStrictEqual({ greeting: \u0026#34;hello there\u0026#34; }); }); it(\u0026#34;api error\u0026#34;, async () =\u0026gt; { server.use( http.get(\u0026#34;https://example.com/greeting\u0026#34;, () =\u0026gt; { return new HttpResponse(null, { status: 500 }); }) ); const res = await fetch(\u0026#34;https://example.com/greeting\u0026#34;); expect(res.status).toBe(500); }); ","permalink":"https://okiyama.dev/posts/2024-04-06-vite-ts-react/","summary":"\u003cp\u003e\u003cem\u003e2024-04-13 Updated: eslint-plugin-tailwindcss の章を追加\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003e\u003cem\u003e2024-09-25 Updated: eslint flat config に対応\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003eVite で TypeScript の React プロジェクトを作る手順のメモです。\u003c/p\u003e\n\u003cp\u003eTailwind や Redux など常に必要なわけではないライブラリも含まれるのでご注意ください。\u003c/p\u003e\n\u003ch1 id=\"プロジェクト作成\"\u003eプロジェクト作成\u003c/h1\u003e\n\u003cp\u003e以前は Create React App というツールが使われていましたが、現在ではメンテナンスされていないようです。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://zenn.dev/nekoya/articles/dd0f0e8a2fa35f\" target=\"_blank\"\u003eCreate React App は役割を終えました\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://zenn.dev/ishiyama/scraps/a8abf192857f9f\" target=\"_blank\"\u003eVite にたどり着くまで（Webpack 以降のモジュールバンドラー振り返り）\u003c/a\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003ca href=\"https://react.dev/learn/start-a-new-react-project\" target=\"_blank\"\u003e公式\u003c/a\u003e\n には \u003ca href=\"https://nextjs.org/\" target=\"_blank\"\u003eNext.js\u003c/a\u003e\n や \u003ca href=\"https://remix.run/\" target=\"_blank\"\u003eRemix\u003c/a\u003e\n が推奨されていますが、フレームワークを使わずに始めたい場合は \u003ca href=\"https://vitejs.dev/\" target=\"_blank\"\u003eVite\u003c/a\u003e\n がよく使われるようです。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca 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\"\u003eはじめに | Vite\u003c/a\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eテンプレートに \u003ca href=\"https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts\" target=\"_blank\"\u003e\u003ccode\u003ereact-ts\u003c/code\u003e\u003c/a\u003e\n を指定して作成します。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epnpm create vite@latest --template react-ts \u0026lt;app-name\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003e.node-version\u003c/code\u003e ファイルを作成しておきます。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# .node-version を作成\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enode -v \u0026gt; .node-version\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# もしくは major version のみ記載する場合\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enode -p \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;process.versions.node.split(\u0026#34;.\u0026#34;)[0]\u0026#39;\u003c/span\u003e \u0026gt; .node-version\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch1 id=\"prettier\"\u003ePrettier\u003c/h1\u003e\n\u003cp\u003e\u003ca href=\"https://prettier.io/docs/en/install\" target=\"_blank\"\u003eInstall · Prettier\u003c/a\u003e\n\u003c/p\u003e","title":"Vite, TypeScript, React のセットアップ"},{"content":"前回の記事 では Vite に移行しましたが、テストは Jest のままでした。今回は Vitest に移行した際のログです。\nライブラリのインストール happy-dom だと通らなくなるテストがいくつかありました。 screen.getByRole などの取得ができないなどがあり、今回は jsdom で進めることにしました。\nyarn add -D vitest jsdom package.json の scripts を変更しておきます。\n\u0026#34;start\u0026#34;: \u0026#34;vite\u0026#34;, \u0026#34;build\u0026#34;: \u0026#34;tsc \u0026amp;\u0026amp; vite build\u0026#34;, \u0026#34;preview\u0026#34;: \u0026#34;vite preview\u0026#34;, - \u0026#34;test\u0026#34;: \u0026#34;jest\u0026#34;, - \u0026#34;test-coverage\u0026#34;: \u0026#34;jest --coverage --watchAll=false\u0026#34;, + \u0026#34;test\u0026#34;: \u0026#34;vitest\u0026#34;, + \u0026#34;test-coverage\u0026#34;: \u0026#34;vitest --coverage\u0026#34;, \u0026#34;compile\u0026#34;: \u0026#34;tsc\u0026#34;, \u0026#34;lint\u0026#34;: \u0026#34;eslint src/**/*.ts src/**/*.tsx --quiet \u0026amp;\u0026amp; prettier --check .\u0026#34;, \u0026#34;verify-code\u0026#34;: \u0026#34;yarn compile \u0026amp;\u0026amp; yarn lint\u0026#34;, 設定 vitest.config.ts を作成:\nimport { defineConfig, mergeConfig } from \u0026#34;vitest/config\u0026#34;; import viteConfig from \u0026#34;./vite.config\u0026#34;; export default mergeConfig( viteConfig, defineConfig({ test: { globals: true, environment: \u0026#34;jsdom\u0026#34;, }, }) ); globals: true を設定すると、テストコードで describe, it, vi などを import せず使えるようになります。これらの型定義を使えるようにするため tsconfig にも設定が必要です。\ntsconfig.json を修正:\n\u0026#34;strict\u0026#34;: true, //\u0026#34;noUnusedLocals\u0026#34;: true, //\u0026#34;noUnusedParameters\u0026#34;: true, - \u0026#34;noFallthroughCasesInSwitch\u0026#34;: true + \u0026#34;noFallthroughCasesInSwitch\u0026#34;: true, + + /* Vitest */ + \u0026#34;types\u0026#34;: [\u0026#34;vitest/globals\u0026#34;] }, \u0026#34;include\u0026#34;: [\u0026#34;src\u0026#34;, \u0026#34;graphql/codegen.ts\u0026#34;], \u0026#34;references\u0026#34;: [{ \u0026#34;path\u0026#34;: \u0026#34;./tsconfig.node.json\u0026#34; }], また、 vitest.config.ts が型チェックに含まれるようにします。\ntsconfig.node.json を修正:\n\u0026#34;moduleResolution\u0026#34;: \u0026#34;bundler\u0026#34;, \u0026#34;allowSyntheticDefaultImports\u0026#34;: true }, - \u0026#34;include\u0026#34;: [\u0026#34;vite.config.ts\u0026#34;, \u0026#34;.eslintrc.cjs\u0026#34;] + \u0026#34;include\u0026#34;: [\u0026#34;vite.config.ts\u0026#34;, \u0026#34;vitest.config.ts\u0026#34;, \u0026#34;.eslintrc.cjs\u0026#34;] } testPathIgnorePatterns を exclude に移行 Jest 使用時は以下のように testPathIgnorePatterns を正規表現で指定して、ファイル名が _ で始まるファイルを除外していました。\n{ \u0026#34;testPathIgnorePatterns\u0026#34;: [\u0026#34;__tests__/_(.)+.ts(x)?$\u0026#34;] } Vitest の exclude は glob pattern を指定します。また defaultExclude を含めるようにします。\n-import { defineConfig, mergeConfig } from \u0026#39;vitest/config\u0026#39;; +import { defineConfig, mergeConfig, defaultExclude } from \u0026#39;vitest/config\u0026#39;; import viteConfig from \u0026#39;./vite.config\u0026#39;; export default mergeConfig( viteConfig, defineConfig({ test: { globals: true, + exclude: [...defaultExclude, \u0026#39;**/__tests__/_*.ts\u0026#39;, \u0026#39;**/__tests__/_*.tsx\u0026#39;], テストエラー対応 エラー Invalid Chai property: toHaveTextContent toHaveTextContent の呼び出しでエラーになりいました。 これは @testing-library/jest-dom の提供するメソッドです。 setupTests.ts に import を追加し、このファイルをロードするよう設定します。\nsrc/setupTests.ts を作成:\nimport \u0026#34;@testing-library/jest-dom\u0026#34;; vitest.test.config を修正:\ntest: { globals: true, exclude: [...defaultExclude, \u0026#39;**/__tests__/_*.ts\u0026#39;, \u0026#39;**/__tests__/_*.tsx\u0026#39;], environment: \u0026#39;happy-dom\u0026#39;, + setupFiles: [\u0026#39;./src/setupTests.ts\u0026#39;], }, }), ); クラス名が scoped のものになっていてセレクタで取れずアサーション失敗 このアプリケーションでは module css (scss) を使用しています。 Jest で実行していた際は元のクラス名で render されていたのが、 scoped なクラス名になっていました。\n- \u0026lt;div class=\u0026#34;Info\u0026#34; \u0026gt; + \u0026lt;div class=\u0026#34;_Info_78db8b\u0026#34; \u0026gt; これをテストで expect(container.querySelector(\u0026quot;.Info\u0026quot;)).toHavTextContent(...) としていて、取得できずエラーになりました。\nJest を使っていた時は identity-obj-proxy を使ってこの問題に対処していました。 Vitest では以下の設定で対処できます。\nglobals: true, exclude: [...defaultExclude, \u0026#39;**/__tests__/_*.ts\u0026#39;, \u0026#39;**/__tests__/_*.tsx\u0026#39;], environment: \u0026#39;jsdom\u0026#39;, setupFiles: [\u0026#39;./src/setupTests.ts\u0026#39;], + css: { + modules: { + classNameStrategy: \u0026#39;non-scoped\u0026#39;, + }, + }, }, }), ); Configuring Vitest | Vitest jest-global-setup.cjs を移行 jest-global-setup.cjs で dotenv のロードなどをしていたので移行します。 TypeScript ファイルに変更し、 vitest.config.ts でこのファイルをロードするようにします。\nglobals: true, exclude: [...defaultExclude, \u0026#39;**/__tests__/_*.ts\u0026#39;, \u0026#39;**/__tests__/_*.tsx\u0026#39;], environment: \u0026#39;jsdom\u0026#39;, setupFiles: [\u0026#39;./src/setupTests.ts\u0026#39;], + globalSetup: [\u0026#39;./src/vitestGlobalSetup.ts\u0026#39;], css: { modules: { classNameStrategy: \u0026#39;non-scoped\u0026#39;, expected \u0026ldquo;spy\u0026rdquo; to not be called at all, but actually been called 1 times 特定のケースでモックのアサーションが失敗していました。\nAssertionError: expected \u0026#34;spy\u0026#34; to not be called at all, but actually been called 1 times Received: 1st spy call: Array [] Number of calls: 1 モックのリセットをしていないことが原因でした。\nvitest.config.ts を修正:\nglobalSetup: [\u0026#39;./src/vitestGlobalSetup.ts\u0026#39;], css: { modules: { classNameStrategy: \u0026#39;non-scoped\u0026#39;, }, }, + mockReset: true, }, }), ); Vitest テスト実行がハングアップする問題 テストがハングアップする問題が起きました。具体的には、コマンドを実行して特定のテストで止まり、そのまま応答がなくなるもののプロセスは動き続けています。\nsetupTests.ts にモックを追加していたのですが、この定義方法に問題がありました。\nvi.mock(\u0026#39;react-i18next\u0026#39;, () =\u0026gt; ({ useTranslation: () =\u0026gt; ({ t: (key: string) =\u0026gt; key, }), }); useTranslation の戻り値のプロパティ t がコード内で useCallback の依存配列に含まれているとハングアップが発生します。この定義方法だと t が毎回新しい関数オブジェクトになるため、 useCallback が無限ループのような状態になっていたものと思われます。\n以下のように t をトップレベルの変数に変更すると解消しました。\nconst t = (key: string) =\u0026gt; key; vi.mock(\u0026#39;react-i18next\u0026#39;, () =\u0026gt; ({ useTranslation: () =\u0026gt; ({ t, }), }); ちなみに vi.mock はファイル上部に移動されます (巻き上げ)。変数定義を vi.mock よりも前にしたい場合 vi.hoisted を使います。\nconst t = vi.hoisted((key: string) =\u0026gt; key); vi.mock(\u0026#39;react-i18next\u0026#39;, () =\u0026gt; ({ useTranslation: () =\u0026gt; ({ t, }), }); ESM の mock 巻き上げ問題と Vitest の vi.hoisted について テストコード修正 jest 関数を置換していきます。\njest.fn =\u0026gt; vi.fn jest.mock =\u0026gt; vi.mock jest.requireActual =\u0026gt; await vi.importActual jest.spyOn =\u0026gt; vi.spyOn vi.importActual vi.importActual は Promise を返すため以下のような変更が必要でした。\n@@ -33,8 +33,8 @@ import { const mockedNavigate = vi.fn(); -jest.mock(\u0026#39;react-router-dom\u0026#39;, () =\u0026gt; ({ - ...jest.requireActual(\u0026#39;react-router-dom\u0026#39;), +vi.mock(\u0026#39;react-router-dom\u0026#39;, async () =\u0026gt; ({ + ...(await vi.importActual(\u0026#39;react-router-dom\u0026#39;)), useNavigate: () =\u0026gt; mockedNavigate, })); requireActual で複数行にわたるものがなかったため正規表現で一括置換できました。 (ただし外側の関数に async をつけるのは手動)\npattern: jest\\.requireActual\\('(.*?)'\\) replace: (await vi.importActual('$1')) spyOn, mockImplementation mockImplementation に何も渡していないコードがあり、 jest ではこの場合元の処理が使われます。つまりアサーションでコール回数を数えるためだけのために spyOn を使っているということです。\n今回は単に削除で対応できました。\n- const mocked = vi.spyOn(MyClass, \u0026#39;method\u0026#39;).mockImplementation(); + const mocked = vi.spyOn(MyClass, \u0026#39;method\u0026#39;); 型エラー vi.fn\u0026lt;void, []\u0026gt;() -vi.fn\u0026lt;void, []\u0026gt;() +vi.fn\u0026lt;[], []\u0026gt;() close timed out after 10000ms テスト実行の終了後にこのメッセージが表示されて、プロセスが終了しない状態になりました。\nclose timed out after 10000ms Failed to terminate worker while running \u0026lt;snip\u0026gt;.test.tsx. See https://vitest.dev/guide/common-errors.html#failed-to-terminate-worker for troubleshooting. Tests closed successfully but something prevents Vite server from exiting You can try to identify the cause by enabling \u0026ldquo;hanging-process\u0026rdquo; reporter. See https://vitest.dev/config/#reporters ドキュメントの通り vitest.config.ts に pool: 'forks' を設定して解消しました。\ncss: { modules: { classNameStrategy: \u0026#39;non-scoped\u0026#39;, }, }, mockReset: true, + pool: \u0026#39;forks\u0026#39;, }, Common Errors | Guide | Vitest 移行完了 最後に Jest で使っていた設定ファイル、パッケージを削除して完了です。\nrm babel.config.json yarn remove babel-preset-vite identity-obj-proxy ここまでの修正でほぼ全てのテストが通るようになりました。\nJest を使っていた頃と比べると、テストの実行速度が上がっておよそ半分の時間で実行できるようになりました。 Node v18 から v20 へのアップデートでさらに半分になり、当初の 4 倍速くなりました。\n","permalink":"https://okiyama.dev/posts/2024-03-31-migrate-to-vitest-from-jest/","summary":"\u003cp\u003e\u003ca href=\"/posts/2024-03-30-migrate-to-vite-from-create-react-app\"\u003e前回の記事\u003c/a\u003e\n では \u003ca href=\"https://vitejs.dev/\" target=\"_blank\"\u003eVite\u003c/a\u003e\n に移行しましたが、テストは \u003ca href=\"https://jestjs.io/\" target=\"_blank\"\u003eJest\u003c/a\u003e\n のままでした。今回は \u003ca href=\"https://vitest.dev/\" target=\"_blank\"\u003eVitest\u003c/a\u003e\n に移行した際のログです。\u003c/p\u003e\n\u003ch2 id=\"ライブラリのインストール\"\u003eライブラリのインストール\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/capricorn86/happy-dom\" target=\"_blank\"\u003ehappy-dom\u003c/a\u003e\n だと通らなくなるテストがいくつかありました。 \u003ccode\u003escreen.getByRole\u003c/code\u003e などの取得ができないなどがあり、今回は \u003ca href=\"https://github.com/jsdom/jsdom\" target=\"_blank\"\u003ejsdom\u003c/a\u003e\n で進めることにしました。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eyarn add -D vitest jsdom\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003epackage.json\u003c/code\u003e の scripts を変更しておきます。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-diff\" data-lang=\"diff\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e     \u0026#34;start\u0026#34;: \u0026#34;vite\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e     \u0026#34;build\u0026#34;: \u0026#34;tsc \u0026amp;\u0026amp; vite build\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e     \u0026#34;preview\u0026#34;: \u0026#34;vite preview\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e-    \u0026#34;test\u0026#34;: \u0026#34;jest\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e-    \u0026#34;test-coverage\u0026#34;: \u0026#34;jest --coverage --watchAll=false\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003e+    \u0026#34;test\u0026#34;: \u0026#34;vitest\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003e+    \u0026#34;test-coverage\u0026#34;: \u0026#34;vitest --coverage\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003e\u003c/span\u003e     \u0026#34;compile\u0026#34;: \u0026#34;tsc\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e     \u0026#34;lint\u0026#34;: \u0026#34;eslint src/**/*.ts src/**/*.tsx --quiet \u0026amp;\u0026amp; prettier --check .\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e     \u0026#34;verify-code\u0026#34;: \u0026#34;yarn compile \u0026amp;\u0026amp; yarn lint\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"設定\"\u003e設定\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003evitest.config.ts\u003c/code\u003e を作成:\u003c/p\u003e","title":"Jest から Vitest への移行"},{"content":"CRA と react-scripts で構築していた React アプリを Vite に移行した際のログです。\nライブラリのインストール yarn add -D vite @vitejs/plugin-react index.html をルートフォルダに移動 Vite ではここに置く必要があるようです。\nまた public/ ディレクトリにあるファイルへは / でアクセスできるので、以下のような変更をします。\n- \u0026lt;link rel=\u0026#34;icon\u0026#34; href=\u0026#34;%PUBLIC_URL%/favicon.ico\u0026#34; /\u0026gt; + \u0026lt;link rel=\u0026#34;icon\u0026#34; href=\u0026#34;/favicon.ico\u0026#34; /\u0026gt; import path を ~/ で絶対参照できるようにする vite.config.ts で alias を設定します。\nexport default defineConfig({ plugins: [react()], resolve: { alias: [ { find: \u0026#34;~\u0026#34;, replacement: path.resolve(__dirname, \u0026#34;./src\u0026#34;), }, ], }, }); エラー: sass の import ~@ この状態で起動してブラウザで表示すると以下のエラーが発生しました。\n[plugin:vite:css] [sass] Can\u0026#39;t find stylesheet to import. ╷ 6 │ @import \u0026#39;~@progress/kendo-theme-default/scss/core/_index.scss\u0026#39;; │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ╵ src/index.scss 6:9 root stylesheet Error: Can\u0026rsquo;t find stylesheet to import. · Issue #5764 · vitejs/vite · GitHub vitest.config.ts の alias に以下を追加して解消しました。\n{ find: /^~@progress\\/kendo-theme-default/, replacement: \u0026#39;@progress/kendo-theme-default\u0026#39;, }, エラー: sass の import ~/ 起動時に以下のエラーが表示されました。\n[plugin:vite:css] [sass] ENOENT: no such file or directory, open \u0026#39;\u0026lt;snip\u0026gt;/src/themes/mixin\u0026#39; ╷ 1 │ @import \u0026#39;~/src/themes/mixin\u0026#39;; │ ^^^^^^^^^^^^^^^^^^^^ ╵ scss ファイルにある import を以下のように修正して解消しました。\n-@import \u0026#39;~/src/themes/mixin\u0026#39;; +@import \u0026#39;~/themes/mixin\u0026#39;; エラー: process is not defined ブラウザ画面が何も表示されなくなり、 devtools の console にエラーが出力されていました:\nUncaught ReferenceError: process is not defined at index.ts:2:24 Vite では環境変数の扱いが異なるためです。以下の変更で解消しました。\nprocess.env を import.meta.env に変更 環境変数の prefix を REACT_APP_ から VITE_ に変更 react-app-env.d.ts を vite-app-env.d.ts にリネーム＆修正 エラー: require is not defined 一部で require を使っていた箇所があったためエラーになりました。\nUncaught ReferenceError: require is not defined require を全て import に置き換えて解消しました。なお moment のロケールを条件付きで require していた処理があり、以下のように置き換えました。\n+import \u0026#39;moment/locale/ja\u0026#39;; +import moment from \u0026#39;moment\u0026#39;; +moment.locale(\u0026#39;en\u0026#39;); if (i18n.language.startsWith(\u0026#39;ja\u0026#39;)) { - require(\u0026#39;moment/locale/ja\u0026#39;); + moment.locale(\u0026#39;ja\u0026#39;); } Vite だと require() が使えないよ〜 Uncaught TypeError: moment.duration(\u0026hellip;).format is not a function ここまでの変更で yarn start で起動してブラウザで動作することを確認できました。\nしかし、 devtools の console に以下のエラーが出ています。\nUncaught 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 src/index.tsx に以下を追加して解消しました。 ESM になった影響と思われます。\nimport momentDurationFormatSetup from \u0026#34;moment-duration-format\u0026#34;; // eslint-disable-next-line @typescript-eslint/no-explicit-any momentDurationFormatSetup(moment as any); なお Moment.js 自体が現在は非推奨とされているようです。今回のアプリケーションでは date-fns へと段階的に移行しています。\nWe now generally consider Moment to be a legacy project in maintenance mode. It is not dead, but it is indeed done.\nhttps://momentjs.com/docs/#/-project-status/ ESLint エラー: require() of ES Module .eslintrc.js from node_modules/@eslint/eslintrc/dist/eslintrc.cjs not supported. 公式サイトに以下の説明がありました。 .eslintrc.cjs にリネームして解消しました。\nJavaScript (ESM) - use .eslintrc.cjs when running ESLint in JavaScript packages that specify \u0026quot;type\u0026quot;:\u0026quot;module\u0026quot; in their package.json. Note that ESLint does not support ESM configuration at this time.\nConfiguration Files - ESLint - Pluggable JavaScript Linter global is not defined 特定の処理で global is not defined というエラーが発生することがありました。 Webpack ではこの変数がデフォルトで含まれていましたが、 Vite では設定をすることで含まれるようになります。\nvite.config.ts を修正:\n@@ -20,4 +20,7 @@ export default defineConfig({ }, ], }, + define: { + global: \u0026#39;window\u0026#39;, + }, }); vite import dragula error: global is not defined · Issue #2778 · vitejs/vite · GitHub TypeError: Cannot read properties of undefined class 構文を使っている処理で TypeError: Cannot read properties of undefined が発生することがありました。\nTS のコンパイラオプションで useDefineForClassFields というものがあり、Vite+React のテンプレート が true だったためそれに倣って設定していましたが、これにより class のトランスパイル結果が変わってエラーになっていました。\ntsconfig.json を修正:\n@@ -2,7 +2,7 @@ \u0026#34;extends\u0026#34;: \u0026#34;./tsconfig.paths.json\u0026#34;, \u0026#34;compilerOptions\u0026#34;: { \u0026#34;target\u0026#34;: \u0026#34;ES2020\u0026#34;, - \u0026#34;useDefineForClassFields\u0026#34;: true, + \u0026#34;useDefineForClassFields\u0026#34;: false, \u0026#34;lib\u0026#34;: [\u0026#34;ESNext\u0026#34;, \u0026#34;DOM\u0026#34;, \u0026#34;DOM.Iterable\u0026#34;], \u0026#34;module\u0026#34;: \u0026#34;ESNext\u0026#34;, \u0026#34;skipLibCheck\u0026#34;: true, 最近の React では class 構文はほとんど使わなくなっていますが、今回のアプリケーションでは一部にレガシーなコードベースが残っており、その中で以前のトランスパイル結果に依存している箇所があったようです。\n特徴 | Vite TypeScript v3.7.2 変更点 #TypeScript - Qiita デプロイ関連のエラー ここまでの変更でローカルでは問題なく動作するようになりました。次にデプロイ周りで起きた問題を記載します。\nCI デプロイジョブがエラー: The user-provided path ./build does not exist ビルド結果の出力先ディレクトリ名が Vite のデフォルトは dist になっています。今までは build だったので、デプロイコマンドを変更しました。\nCloudflare の最適化で JS ファイルが壊れる デプロイはできたものの、アクセスしても何も表示されません。 devtools の console には以下のように出力されていました。\nindex-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 エラーの発生箇所を見ると、 minify されたコードですが 1.toString という箇所でエラーになっていました。\n...Lot=Math.random(),Rot=jot(1.toString),OA=function(e){return\u0026#34;Symbol(... ^^^^^^^^^^ 原因は Cloudflare の最適化機能である minify によって JavaScript ファイルが変わってしまっていたためでした。\n\u0026ldquo;Auto Minify\u0026rdquo; breaks javascript syntax このアプリケーションは AWS S3 でホストして Cloudflare で配信する構成になっています。 Cloudflare の設定で minify をオフにすることで解消しました。1\n元のコード Rot=jot(1 .toString) のスペースが除去されて Rot=jot(1.toString) になって SyntaxError が起きていたようです。\nこれは esbuild によるビルド特有のようで、 Vite はビルドに esbuild を使用しているためこれが起きるようになりました。\nCloudflare Auto Minify breaks esbuild minified JS Cloudflare Minification breaks esbuild\u0026rsquo;s \u0026ldquo;1 .toString\u0026rdquo; · Issue #3116 · evanw/esbuild · GitHub そもそもビルド時に minify しているので Cloudflare 側でさらに minify する意味はなかったわけですが、たまたまオンにしてしまっていました。\nところで 1 .toString というコードは何を意味するのでしょうか。 minify された結果なのではっきりとはわかりませんが、 Number.prototype.toString を関数として取り出しているように見えます。書き方としては (1).toString と等価で、 1 .toString の方が 1 文字節約できるため esbuild がこのような出力をしているのだと思われます。\nWhat happened inside of (1).toString() and 1.toString() in Javascript Browserslist を Vite で使用するための設定 Vite では browserslist を使うのに設定が必要です。\nBrowserslist support · vitejs vite · Discussion #6849 yarn add -D browserslist-to-esbuild vite.config.ts に以下を追加:\nexport default defineConfig({ plugins: [react()], + build: { + target: + process.env.NODE_ENV === \u0026#39;production\u0026#39; \u0026amp;\u0026amp; + browserslistToEsbuild([\u0026#39;\u0026gt;0.2%\u0026#39;, \u0026#39;not dead\u0026#39;, \u0026#39;not op_mini all\u0026#39;]), + }, 設定変更: ie11 を削除 上記の設定では既に削除されていますが、 ie11 があると Maximum call stack size というエラーが発生するため削除しました。 (既に Internet Explorer はサポート対象外だったのですが、 Browserslist 設定に残ってしまっていた)\nエラーログ:\n✓ 5956 modules transformed. ✓ built in 6m 29s error during build: RangeError: Maximum call stack size exceeded at String.substring (\u0026lt;anonymous\u0026gt;) at replaceClose (file:///\u0026lt;snip\u0026gt;/node_modules/vite/dist/node/chunks/dep-R0I0XnyH.js:109:21) at replaceClose (file:///\u0026lt;snip\u0026gt;/node_modules/vite/dist/node/chunks/dep-R0I0XnyH.js:112:30) at replaceClose (file:///\u0026lt;snip\u0026gt;/node_modules/vite/dist/node/chunks/dep-R0I0XnyH.js:112:30) at replaceClose (file:///\u0026lt;snip\u0026gt;/node_modules/vite/dist/node/chunks/dep-R0I0XnyH.js:112:30) at replaceClose (file:///\u0026lt;snip\u0026gt;/node_modules/vite/dist/node/chunks/dep-R0I0XnyH.js:112:30) at replaceClose (file:///\u0026lt;snip\u0026gt;/node_modules/vite/dist/node/chunks/dep-R0I0XnyH.js:112:30) at replaceClose (file:///\u0026lt;snip\u0026gt;/node_modules/vite/dist/node/chunks/dep-R0I0XnyH.js:112:30) at replaceClose (file:///\u0026lt;snip\u0026gt;/node_modules/vite/dist/node/chunks/dep-R0I0XnyH.js:112:30) at replaceClose (file:///\u0026lt;snip\u0026gt;/node_modules/vite/dist/node/chunks/dep-R0I0XnyH.js:112:30) Jest 関連の移行 のちに Vitest へと移行するのですが、まずは Jest のまま動かすことにしました。\nJest をインストールしてみたがうまく動かない インストールして多少の設定をしてみましたが、うまく動きませんでした。\nyarn add -D jest @types/jest テスト実行するとエラー:\nReferenceError: Jest: Got error running globalSetup - \u0026lt;snip\u0026gt;/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 \u0026#39;.js\u0026#39; file extension and \u0026#39;\u0026lt;snip\u0026gt;/package.json\u0026#39; contains \u0026#34;type\u0026#34;: \u0026#34;module\u0026#34;. To treat it as a CommonJS script, rename it to use the \u0026#39;.cjs\u0026#39; file extension. at file:///\u0026lt;snip\u0026gt;/jest-global-setup.js:3:1 at ModuleJob.run (node:internal/modules/esm/module_job:194:25) → jest-global-setup.cjs にリネーム。ファイル内容も変更が必要で、 require を import に置き換えました。\nこの状態でテスト実行すると走り始めるようになりましたが、すべてのテストケースが以下のエラーになります。\nJest encountered an unexpected token\nJest 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.\nOut of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.\nこのアプローチは諦めて、 eject して必要な設定をしていくことにしました。\neject 実行時のエラー eject コマンドを実行したところエラーになりました。\n❯ yarn run eject 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 ✔ Are you sure you want to eject? This action is permanent. … yes Ejecting... Out of the box, Create React App only supports overriding these Jest options: • clearMocks • collectCoverageFrom • coveragePathIgnorePatterns • coverageReporters • coverageThreshold • displayName • extraGlobals • globalSetup • globalTeardown • moduleNameMapper • resetMocks • resetModules • restoreMocks • snapshotSerializers • testMatch • transform • transformIgnorePatterns • watchPathIgnorePatterns. These options in your package.json Jest configuration are not currently supported by Create React App: • testPathIgnorePatterns • collectCoverage If you wish to override other Jest options, you need to eject from the default setup. You can do 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. Jest の config から testPathIgnorePatterns と collectCoverage を削除して再実行すると eject できました。\neject した設定から必要なものを残す その後はこちらの記事の Jest の CRA 依存を外す に書いてあるとおり修正を行い、正常にテスト実行ができるようになりました。内容そのままなので詳細は記事に譲ります。\ncreate-react-app から Vite への移行 Vitest 移行 まずは Jest 環境を維持したまま移行したのですが、その後 Vitest に移行しました。次の記事を参照してください。\nJest から Vitest への移行 正確には Cloudflare の設定変更後、再度デプロイすると解消しました。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://okiyama.dev/posts/2024-03-30-migrate-to-vite-from-create-react-app/","summary":"\u003cp\u003e\u003ca href=\"https://create-react-app.dev/\" target=\"_blank\"\u003eCRA\u003c/a\u003e\n と \u003ca href=\"https://www.npmjs.com/package/react-scripts\" target=\"_blank\"\u003ereact-scripts\u003c/a\u003e\n で構築していた React アプリを \u003ca href=\"https://vitejs.dev/\" target=\"_blank\"\u003eVite\u003c/a\u003e\n に移行した際のログです。\u003c/p\u003e\n\u003ch2 id=\"ライブラリのインストール\"\u003eライブラリのインストール\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eyarn add -D vite @vitejs/plugin-react\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"indexhtml-をルートフォルダに移動\"\u003eindex.html をルートフォルダに移動\u003c/h2\u003e\n\u003cp\u003eVite ではここに置く必要があるようです。\u003c/p\u003e\n\u003cp\u003eまた \u003ccode\u003epublic/\u003c/code\u003e ディレクトリにあるファイルへは \u003ccode\u003e/\u003c/code\u003e でアクセスできるので、以下のような変更をします。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-diff\" data-lang=\"diff\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e- \u0026lt;link rel=\u0026#34;icon\u0026#34; href=\u0026#34;%PUBLIC_URL%/favicon.ico\u0026#34; /\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003e+ \u0026lt;link rel=\u0026#34;icon\u0026#34; href=\u0026#34;/favicon.ico\u0026#34; /\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"import-path-を--で絶対参照できるようにする\"\u003eimport path を \u003ccode\u003e~/\u003c/code\u003e で絶対参照できるようにする\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003evite.config.ts\u003c/code\u003e で alias を設定します。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-ts\" data-lang=\"ts\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eexport\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003edefault\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003edefineConfig\u003c/span\u003e({\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eplugins\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e [\u003cspan style=\"color:#a6e22e\"\u003ereact\u003c/span\u003e()],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eresolve\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    \u003cspan style=\"color:#a6e22e\"\u003ealias\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003efind\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;~\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003ereplacement\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003epath.resolve\u003c/span\u003e(\u003cspan style=\"color:#a6e22e\"\u003e__dirname\u003c/span\u003e, \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;./src\u0026#34;\u003c/span\u003e),\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  },\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e});\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"エラー-sass-の-import-\"\u003eエラー: sass の import ~@\u003c/h2\u003e\n\u003cp\u003eこの状態で起動してブラウザで表示すると以下のエラーが発生しました。\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e[plugin:vite:css] [sass] Can\u0026#39;t find stylesheet to import.\n  ╷\n6 │ @import \u0026#39;~@progress/kendo-theme-default/scss/core/_index.scss\u0026#39;;\n  │         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  ╵\n  src/index.scss 6:9  root stylesheet\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e\u003ca href=\"https://github.com/vitejs/vite/issues/5764\" title=\"https://github.com/vitejs/vite/issues/5764\" target=\"_blank\"\u003eError: Can\u0026rsquo;t find stylesheet to import. · Issue #5764 · vitejs/vite · GitHub\u003c/a\u003e\n\u003c/p\u003e","title":"CRA から Vite への移行"},{"content":"Tailwind CSS はクラス名の記述が長くなりがちという問題がある。\n\u0026lt;div className=\u0026#34;transform bg-red-500 text-center text-xl text-red-900 duration-500 ease-in hover:bg-blue-500 hover:text-blue-900\u0026#34;\u0026gt; ... \u0026lt;/div\u0026gt; 改行を入れることで多少改善するが、 Prettier にやって欲しいところ。\n\u0026lt;div className=\u0026#34;transform bg-red-500 text-center text-xl text-red-900 duration-500 ease-in hover:bg-blue-500 hover:text-blue-900\u0026#34; \u0026gt; ... \u0026lt;/div\u0026gt; 公式の Prettier プラグインでも検討はされているが実装されていない1。\n非公式であるが prettier-plugin-classnames というプラグインがクラス名を改行する機能を提供している。 (Discussion のコメント より)\nprettier-plugin-classnames - npm 公式のプラグインと併用するには prettier-plugin-merge も必要。\nprettier-plugin-merge - npm pnpm install -D prettier-plugin-classnames prettier-plugin-merge .prettierrc:\n{ \u0026#34;singleQuote\u0026#34;: true, \u0026#34;semi\u0026#34;: false, - \u0026#34;plugins\u0026#34;: [\u0026#34;prettier-plugin-tailwindcss\u0026#34;] + \u0026#34;plugins\u0026#34;: [ + \u0026#34;prettier-plugin-tailwindcss\u0026#34;, + \u0026#34;prettier-plugin-classnames\u0026#34;, + \u0026#34;prettier-plugin-merge\u0026#34; + ], + \u0026#34;endingPosition\u0026#34;: \u0026#34;absolute-with-indent\u0026#34;, \u0026#34;tailwindFunctions\u0026#34;: [\u0026#34;clsx\u0026#34;] } https://github.com/rokiyama/example-vite-react-app/blob/507b5f91df552016413fa53217f8f6d27c861c47/.prettierrc endingPosition は改行位置を決めるもの。 relative, absolute, absolute-with-indent のいずれかを設定する。 デフォルトは relative であるが、 absolute-with-indent が自然な挙動に思えたので設定。\n一度実装されたが revert されたらしい: Tailwind CSS のクラス属性長くなりがちな問題について \u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://okiyama.dev/posts/2024-03-17-prettier-plugin-classnames/","summary":"\u003cp\u003eTailwind CSS はクラス名の記述が長くなりがちという問題がある。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-jsx\" data-lang=\"jsx\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;\u003cspan style=\"color:#f92672\"\u003ediv\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003eclassName\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;transform bg-red-500 text-center text-xl text-red-900 duration-500 ease-in hover:bg-blue-500 hover:text-blue-900\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;/\u003cspan style=\"color:#f92672\"\u003ediv\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e改行を入れることで多少改善するが、 Prettier にやって欲しいところ。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-jsx\" data-lang=\"jsx\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;\u003cspan style=\"color:#f92672\"\u003ediv\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#a6e22e\"\u003eclassName\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;transform bg-red-500 text-center text-xl text-red-900\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#e6db74\"\u003e    duration-500 ease-in hover:bg-blue-500 hover:text-blue-900\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ...\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026lt;/\u003cspan style=\"color:#f92672\"\u003ediv\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e公式の Prettier プラグインでも検討はされているが実装されていない\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e。\u003c/p\u003e\n\u003cp\u003e非公式であるが \u003ccode\u003eprettier-plugin-classnames\u003c/code\u003e というプラグインがクラス名を改行する機能を提供している。 (\u003ca href=\"https://github.com/tailwindlabs/tailwindcss/discussions/7763#discussioncomment-7904679\" target=\"_blank\"\u003eDiscussion のコメント\u003c/a\u003e\n より)\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.npmjs.com/package/prettier-plugin-classnames\" target=\"_blank\"\u003eprettier-plugin-classnames - npm\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003e公式のプラグインと併用するには \u003ccode\u003eprettier-plugin-merge\u003c/code\u003e も必要。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.npmjs.com/package/prettier-plugin-merge\" target=\"_blank\"\u003eprettier-plugin-merge - npm\u003c/a\u003e\n\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epnpm install -D prettier-plugin-classnames prettier-plugin-merge\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ccode\u003e.prettierrc\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-diff\" data-lang=\"diff\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e   \u0026#34;singleQuote\u0026#34;: true,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e   \u0026#34;semi\u0026#34;: false,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e-  \u0026#34;plugins\u0026#34;: [\u0026#34;prettier-plugin-tailwindcss\u0026#34;]\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#f92672\"\u003e\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003e+  \u0026#34;plugins\u0026#34;: [\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003e+    \u0026#34;prettier-plugin-tailwindcss\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003e+    \u0026#34;prettier-plugin-classnames\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003e+    \u0026#34;prettier-plugin-merge\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003e+  ],\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003e+  \u0026#34;endingPosition\u0026#34;: \u0026#34;absolute-with-indent\u0026#34;,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003e\u003c/span\u003e   \u0026#34;tailwindFunctions\u0026#34;: [\u0026#34;clsx\u0026#34;]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e }\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e\u003ca href=\"https://github.com/rokiyama/example-vite-react-app/blob/507b5f91df552016413fa53217f8f6d27c861c47/.prettierrc\" target=\"_blank\"\u003ehttps://github.com/rokiyama/example-vite-react-app/blob/507b5f91df552016413fa53217f8f6d27c861c47/.prettierrc\u003c/a\u003e\n\u003c/p\u003e","title":"prettier-plugin-classnames でクラス名を改行する"},{"content":"レガシーな Redux コードを移行する機会があったのでメモ\n基本的に Migrating to Modern Redux という公式のガイドの通り。 比較的スムーズに移行できたポイントとして、古い書き方と現代的な書き方を共存したまま進めることができるのが大きかった。\nMany users are working on older Redux codebases that have been around since before these \u0026ldquo;modern Redux\u0026rdquo; patterns existed. Migrating those codebases to today\u0026rsquo;s recommended modern Redux patterns will result in codebases that are much smaller and easier to maintain.\nThe good news is that you can migrate your code to modern Redux incrementally, piece by piece, with old and new Redux code coexisting and working together!\nhttps://redux.js.org/usage/migrating-to-modern-redux#overview 移行作業の概要 大まかな流れは以下の通り:\nredux, react-redux のバージョン最新化と @reduxjs/toolkit のインストール createStore から configureStore への移行 connect から Hooks API への移行 action, actionCreator, reducer の構成から createSlice または RTK Query への移行 1, 2 は npm の依存関係とアプリケーションの初期化プロセスの変更で、主に一箇所を書き換えるだけの変更。 3, 4 は機能ごとに書き換えの作業が必要だった。\nすべての機能を移行できたわけではなく、 3, 4 の変更は段階的に進めていてレガシーな部分も残っている。 移行が終わったコンポーネントはテストも書きやすくなったが、古い構成の Redux コードに依存しているコンポーネントはテストコードも以前のままである。\nユニットテストについては Enzyme から React Testing Library と MSW の導入も必要だった。\nその他の公式ドキュメント Writing Tests | Redux ユニットテストで preloadedState を渡せる configureStore の書き方 Usage With TypeScript | Redux Define Typed Hooks に Hooks API にアプリケーション固有の useAppSelector, useAppDispatcher 型を作る例 Tutorials Overview | Redux Toolkit Redux とは別のリポジトリに Redux Toolkit のドキュメントがある。 createSlice や RTK Query の詳しい使い方はこちらを参照 Writing Reducers with Immer | Redux Toolkit reducer に渡されるステートが Immer のオブジェクトになっている ","permalink":"https://okiyama.dev/posts/2024-03-16-migrating-to-modern-redux/","summary":"\u003cp\u003eレガシーな Redux コードを移行する機会があったのでメモ\u003c/p\u003e\n\u003cp\u003e基本的に \u003ca href=\"https://redux.js.org/usage/migrating-to-modern-redux\" target=\"_blank\"\u003eMigrating to Modern Redux\u003c/a\u003e\n という公式のガイドの通り。\n比較的スムーズに移行できたポイントとして、古い書き方と現代的な書き方を共存したまま進めることができるのが大きかった。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eMany users are working on older Redux codebases that have been around since before these \u0026ldquo;modern Redux\u0026rdquo; patterns existed. Migrating those codebases to today\u0026rsquo;s recommended modern Redux patterns will result in codebases that are much smaller and easier to maintain.\u003c/p\u003e\n\u003cp\u003eThe good news is that \u003cstrong\u003eyou can migrate your code to modern Redux incrementally, piece by piece, with old and new Redux code coexisting and working together!\u003c/strong\u003e\u003c/p\u003e","title":"レガシーな Redux コードの移行"},{"content":"このサイトのドメイン名 okiyama.dev を Google Domains から Vercel に移管しました。\nもともと Google Domains で購入したドメイン名でしたが、 Google Domains のサービス終了がアナウンスされ Squarespace に移管される予定になっていました。\nSquarespace への Google Domains のドメイン登録の譲渡について - Google Domains ヘルプ そのまま Squarespace に移行しても構わなかったのですが、ホスティングを Vercel で行っている関係でドメインの管理も同じサービスに任せることにしました。\n以下の手順で移管を行いました。\nGoogle Domains ドメインのロックを解除 認証コードを取得 Vercel クレジットカードを登録しておく (設定画面 → Payment method) Domains から Transfer In よりドメイン名と認証コードを入力 元のレジストラの確認待ちと、カスタム DNS ゾーンの設定移行を促すダイアログが表示される 今回はゾーンの設定は不要なので、移行が完了するまで待つ Google Domains 移管の承認・キャンセルのメールが届くので、リンクから承認する Vercel ドメイン移管完了のメールが届く 今回は Google Domains から承認確認のメールが届くまで 10 分程度、 Vercel から移管完了のメールが届くまで 1 時間程度かかりました。\n参考\nGoogle Domains から別の登録事業者に移管する - Google Domains ヘルプ Transferring Domains to Another Team or Project ","permalink":"https://okiyama.dev/posts/2024-02-18-migrate-from-google-domains-to-vercel/","summary":"\u003cp\u003eこのサイトのドメイン名 \u003ccode\u003eokiyama.dev\u003c/code\u003e を Google Domains から Vercel に移管しました。\u003c/p\u003e\n\u003cp\u003eもともと Google Domains で購入したドメイン名でしたが、 Google Domains のサービス終了がアナウンスされ Squarespace に移管される予定になっていました。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://support.google.com/domains/answer/13689670?hl=ja\" target=\"_blank\"\u003eSquarespace への Google Domains のドメイン登録の譲渡について - Google Domains ヘルプ\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003eそのまま Squarespace に移行しても構わなかったのですが、ホスティングを Vercel で行っている関係でドメインの管理も同じサービスに任せることにしました。\u003c/p\u003e\n\u003cp\u003e以下の手順で移管を行いました。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eGoogle Domains\n\u003cul\u003e\n\u003cli\u003eドメインのロックを解除\u003c/li\u003e\n\u003cli\u003e認証コードを取得\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eVercel\n\u003cul\u003e\n\u003cli\u003eクレジットカードを登録しておく (設定画面 → Payment method)\u003c/li\u003e\n\u003cli\u003eDomains から Transfer In よりドメイン名と認証コードを入力\u003c/li\u003e\n\u003cli\u003e元のレジストラの確認待ちと、カスタム DNS ゾーンの設定移行を促すダイアログが表示される\n\u003cul\u003e\n\u003cli\u003e今回はゾーンの設定は不要なので、移行が完了するまで待つ\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eGoogle Domains\n\u003cul\u003e\n\u003cli\u003e移管の承認・キャンセルのメールが届くので、リンクから承認する\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eVercel\n\u003cul\u003e\n\u003cli\u003eドメイン移管完了のメールが届く\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e今回は Google Domains から承認確認のメールが届くまで 10 分程度、 Vercel から移管完了のメールが届くまで 1 時間程度かかりました。\u003c/p\u003e\n\u003cp\u003e参考\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://support.google.com/domains/answer/3251178?sjid=1035049167829836067-AP\" target=\"_blank\"\u003eGoogle Domains から別の登録事業者に移管する - Google Domains ヘルプ\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://vercel.com/docs/projects/domains/transfer-your-domain#transfer-a-domain-to-vercel\" target=\"_blank\"\u003eTransferring Domains to Another Team or Project\u003c/a\u003e\n\u003c/li\u003e\n\u003c/ul\u003e","title":"ドメイン名を Google Domains から Vercel に移管した"},{"content":"TL;DR expo install --check で TypeScript のバージョンを上げたら、 eslint が動かなくなった @typescript-eslint/eslint-plugin を v6 に上げる必要があるが @react-native/eslint-config が v5 系に依存しているためアップデートできない TypeScript のバージョンを 5.0.x に落としたら ESLint が実行できるようになった expo install \u0026ndash;check とは Expo CLI に用意されている expo install --check は、バージョンを検証・修正するコマンドです。\nhttps://docs.expo.dev/more/expo-cli/#version-validation ローカルで実行すると対話式で修正でき、 CI ではチェック結果に応じてエラーになります。\n最近、 Expo SDK のアップデートに合わせて実行したところ、 TypeScript のバージョンが上がりました。\n❯ npx expo install --check Some dependencies are incompatible with the installed expo version: typescript@5.0.4 - expected version: ^5.1.3 Your project may not work correctly until you install the correct versions of the packages. Fix with: npx expo install --fix ✔ Fix dependencies? … yes › Installing 1 SDK 49.0.0 compatible native module using npm \u0026gt; npm install # snip changed 1 package, and audited 1600 packages in 9s (実際には SDK アップデートと同時に行ったため、もっとログがありました。上記は後日再実行した際のログです)\n上記の通り TypeScript のバージョンが上がりましたが、これにより ESLint が動かなくなりました。\n❯ npm run lint \u0026gt; eslint ./src ============= WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree. You may find that it works just fine, or you may not. SUPPORTED TYPESCRIPT VERSIONS: \u0026gt;=3.3.1 \u0026lt;5.1.0 YOUR TYPESCRIPT VERSION: 5.2.2 Please only submit bug reports when using the officially supported version. 確認すると、 @typescript-eslint/eslint-plugin のバージョンが古く、最新の v6 系ではなく v5 系を使っていました。\nv6 系にアップデートしようとしましたが、 @react-native-community/eslint-config@3.2.0 が v5 系に依存しておりアップデートできません。そもそもこのパッケージ自体が古く、 React Native 0.72 で名称が変更されています。\nhttps://reactnative.dev/blog/2023/06/21/0.72-metro-package-exports-symlinks#package-renames OLD: @react-native-community/eslint-config NEW: @react-native/eslint-config というわけで @react-native/eslint-config の最新をインストールしましたが、こちらも v5 系に依存していました。リポジトリを見たところ以下のようになっています。\nrevision directory package name version 依存している @typescript-eslint/eslint-plugin のバージョン main packages/eslint-config-react-native @react-native/eslint-config 0.73.0 ^5.57.1 v0.72.4 packages/eslint-config-react-native-community @react-native/eslint-config 0.72.2 ^5.30.5 ディレクトリ名が異なる理由は不明ですが、パッケージ名は一致しています。 npm.js によると 0.72.2 が最新で、 0.73.0 は nightly となっています。いずれにせよ、どちらも @typescript-eslint/eslint-plugin の v5 系に依存しています。\n依存関係を無視して @typescript-eslint/eslint-plugin を最新化することも考えられますが、今回は手っ取り早く対応するため TypeScript のバージョンを下げます。 ESLint のエラーメッセージによると \u0026lt;5.1.0 が必要なので、 v5.0.x 系にします。\n❯ npm install -D typescript@~5.0.0 changed 1 package, and audited 1600 packages in 2s ESLint が実行できるようになりました。\n❯ npm run lint \u0026gt; expo-chat-command-gpt@1.0.0 lint \u0026gt; eslint ./src Warning: React version not specified in eslint-plugin-react settings. See https://github.com/jsx-eslint/eslint-plugin-react#configuration . 実際のコミット https://github.com/rokiyama/gpt-prompter-frontend/commit/2c877c25f13ac8de4bc38d1139bc8591b6ca65cd ","permalink":"https://okiyama.dev/posts/2023-09-16-expo-typescript-eslint-versions/","summary":"\u003ch2 id=\"tldr\"\u003eTL;DR\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eexpo install --check\u003c/code\u003e で TypeScript のバージョンを上げたら、 eslint が動かなくなった\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003e@typescript-eslint/eslint-plugin\u003c/code\u003e を v6 に上げる必要があるが \u003ccode\u003e@react-native/eslint-config\u003c/code\u003e が v5 系に依存しているためアップデートできない\u003c/li\u003e\n\u003cli\u003eTypeScript のバージョンを 5.0.x に落としたら ESLint が実行できるようになった\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"expo-install-check-とは\"\u003eexpo install \u0026ndash;check とは\u003c/h2\u003e\n\u003cp\u003eExpo CLI に用意されている \u003ccode\u003eexpo install --check\u003c/code\u003e は、バージョンを検証・修正するコマンドです。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://docs.expo.dev/more/expo-cli/#version-validation\" target=\"_blank\"\u003ehttps://docs.expo.dev/more/expo-cli/#version-validation\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003eローカルで実行すると対話式で修正でき、 CI ではチェック結果に応じてエラーになります。\u003c/p\u003e\n\u003cp\u003e最近、 Expo SDK のアップデートに合わせて実行したところ、 TypeScript のバージョンが上がりました。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e❯ npx expo install --check\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eSome dependencies are incompatible with the installed expo version:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  typescript@5.0.4 - expected version: ^5.1.3\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eYour project may not work correctly \u003cspan style=\"color:#66d9ef\"\u003euntil\u003c/span\u003e you install the correct versions of the packages.\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eFix with: npx expo install --fix\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e✔ Fix dependencies? … yes\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e› Installing \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e SDK 49.0.0 compatible native module using npm\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u0026gt; npm install\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# snip\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003echanged \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e package, and audited \u003cspan style=\"color:#ae81ff\"\u003e1600\u003c/span\u003e packages in 9s\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e(実際には SDK アップデートと同時に行ったため、もっとログがありました。上記は後日再実行した際のログです)\u003c/p\u003e","title":"expo install --check で TypeScript をアップデートしたら ESLint が動かなくなったのでダウングレードした"},{"content":"要約:\nしばらく Go に触れていなかった 最近 Xcode 15 beta 6 をインストールした VSCode で Go のプロジェクトを開くと gopls のエラーが出た issue のコメント に従って gopls を一度削除して Homebrew で入れ直したら解消した VSCode で Go のプロジェクトを開くとエラーが２つ通知されました。\nThe gopls server failed to initialize. gopls client: couldn\u0026#39;t create connection to server. コマンドラインで gopls を実行してみるとエラーでした。\n❯ gopls version R PAKAYAH LI LETTER PHAfish: Job 1, \u0026#39;gopls version\u0026#39; terminated by signal SIGSEGV (Address boundary error) Issue が上がっていました。\nLanguage server fails to start · Issue #2909 · golang/vscode-go · GitHub runtime: gopls -v crashes immediately when linked with Xcode 15 beta · Issue #61190 · golang/go · GitHub 一度 Go modules でインストールされたバイナリを削除して、 Homebrew のバージョンをインストールし直すと解消するようです。\n# 削除 ❯ rm ~/go/bin/gopls # Homebrew でインストール ❯ brew install gopls # (snip) ==\u0026gt; Fetching gopls ==\u0026gt; Downloading https://ghcr.io/v2/homebrew/core/gopls/manifests/0.13.2 ######################################################################################################################################################### 100.0% ==\u0026gt; Downloading https://ghcr.io/v2/homebrew/core/gopls/blobs/sha256:a70553eebb2218b4062c6b452eb7a5168e33224eaa396e847c45abb1825fbf5e ######################################################################################################################################################### 100.0% ==\u0026gt; Pouring gopls--0.13.2.arm64_ventura.bottle.tar.gz 🍺 /opt/homebrew/Cellar/gopls/0.13.2: 5 files, 25.9MB # 正常になった ❯ gopls version golang.org/x/tools/gopls v0.13.2 golang.org/x/tools/gopls@(devel) この後、 VSCode で Reload Window を実行すると正常になりました。\n","permalink":"https://okiyama.dev/posts/2023-08-26-xcode15-beta-gopls-error/","summary":"\u003cp\u003e要約:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eしばらく Go に触れていなかった\u003c/li\u003e\n\u003cli\u003e最近 Xcode 15 beta 6 をインストールした\u003c/li\u003e\n\u003cli\u003eVSCode で Go のプロジェクトを開くと gopls のエラーが出た\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/golang/go/issues/61190#issuecomment-1663426102\" target=\"_blank\"\u003eissue のコメント\u003c/a\u003e\nに従って gopls を一度削除して Homebrew で入れ直したら解消した\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eVSCode で Go のプロジェクトを開くとエラーが２つ通知されました。\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003eThe gopls server failed to initialize.\n\u003c/code\u003e\u003c/pre\u003e\u003cpre tabindex=\"0\"\u003e\u003ccode\u003egopls client: couldn\u0026#39;t create connection to server.\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eコマンドラインで \u003ccode\u003egopls\u003c/code\u003e を実行してみるとエラーでした。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e❯ gopls version\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eR PAKAYAH LI LETTER PHAfish: Job 1, \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;gopls version\u0026#39;\u003c/span\u003e terminated by signal SIGSEGV \u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003eAddress boundary error\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIssue が上がっていました。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/golang/vscode-go/issues/2909\" target=\"_blank\"\u003eLanguage server fails to start · Issue #2909 · golang/vscode-go · GitHub\u003c/a\u003e\n\u003c/p\u003e","title":"Xcode 15 beta をインストールすると gopls がエラー"},{"content":"Prettier 公式サイトの説明 によると、 eslint-config-prettier が紹介される一方、 eslint-plugin-prettier は非推奨とされています。\nそれぞれがどのような機能のパッケージなのか調べてみました。\neslint-config-prettier: Prettier と競合する ESLint ルールを無効にする https://github.com/prettier/eslint-config-prettier ESLint には、不具合の原因となるコードを検出するためのルールのほか、フォーマットに関するルールも存在します。 Prettier を使っている場合、フォーマットのルールは不要なだけでなく、 Prettier の設定とコンフリクトする場合もあります。\neslint-config-prettier はそのようなルールを無効にします。\neslint-plugin-prettier: ESLint 実行時に Prettier によるフォーマットを実行する https://github.com/prettier/eslint-plugin-prettier eslint-plugin-prettier は ESLint 実行時に Prettier によるフォーマットを実行する ESLint プラグインです。フォーマットが適用されていないファイルを検出する機能もあります。\nPrettier が登場した当初は、エディタとの統合はまだ存在していませんでした。一方で ESLint (などの Linter ツール) は以前から存在していたため、多くのエディタがサポートしていました。このような時代に、 ESLint 経由で実行できる eslint-plugin-prettier は有用だったようです。\nしかし現在は多くのエディタが Prettier をサポートしています。そして、 ESLint のプラグインとして Prettier を実行することにはデメリットがあり (不要な警告が出る・パフォーマンスが悪いなど) 、現在では eslint-plugin-prettier を使う理由はありません。\nESLint 設定の extends に plugin:prettier/recommended を書くとどうなるのか README によると、これ一つで eslint-config-prettier と eslint-plugin-prettier の両方が有効になります。以下のように展開されます。\n{ \u0026#34;extends\u0026#34;: [\u0026#34;prettier\u0026#34;], \u0026#34;plugins\u0026#34;: [\u0026#34;prettier\u0026#34;], \u0026#34;rules\u0026#34;: { \u0026#34;prettier/prettier\u0026#34;: \u0026#34;error\u0026#34;, \u0026#34;arrow-body-style\u0026#34;: \u0026#34;off\u0026#34;, \u0026#34;prefer-arrow-callback\u0026#34;: \u0026#34;off\u0026#34; } } 現在は eslint-config-prettier だけを有効にすればよいので、 Prettier 関連で eslintrc に必要な設定はこれだけです。\n{ \u0026#34;extends\u0026#34;: [\u0026#34;prettier\u0026#34;] } なお、以前は prettier/react や prettier/vue のような他のプラグインと共存するための設定も存在したようですが、 eslint-config-prettier の 8.0.0 から不要になったようです。\nCI で ESLint と同時に Prettier のチェックも実行する CI で ESLint を実行しているなら、 prettier --check コマンドも同時に実行するとよいかもしれません。フォーマットが適用されていないファイルを検出できます。\n例として CI で npm run lint を実行している場合、以下のように設定します。\n{ \u0026#34;scripts\u0026#34;: { \u0026#34;lint\u0026#34;: \u0026#34;eslint ./src \u0026amp;\u0026amp; prettier --check .\u0026#34; } } 参考文献 Integrating with Linters · Prettier いつのまにか eslint-plugin-prettier が推奨されないものになってた | K note.dev eslint と prettier を併用する時の設定 - すな.dev ","permalink":"https://okiyama.dev/posts/2023-08-09-eslint-config-prettier-vs-eslint-plugin-prettier/","summary":"\u003cp\u003e\u003ca href=\"https://prettier.io/docs/en/integrating-with-linters\" target=\"_blank\"\u003ePrettier 公式サイトの説明\u003c/a\u003e\n によると、 eslint-config-prettier が紹介される一方、 eslint-plugin-prettier は非推奨とされています。\u003c/p\u003e\n\u003cp\u003eそれぞれがどのような機能のパッケージなのか調べてみました。\u003c/p\u003e\n\u003ch2 id=\"eslint-config-prettier-prettier-と競合する-eslint-ルールを無効にする\"\u003eeslint-config-prettier: Prettier と競合する ESLint ルールを無効にする\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/prettier/eslint-config-prettier\" target=\"_blank\"\u003ehttps://github.com/prettier/eslint-config-prettier\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003eESLint には、不具合の原因となるコードを検出するためのルールのほか、フォーマットに関するルールも存在します。 Prettier を使っている場合、フォーマットのルールは不要なだけでなく、 Prettier の設定とコンフリクトする場合もあります。\u003c/p\u003e\n\u003cp\u003eeslint-config-prettier はそのようなルールを無効にします。\u003c/p\u003e\n\u003ch2 id=\"eslint-plugin-prettier-eslint-実行時に-prettier-によるフォーマットを実行する\"\u003eeslint-plugin-prettier: ESLint 実行時に Prettier によるフォーマットを実行する\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/prettier/eslint-plugin-prettier\" target=\"_blank\"\u003ehttps://github.com/prettier/eslint-plugin-prettier\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003eeslint-plugin-prettier は ESLint 実行時に Prettier によるフォーマットを実行する ESLint プラグインです。フォーマットが適用されていないファイルを検出する機能もあります。\u003c/p\u003e\n\u003cp\u003ePrettier が登場した当初は、エディタとの統合はまだ存在していませんでした。一方で ESLint (などの Linter ツール) は以前から存在していたため、多くのエディタがサポートしていました。このような時代に、 ESLint 経由で実行できる eslint-plugin-prettier は有用だったようです。\u003c/p\u003e\n\u003cp\u003eしかし現在は多くのエディタが Prettier をサポートしています。そして、 ESLint のプラグインとして Prettier を実行することにはデメリットがあり (不要な警告が出る・パフォーマンスが悪いなど) 、現在では eslint-plugin-prettier を使う理由はありません。\u003c/p\u003e\n\u003ch2 id=\"eslint-設定の-extends-に-pluginprettierrecommended-を書くとどうなるのか\"\u003eESLint 設定の extends に plugin:prettier/recommended を書くとどうなるのか\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/prettier/eslint-plugin-prettier/tree/910aeb60a7456beb6193c634bb8dec1b7181312d#recommended-configuration\" target=\"_blank\"\u003eREADME\u003c/a\u003e\n によると、これ一つで eslint-config-prettier と eslint-plugin-prettier の両方が有効になります。以下のように展開されます。\u003c/p\u003e","title":"eslint-config-prettier と eslint-plugin-prettier"},{"content":"Expo で作ったアプリを App Store に公開しました。 (今のところ iOS のみ)\nhttps://apps.apple.com/jp/app/ai-prompt-editor/id6448696132 モバイルで OpenAI API の GPT を使うためのアプリです。テンプレートを穴埋め形式で入力することで、モバイルでも長文のプロンプトを作成しやすくします。\nシステム構成は以下のようになっています。\nフロントエンドは TypeScript で書かれていて、 Expo を使った React Native アプリになっています。\nバックエンドは Go 言語で、 AWS Lambda で動いています。 OpenAI API は Server Sent Events に対応しているので、それをアプリに WebSocket で送ります。\nVercel はテンプレート文言を JSON ファイルとしてホストするために使用しています。\nデプロイは、アプリについては手動 (ローカルマシンから EAS Build をトリガー) で、バックエンドについては GitHub Actions で CDK を実行しています。\nリポジトリ:\nhttps://github.com/rokiyama/gpt-prompter-frontend https://github.com/rokiyama/gpt-prompter-backend https://github.com/rokiyama/gpt-chat-misc Expo での開発について アプリで使用しているライブラリのうち主なものは以下の通りです。\nExpo / React Native Redux Toolkit Tailwind CSS / tailwind-rn Gifted Chat Expo を使うことで、ビルド環境をローカルに用意することなく開発からストア公開まで行うことができました。\nExpo はライブラリや CLI ツールをはじめとした統合的なエコシステムになっていて、プロジェクトの生成を行う create-expo-app のほか、以下のようなアプリ・サービスを提供しています。\nExpo Go 開発中のプレビューは Expo Go というモバイルアプリを使用します。このアプリは開発用のクライアントで、 npm run start で起動したローカルのサーバに接続してアプリを動かせます。\nflowchart LR subgraph PC node[Expo dev server] subgraph Simulator expoGoS[Expo Go] end end subgraph Phone expoGo[Expo Go] end expoGo --\u003e node expoGoS --\u003e node このアプリはよくできていて、普通の React アプリを Web ブラウザでプレビューしながら開発するのとそんなに変わらない体験です。 Expo Go を使っている限りは、 JS のライブラリ関連でエラーに悩まされることはありますが、ネイティブ関連のビルドエラーとは無縁です。\nシミュレータだけでなく実機でも動作し、 Expo ユーザとしてログインしていれば LAN 内の自分のサーバを自動で見つけてくれるなど、非常に便利です。\nただし Expo Go でできることは用意された Expo SDK の範囲でできることに限られ、それを逸脱する実装をしたい場合はカスタムのネイティブコードを含めてビルドする必要があります。このような場合の対応方法も用意されており、 Development builds という自分専用にカスタムされた Expo Go アプリのようなものをビルドする方法があります。\n今回のアプリではカスタムビルドが必要になることはなかったのですが、一度試したところ後述の EAS Build を使えば特に難しいところもなくカスタムビルドを行うことができました。\nExpo Application Services (EAS) Expo 社が提供するクラウドサービスです。このサービスにネイティブアプリのビルドと、 App Store 申請に必要な証明書の管理などを任せることができ、ストア公開まで比較的簡単にできました。\nhttps://expo.dev/eas 一部、 Apple のサイトで手動による設定が必要なところもありますが、ドキュメントに従っていれば特に大きなトラブルもなく進められました。\n無料で使い始めることができ、無料枠だとビルド回数が制限される、キューの待ちが発生する、インスタンスの性能も低いなどの制限があるようです。今回の開発では無料枠のまま使えていますが、頻繁にアップデートしたいなどの場合は有料プランが必要になりそうです。\nhttps://expo.dev/pricing ちょっと困ったのは、アプリバージョンとビルド番号の管理です。既に提出済みのビルド番号を再提出しようとするとエラーになるので、番号をインクリメントしてからビルドをトリガーする必要があるのですが、エラーになるのはビルドが完了した後の Apple に提出するタイミングです。なので、インクリメントを忘れるともう一度ビルドからやり直す必要が生じます。運用でカバーする方法を考えたいところです。\n参考にしたもの Udemy の講座が参考になりました。\nhttps://www.udemy.com/course/react-native-expo-firebase/ https://www.udemy.com/course/react-native-first-step/ ","permalink":"https://okiyama.dev/posts/2023-07-16-expo-development-react-native-app/","summary":"\u003cp\u003eExpo で作ったアプリを App Store に公開しました。 (今のところ iOS のみ)\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://apps.apple.com/jp/app/ai-prompt-editor/id6448696132\" target=\"_blank\"\u003ehttps://apps.apple.com/jp/app/ai-prompt-editor/id6448696132\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003eモバイルで OpenAI API の GPT を使うためのアプリです。テンプレートを穴埋め形式で入力することで、モバイルでも長文のプロンプトを作成しやすくします。\u003c/p\u003e\n\u003cp\u003eシステム構成は以下のようになっています。\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/images/expo-app.drawio.svg\"\u003e\u003c/p\u003e\n\u003cp\u003eフロントエンドは TypeScript で書かれていて、 Expo を使った React Native アプリになっています。\u003c/p\u003e\n\u003cp\u003eバックエンドは Go 言語で、 AWS Lambda で動いています。 OpenAI API は Server Sent Events に対応しているので、それをアプリに WebSocket で送ります。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://vercel.com/\" target=\"_blank\"\u003eVercel\u003c/a\u003e\n はテンプレート文言を JSON ファイルとしてホストするために使用しています。\u003c/p\u003e\n\u003cp\u003eデプロイは、アプリについては手動 (ローカルマシンから EAS Build をトリガー) で、バックエンドについては GitHub Actions で CDK を実行しています。\u003c/p\u003e\n\u003cp\u003eリポジトリ:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/rokiyama/gpt-prompter-frontend\" target=\"_blank\"\u003ehttps://github.com/rokiyama/gpt-prompter-frontend\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/rokiyama/gpt-prompter-backend\" target=\"_blank\"\u003ehttps://github.com/rokiyama/gpt-prompter-backend\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/rokiyama/gpt-chat-misc\" target=\"_blank\"\u003ehttps://github.com/rokiyama/gpt-chat-misc\u003c/a\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"expo-での開発について\"\u003eExpo での開発について\u003c/h2\u003e\n\u003cp\u003eアプリで使用しているライブラリのうち主なものは以下の通りです。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://expo.dev/\" target=\"_blank\"\u003eExpo\u003c/a\u003e\n / \u003ca href=\"https://reactnative.dev/\" target=\"_blank\"\u003eReact Native\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://redux-toolkit.js.org/\" target=\"_blank\"\u003eRedux Toolkit\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://tailwindcss.com/\" target=\"_blank\"\u003eTailwind CSS\u003c/a\u003e\n / \u003ca href=\"https://github.com/vadimdemedes/tailwind-rn\" target=\"_blank\"\u003etailwind-rn\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://github.com/FaridSafi/react-native-gifted-chat\" target=\"_blank\"\u003eGifted Chat\u003c/a\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eExpo を使うことで、ビルド環境をローカルに用意することなく開発からストア公開まで行うことができました。\u003c/p\u003e","title":"Expo でのモバイルアプリ開発、ローカル環境からストア公開まで"},{"content":"QMK Firmware でキーマップの変更とコンパイル、自作キーボードへの書き込みを行う手順のメモです。\nmeishi2 という自作キーボード初心者向けのキットで実施しました。\nインストール・セットアップ macOS の場合、 Homebrew でインストールします。\nbrew install qmk/qmk/qmk インストール後にセットアップコマンドを実行します。\nqmk setup セットアップ中に 公式リポジトリ の git clone が行われます。事前に任意の場所に clone しておき、そのパスを指定することもできます。\nqmk setup -H ~/ghq/github.com/qmk/qmk_firmware ファイル作成 QMK のリポジトリ qmk_firmware のディレクトリに移動します。このリポジトリの keyboards/ ディレクトリに、様々なキーボードのキーマップ設定が収められているようです。\nmeishi2 の場合は以下のディレクトリです。\nkeyboards/biacco42/meishi2/keymaps/\n以下のコマンドを実行すると、このディレクトリにある default をコピーして、新たなディレクトリが作成されます。\nqmk new-keymap -kb biacco42/meishi2 実行すると、名前の入力を求めるプロンプトになります。\nKeymap Name: 適当に my_meishi2 と名付けることにして、プロンプトに入力して enter を入力します。すると、次のように表示されます。\nΨ my_meishi2 keymap directory created in: /Users/okiyama/ghq/github.com/qmk/qmk_firmware/keyboards/biacco42/meishi2/keymaps/my_meishi2 Ψ Compile a firmware with your new keymap by typing: qmk compile -kb biacco42/meishi2 -km my_meishi2 メッセージの通り、 my_meishi2 という名前のディレクトリが作成されます。作成されたファイルを編集し、任意のキーマップに変更します。\nコンパイル・書き込み 以下のコマンドを実行すると、指定したキーマップでファームウェアがコンパイルされます。\nqmk compile -kb biacco42/meishi2 -km my_meishi2 コンパイルのログが表示されます。以下は成功時のログです。\nΨ Compiling keymap with gmake --jobs=1 biacco42/meishi2:my_meishi2 QMK Firmware 0.19.12 Making biacco42/meishi2 with keymap my_meishi2 avr-gcc (Homebrew AVR GCC 8.5.0) 8.5.0 Copyright (C) 2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Size before: text data bss dec hex filename 0 20354 0 20354 4f82 biacco42_meishi2_my_meishi2.hex Compiling: quantum/keymap_introspection.c [OK] Compiling: quantum/command.c [OK] Linking: .build/biacco42_meishi2_my_meishi2.elf [OK] Creating load file for flashing: .build/biacco42_meishi2_my_meishi2.hex [OK] Copying biacco42_meishi2_my_meishi2.hex to qmk_firmware folder [OK] Checking file size of biacco42_meishi2_my_meishi2.hex [OK] * The firmware size is fine - 20354/28672 (70%, 8318 bytes free) コンパイルに成功したので、キーボードに書き込みます。以下のコマンドを実行します。\nqmk flash -kb biacco42/meishi2 -km my_meishi2 実行すると (このとき再びコンパイルが行われるようです)、以下のようにキーボードのリセット待ち状態になります。\nΨ Compiling keymap with gmake --jobs=1 biacco42/meishi2:my_meishi2:flash # snip * The firmware size is fine - 20354/28672 (70%, 8318 bytes free) Flashing for bootloader: caterina Waiting for USB serial port - reset your controller now (Ctrl+C to cancel)...... ここでまだキーボードを接続していなければ接続し、リセットスイッチを押します。するとキーボードが認識され、書き込みが行われる様子がログで流れます。\nDevice /dev/tty.usbmodem101 has appeared; assuming it is the controller. Waiting for /dev/tty.usbmodem101 to become writable. Connecting to programmer: . Found programmer: Id = \u0026#34;CATERIN\u0026#34;; type = S Software Version = 1.0; No Hardware Version given. Programmer supports auto addr increment. Programmer supports buffered memory access with buffersize=128 bytes. Programmer supports the following devices: Device code: 0x44 avrdude: AVR device initialized and ready to accept instructions Reading | ################################################## | 100% 0.00s avrdude: Device signature = 0x1e9587 (probably m32u4) avrdude: NOTE: \u0026#34;flash\u0026#34; memory has been specified, an erase cycle will be performed To disable this feature, specify the -D option. avrdude: erasing chip avrdude: reading input file \u0026#34;.build/biacco42_meishi2_my_meishi2.hex\u0026#34; avrdude: input file .build/biacco42_meishi2_my_meishi2.hex auto detected as Intel Hex avrdude: writing flash (20354 bytes): Writing | ################################################## | 100% 1.57s avrdude: 20354 bytes of flash written avrdude: verifying flash memory against .build/biacco42_meishi2_my_meishi2.hex: avrdude: input file .build/biacco42_meishi2_my_meishi2.hex auto detected as Intel Hex Reading | ################################################## | 100% 0.17s avrdude: 20354 bytes of flash verified avrdude done. Thank you. 上記のように avrdude done と表示されれば完了で、キーボードとして使える状態になります。\n補足 QMK Firmware を使ったファームウェア書き込みは最も基本的な方法の一つですが、他にもいくつかツールがあり、 2023 年現在は Remap が人気のようです。\n","permalink":"https://okiyama.dev/posts/2023-02-23-qmk-firmware/","summary":"\u003cp\u003e\u003ca href=\"https://qmk.fm/ja/\" target=\"_blank\"\u003eQMK Firmware\u003c/a\u003e\n でキーマップの変更とコンパイル、自作キーボードへの書き込みを行う手順のメモです。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://shop.yushakobo.jp/products/834?variant=37665283571873\" target=\"_blank\"\u003emeishi2\u003c/a\u003e\n という自作キーボード初心者向けのキットで実施しました。\u003c/p\u003e\n\u003ch2 id=\"インストールセットアップ\"\u003eインストール・セットアップ\u003c/h2\u003e\n\u003cp\u003emacOS の場合、 Homebrew でインストールします。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebrew install qmk/qmk/qmk\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eインストール後にセットアップコマンドを実行します。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eqmk setup\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eセットアップ中に \u003ca href=\"https://github.com/qmk/qmk_firmware/\" target=\"_blank\"\u003e公式リポジトリ\u003c/a\u003e\n の git clone が行われます。事前に任意の場所に clone しておき、そのパスを指定することもできます。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eqmk setup -H ~/ghq/github.com/qmk/qmk_firmware\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"ファイル作成\"\u003eファイル作成\u003c/h2\u003e\n\u003cp\u003eQMK のリポジトリ \u003ccode\u003eqmk_firmware\u003c/code\u003e のディレクトリに移動します。このリポジトリの \u003ccode\u003ekeyboards/\u003c/code\u003e ディレクトリに、様々なキーボードのキーマップ設定が収められているようです。\u003c/p\u003e\n\u003cp\u003emeishi2 の場合は以下のディレクトリです。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ekeyboards/biacco42/meishi2/keymaps/\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e以下のコマンドを実行すると、このディレクトリにある \u003ccode\u003edefault\u003c/code\u003e をコピーして、新たなディレクトリが作成されます。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eqmk new-keymap -kb biacco42/meishi2\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e実行すると、名前の入力を求めるプロンプトになります。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eKeymap Name:\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e適当に \u003ccode\u003emy_meishi2\u003c/code\u003e と名付けることにして、プロンプトに入力して enter を入力します。すると、次のように表示されます。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eΨ my_meishi2 keymap directory created in: /Users/okiyama/ghq/github.com/qmk/qmk_firmware/keyboards/biacco42/meishi2/keymaps/my_meishi2\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eΨ Compile a firmware with your new keymap by typing:\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        qmk compile -kb biacco42/meishi2 -km my_meishi2\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eメッセージの通り、 \u003ccode\u003emy_meishi2\u003c/code\u003e という名前のディレクトリが作成されます。作成されたファイルを編集し、任意のキーマップに変更します。\u003c/p\u003e","title":"QMK Firmware でキーマップの変更とコンパイル、自作キーボードへの書き込みを行う"},{"content":"Bitbucket canonical URL という Chrome 拡張を作りました。 Chrome ウェブストアでインストールできます。\nBitbucket の PR を開いて URL が変に長いときに、アイコンをクリックすると短い URL に置き換わるという拡張です。\nソースコード: https://github.com/rokiyama/bitbucket-canonical-url 概要 Bitbucket の PR を使っていると URL が妙に長くなることがあります。\n# 長い 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 UUID のようなものが埋め込まれていて、リポジトリ名が判別できません。無駄にブランチ名も入っています。 どうやら JIRA から PR に飛んだ時などに発生するようです。\nこの拡張をインストールしてツールバーのアイコンをクリックすると、短い URL に書き換えられます。\nBitbucket と JIRA を併用していて、かつ URL の見映えを気にする人がターゲットユーザーでしょうか。\nクリックしたら書き換える方式にした理由 Amazon URL Shortener という拡張があり、これを参考に開発を始めました。\n最初はこれと同様、ページを開いた瞬間に URL を書き換えるようにしたかったのですが、 PR 内でコメントにジャンプしたりした際にも URL が長くなる現象が起きることがあったため、 任意のタイミングで短縮処理が行えるようにしています。\nbackground.ts と content.ts クリック時に行う処理は background.ts で行っており、これは service worker として動作します。\nまた URL が https://bitbucket.org/foo/%7B840d6a13-5d55-4c1a-a86b-3372c3ceeef1%7D/pull-requests/... のような場合、リポジトリ名をどこかから取ってくる必要があり、これは DOM から取得する必要がありました。\nDOM の取得は service worker では行えず、 content scripts で行う必要があります。 この処理は content.ts で行っています。\nそれぞれのファイルの役割は マニフェスト で指定するようになっています。\nストアでの公開 開発者登録料として $5 支払いました。一度払えばそれ以降の支払いは不要なようです。\n11/19(土) に審査を申請して、 11/21(月) に公開されました。 Eメールで通知などはしてくれないようで、いつの間にか公開されていたという感じでした。\n今後の課題 バージョン管理 GitHub Actions でテスト、リリース 参考にした記事 拡張の作り方はこちらの記事と、この筆者の方の作られた拡張を参考にさせていただきました。\nChrome拡張をつくるチュートリアル Amazon URL Shortener - Chrome ウェブストア こちらも参考にさせていただきました。ストア公開の手順までが簡潔にまとめられていてわかりやすかったです。\nChrome拡張をVite+TypeScript+Reactで作る こちらは開発に必要な概念が詳しく説明されていて参考になりました。内容はマニフェスト V2 に基づいていますが、基本的な考え方は V3 でも大きくは変わらないようです。\nChrome Extension の作り方 (その1: 3つの世界) - Qiita ","permalink":"https://okiyama.dev/posts/2022-11-28-chrome-extension-bitbucket-canonical-url/","summary":"\u003cp\u003e\u003ca href=\"https://chrome.google.com/webstore/detail/bitbucket-canonical-url/kfckcleaglfgjkobhcbclfflhcllmnmm\" target=\"_blank\"\u003eBitbucket canonical URL\u003c/a\u003e\n という Chrome 拡張を作りました。 Chrome ウェブストアでインストールできます。\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/images/bitbucket-canonical-url-usage.gif\"\u003e\u003c/p\u003e\n\u003cp\u003eBitbucket の PR を開いて URL が変に長いときに、アイコンをクリックすると短い URL に置き換わるという拡張です。\u003c/p\u003e\n\u003cp\u003eソースコード: \u003ca href=\"https://github.com/rokiyama/bitbucket-canonical-url\" target=\"_blank\"\u003ehttps://github.com/rokiyama/bitbucket-canonical-url\u003c/a\u003e\n\u003c/p\u003e\n\u003ch2 id=\"概要\"\u003e概要\u003c/h2\u003e\n\u003cp\u003eBitbucket の PR を使っていると URL が妙に長くなることがあります。\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e# 長い\nhttps://bitbucket.org/foo/%7B840d6a13-5d55-4c1a-a86b-3372c3ceeef1%7D/pull-requests/123/branch-name#comment-123456789\n\n↓\n\n# 短くできる\nhttps://bitbucket.org/foo/bar/pull-requests/123#comment-123456789\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eUUID のようなものが埋め込まれていて、リポジトリ名が判別できません。無駄にブランチ名も入っています。\nどうやら JIRA から PR に飛んだ時などに発生するようです。\u003c/p\u003e\n\u003cp\u003eこの拡張をインストールしてツールバーのアイコンをクリックすると、短い URL に書き換えられます。\u003c/p\u003e\n\u003cp\u003eBitbucket と JIRA を併用していて、かつ URL の見映えを気にする人がターゲットユーザーでしょうか。\u003c/p\u003e\n\u003ch2 id=\"クリックしたら書き換える方式にした理由\"\u003eクリックしたら書き換える方式にした理由\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://chrome.google.com/webstore/detail/amazon-url-shortener/bonkcfmjkpdnieejahndognlbogaikdg\" target=\"_blank\"\u003eAmazon URL Shortener\u003c/a\u003e\n\nという拡張があり、これを参考に開発を始めました。\u003c/p\u003e\n\u003cp\u003e最初はこれと同様、ページを開いた瞬間に URL を書き換えるようにしたかったのですが、\nPR 内でコメントにジャンプしたりした際にも URL が長くなる現象が起きることがあったため、\n任意のタイミングで短縮処理が行えるようにしています。\u003c/p\u003e\n\u003ch2 id=\"backgroundts-と-contentts\"\u003ebackground.ts と content.ts\u003c/h2\u003e\n\u003cp\u003eクリック時に行う処理は\n\u003ca href=\"https://github.com/rokiyama/bitbucket-canonical-url/blob/main/src/background.ts\" target=\"_blank\"\u003ebackground.ts\u003c/a\u003e\n\nで行っており、これは service worker として動作します。\u003c/p\u003e\n\u003cp\u003eまた URL が\n\u003ccode\u003ehttps://bitbucket.org/foo/%7B840d6a13-5d55-4c1a-a86b-3372c3ceeef1%7D/pull-requests/...\u003c/code\u003e\nのような場合、リポジトリ名をどこかから取ってくる必要があり、これは DOM から取得する必要がありました。\u003c/p\u003e","title":"Chrome 拡張 \"Bitbucket Canonical Url\" を作った"},{"content":"copy-pipe を使うようキーバインドを設定します。\nbind-key -T copy-mode-vi Enter send-keys -X copy-pipe プラグイン tmux-yank を使用している場合は以下を設定します。\nset -g @yank_action \u0026#39;copy-pipe\u0026#39; tmux 3.3a, tmux-yank 2.3.0 で確認しました。\nキーバインドを確認する コマンド tmux list-key で、現在のキーバインドが確認できます。\n# デフォルト状態の tmux キーバインド一覧 ❯ tmux -f /dev/null list-key | rg \u0026#39;copy-pipe\u0026#39; bind-key -T copy-mode C-k send-keys -X copy-pipe-end-of-line-and-cancel bind-key -T copy-mode C-w send-keys -X copy-pipe-and-cancel bind-key -T copy-mode MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel bind-key -T copy-mode DoubleClick1Pane select-pane \\; send-keys -X select-word \\; run-shell -d 0.3 \\; send-keys -X copy-pipe-and-cancel bind-key -T copy-mode TripleClick1Pane select-pane \\; send-keys -X select-line \\; run-shell -d 0.3 \\; send-keys -X copy-pipe-and-cancel bind-key -T copy-mode M-w send-keys -X copy-pipe-and-cancel bind-key -T copy-mode-vi C-j send-keys -X copy-pipe-and-cancel bind-key -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel bind-key -T copy-mode-vi D send-keys -X copy-pipe-end-of-line-and-cancel bind-key -T copy-mode-vi MouseDragEnd1Pane send-keys -X copy-pipe-and-cancel bind-key -T copy-mode-vi DoubleClick1Pane select-pane \\; send-keys -X select-word \\; run-shell -d 0.3 \\; send-keys -X copy-pipe-and-cancel bind-key -T copy-mode-vi TripleClick1Pane select-pane \\; send-keys -X select-line \\; run-shell -d 0.3 \\; send-keys -X copy-pipe-and-cancel bind-key -T root DoubleClick1Pane select-pane -t = \\; if-shell -F \u0026#34;#{||:#{pane_in_mode},#{mouse_any_flag}}\u0026#34; { send-keys -M } { copy-mode -H ; send-keys -X select-word ; run-shell -d 0.3 ; send-keys -X copy-pipe-and-cancel } bind-key -T root TripleClick1Pane select-pane -t = \\; if-shell -F \u0026#34;#{||:#{pane_in_mode},#{mouse_any_flag}}\u0026#34; { send-keys -M } { copy-mode -H ; send-keys -X select-line ; run-shell -d 0.3 ; send-keys -X copy-pipe-and-cancel } vi モードのデフォルトでは Enter にコピー機能が割り当てられていて、コマンドは copy-pipe-and-cancel となっています。 キーバインドを上書きして copy-pipe に変更すれば、コピーモードから抜けないようになります。\nプラグイン tmux-yank について コピー機能は OS 特有の対応が必要な場合があります。\n現在は不要になりましたが、 tmux 2.6 以前は macOS では reattach-to-user-namespace が必要で、 これを使うようなキーバインドを設定する必要がありました。 Linux では xsel や xclip を使う、 WSL では clip.exe を使うなど OS によって異なります。\ntmux-yank はこの問題にまとめて対処するプラグインです。 インストールすると、使用している環境に合わせたコマンドでいくつかのキーバインドが追加されます。\nこのプラグインにも copy-pipe に変更する方法が用意されていて、記事冒頭に記載の方法で変更できます。\n参考にした記事 tmux 3.0でコピーモードの設定を行う - りんごとバナナとエンジニア ","permalink":"https://okiyama.dev/posts/2022-11-09-tmux-copy-text-without-leaving-copy-mode/","summary":"\u003cp\u003e\u003ccode\u003ecopy-pipe\u003c/code\u003e を使うようキーバインドを設定します。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-properties\" data-lang=\"properties\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003ebind-key\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e-T copy-mode-vi Enter send-keys -X copy-pipe\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eプラグイン\n\u003ca href=\"https://github.com/tmux-plugins/tmux-yank\" target=\"_blank\"\u003etmux-yank\u003c/a\u003e\n\nを使用している場合は以下を設定します。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-properties\" data-lang=\"properties\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eset\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e-g @yank_action \u0026#39;copy-pipe\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003etmux 3.3a, tmux-yank 2.3.0 で確認しました。\u003c/p\u003e\n\u003ch2 id=\"キーバインドを確認する\"\u003eキーバインドを確認する\u003c/h2\u003e\n\u003cp\u003eコマンド \u003ccode\u003etmux list-key\u003c/code\u003e で、現在のキーバインドが確認できます。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# デフォルト状態の tmux キーバインド一覧\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e❯ tmux -f /dev/null list-key | rg \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;copy-pipe\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebind-key    -T copy-mode    C-k                  send-keys -X copy-pipe-end-of-line-and-cancel\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebind-key    -T copy-mode    C-w                  send-keys -X copy-pipe-and-cancel\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebind-key    -T copy-mode    MouseDragEnd1Pane    send-keys -X copy-pipe-and-cancel\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebind-key    -T copy-mode    DoubleClick1Pane     \u003cspan style=\"color:#66d9ef\"\u003eselect\u003c/span\u003e-pane \u003cspan style=\"color:#ae81ff\"\u003e\\;\u003c/span\u003e send-keys -X \u003cspan style=\"color:#66d9ef\"\u003eselect\u003c/span\u003e-word \u003cspan style=\"color:#ae81ff\"\u003e\\;\u003c/span\u003e run-shell -d 0.3 \u003cspan style=\"color:#ae81ff\"\u003e\\;\u003c/span\u003e send-keys -X copy-pipe-and-cancel\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebind-key    -T copy-mode    TripleClick1Pane     \u003cspan style=\"color:#66d9ef\"\u003eselect\u003c/span\u003e-pane \u003cspan style=\"color:#ae81ff\"\u003e\\;\u003c/span\u003e send-keys -X \u003cspan style=\"color:#66d9ef\"\u003eselect\u003c/span\u003e-line \u003cspan style=\"color:#ae81ff\"\u003e\\;\u003c/span\u003e run-shell -d 0.3 \u003cspan style=\"color:#ae81ff\"\u003e\\;\u003c/span\u003e send-keys -X copy-pipe-and-cancel\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebind-key    -T copy-mode    M-w                  send-keys -X copy-pipe-and-cancel\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebind-key    -T copy-mode-vi C-j                  send-keys -X copy-pipe-and-cancel\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebind-key    -T copy-mode-vi Enter                send-keys -X copy-pipe-and-cancel\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebind-key    -T copy-mode-vi D                    send-keys -X copy-pipe-end-of-line-and-cancel\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebind-key    -T copy-mode-vi MouseDragEnd1Pane    send-keys -X copy-pipe-and-cancel\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebind-key    -T copy-mode-vi DoubleClick1Pane     \u003cspan style=\"color:#66d9ef\"\u003eselect\u003c/span\u003e-pane \u003cspan style=\"color:#ae81ff\"\u003e\\;\u003c/span\u003e send-keys -X \u003cspan style=\"color:#66d9ef\"\u003eselect\u003c/span\u003e-word \u003cspan style=\"color:#ae81ff\"\u003e\\;\u003c/span\u003e run-shell -d 0.3 \u003cspan style=\"color:#ae81ff\"\u003e\\;\u003c/span\u003e send-keys -X copy-pipe-and-cancel\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebind-key    -T copy-mode-vi TripleClick1Pane     \u003cspan style=\"color:#66d9ef\"\u003eselect\u003c/span\u003e-pane \u003cspan style=\"color:#ae81ff\"\u003e\\;\u003c/span\u003e send-keys -X \u003cspan style=\"color:#66d9ef\"\u003eselect\u003c/span\u003e-line \u003cspan style=\"color:#ae81ff\"\u003e\\;\u003c/span\u003e run-shell -d 0.3 \u003cspan style=\"color:#ae81ff\"\u003e\\;\u003c/span\u003e send-keys -X copy-pipe-and-cancel\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebind-key    -T root         DoubleClick1Pane     \u003cspan style=\"color:#66d9ef\"\u003eselect\u003c/span\u003e-pane -t \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e\\;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e-shell -F \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;#{||:#{pane_in_mode},#{mouse_any_flag}}\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e send-keys -M \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e copy-mode -H ; send-keys -X \u003cspan style=\"color:#66d9ef\"\u003eselect\u003c/span\u003e-word ; run-shell -d 0.3 ; send-keys -X copy-pipe-and-cancel \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ebind-key    -T root         TripleClick1Pane     \u003cspan style=\"color:#66d9ef\"\u003eselect\u003c/span\u003e-pane -t \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#ae81ff\"\u003e\\;\u003c/span\u003e \u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e-shell -F \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;#{||:#{pane_in_mode},#{mouse_any_flag}}\u0026#34;\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e send-keys -M \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e \u003cspan style=\"color:#f92672\"\u003e{\u003c/span\u003e copy-mode -H ; send-keys -X \u003cspan style=\"color:#66d9ef\"\u003eselect\u003c/span\u003e-line ; run-shell -d 0.3 ; send-keys -X copy-pipe-and-cancel \u003cspan style=\"color:#f92672\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003evi モードのデフォルトでは Enter にコピー機能が割り当てられていて、コマンドは \u003ccode\u003ecopy-pipe-and-cancel\u003c/code\u003e となっています。\nキーバインドを上書きして \u003ccode\u003ecopy-pipe\u003c/code\u003e に変更すれば、コピーモードから抜けないようになります。\u003c/p\u003e","title":"tmux で文字列をコピーした後にコピーモードを抜けないようにする"},{"content":"結論: 非推奨ではありませんが今後積極的に使うものではなく、 gopter, rapid など別のライブラリを検討するのが良さそうです。\nfrozen だが deprecated とは書かれていない 2016 年の時点で「このパッケージはフリーズし、今後修正や機能追加はしない」と宣言されています。\nドキュメントには以下のように書かれていて\nThe testing/quick package is frozen and is not accepting new features.\nhttps://pkg.go.dev/testing/quick ソースの blame を見ると 2016/10 のコミットで追加されたようです。関連 issue は golang/go#15557 です。\nその後 2017 年に出された 機能追加プロポーザルの issue を読むと、 サードパーティのライブラリの利用が示唆されるなど、開発の方針がわかります。\n比較のため他のパッケージを見ると、 2020 年に廃止となった x/lint は deprecated and frozen と書かれています1。\nサードパーティのライブラリはどんなものがある？ testing/quick のようなランダム値によるテスト手法は Property based testing と呼ばれています。\ngolang で Property based testing の実践をサポートするライブラリとしては leanovate/gopter と flyingmutant/rapid が有名なようです。\n参考記事 gopterを使ってGoでProperty Based Testingする - Qiita Goにproperty based testingを布教したい - These Walls ただし x 配下のパッケージは法則が異なるかもしれない\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://okiyama.dev/posts/2022-10-19-golang-package-testing-quick/","summary":"\u003cp\u003e結論: 非推奨ではありませんが今後積極的に使うものではなく、\ngopter, rapid など別のライブラリを検討するのが良さそうです。\u003c/p\u003e\n\u003ch2 id=\"frozen-だが-deprecated-とは書かれていない\"\u003efrozen だが deprecated とは書かれていない\u003c/h2\u003e\n\u003cp\u003e2016 年の時点で「このパッケージはフリーズし、今後修正や機能追加はしない」と宣言されています。\u003c/p\u003e\n\u003cp\u003eドキュメントには以下のように書かれていて\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThe testing/quick package is frozen and is not accepting new features.\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://pkg.go.dev/testing/quick\" target=\"_blank\"\u003ehttps://pkg.go.dev/testing/quick\u003c/a\u003e\n\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/golang/go/blame/19309779ac5e2f5a2fd3cbb34421dafb2855ac21/src/testing/quick/quick.go\" target=\"_blank\"\u003eソースの blame\u003c/a\u003e\n\nを見ると 2016/10 のコミットで追加されたようです。関連 issue は \u003ca href=\"https://github.com/golang/go/issues/15557\" target=\"_blank\"\u003egolang/go#15557\u003c/a\u003e\n です。\u003c/p\u003e\n\u003cp\u003eその後 2017 年に出された \u003ca href=\"https://github.com/golang/go/issues/23135\" target=\"_blank\"\u003e機能追加プロポーザルの issue\u003c/a\u003e\n を読むと、\nサードパーティのライブラリの利用が示唆されるなど、開発の方針がわかります。\u003c/p\u003e\n\u003cp\u003e比較のため他のパッケージを見ると、 2020 年に廃止となった \u003ca href=\"https://pkg.go.dev/golang.org/x/lint\" target=\"_blank\"\u003ex/lint\u003c/a\u003e\n は\n\u003cem\u003edeprecated and frozen\u003c/em\u003e と書かれています\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e。\u003c/p\u003e\n\u003ch2 id=\"サードパーティのライブラリはどんなものがある\"\u003eサードパーティのライブラリはどんなものがある？\u003c/h2\u003e\n\u003cp\u003etesting/quick のようなランダム値によるテスト手法は Property based testing と呼ばれています。\u003c/p\u003e\n\u003cp\u003egolang で Property based testing の実践をサポートするライブラリとしては\n\u003ca href=\"https://github.com/leanovate/gopter\" target=\"_blank\"\u003eleanovate/gopter\u003c/a\u003e\n と\n\u003ca href=\"https://github.com/flyingmutant/rapid\" target=\"_blank\"\u003eflyingmutant/rapid\u003c/a\u003e\n が有名なようです。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://star-history.com/#leanovate/gopter\u0026amp;flyingmutant/rapid\u0026amp;Date\" target=\"_blank\"\u003e\u003cimg alt=\"GitHub Star History\" loading=\"lazy\" src=\"/images/star-history-20221019.png\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\u003ch2 id=\"参考記事\"\u003e参考記事\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://qiita.com/rerorero/items/568e227da3939dbf9532\" target=\"_blank\"\u003egopterを使ってGoでProperty Based Testingする - Qiita\u003c/a\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://kazchimo.com/2021/03/30/go-pbt-testing/\" target=\"_blank\"\u003eGoにproperty based testingを布教したい - These Walls\u003c/a\u003e\n\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"footnotes\" role=\"doc-endnotes\"\u003e\n\u003chr\u003e\n\u003col\u003e\n\u003cli id=\"fn:1\"\u003e\n\u003cp\u003eただし x 配下のパッケージは法則が異なるかもしれない\u0026#160;\u003ca href=\"#fnref:1\" class=\"footnote-backref\" role=\"doc-backlink\"\u003e\u0026#x21a9;\u0026#xfe0e;\u003c/a\u003e\u003c/p\u003e","title":"golang の標準パッケージ testing/quick は非推奨？"},{"content":"node コマンドでワンライナーを書くための基本的な知識をまとめました。\n参照: https://nodejs.org/dist/latest-v16.x/docs/api/cli.html node コマンドのオプション 使うオプションは基本的にこの 3 つです。おおむね -p と -r でなんとかなりそうです。\n-e, --eval \u0026quot;script” \u0026hellip; スクリプトを実行 -p, --print \u0026quot;script\u0026quot; \u0026hellip; スクリプトを実行し、結果を標準出力に渡す -r, --require module \u0026hellip; モジュールをロード 例: 標準入力を文字列置換する 標準入力を受け取り、先頭の空白を除き、標準出力に渡す例です。\ncat FILE.txt | node -r fs -p \u0026#39;fs.readFileSync(0, \u0026#34;utf-8\u0026#34;).replaceAll(/^\\s+/gm, \u0026#34;\u0026#34;)\u0026#39; 標準入力だけでなく、ファイル名も指定できます。\nnode -r fs -p \u0026#39;fs.readFileSync(\u0026#34;FILE.txt\u0026#34;, \u0026#34;utf-8\u0026#34;).replaceAll(/^\\s+/gm, \u0026#34;\u0026#34;)\u0026#39; ポイントは以下の通りです。\n-r fs \u0026hellip; 標準入力を読むのに fs モジュールを使います。 fs.readFileSync(0, \u0026quot;utf-8\u0026quot;) \u0026hellip; 第一引数に 0 を渡すと標準入力を読むことができます1。第二引数に encoding を指定すると string が得られます (指定しないと Buffer 型になります)。 replaceAll に正規表現を指定する場合は g フラグが必須です2。 正規表現フラグ m で各行にマッチするようになります。ここでは ^\\s+ を各行の行頭にマッチさせるため指定しています3。 注意: オプションの順序 node -r fs -p \u0026quot;script\u0026quot; OK node -p \u0026quot;script\u0026quot; -r fs OK node -p -r fs \u0026quot;script\u0026quot; これは失敗します。 \u0026quot;script\u0026quot; が -p オプションの引数であるためだと思われます。 0 はファイルディスクリプタで、標準入力を表します。参照: fs.readFileSync(path[, options]) \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nString.prototype.replaceAll() \u0026#160;\u0026#x21a9;\u0026#xfe0e;\nフラグを用いた高度な検索 - 正規表現 - JavaScript | MDN \u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://okiyama.dev/posts/2022-08-29-one-liner-nodejs/","summary":"\u003cp\u003enode コマンドでワンライナーを書くための基本的な知識をまとめました。\u003c/p\u003e\n\u003cp\u003e参照: \u003ca href=\"https://nodejs.org/dist/latest-v16.x/docs/api/cli.html\" target=\"_blank\"\u003ehttps://nodejs.org/dist/latest-v16.x/docs/api/cli.html\u003c/a\u003e\n\u003c/p\u003e\n\u003ch2 id=\"node-コマンドのオプション\"\u003enode コマンドのオプション\u003c/h2\u003e\n\u003cp\u003e使うオプションは基本的にこの 3 つです。おおむね \u003ccode\u003e-p\u003c/code\u003e と \u003ccode\u003e-r\u003c/code\u003e でなんとかなりそうです。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003e-e, --eval \u0026quot;script”\u003c/code\u003e \u0026hellip; スクリプトを実行\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003e-p, --print \u0026quot;script\u0026quot;\u003c/code\u003e \u0026hellip; スクリプトを実行し、結果を標準出力に渡す\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003e-r, --require module\u003c/code\u003e \u0026hellip; モジュールをロード\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"例-標準入力を文字列置換する\"\u003e例: 標準入力を文字列置換する\u003c/h2\u003e\n\u003cp\u003e標準入力を受け取り、先頭の空白を除き、標準出力に渡す例です。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ecat FILE.txt | node -r fs -p \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;fs.readFileSync(0, \u0026#34;utf-8\u0026#34;).replaceAll(/^\\s+/gm, \u0026#34;\u0026#34;)\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e標準入力だけでなく、ファイル名も指定できます。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-sh\" data-lang=\"sh\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enode -r fs -p \u003cspan style=\"color:#e6db74\"\u003e\u0026#39;fs.readFileSync(\u0026#34;FILE.txt\u0026#34;, \u0026#34;utf-8\u0026#34;).replaceAll(/^\\s+/gm, \u0026#34;\u0026#34;)\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eポイントは以下の通りです。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003e-r fs\u003c/code\u003e \u0026hellip; 標準入力を読むのに fs モジュールを使います。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003efs.readFileSync(0, \u0026quot;utf-8\u0026quot;)\u003c/code\u003e \u0026hellip; 第一引数に \u003ccode\u003e0\u003c/code\u003e を渡すと標準入力を読むことができます\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e。第二引数に encoding を指定すると \u003ccode\u003estring\u003c/code\u003e が得られます (指定しないと \u003ccode\u003eBuffer\u003c/code\u003e 型になります)。\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ereplaceAll\u003c/code\u003e に正規表現を指定する場合は \u003ccode\u003eg\u003c/code\u003e フラグが必須です\u003csup id=\"fnref:2\"\u003e\u003ca href=\"#fn:2\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e2\u003c/a\u003e\u003c/sup\u003e。\u003c/li\u003e\n\u003cli\u003e正規表現フラグ \u003ccode\u003em\u003c/code\u003e で各行にマッチするようになります。ここでは \u003ccode\u003e^\\s+\u003c/code\u003e を各行の行頭にマッチさせるため指定しています\u003csup id=\"fnref:3\"\u003e\u003ca href=\"#fn:3\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e3\u003c/a\u003e\u003c/sup\u003e。\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"注意-オプションの順序\"\u003e注意: オプションの順序\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003enode -r fs -p \u0026quot;script\u0026quot;\u003c/code\u003e OK\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003enode -p \u0026quot;script\u0026quot; -r fs\u003c/code\u003e OK\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003enode -p -r fs \u0026quot;script\u0026quot;\u003c/code\u003e これは失敗します。 \u003ccode\u003e\u0026quot;script\u0026quot;\u003c/code\u003e が \u003ccode\u003e-p\u003c/code\u003e オプションの引数であるためだと思われます。\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"footnotes\" role=\"doc-endnotes\"\u003e\n\u003chr\u003e\n\u003col\u003e\n\u003cli id=\"fn:1\"\u003e\n\u003cp\u003e\u003ccode\u003e0\u003c/code\u003e はファイルディスクリプタで、標準入力を表します。参照: \u003ca href=\"https://nodejs.org/dist/latest-v16.x/docs/api/fs.html#fsreadfilesyncpath-options\" target=\"_blank\"\u003efs.readFileSync(path[, options])\u003c/a\u003e\n\u0026#160;\u003ca href=\"#fnref:1\" class=\"footnote-backref\" role=\"doc-backlink\"\u003e\u0026#x21a9;\u0026#xfe0e;\u003c/a\u003e\u003c/p\u003e","title":"Node.js ワンライナーの基本"},{"content":"iOS や macOS のショートカットアプリ (Shortcuts.app) で現在地を取得し、住所から文字列を検索して if の条件にしたい場合、「一致するテキスト」を使う。\nこれを使って、上から順に「現在地取得」→「一致するテキスト」→「if」と並べ、条件は「任意の値」にする。\n以下は、現在地が特定の住所ならば Sesame を実行、そうでなければ確認ダイアログを出すという例。\n条件の「任意の値」とは not empty という意味らしい。擬似コードで表すとこのようなイメージ:\nposition = getPosition() matched = position.find(\u0026#34;東京都千代田区千代田1-1\u0026#34;) if (matched != \u0026#34;\u0026#34;) // 以下略 ","permalink":"https://okiyama.dev/posts/2022-08-27-shortcuts-app-gps-condition/","summary":"\u003cp\u003eiOS や macOS のショートカットアプリ (Shortcuts.app) で現在地を取得し、住所から文字列を検索して if の条件にしたい場合、「一致するテキスト」を使う。\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/images/text-match.png\"\u003e\u003c/p\u003e\n\u003cp\u003eこれを使って、上から順に「現在地取得」→「一致するテキスト」→「if」と並べ、条件は「任意の値」にする。\u003c/p\u003e\n\u003cp\u003e以下は、現在地が特定の住所ならば \u003ca href=\"https://jp.candyhouse.co/\" target=\"_blank\"\u003eSesame\u003c/a\u003e\n を実行、そうでなければ確認ダイアログを出すという例。\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"/images/shortcuts-gps.png\"\u003e\u003c/p\u003e\n\u003cp\u003e条件の「任意の値」とは \u003ccode\u003enot empty\u003c/code\u003e という意味らしい。擬似コードで表すとこのようなイメージ:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-c\" data-lang=\"c\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eposition \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003egetPosition\u003c/span\u003e()\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003ematched \u003cspan style=\"color:#f92672\"\u003e=\u003c/span\u003e position.\u003cspan style=\"color:#a6e22e\"\u003efind\u003c/span\u003e(\u003cspan style=\"color:#e6db74\"\u003e\u0026#34;東京都千代田区千代田1-1\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eif\u003c/span\u003e (matched \u003cspan style=\"color:#f92672\"\u003e!=\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;\u0026#34;\u003c/span\u003e)\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// 以下略\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"ショートカットアプリで「現在地」を if の条件にする"},{"content":"以下のように操作できるようにする。\nCmd + Option + G \u0026hellip; タブグループ化 / タブグループ解除 Cmd + T \u0026hellip; タブを右隣に開く 本来の「新規タブ」と異なり、現在タブがグループの場合はグループにタブが追加される。 Cmd + Option + T \u0026hellip; 本来の「新規タブ」 macOS では、システム設定でキーボードショートカットを任意のメニュー項目に割り当てることができる。以下のように設定することで上記の操作が実現可能1。\nタブグループとは関係ないが、このスクショでは以下の設定もある。\nCmd + D \u0026hellip; タブ複製 Cmd + Option + D \u0026hellip; ブックマークする All Applications: ウィンドウ最小化をデフォルトの Cmd + M から Cmd + Option + M に変更 Windows の場合、このようなカスタマイズは のどか や AutoHotKey のようなツールで実現できると思われる。 (未検証)\nこの例では OS を英語設定にしてあるためメニュー名も英語となっている。 OS が日本語設定の場合は日本語でメニュー名を指定する必要がある。\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://okiyama.dev/posts/2022-08-27-chrome-tabgroup-keyboard/","summary":"\u003cp\u003e以下のように操作できるようにする。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eCmd + Option + G\u003c/code\u003e \u0026hellip; タブグループ化 / タブグループ解除\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eCmd + T\u003c/code\u003e \u0026hellip; タブを右隣に開く\n\u003cul\u003e\n\u003cli\u003e本来の「新規タブ」と異なり、現在タブがグループの場合はグループにタブが追加される。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eCmd + Option + T\u003c/code\u003e \u0026hellip; 本来の「新規タブ」\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003emacOS では、システム設定でキーボードショートカットを任意のメニュー項目に割り当てることができる。以下のように設定することで上記の操作が実現可能\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003e。\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"macOS のシステム設定 → キーボードショートカットの設定\" loading=\"lazy\" src=\"/images/chrome-tabgroup-keyboard.png\"\u003e\u003c/p\u003e\n\u003cp\u003eタブグループとは関係ないが、このスクショでは以下の設定もある。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eCmd + D\u003c/code\u003e \u0026hellip; タブ複製\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eCmd + Option + D\u003c/code\u003e \u0026hellip; ブックマークする\u003c/li\u003e\n\u003cli\u003eAll Applications: ウィンドウ最小化をデフォルトの \u003ccode\u003eCmd + M\u003c/code\u003e から \u003ccode\u003eCmd + Option + M\u003c/code\u003e に変更\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eWindows の場合、このようなカスタマイズは \u003ca href=\"https://appletllc.com/%e3%82%bd%e3%83%95%e3%83%88%e3%82%a6%e3%82%a7%e3%82%a2/\" target=\"_blank\"\u003eのどか\u003c/a\u003e\n や \u003ca href=\"https://www.autohotkey.com/\" target=\"_blank\"\u003eAutoHotKey\u003c/a\u003e\n のようなツールで実現できると思われる。 (未検証)\u003c/p\u003e\n\u003cdiv class=\"footnotes\" role=\"doc-endnotes\"\u003e\n\u003chr\u003e\n\u003col\u003e\n\u003cli id=\"fn:1\"\u003e\n\u003cp\u003eこの例では OS を英語設定にしてあるためメニュー名も英語となっている。 OS が日本語設定の場合は日本語でメニュー名を指定する必要がある。\u0026#160;\u003ca href=\"#fnref:1\" class=\"footnote-backref\" role=\"doc-backlink\"\u003e\u0026#x21a9;\u0026#xfe0e;\u003c/a\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003c/div\u003e","title":"Chrome のタブグループを活用するキーボードショートカットのカスタマイズ (macOS 向け)"},{"content":"対処法 環境変数を追加する。\nset -Ux COLORTERM 1 もしこれで直らなければシェルを再起動するか、以下 2 つのグローバル変数を消去する。\nset -e __fish_ls_command set -e __fish_ls_color_opt あるいは athityakumar/colorls をインストールするとそちらが使われるようになる。\nまた、試していないが coreutils をインストールすることでも解消すると思われる。\n補足 type ls で確認するとわかるが、 fish では ls コマンドは標準の関数でラップされており、 カラー化するオプションを追加してくれている。\nhttps://github.com/fish-shell/fish-shell/blob/c16e30931b44628bccf2abc3082ddeb53e08971e/share/functions/ls.fish#L20-L47 分岐を見ると、特定の条件を満たした場合1はカラー化オプションを決定するために\nls --color=auto (GNU 系で有効) ls -G (macOS, BSD 系で有効) ls --color ls -F の順で試行し、エラーにならなければそのオプションを採用するようだ。\nしかし macOS Monterey でコマンドを実行してみると、 ls --color=auto はエラーにならないしカラーにもならない。\nmacOS Monterey の ls --color=auto は、 stdout が tty でありかつ COLORTERM 環境変数が設定されている場合のみカラー表示を行うためだ2。\nつまり環境変数 COLORTERM を定義しておけばカラー表示されるようになる。値はなんでも良いようだ。\nさらに補足 ls --color=auto は以前の macOS ではエラーになっていた。 (おそらく Big Sur まで？)\n❯ ls --color=auto ls: illegal option -- - usage: ls [-@ABCFGHLOPRSTUWabcdefghiklmnopqrstuwx1%] [file ...] このため以前までは、前述のラッパー関数の判定で ls -G が実際に実行されるコマンドになっていた。\nman を見るとこのような違いがある。 BSD の実装から独自実装に変わったのだろうか。これについては検索しても情報が見つけられなかった。\nBig Sur:\nLS(1) BSD General Commands Manual LS(1) NAME ls -- list directory contents SYNOPSIS ls [-ABCFGHLOPRSTUW@abcdefghiklmnopqrstuwx1%] [file ...] ... (略) BSD May 19, 2002 BSD Monterey:\nLS(1) General Commands Manual LS(1) NAME ls – list directory contents SYNOPSIS ls [-@ABCFGHILOPRSTUWabcdefghiklmnopqrstuvwxy1%,] [--color=when] [-D format] [file ...] ... (略) macOS 12.0 August 31, 2020 macOS 12.0 追記: Big Sur は Intel Mac で、 Monterey は M1 Mac で使用していた。\n変数 __fish_ls_color_opt が未設定で colorls コマンドが存在しない場合\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nなお -G オプションは、 COLORTERM を設定した上で --color=auto を指定するのと同等\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","permalink":"https://okiyama.dev/posts/2021-11-12-no-colors-in-fish-ls-on-macos/","summary":"\u003ch2 id=\"対処法\"\u003e対処法\u003c/h2\u003e\n\u003cp\u003e環境変数を追加する。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fish\" data-lang=\"fish\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eset\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e-Ux\u003c/span\u003e COLORTERM \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eもしこれで直らなければシェルを再起動するか、以下 2 つのグローバル変数を消去する。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fish\" data-lang=\"fish\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eset\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e-e\u003c/span\u003e __fish_ls_command\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#66d9ef\"\u003eset\u003c/span\u003e \u003cspan style=\"color:#a6e22e\"\u003e-e\u003c/span\u003e __fish_ls_color_opt\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eあるいは \u003ca href=\"https://github.com/athityakumar/colorls\" target=\"_blank\"\u003eathityakumar/colorls\u003c/a\u003e\n をインストールするとそちらが使われるようになる。\u003c/p\u003e\n\u003cp\u003eまた、試していないが coreutils をインストールすることでも解消すると思われる。\u003c/p\u003e\n\u003ch2 id=\"補足\"\u003e補足\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003etype ls\u003c/code\u003e で確認するとわかるが、 fish では \u003ccode\u003els\u003c/code\u003e コマンドは標準の関数でラップされており、\nカラー化するオプションを追加してくれている。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/fish-shell/fish-shell/blob/c16e30931b44628bccf2abc3082ddeb53e08971e/share/functions/ls.fish#L20-L47\" target=\"_blank\"\u003ehttps://github.com/fish-shell/fish-shell/blob/c16e30931b44628bccf2abc3082ddeb53e08971e/share/functions/ls.fish#L20-L47\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003e分岐を見ると、特定の条件を満たした場合\u003csup id=\"fnref:1\"\u003e\u003ca href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e1\u003c/a\u003e\u003c/sup\u003eはカラー化オプションを決定するために\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003els --color=auto\u003c/code\u003e (GNU 系で有効)\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003els -G\u003c/code\u003e (macOS, BSD 系で有効)\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003els --color\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003els -F\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eの順で試行し、エラーにならなければそのオプションを採用するようだ。\u003c/p\u003e\n\u003cp\u003eしかし macOS Monterey でコマンドを実行してみると、 \u003ccode\u003els --color=auto\u003c/code\u003e はエラーにならないしカラーにもならない。\u003c/p\u003e\n\u003cp\u003emacOS Monterey の \u003ccode\u003els --color=auto\u003c/code\u003e は、 stdout が tty でありかつ \u003ccode\u003eCOLORTERM\u003c/code\u003e 環境変数が設定されている場合のみカラー表示を行うためだ\u003csup id=\"fnref:2\"\u003e\u003ca href=\"#fn:2\" class=\"footnote-ref\" role=\"doc-noteref\"\u003e2\u003c/a\u003e\u003c/sup\u003e。\u003c/p\u003e\n\u003cp\u003eつまり環境変数 \u003ccode\u003eCOLORTERM\u003c/code\u003e を定義しておけばカラー表示されるようになる。値はなんでも良いようだ。\u003c/p\u003e\n\u003ch2 id=\"さらに補足\"\u003eさらに補足\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003els --color=auto\u003c/code\u003e は以前の macOS ではエラーになっていた。 (おそらく Big Sur まで？)\u003c/p\u003e","title":"macOS Monterey (M1 Mac) にしたら fish shell で ls がカラー表示されない"},{"content":"fish shell では他のシェルと同じように ↑, ↓ や Ctrl + n, Ctrl + p で行ごとに履歴をたどることができる。これに加えて Alt + ↑, Alt + ↓ で単語ごとの履歴をたどって補完することができる。\n例えば git status で変更のあるファイルを一覧し、特定のファイルだけをコミットしたいとする。\ngit status -sb # ## master...origin/master # M dotconfig/karabiner/karabiner.json # M init.sh # ?? dotconfig/flutter/ いくつか変更があるが init.sh だけをコミットしたい。 git diff で差分確認してから git add を行う。\ngit diff init.sh # diff --git a/init.sh b/init.sh # (snip) git add init.sh ここで git add を入力してから Alt + ↑ を押すと init.sh が補完される。 (もう一度押すと diff に変わる)\nこの例だとファイル名が短いのであまりメリットはないが、長いパスを指定する時などに便利。\n","permalink":"https://okiyama.dev/posts/2021-07-20-fish-argument-history-insertion/","summary":"\u003cp\u003efish shell では他のシェルと同じように \u003ccode\u003e↑\u003c/code\u003e, \u003ccode\u003e↓\u003c/code\u003e や \u003ccode\u003eCtrl + n\u003c/code\u003e, \u003ccode\u003eCtrl + p\u003c/code\u003e で行ごとに履歴をたどることができる。これに加えて \u003ccode\u003eAlt + ↑\u003c/code\u003e, \u003ccode\u003eAlt + ↓\u003c/code\u003e で単語ごとの履歴をたどって補完することができる。\u003c/p\u003e\n\u003cp\u003e例えば \u003ccode\u003egit status\u003c/code\u003e で変更のあるファイルを一覧し、特定のファイルだけをコミットしたいとする。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fish\" data-lang=\"fish\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003egit\u003c/span\u003e status \u003cspan style=\"color:#a6e22e\"\u003e-sb\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# ## master...origin/master\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#  M dotconfig/karabiner/karabiner.json\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#  M init.sh\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# ?? dotconfig/flutter/\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eいくつか変更があるが \u003ccode\u003einit.sh\u003c/code\u003e だけをコミットしたい。 \u003ccode\u003egit diff\u003c/code\u003e で差分確認してから \u003ccode\u003egit add\u003c/code\u003e を行う。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fish\" data-lang=\"fish\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003egit\u003c/span\u003e diff init.sh\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# diff --git a/init.sh b/init.sh\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# (snip)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003egit\u003c/span\u003e add init.sh\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eここで \u003ccode\u003egit add\u003c/code\u003e を入力してから \u003ccode\u003eAlt + ↑\u003c/code\u003e を押すと \u003ccode\u003einit.sh\u003c/code\u003e が補完される。 (もう一度押すと \u003ccode\u003ediff\u003c/code\u003e に変わる)\u003c/p\u003e","title":"fish shell は `Alt + ↑` で引数の履歴を補完してくれる"},{"content":"android ライセンスでエラー セットアップの手順に flutter doctor --android-licenses で正しく出力されることを確認する項目があるが、ここでエラーが発生した。\n❯ flutter doctor --android-licenses Exception in thread \u0026#34;main\u0026#34; java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlSchema at com.android.repository.api.SchemaModule$SchemaModuleVersion.\u0026lt;init\u0026gt;(SchemaModule.java:156) at com.android.repository.api.SchemaModule.\u0026lt;init\u0026gt;(SchemaModule.java:75) at com.android.sdklib.repository.AndroidSdkHandler.\u0026lt;clinit\u0026gt;(AndroidSdkHandler.java:81) at com.android.sdklib.tool.sdkmanager.SdkManagerCli.main(SdkManagerCli.java:73) at com.android.sdklib.tool.sdkmanager.SdkManagerCli.main(SdkManagerCli.java:48) Caused by: java.lang.ClassNotFoundException: javax.xml.bind.annotation.XmlSchema at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522) ... 5 more Android Studio で設定を行うと解消した。\nAndroid Studio で設定を開き Android SDK を選択 SDK Tools タブを選択 Android SDK Command-line tools をチェックし Apply を実行 参考: https://stackoverflow.com/questions/61993738/flutter-doctor-android-licenses-gives-a-java-error/66363044 エミュレータの選択でエラー Chrome でのデバッグはできるが、デバイスから Create Android emulator を選ぶと No device definitions are available というエラーが発生するが、そもそも create する必要はない。\nリストに既に Pixel_3a というエミュレータがあり、それを使えば良い。\nセキュリティソフトのファイアウォール機能を切っておかないと emulator が動作しない セキュリティソフトの種類にもよるが、自分の環境ではオフにする必要があった。\nflutter_driver を追加した際に incompatible エラーが発生する ERR : Because every version of flutter_driver from sdk depends on args 1.6.0 and flutter_launcher_icons 0.9.0 depends on args 2.0.0, flutter_driver from sdk is incompatible with flutter_launcher_icons 0.9.0. And because no versions of flutter_launcher_icons match \u0026gt;0.9.0 \u0026lt;0.10.0, flutter_driver from sdk is incompatible with flutter_launcher_icons ^0.9.0. So, because hello_world depends on both flutter_launcher_icons ^0.9.0 and flutter_driver any from sdk, version solving failed. pubspec.yaml で flutter_launcher_icons を古いバージョンに変更して解消した。\n- flutter_launcher_icons: ^0.9.0 + flutter_launcher_icons: ^0.8.1 参考: https://github.com/fluttercommunity/flutter_launcher_icons/issues/241 エラー Unexpected text 'late' が発生する Unexpected text \u0026#39;late\u0026#39;. Try removing the text.dart(unexpected_token) dart sdk のバージョンが古いことが原因。 late キーワードは dart 2.12.0 以降で使える。 pubspec.yaml でバージョン指定を変更すれば良い。\nenvironment: - sdk: \u0026#34;\u0026gt;=2.7.0 \u0026lt;3.0.0\u0026#34; + sdk: \u0026#34;\u0026gt;=2.12.0 \u0026lt;3.0.0\u0026#34; 参考: https://stackoverflow.com/questions/67113942/dart-unexpected-text-late これを行うと以下のエラーが出るようになるが、これは null-safe ではないコードが残っているため。\nエラー:\nThe non-nullable local variable \u0026#39;driver\u0026#39; must be assigned before it can be used. Try giving it an initializer expression, or ensure that it\u0026#39;s assigned on every execution path.dart(not_assigned_potentially_non_nullable_local_variable) 以下のように、宣言時に初期化していないメンバー変数がある場合に発生する。これは late を追加すると解消する。\n- FlutterDriver driver; + late FlutterDriver driver; ","permalink":"https://okiyama.dev/posts/2021-07-09-getting-started-with-flutter/","summary":"\u003ch2 id=\"android-ライセンスでエラー\"\u003eandroid ライセンスでエラー\u003c/h2\u003e\n\u003cp\u003eセットアップの手順に \u003ccode\u003eflutter doctor --android-licenses\u003c/code\u003e で正しく出力されることを確認する項目があるが、ここでエラーが発生した。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-fish\" data-lang=\"fish\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e❯ flutter doctor \u003cspan style=\"color:#a6e22e\"\u003e--android-licenses\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eException\u003c/span\u003e in thread \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;main\u0026#34;\u003c/span\u003e java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlSchema\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eat\u003c/span\u003e com.android.repository.api.SchemaModule$SchemaModuleVersion.\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003einit\u003cspan style=\"color:#f92672\"\u003e\u0026gt;(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eSchemaModule\u003c/span\u003e.java:\u003cspan style=\"color:#ae81ff\"\u003e156\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eat\u003c/span\u003e com.android.repository.api.SchemaModule.\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003einit\u003cspan style=\"color:#f92672\"\u003e\u0026gt;(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eSchemaModule\u003c/span\u003e.java:\u003cspan style=\"color:#ae81ff\"\u003e75\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eat\u003c/span\u003e com.android.sdklib.repository.AndroidSdkHandler.\u003cspan style=\"color:#f92672\"\u003e\u0026lt;\u003c/span\u003eclinit\u003cspan style=\"color:#f92672\"\u003e\u0026gt;(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eAndroidSdkHandler\u003c/span\u003e.java:\u003cspan style=\"color:#ae81ff\"\u003e81\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eat\u003c/span\u003e com.android.sdklib.tool.sdkmanager.SdkManagerCli.main\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eSdkManagerCli\u003c/span\u003e.java:\u003cspan style=\"color:#ae81ff\"\u003e73\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eat\u003c/span\u003e com.android.sdklib.tool.sdkmanager.SdkManagerCli.main\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eSdkManagerCli\u003c/span\u003e.java:\u003cspan style=\"color:#ae81ff\"\u003e48\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#a6e22e\"\u003eCaused\u003c/span\u003e by: java.lang.ClassNotFoundException: javax.xml.bind.annotation.XmlSchema\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eat\u003c/span\u003e java.base/jdk.internal.loader.BuiltinClassLoader.loadClass\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eBuiltinClassLoader\u003c/span\u003e.java:\u003cspan style=\"color:#ae81ff\"\u003e581\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eat\u003c/span\u003e java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eClassLoaders\u003c/span\u003e.java:\u003cspan style=\"color:#ae81ff\"\u003e178\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        \u003cspan style=\"color:#a6e22e\"\u003eat\u003c/span\u003e java.base/java.lang.ClassLoader.loadClass\u003cspan style=\"color:#f92672\"\u003e(\u003c/span\u003e\u003cspan style=\"color:#a6e22e\"\u003eClassLoader\u003c/span\u003e.java:\u003cspan style=\"color:#ae81ff\"\u003e522\u003c/span\u003e\u003cspan style=\"color:#f92672\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        ... \u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e more\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eAndroid Studio で設定を行うと解消した。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eAndroid Studio で設定を開き \u003ccode\u003eAndroid SDK\u003c/code\u003e を選択\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eSDK Tools\u003c/code\u003e タブを選択\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eAndroid SDK Command-line tools\u003c/code\u003e をチェックし Apply を実行\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e参考: \u003ca href=\"https://stackoverflow.com/questions/61993738/flutter-doctor-android-licenses-gives-a-java-error/66363044\" target=\"_blank\"\u003ehttps://stackoverflow.com/questions/61993738/flutter-doctor-android-licenses-gives-a-java-error/66363044\u003c/a\u003e\n\u003c/p\u003e\n\u003ch2 id=\"エミュレータの選択でエラー\"\u003eエミュレータの選択でエラー\u003c/h2\u003e\n\u003cp\u003eChrome でのデバッグはできるが、デバイスから \u003ccode\u003eCreate Android emulator\u003c/code\u003e を選ぶと \u003ccode\u003eNo device definitions are available\u003c/code\u003e というエラーが発生するが、そもそも create する必要はない。\u003c/p\u003e","title":"Flutter 入門中のトラブルシューティング"},{"content":"tpm (Tmux Plugin Manager) を導入した。\nhttps://github.com/tmux-plugins/tpm tpm の導入後、 .tmux.conf に使いたいプラグインを記述して prefix + I を押すとプラグインをインストール・ロードしてくれる。\nプラグインのリストもあり、いくつかインストールしてみた。\nhttps://github.com/tmux-plugins/list 私の設定ファイルは GitHub に置いてある。\ntmux-sensible, tmux-yank 自分は .tmux.conf をほとんどカスタムしておらず、基本的な設定とクリップボード連携の設定をしていたくらいだったが、プラグインを導入することでその記述すら不要になった。\ntmux-sensible はいくつかの基本的な設定に、多くのユーザにとって快適になるようなデフォルト値を提供してくれる (ユーザが変更している場合はそれを上書きしないよう配慮されている) 。\nhttps://github.com/tmux-plugins/tmux-sensible tmux-yank はクリップボード連携の設定をしてくれる。\nhttps://github.com/tmux-plugins/tmux-yank tmux-prefix-highlight ステータスラインに #{prefix_highlight} と記述すると prefix キーの状態が表示されるようになる。\nhttps://github.com/tmux-plugins/tmux-prefix-highlight tmux-urlview 現在の画面から URL を探し、ブラウザで開くプラグイン。 prefix + u で URL リストが開き、選ぶとブラウザが立ち上がる。\nhttps://github.com/tmux-plugins/tmux-urlview extrakto 現在の画面から単語を探し、コマンドラインにペーストできるプラグイン。 prefix + Tab で単語リストのポップアップが開き、リストから fzf で絞り込んで選択する。\nmac で使う場合は最新の bash を入れておく必要がある。\nhttps://github.com/laktak/extrakto vim プラグイン: tmuxline.vim tmux ではなく vim プラグインだが、実行すると vim のステータスラインを元に tmux のステータスライン設定を生成してくれる。\nhttps://github.com/edkolev/tmuxline.vim プラグインにより用意されるキーバインド 以上のプラグインが用意してくれるキーバインドをリストしておく。\ntpm prefix + I Installs new plugins from GitHub or any other git repository Refreshes TMUX environment prefix + U \u0026hellip; updates plugin(s) prefix + M-u \u0026hellip; remove/uninstall plugins not on the plugin list urlview prefix + u \u0026hellip; listing all urls on bottom pane extrakto prefix + Tab \u0026hellip; start extrakto プラグインが有効にならない場合は プラグインをインストールしたのに有効にならない時は、エラーになっていないかを確認する。\nプラグインファイルは実行ファイルになっており、以下のように実行するとエラーメッセージを確認できる。\n❯ ~/.tmux/plugins/extrakto/extrakto.tmux /Users/p563/.tmux/plugins/extrakto/extrakto.tmux: line 10: ${extrakto_key,,}: bad substitution 上記のエラーは mac の古い bash を使っているために発生しているエラーで、 homebrew で bash をインストールすると解消する\n参考 tmux プラグインの仕組みについてはこちらの記事が参考になった。\ntmuxを使いこなす / プラグイン開発で機能を拡張 | DevelopersIO ","permalink":"https://okiyama.dev/posts/2021-05-01-tpm-tmux-plugin-manager/","summary":"\u003cp\u003etpm (Tmux Plugin Manager) を導入した。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/tmux-plugins/tpm\" target=\"_blank\"\u003ehttps://github.com/tmux-plugins/tpm\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003etpm の導入後、 \u003ccode\u003e.tmux.conf\u003c/code\u003e に使いたいプラグインを記述して \u003ccode\u003eprefix\u003c/code\u003e + \u003ccode\u003eI\u003c/code\u003e を押すとプラグインをインストール・ロードしてくれる。\u003c/p\u003e\n\u003cp\u003eプラグインのリストもあり、いくつかインストールしてみた。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/tmux-plugins/list\" target=\"_blank\"\u003ehttps://github.com/tmux-plugins/list\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003e私の設定ファイルは \u003ca href=\"https://github.com/rokiyama/dotfiles/blob/2f3092b79928bd7ac2979339a042fef9c3cc87f3/.tmux.conf\" target=\"_blank\"\u003eGitHub\u003c/a\u003e\n に置いてある。\u003c/p\u003e\n\u003ch2 id=\"tmux-sensible-tmux-yank\"\u003etmux-sensible, tmux-yank\u003c/h2\u003e\n\u003cp\u003e自分は \u003ccode\u003e.tmux.conf\u003c/code\u003e をほとんどカスタムしておらず、基本的な設定とクリップボード連携の設定をしていたくらいだったが、プラグインを導入することでその記述すら不要になった。\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003etmux-sensible\u003c/code\u003e はいくつかの基本的な設定に、多くのユーザにとって快適になるようなデフォルト値を提供してくれる (ユーザが変更している場合はそれを上書きしないよう配慮されている) 。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/tmux-plugins/tmux-sensible\" target=\"_blank\"\u003ehttps://github.com/tmux-plugins/tmux-sensible\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003etmux-yank\u003c/code\u003e はクリップボード連携の設定をしてくれる。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/tmux-plugins/tmux-yank\" target=\"_blank\"\u003ehttps://github.com/tmux-plugins/tmux-yank\u003c/a\u003e\n\u003c/p\u003e\n\u003ch2 id=\"tmux-prefix-highlight\"\u003etmux-prefix-highlight\u003c/h2\u003e\n\u003cp\u003eステータスラインに \u003ccode\u003e#{prefix_highlight}\u003c/code\u003e と記述すると prefix キーの状態が表示されるようになる。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/tmux-plugins/tmux-prefix-highlight\" target=\"_blank\"\u003ehttps://github.com/tmux-plugins/tmux-prefix-highlight\u003c/a\u003e\n\u003c/p\u003e\n\u003ch2 id=\"tmux-urlview\"\u003etmux-urlview\u003c/h2\u003e\n\u003cp\u003e現在の画面から URL を探し、ブラウザで開くプラグイン。 \u003ccode\u003eprefix\u003c/code\u003e + \u003ccode\u003eu\u003c/code\u003e で URL リストが開き、選ぶとブラウザが立ち上がる。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/tmux-plugins/tmux-urlview\" target=\"_blank\"\u003ehttps://github.com/tmux-plugins/tmux-urlview\u003c/a\u003e\n\u003c/p\u003e\n\u003ch2 id=\"extrakto\"\u003eextrakto\u003c/h2\u003e\n\u003cp\u003e現在の画面から単語を探し、コマンドラインにペーストできるプラグイン。 \u003ccode\u003eprefix\u003c/code\u003e + \u003ccode\u003eTab\u003c/code\u003e で単語リストのポップアップが開き、リストから fzf で絞り込んで選択する。\u003c/p\u003e\n\u003cp\u003emac で使う場合は最新の bash を入れておく必要がある。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/laktak/extrakto\" target=\"_blank\"\u003ehttps://github.com/laktak/extrakto\u003c/a\u003e\n\u003c/p\u003e\n\u003ch2 id=\"vim-プラグイン-tmuxlinevim\"\u003evim プラグイン: tmuxline.vim\u003c/h2\u003e\n\u003cp\u003etmux ではなく vim プラグインだが、実行すると vim のステータスラインを元に tmux のステータスライン設定を生成してくれる。\u003c/p\u003e","title":"tpm (Tmux Plugin Manager) を導入"},{"content":"VSCode Vim というエクステンションを入れると vim キーバインドが使える。エディタだけでなく、 explorer ペインでも以下のようなキーバインドが使えるようになる。\nj, k で選択 l, h でツリーの開閉 l でファイルオープン Space でファイルプレビュー (これは標準機能かも) ある時これが使えなくなったので調べたところ、以下の二つを設定することで解消した。\n\u0026quot;workbench.list.keyboardNavigation\u0026quot;: \u0026quot;simple\u0026quot; \u0026quot;workbench.list.automaticKeyboardNavigation\u0026quot;: false 参考: Navigation in the explorer pane vim way (j , k) doesn\u0026rsquo;t work after window reload 各設定の意味は以下の通り。\nworkbench.list.keyboardNavigation\nControls the keyboard navigation style for lists and trees in the workbench. Can be simple, highlight and filter.\nsimple: Simple keyboard navigation focuses elements which match the keyboard input. Matching is done only on prefixes. highlight: Highlight keyboard navigation highlights elements which match the keyboard input. Further up and down navigation will traverse only the highlighted elements. filter: Filter keyboard navigation will filter out and hide all the elements which do not match the keyboard input. workbench.list.automaticKeyboardNavigation\nControls whether keyboard navigation in lists and trees is automatically triggered simply by typing. If set to false, keyboard navigation is only triggered when executing the list.toggleKeyboardNavigation command, for which you can assign a keyboard shortcut.\nhttps://code.visualstudio.com/docs/getstarted/settings ","permalink":"https://okiyama.dev/posts/2021-04-19-vscode-vim-keybind-in-the-explorer-pane-doesnt-working/","summary":"\u003cp\u003e\u003ca href=\"http://aka.ms/vscodevim\" target=\"_blank\"\u003eVSCode Vim\u003c/a\u003e\n というエクステンションを入れると vim キーバインドが使える。エディタだけでなく、 explorer ペインでも以下のようなキーバインドが使えるようになる。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003ej\u003c/code\u003e, \u003ccode\u003ek\u003c/code\u003e で選択\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003el\u003c/code\u003e, \u003ccode\u003eh\u003c/code\u003e でツリーの開閉\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003el\u003c/code\u003e でファイルオープン\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eSpace\u003c/code\u003e でファイルプレビュー (これは標準機能かも)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eある時これが使えなくなったので調べたところ、以下の二つを設定することで解消した。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003e\u0026quot;workbench.list.keyboardNavigation\u0026quot;: \u0026quot;simple\u0026quot;\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003e\u0026quot;workbench.list.automaticKeyboardNavigation\u0026quot;: false\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e参考: \u003ca href=\"https://github.com/VSCodeVim/Vim/issues/3760\" target=\"_blank\"\u003eNavigation in the explorer pane vim way (j , k) doesn\u0026rsquo;t work after window reload\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003e各設定の意味は以下の通り。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ccode\u003eworkbench.list.keyboardNavigation\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eControls the keyboard navigation style for lists and trees in the workbench. Can be simple, highlight and filter.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003esimple: Simple keyboard navigation focuses elements which match the keyboard input. Matching is done only on prefixes.\u003c/li\u003e\n\u003cli\u003ehighlight: Highlight keyboard navigation highlights elements which match the keyboard input. Further up and down navigation will traverse only the highlighted elements.\u003c/li\u003e\n\u003cli\u003efilter: Filter keyboard navigation will filter out and hide all the elements which do not match the keyboard input.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003ccode\u003eworkbench.list.automaticKeyboardNavigation\u003c/code\u003e\u003c/p\u003e","title":"VSCode Vim で explorer ペインのキーバインドが使えなくなった時の対処法"},{"content":"Google Nest Wifi ルーターと拡張ポイントを買った。\nhttps://store.google.com/jp/product/nest_wifi スマホのアプリが良く、セットアップがとても簡単だった。 Google Home アプリまたは Google Wifi アプリから設定するのだが、最初に繋ぐときはアプリが勝手にやってくれるため SSID などの入力は必要ない (Bluetooth あるいはスマホを Wifi AP にして接続するらしい)。\n電源の抜き差しを何度か行った際にインターネット接続がなかなか復帰しないことがあったが、それ以外は特に問題なく使えている。 LAN 側の Ethernet ポートが一個しかない点は少し不便。\nつまづいたのが、 2.4GHz と 5GHz が同じ SSID なので 2.4GHz のみ対応のスマート照明に 5GHz で繋がっているスマホから接続できず、セットアップを行えないという問題。なお照明は +Style というメーカーの製品 である。\n2.4GHz と 5GHz の SSID を分けるのは Nest Wifi ではできないらしい。スマホが 2.4GHz だけに繋がれば良いのだが、今持っているものはそのように設定できない。 2.4GHz のみ対応の古い Fire HD タブレットがあったが、これは Amazon のカスタム OS が入っており +Style のアプリを入れるのがやや面倒だ。\n+Style のアプリにはスマホを一時的に Wifi AP にしてセットアップする互換モードという方式が用意されている。今回はこれを使うことでうまくいったが、このような機能がない製品の場合はいくつか対応手段がある。\n2.4GHz のみ対応の古いスマホを買う 2.4GHz で接続される程度に家から離れてからセットアップする Nest Wifi を停止した状態でスマホを Wifi AP にし、 Nest Wifi で使う SSID/WPA キーと同じものにしてデバイスを接続・セットアップする 2 の家から離れるというのは、 5GHz は遠くまで届かないが 2.4GHz は低周波で遠くまで届く特性があり、両方の周波数に対応しているスマホは 5GHz に優先して接続するが、ルーターから離れて電波が減衰すると 2.4GHz にフォールバックするのでその状態で IoT デバイスのセットアップをする、ということらしい。\n参考: How do force Google Wifi to 2.4 Ghz only? - Google Nest Community ","permalink":"https://okiyama.dev/posts/2021-04-18-google-nest-wifi-and-smart-light/","summary":"\u003cp\u003eGoogle Nest Wifi ルーターと拡張ポイントを買った。\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://store.google.com/jp/product/nest_wifi\" target=\"_blank\"\u003ehttps://store.google.com/jp/product/nest_wifi\u003c/a\u003e\n\u003c/p\u003e\n\u003cp\u003eスマホのアプリが良く、セットアップがとても簡単だった。 Google Home アプリまたは Google Wifi アプリから設定するのだが、最初に繋ぐときはアプリが勝手にやってくれるため SSID などの入力は必要ない (Bluetooth あるいはスマホを Wifi AP にして接続するらしい)。\u003c/p\u003e\n\u003cp\u003e電源の抜き差しを何度か行った際にインターネット接続がなかなか復帰しないことがあったが、それ以外は特に問題なく使えている。 LAN 側の Ethernet ポートが一個しかない点は少し不便。\u003c/p\u003e\n\u003cp\u003eつまづいたのが、 2.4GHz と 5GHz が同じ SSID なので 2.4GHz のみ対応のスマート照明に 5GHz で繋がっているスマホから接続できず、セットアップを行えないという問題。なお照明は \u003ca href=\"https://amzn.to/3mVS1UR\" target=\"_blank\"\u003e+Style というメーカーの製品\u003c/a\u003e\n である。\u003c/p\u003e\n\u003cp\u003e2.4GHz と 5GHz の SSID を分けるのは Nest Wifi ではできないらしい。スマホが 2.4GHz だけに繋がれば良いのだが、今持っているものはそのように設定できない。 2.4GHz のみ対応の古い Fire HD タブレットがあったが、これは Amazon のカスタム OS が入っており +Style のアプリを入れるのがやや面倒だ。\u003c/p\u003e\n\u003cp\u003e+Style のアプリにはスマホを一時的に Wifi AP にしてセットアップする互換モードという方式が用意されている。今回はこれを使うことでうまくいったが、このような機能がない製品の場合はいくつか対応手段がある。\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e2.4GHz のみ対応の古いスマホを買う\u003c/li\u003e\n\u003cli\u003e2.4GHz で接続される程度に家から離れてからセットアップする\u003c/li\u003e\n\u003cli\u003eNest Wifi を停止した状態でスマホを Wifi AP にし、 Nest Wifi で使う SSID/WPA キーと同じものにしてデバイスを接続・セットアップする\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e2 の家から離れるというのは、 5GHz は遠くまで届かないが 2.4GHz は低周波で遠くまで届く特性があり、両方の周波数に対応しているスマホは 5GHz に優先して接続するが、ルーターから離れて電波が減衰すると 2.4GHz にフォールバックするのでその状態で IoT デバイスのセットアップをする、ということらしい。\u003c/p\u003e","title":"Google Nest Wifi ルーターで 2.4GHz のみ対応の IoT デバイスをセットアップする方法"},{"content":"fish にはディレクトリ履歴を辿る方法が用意されている。\ndirh 履歴を表示 cdh 履歴を素早く操作するためのプロンプトを表示 prevd 履歴を戻る。 Alt+← に対応 nextd 履歴を進む。 Alt+→ に対応 Introduction — fish-shell 3.2.1 documentation 実際によく使うのは履歴を戻る Alt+← と、 cdh\u0026lt;Space\u0026gt;\u0026lt;Tab\u0026gt; で補完候補から戻り先を選ぶというもの。\n補足: pushd について どのシェルにもあるビルトインコマンドとして pushd, popd があり、履歴を残して cd するコマンドである。 pushd \u0026lt;path/to/dir\u0026gt; で現在のパスがスタックに積まれた上で移動し、 popd でスタックからパスが取り出されてそこに移動する。\nzsh では AUTO_PUSHD を設定すると cd した時に自動でパスがスタックに積まれるようになる。\n","permalink":"https://okiyama.dev/posts/2021-03-23-fish-cdh/","summary":"\u003cp\u003efish にはディレクトリ履歴を辿る方法が用意されている。\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://fishshell.com/docs/current/cmds/dirh.html#cmd-dirh\" target=\"_blank\"\u003edirh\u003c/a\u003e\n 履歴を表示\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://fishshell.com/docs/current/cmds/cdh.html#cmd-cdh\" target=\"_blank\"\u003ecdh\u003c/a\u003e\n 履歴を素早く操作するためのプロンプトを表示\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://fishshell.com/docs/current/cmds/prevd.html#cmd-prevd\" target=\"_blank\"\u003eprevd\u003c/a\u003e\n 履歴を戻る。 \u003ccode\u003eAlt\u003c/code\u003e+\u003ccode\u003e←\u003c/code\u003e に対応\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://fishshell.com/docs/current/cmds/nextd.html#cmd-nextd\" target=\"_blank\"\u003enextd\u003c/a\u003e\n 履歴を進む。 \u003ccode\u003eAlt\u003c/code\u003e+\u003ccode\u003e→\u003c/code\u003e に対応\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003ca href=\"https://fishshell.com/docs/current/index.html#id34\" target=\"_blank\"\u003eIntroduction — fish-shell 3.2.1 documentation\u003c/a\u003e\n\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003e実際によく使うのは履歴を戻る \u003ccode\u003eAlt\u003c/code\u003e+\u003ccode\u003e←\u003c/code\u003e と、 \u003ccode\u003ecdh\u0026lt;Space\u0026gt;\u0026lt;Tab\u0026gt;\u003c/code\u003e で補完候補から戻り先を選ぶというもの。\u003c/p\u003e\n\u003ch2 id=\"補足-pushd-について\"\u003e補足: \u003ccode\u003epushd\u003c/code\u003e について\u003c/h2\u003e\n\u003cp\u003eどのシェルにもあるビルトインコマンドとして \u003ccode\u003epushd\u003c/code\u003e, \u003ccode\u003epopd\u003c/code\u003e があり、履歴を残して \u003ccode\u003ecd\u003c/code\u003e するコマンドである。 \u003ccode\u003epushd \u0026lt;path/to/dir\u0026gt;\u003c/code\u003e で現在のパスがスタックに積まれた上で移動し、 \u003ccode\u003epopd\u003c/code\u003e でスタックからパスが取り出されてそこに移動する。\u003c/p\u003e\n\u003cp\u003ezsh では \u003ccode\u003eAUTO_PUSHD\u003c/code\u003e を設定すると \u003ccode\u003ecd\u003c/code\u003e した時に自動でパスがスタックに積まれるようになる。\u003c/p\u003e","title":"fish を使っているなら pushd より cdh が便利"},{"content":"envsubst というコマンドを知った。テキストファイルに環境変数を埋め込んでくれるツール。\n置換前\n// $ cat config.json { \u0026#34;TargetCapacity\u0026#34;: 1, \u0026#34;Type\u0026#34;: \u0026#34;request\u0026#34;, \u0026#34;TagSpecifications\u0026#34;: [ { \u0026#34;ResourceType\u0026#34;: \u0026#34;spot-fleet-request\u0026#34;, \u0026#34;Tags\u0026#34;: [ { \u0026#34;Key\u0026#34;: \u0026#34;Name\u0026#34;, \u0026#34;Value\u0026#34;: \u0026#34;$EC2_INSTANCE_NAME\u0026#34; } ] } ], // ... } 環境変数を設定し、テキストを envsubst に渡すと置換される。\n// $ export EC2_INSTANCE_NAME=my-instance // $ cat config.json | envsubst { \u0026#34;TargetCapacity\u0026#34;: 1, \u0026#34;Type\u0026#34;: \u0026#34;request\u0026#34;, \u0026#34;TagSpecifications\u0026#34;: [ { \u0026#34;ResourceType\u0026#34;: \u0026#34;spot-fleet-request\u0026#34;, \u0026#34;Tags\u0026#34;: [ { \u0026#34;Key\u0026#34;: \u0026#34;Name\u0026#34;, \u0026#34;Value\u0026#34;: \u0026#34;my-instance\u0026#34; } ] } ], // ... } GNU gettext というパッケージに含まれており、割と多くのディストリで標準で使えるようだ。\n以前は sed で置換するとか、 JSON ファイルなら jq でセットするなどしていたが、単純な処理ならこちらの方が簡単だ。\n","permalink":"https://okiyama.dev/posts/2021-03-11-envsubst/","summary":"\u003cp\u003e\u003ccode\u003eenvsubst\u003c/code\u003e というコマンドを知った。テキストファイルに環境変数を埋め込んでくれるツール。\u003c/p\u003e\n\u003cp\u003e置換前\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// $ cat config.json\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;TargetCapacity\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;Type\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;request\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;TagSpecifications\u0026#34;\u003c/span\u003e: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e\u0026#34;ResourceType\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;spot-fleet-request\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e\u0026#34;Tags\u0026#34;\u003c/span\u003e: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003e\u0026#34;Key\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Name\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003e\u0026#34;Value\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;$EC2_INSTANCE_NAME\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#75715e\"\u003e// ...\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e環境変数を設定し、テキストを envsubst に渡すと置換される。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-json\" data-lang=\"json\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// $ export EC2_INSTANCE_NAME=my-instance\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e// $ cat config.json | envsubst\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e{\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;TargetCapacity\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#ae81ff\"\u003e1\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;Type\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;request\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#f92672\"\u003e\u0026#34;TagSpecifications\u0026#34;\u003c/span\u003e: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e\u0026#34;ResourceType\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;spot-fleet-request\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      \u003cspan style=\"color:#f92672\"\u003e\u0026#34;Tags\u0026#34;\u003c/span\u003e: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003e\u0026#34;Key\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;Name\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e          \u003cspan style=\"color:#f92672\"\u003e\u0026#34;Value\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;my-instance\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e        }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e      ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e    }\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  ],\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e  \u003cspan style=\"color:#75715e\"\u003e// ...\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e\u003c/span\u003e}\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eGNU gettext というパッケージに含まれており、割と多くのディストリで標準で使えるようだ。\u003c/p\u003e","title":"envsubst: シェルでちょっとしたテンプレート処理をする"},{"content":"https://pkg.go.dev/google.golang.org/protobuf/testing/protocmp より:\nThe 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.\nbefore\nif diff := cmp.Diff(want, got); diff != \u0026#34;\u0026#34; { t.Errorf(\u0026#34;mismatch (-want +got):\\n%s\u0026#34;, diff) } after\nif diff := cmp.Diff(want, got, protocmp.Transform()); diff != \u0026#34;\u0026#34; { t.Errorf(\u0026#34;mismatch (-want +got):\\n%s\u0026#34;, diff) } サンプルコードは https://pkg.go.dev/github.com/google/go-cmp/cmp?tab=doc#example-Diff-Testing を参照。\n","permalink":"https://okiyama.dev/posts/2020-09-10-gocmp-proto/","summary":"\u003cp\u003e\u003ca href=\"https://pkg.go.dev/google.golang.org/protobuf/testing/protocmp\" target=\"_blank\"\u003ehttps://pkg.go.dev/google.golang.org/protobuf/testing/protocmp\u003c/a\u003e\n より:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThe 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.\u003c/p\u003e\u003c/blockquote\u003e\n\u003cp\u003ebefore\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e\tif diff := cmp.Diff(want, got); diff != \u0026#34;\u0026#34; {\n\t\tt.Errorf(\u0026#34;mismatch (-want +got):\\n%s\u0026#34;, diff)\n\t}\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eafter\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e\tif diff := cmp.Diff(want, got, protocmp.Transform()); diff != \u0026#34;\u0026#34; {\n\t\tt.Errorf(\u0026#34;mismatch (-want +got):\\n%s\u0026#34;, diff)\n\t}\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eサンプルコードは \u003ca href=\"https://pkg.go.dev/github.com/google/go-cmp/cmp?tab=doc#example-Diff-Testing\" target=\"_blank\"\u003ehttps://pkg.go.dev/github.com/google/go-cmp/cmp?tab=doc#example-Diff-Testing\u003c/a\u003e\n を参照。\u003c/p\u003e","title":"Golang の go-cmp で protobuf の struct を Diff する時、 cannot handle unexported field のような panic になる場合は protocmp.Transform() を試してみると良い"},{"content":"CRA で作り始める時の手順メモ。入門中なのでおかしい所があるかも。\nプロジェクト作成 参考: https://create-react-app.dev/docs/getting-started#creating-a-typescript-app npx create-react-app myapp --template typescript prettier 追加 prettier と eslint の設定を追加する。\nyarn add -D eslint-config-prettier eslint-plugin-prettier prettier eslint-config-prettier は、一部ルールが prettier と eslint で重複するため、片方をオフにするためのパッケージ。\neslintConfig 修正 元の react-app に prettier/recommended を追加。\nデフォルト値が https://prettier.io/docs/en/options.html に記載されているので、変更したいものは設定する。\n\u0026#34;eslintConfig\u0026#34;: { - \u0026#34;extends\u0026#34;: \u0026#34;react-app\u0026#34; + \u0026#34;extends\u0026#34;: [ + \u0026#34;react-app\u0026#34;, + \u0026#34;plugin:prettier/recommended\u0026#34; + ] + }, + \u0026#34;prettier\u0026#34;: { + \u0026#34;trailingComma\u0026#34;: \u0026#34;all\u0026#34;, + \u0026#34;semi\u0026#34;: false, + \u0026#34;singleQuote\u0026#34;: true, + \u0026#34;jsxSingleQuote\u0026#34;: true }, script 追加 lint (書式チェック) と format (書式修正) を追加する。\n- \u0026#34;eject\u0026#34;: \u0026#34;react-scripts eject\u0026#34; + \u0026#34;eject\u0026#34;: \u0026#34;react-scripts eject\u0026#34;, + \u0026#34;lint\u0026#34;: \u0026#34;eslint --ext .js,.jsx,.ts,.tsx ./src --color\u0026#34;, + \u0026#34;format\u0026#34;: \u0026#34;prettier --write ./src\u0026#34; ","permalink":"https://okiyama.dev/posts/2020-06-04-cra-typescript-memo/","summary":"\u003cp\u003eCRA で作り始める時の手順メモ。入門中なのでおかしい所があるかも。\u003c/p\u003e\n\u003ch2 id=\"プロジェクト作成\"\u003eプロジェクト作成\u003c/h2\u003e\n\u003cp\u003e参考: \u003ca href=\"https://create-react-app.dev/docs/getting-started#creating-a-typescript-app\" target=\"_blank\"\u003ehttps://create-react-app.dev/docs/getting-started#creating-a-typescript-app\u003c/a\u003e\n\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enpx create-react-app myapp --template typescript\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"prettier-追加\"\u003eprettier 追加\u003c/h2\u003e\n\u003cp\u003eprettier と eslint の設定を追加する。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003eyarn add -D eslint-config-prettier eslint-plugin-prettier prettier\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eeslint-config-prettier は、一部ルールが prettier と eslint で重複するため、片方をオフにするためのパッケージ。\u003c/p\u003e\n\u003ch2 id=\"eslintconfig-修正\"\u003eeslintConfig 修正\u003c/h2\u003e\n\u003cp\u003e元の \u003ccode\u003ereact-app\u003c/code\u003e に \u003ccode\u003eprettier/recommended\u003c/code\u003e を追加。\u003c/p\u003e\n\u003cp\u003eデフォルト値が \u003ca href=\"https://prettier.io/docs/en/options.html\" target=\"_blank\"\u003ehttps://prettier.io/docs/en/options.html\u003c/a\u003e\n に記載されているので、変更したいものは設定する。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-diff:package.json\" data-lang=\"diff:package.json\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e   \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;eslintConfig\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e-\u003c/span\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;extends\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;react-app\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e+\u003c/span\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;extends\u0026#34;\u003c/span\u003e: [\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e+\u003c/span\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;react-app\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e+\u003c/span\u003e      \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;plugin:prettier/recommended\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e+\u003c/span\u003e    ]\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e+\u003c/span\u003e  }\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e+\u003c/span\u003e  \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;prettier\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e+\u003c/span\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;trailingComma\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;all\u0026#34;\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e+\u003c/span\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;semi\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003efalse\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e+\u003c/span\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;singleQuote\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e+\u003c/span\u003e    \u003cspan style=\"color:#f92672\"\u003e\u0026#34;jsxSingleQuote\u0026#34;\u003c/span\u003e: \u003cspan style=\"color:#66d9ef\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e   }\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"script-追加\"\u003escript 追加\u003c/h2\u003e\n\u003cp\u003elint (書式チェック) と format (書式修正) を追加する。\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-diff:package.json\" data-lang=\"diff:package.json\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e-\u003c/span\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;eject\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;react-scripts eject\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e+\u003c/span\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;eject\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;react-scripts eject\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e+\u003c/span\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;lint\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;eslint --ext .js,.jsx,.ts,.tsx ./src --color\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e+\u003c/span\u003e    \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;format\u0026#34;\u003c/span\u003e\u003cspan style=\"color:#960050;background-color:#1e0010\"\u003e:\u003c/span\u003e \u003cspan style=\"color:#e6db74\"\u003e\u0026#34;prettier --write ./src\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e","title":"CRA + TypeScript プロジェクト作成時のメモ"},{"content":"nvm は git clone で入れる brew ではインストールしないこと。 (参照: nvm/README.md at master · nvm-sh/nvm )\ngit clone https://github.com/nvm-sh/nvm.git .nvm nvm で node をインストールする nvm install node \u0026amp;\u0026amp; nvm alias default node yarn は brew install で入れる npm で入れる #brew install yarn --ignore-dependencies npm i -g yarn fish shell の場合 FabioAntunes/fish-nvm: nvm wrapper for fish-shell をインストールする。\n","permalink":"https://okiyama.dev/posts/2019-10-03-mac-node-install-memo/","summary":"\u003ch2 id=\"nvm-は-git-clone-で入れる\"\u003envm は git clone で入れる\u003c/h2\u003e\n\u003cp\u003ebrew ではインストールしないこと。 (参照: \u003ca href=\"https://github.com/nvm-sh/nvm/blob/master/README.md\" target=\"_blank\"\u003envm/README.md at master · nvm-sh/nvm\u003c/a\u003e\n)\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003egit clone https://github.com/nvm-sh/nvm.git .nvm\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"nvm-で-node-をインストールする\"\u003envm で node をインストールする\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003envm install node \u003cspan style=\"color:#f92672\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e nvm alias default node\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"yarn-は-brew-install-で入れる-npm-で入れる\"\u003eyarn は \u003cdel\u003ebrew install で入れる\u003c/del\u003e npm で入れる\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e#brew install yarn --ignore-dependencies\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003enpm i -g yarn\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ch2 id=\"fish-shell-の場合\"\u003efish shell の場合\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/FabioAntunes/fish-nvm\" target=\"_blank\"\u003eFabioAntunes/fish-nvm: nvm wrapper for fish-shell\u003c/a\u003e\n をインストールする。\u003c/p\u003e","title":"mac node インストールメモ"},{"content":"vue-apollo の result 関数が呼ばれるタイミング 2 回ある。\nreactive variables が変更され、ロードが始まったタイミング result.loading === true になる。 ロードが終わったタイミング result.loading === false になる。 apollo 結果がキャッシュされている時に、リアクティブなプロパティを変更しても反映されない fetchPolicy を変更すると動作する。\nfetchPolicy: \u0026quot;cache-and-network\u0026quot; \u0026hellip; OK fetchPolicy: \u0026quot;network-only\u0026quot; \u0026hellip; OK fetchPolicy: \u0026quot;cache-first\u0026quot; \u0026hellip; NG fetchPolicy のデフォルト値は cache-first である。 (参考: apollo-client/watchQueryOptions.ts at 7a2067e33f748372aa6342ef0a097679e3239d29 · apollographql/apollo-client )\n参考: Reactive Variables with \u0026lsquo;cache-first\u0026rsquo; not working in new version · Issue #138 · vuejs/vue-apollo ","permalink":"https://okiyama.dev/posts/2019-10-02-vue-apollo-memo/","summary":"\u003ch2 id=\"vue-apollo-の-result-関数が呼ばれるタイミング\"\u003evue-apollo の \u003ccode\u003eresult\u003c/code\u003e 関数が呼ばれるタイミング\u003c/h2\u003e\n\u003cp\u003e2 回ある。\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003ereactive variables が変更され、ロードが始まったタイミング\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eresult.loading === true\u003c/code\u003e になる。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003eロードが終わったタイミング\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eresult.loading === false\u003c/code\u003e になる。\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"apollo-結果がキャッシュされている時にリアクティブなプロパティを変更しても反映されない\"\u003eapollo 結果がキャッシュされている時に、リアクティブなプロパティを変更しても反映されない\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003efetchPolicy\u003c/code\u003e を変更すると動作する。\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003efetchPolicy: \u0026quot;cache-and-network\u0026quot;\u003c/code\u003e \u0026hellip; OK\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003efetchPolicy: \u0026quot;network-only\u0026quot;\u003c/code\u003e \u0026hellip; OK\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003efetchPolicy: \u0026quot;cache-first\u0026quot;\u003c/code\u003e \u0026hellip; NG\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003ccode\u003efetchPolicy\u003c/code\u003e のデフォルト値は \u003ccode\u003ecache-first\u003c/code\u003e である。 (参考: \u003ca href=\"https://github.com/apollographql/apollo-client/blob/7a2067e33f748372aa6342ef0a097679e3239d29/packages/apollo-client/src/core/watchQueryOptions.ts#L11\" target=\"_blank\"\u003eapollo-client/watchQueryOptions.ts at 7a2067e33f748372aa6342ef0a097679e3239d29 · apollographql/apollo-client\u003c/a\u003e\n)\u003c/p\u003e\n\u003cp\u003e参考: \u003ca href=\"https://github.com/vuejs/vue-apollo/issues/138\" target=\"_blank\"\u003eReactive Variables with \u0026lsquo;cache-first\u0026rsquo; not working in new version · Issue #138 · vuejs/vue-apollo\u003c/a\u003e\n\u003c/p\u003e","title":"vue-apollo メモ"}]