[{"data":1,"prerenderedAt":855},["ShallowReactive",2],{"article-categories":3,"article-tags":12,"latest-articles-5":92,"published-articles":113,"/article/eslint-biome-migration":204},[4,8],{"slug":5,"label":6,"count":7},"blog","Blog",20,{"slug":9,"label":10,"count":11},"book","Books",1,[13,16,18,21,23,27,29,31,33,35,38,40,41,43,45,48,50,53,55,57,59,61,63,65,67,69,72,75,78,80,82,84,86,88,90],{"name":14,"slug":14,"count":15},"beginner",4,{"name":17,"slug":17,"count":15},"linux",{"name":19,"slug":20,"count":15},"nuxt.js","nuxt-js",{"name":22,"slug":22,"count":15},"vim",{"name":24,"slug":25,"count":26},"@nuxt/content","nuxt-content",2,{"name":28,"slug":28,"count":26},"cloudflare",{"name":30,"slug":30,"count":26},"mysql",{"name":32,"slug":32,"count":26},"tools",{"name":34,"slug":34,"count":11},"architecture",{"name":36,"slug":37,"count":11},"AWS","aws",{"name":39,"slug":39,"count":11},"biome",{"name":9,"slug":9,"count":11},{"name":42,"slug":42,"count":11},"css",{"name":44,"slug":44,"count":11},"eslint",{"name":46,"slug":47,"count":11},"ExpoGo","expogo",{"name":49,"slug":49,"count":11},"git",{"name":51,"slug":52,"count":11},"github action","github-action",{"name":54,"slug":54,"count":11},"index",{"name":56,"slug":56,"count":11},"ios",{"name":58,"slug":58,"count":11},"markdown",{"name":60,"slug":60,"count":11},"neovim",{"name":62,"slug":62,"count":11},"obsidian",{"name":64,"slug":64,"count":11},"php",{"name":66,"slug":66,"count":11},"plugins",{"name":68,"slug":68,"count":11},"programing",{"name":70,"slug":71,"count":11},"RDS","rds",{"name":73,"slug":74,"count":11},"ReactNative","reactnative",{"name":76,"slug":77,"count":11},"S3","s3",{"name":79,"slug":79,"count":11},"shell",{"name":81,"slug":81,"count":11},"tailwindcss",{"name":83,"slug":83,"count":11},"terminal",{"name":85,"slug":85,"count":11},"tips",{"name":87,"slug":87,"count":11},"tool",{"name":89,"slug":89,"count":11},"trigger",{"name":91,"slug":91,"count":11},"zsh",[93,97,101,105,109],{"path":94,"title":95,"date":96},"/article/mysql-before-delete-trigger","MySQLのBEFORE DELETEトリガーをちゃんと調べる","2026-09-14",{"path":98,"title":99,"date":100},"/article/eslint-biome-migration","ESLintとPrettierをBiomeやoxlintに乗り換えられるか試す","2026-09-07",{"path":102,"title":103,"date":104},"/article/cloudflare-trailing-slash","Cloudflare Pagesが付ける末尾スラッシュで、記事が「無い」ことになる","2026-09-02",{"path":106,"title":107,"date":108},"/article/tailwind-css-variables","Tailwindのトークンを1箇所にまとめても、ダークモードは切り替わらない","2026-09-01",{"path":110,"title":111,"date":112},"/article/s3-to-rds","AmazonS3のファイルをRDSにロードして使う","2026-02-18",[114,116,118,120,122,124,129,134,139,144,149,154,159,164,169,174,179,184,189,194,199],{"path":94,"title":95,"date":96,"tags":115,"category":5},[30,89],{"path":98,"title":99,"date":100,"tags":117,"category":5},[44,39,19],{"path":102,"title":103,"date":104,"tags":119,"category":5},[28,19,24],{"path":106,"title":107,"date":108,"tags":121,"category":5},[81,19,42],{"path":110,"title":111,"date":112,"tags":123,"category":5},[36,76,70],{"path":125,"title":126,"date":127,"tags":128,"category":5},"/article/tools-lazygit","おすすめツール紹介 - lazygit編","2026-02-10",[87,49,83],{"path":130,"title":131,"date":132,"tags":133,"category":5},"/article/nuxt-content-cloudflare","Nuxt Contentの記事がSSR時に取得できない問題とその解決策","2026-02-07",[19,28,24],{"path":135,"title":136,"date":137,"tags":138,"category":5},"/article/linux-fg-bg","fgとbgでプロセスを操る","2026-02-04",[17],{"path":140,"title":141,"date":142,"tags":143,"category":9},"/article/architecture-textbook","【書評】アーキテクトの教科書","2026-01-30",[9,34],{"path":145,"title":146,"date":147,"tags":148,"category":5},"/article/shell-dev-null","Linuxの標準出力をきちんと理解する","2026-01-28",[79,17],{"path":150,"title":151,"date":152,"tags":153,"category":5},"/article/shell-utilities","シェルの小技・今後使っていきたい機能","2026-01-27",[91,17],{"path":155,"title":156,"date":157,"tags":158,"category":5},"/article/obsidian-action","obsidianのリポジトリをGithubActionsで自動整形する","2026-01-26",[62,51],{"path":160,"title":161,"date":162,"tags":163,"category":5},"/article/react-native-reduce","ReactNativeでのiOS実機確認(Expo Go)で、アプリが動かない事象とその解決法","2026-01-25",[73,46,56],{"path":165,"title":166,"date":167,"tags":168,"category":5},"/article/php-null","phpのnull合体演算子を深掘りする","2026-01-24",[64,68],{"path":170,"title":171,"date":172,"tags":173,"category":5},"/article/mysql-index","6,500万件のテーブルにindexを貼る","2026-01-22",[30,54],{"path":175,"title":176,"date":177,"tags":178,"category":5},"/article/render-markdown","render-markdown.nvimでlinkがうまくレンダリングされなくなった","2026-01-18",[60,66,14],{"path":180,"title":181,"date":182,"tags":183,"category":5},"/article/vim-abbreviation","vimで略語展開設定してみる","2026-01-17",[22,85,14],{"path":185,"title":186,"date":187,"tags":188,"category":5},"/article/vim-folding","neovimのmarkdown折りたたみを改造した","2026-01-16",[22,58],{"path":190,"title":191,"date":192,"tags":193,"category":5},"/article/archlinux-install"," Arch Linux インストールの悪戦苦闘メモ","2026-01-14",[17,14],{"path":195,"title":196,"date":197,"tags":198,"category":5},"/article/vim-standard-memo","Vim標準機能で使いたいものメモ","2026-01-11",[22,32],{"path":200,"title":201,"date":202,"tags":203,"category":5},"/article/vim-command","vim標準で使いたいコマンド","2026-01-10",[22,32,14],{"id":205,"title":99,"body":206,"category":5,"date":100,"description":848,"extension":849,"meta":850,"navigation":423,"path":98,"published":423,"seo":851,"stem":852,"tags":853,"__hash__":854},"article/article/eslint-biome-migration.md",{"type":207,"value":208,"toc":838},"minimark",[209,213,217,220,228,231,253,256,263,326,341,344,347,361,466,480,489,548,551,557,560,563,580,589,613,617,625,645,671,678,684,704,719,730,733,736,743,785,788,791,794,801,804,807,834],[210,211,212],"h2",{"id":212},"はじめに",[214,215,216],"p",{},"みなさん、ESLint と Prettier、使っていますか？",[214,218,219],{},"Biome や oxlint が「Rust製で爆速」という話を聞くたびに、乗り換えたくなってきませんか？私はなります。",[214,221,222,223,227],{},"というわけで、このブログ（Nuxt 4）で実際に試してみました。結論から言うと",[224,225,226],"strong",{},"乗り換えませんでした","。理由が速度とは全然違うところにあったので、その話をします。",[210,229,230],{"id":230},"今の構成",[232,233,234,243,250],"ul",{},[235,236,237,238,242],"li",{},"Nuxt 4.2 / ESLint 10.9 / Prettier 3.9 + ",[239,240,241],"code",{},"prettier-plugin-tailwindcss"," / Tailwind 3.4",[235,244,245,246,249],{},"対象は ",[239,247,248],{},"app/"," の86ファイル、約2,200行",[235,251,252],{},"ESLint はスタイルガイドのプリセットを一切入れておらず、自分で足したルールだけが入っている",[214,254,255],{},"※ 86ファイルなので、正直なところ lint が何秒かかろうと体感は変わりません。完全に趣味です😇",[214,257,258,259,262],{},"足したルールはこういうやつです。",[239,260,261],{},"no-restricted-syntax"," にセレクタを書いて「この構文は書くな」を表現しています。",[264,265,271],"pre",{"className":266,"code":267,"filename":268,"language":269,"meta":270,"style":270},"language-js shiki shiki-themes github-light github-dark","{\n  // bfcache を壊すため、離脱時の処理は pagehide / visibilitychange に置く\n  selector:\n    'CallExpression[callee.property.name=/^(add|remove)EventListener$/] > Literal[value=/^(before)?unload$/]',\n  message: 'unload / beforeunload は購読しない。',\n}\n","eslint.config.mjs","js","",[239,272,273,281,287,297,306,320],{"__ignoreMap":270},[274,275,277],"span",{"class":276,"line":11},"line",[274,278,280],{"class":279},"sVt8B","{\n",[274,282,283],{"class":276,"line":26},[274,284,286],{"class":285},"sJ8bj","  // bfcache を壊すため、離脱時の処理は pagehide / visibilitychange に置く\n",[274,288,290,294],{"class":276,"line":289},3,[274,291,293],{"class":292},"sScJk","  selector",[274,295,296],{"class":279},":\n",[274,298,299,303],{"class":276,"line":15},[274,300,302],{"class":301},"sZZnC","    'CallExpression[callee.property.name=/^(add|remove)EventListener$/] > Literal[value=/^(before)?unload$/]'",[274,304,305],{"class":279},",\n",[274,307,309,312,315,318],{"class":276,"line":308},5,[274,310,311],{"class":292},"  message",[274,313,314],{"class":279},": ",[274,316,317],{"class":301},"'unload / beforeunload は購読しない。'",[274,319,305],{"class":279},[274,321,323],{"class":276,"line":322},6,[274,324,325],{"class":279},"}\n",[214,327,328,329,332,333,336,337,340],{},"これが8本。あとは ",[239,330,331],{},"\u003Ctemplate>"," の Tailwind 任意値（",[239,334,335],{},"w-[264px]","）を禁止するものと、",[239,338,339],{},"\u003Cstyle>"," の色とサイズの直値を禁止する自作ルール（198行）があります。",[210,342,343],{"id":343},"まずはformatterから",[214,345,346],{},"Prettier で整形済みのリポジトリに別の formatter をかけ、出力が変わるか見てみます。",[214,348,349,356,357,360],{},[350,351,355],"a",{"href":352,"rel":353},"https://oxc.rs/docs/guide/usage/formatter.html",[354],"nofollow","oxfmt"," には ",[239,358,359],{},".prettierrc"," を読む移行コマンドがありました。親切ですね。",[264,362,366],{"className":363,"code":364,"language":365,"meta":270,"style":270},"language-sh shiki shiki-themes github-light github-dark","$ oxfmt --migrate=prettier\nMigrated prettier-plugin-tailwindcss options to sortTailwindcss\nMigrated ignore patterns from `.prettierignore`\n\n$ oxfmt --write .\nFinished in 2123ms on 112 files using 4 threads.\n","sh",[239,367,368,380,397,419,425,437],{"__ignoreMap":270},[274,369,370,373,376],{"class":276,"line":11},[274,371,372],{"class":292},"$",[274,374,375],{"class":301}," oxfmt",[274,377,379],{"class":378},"sj4cs"," --migrate=prettier\n",[274,381,382,385,388,391,394],{"class":276,"line":26},[274,383,384],{"class":292},"Migrated",[274,386,387],{"class":301}," prettier-plugin-tailwindcss",[274,389,390],{"class":301}," options",[274,392,393],{"class":301}," to",[274,395,396],{"class":301}," sortTailwindcss\n",[274,398,399,401,404,407,410,413,416],{"class":276,"line":289},[274,400,384],{"class":292},[274,402,403],{"class":301}," ignore",[274,405,406],{"class":301}," patterns",[274,408,409],{"class":301}," from",[274,411,412],{"class":301}," `",[274,414,415],{"class":292},".prettierignore",[274,417,418],{"class":301},"`\n",[274,420,421],{"class":276,"line":15},[274,422,424],{"emptyLinePlaceholder":423},true,"\n",[274,426,427,429,431,434],{"class":276,"line":308},[274,428,372],{"class":292},[274,430,375],{"class":301},[274,432,433],{"class":378}," --write",[274,435,436],{"class":301}," .\n",[274,438,439,442,445,448,451,454,457,460,463],{"class":276,"line":322},[274,440,441],{"class":292},"Finished",[274,443,444],{"class":301}," in",[274,446,447],{"class":301}," 2123ms",[274,449,450],{"class":301}," on",[274,452,453],{"class":378}," 112",[274,455,456],{"class":301}," files",[274,458,459],{"class":301}," using",[274,461,462],{"class":378}," 4",[274,464,465],{"class":301}," threads.\n",[214,467,468,469,472,473,475,476,479],{},"結果、",[224,470,471],{},"112ファイルすべてで差分ゼロ","！！",[239,474,241],{}," のクラス並べ替えまで内蔵していて、",[239,477,478],{},"tailwind.config.ts"," を勝手に見つけて同じ順序を出してくれます。すごい。",[214,481,482,483,488],{},"一方 ",[350,484,487],{"href":485,"rel":486},"https://github.com/g-plane/markup_fmt",[354],"markup_fmt","（dprint 経由）は、104ファイル中56ファイルがずれました。",[264,490,495],{"className":491,"code":492,"filename":493,"language":494,"meta":270,"style":270},"language-diff shiki shiki-themes github-light github-dark","   \u003CNuxtLink\n     :to=\"viewAllPath\"\n     class=\"transition-colors duration-200 hover:text-accent\"\n-    >{{ title }}\u003C/NuxtLink\n-  >\n+    >{{ title }}\u003C/NuxtLink>\n","app/components/article/ArticleShelf.vue","diff",[239,496,497,502,507,512,524,532],{"__ignoreMap":270},[274,498,499],{"class":276,"line":11},[274,500,501],{"class":279},"   \u003CNuxtLink\n",[274,503,504],{"class":276,"line":26},[274,505,506],{"class":279},"     :to=\"viewAllPath\"\n",[274,508,509],{"class":276,"line":289},[274,510,511],{"class":279},"     class=\"transition-colors duration-200 hover:text-accent\"\n",[274,513,516,520],{"class":514,"line":15},[276,515],"diff-remove",[274,517,519],{"class":518},"diff-marker s83E4","-",[274,521,523],{"class":522},"s83E4","    >{{ title }}\u003C/NuxtLink\n",[274,525,527,529],{"class":526,"line":308},[276,515],[274,528,519],{"class":518},[274,530,531],{"class":522},"  >\n",[274,533,536,540,544],{"class":534,"line":322},[276,535],"diff-add",[274,537,539],{"class":538},"diff-marker s9eBZ","+",[274,541,543],{"class":542},"s9eBZ","    >{{ title }}\u003C/NuxtLink",[274,545,547],{"class":546},"diff-word s9eBZ",">\n",[214,549,550],{},"※ こちらは Prettier 互換を名乗っていないので、不具合ではなくそういう設計です。むしろ人間の目には markup_fmt のほうが読みやすい気も...",[214,552,553,554],{},"→ ",[224,555,556],{},"formatter は oxfmt を選べば、そのまま移せそう",[210,558,559],{"id":559},"oxlintを試す",[214,561,562],{},"問題は lint でした。設定を書いて実行したら、いきなりこうなります。",[264,564,568],{"className":565,"code":566,"language":567,"meta":270,"style":270},"language-txt shiki shiki-themes github-light github-dark","Failed to parse oxlint configuration file.\n  x Rule 'no-restricted-syntax' not found in plugin 'eslint'\n","txt",[239,569,570,575],{"__ignoreMap":270},[274,571,572],{"class":276,"line":11},[274,573,574],{},"Failed to parse oxlint configuration file.\n",[274,576,577],{"class":276,"line":26},[274,578,579],{},"  x Rule 'no-restricted-syntax' not found in plugin 'eslint'\n",[214,581,582,584,585,588],{},[239,583,261],{}," が",[224,586,587],{},"実装されていません","。ファイルを1つも読まずに落ちます...",[214,590,591,594,595,598,599,598,602,605,606,609,610,612],{},[239,592,593],{},"no-restricted-imports"," は動きました。ただ ",[239,596,597],{},"require-v-for-key"," も ",[239,600,601],{},"no-v-html",[239,603,604],{},"no-undef-components"," も軒並み ",[239,607,608],{},"not found"," で、",[239,611,331],{}," を見るルールは1本もありません。",[210,614,616],{"id":615},"biomeを試す","Biomeを試す",[214,618,619,624],{},[350,620,623],{"href":621,"rel":622},"https://biomejs.dev/",[354],"Biome"," は良さそうでした。GritQL でルールを自作できます。",[264,626,629],{"className":565,"code":627,"filename":628,"language":567,"meta":270,"style":270},"`$obj.userAgent` as $match where {\n  register_diagnostic(span = $match, message = \"navigator.userAgent で分岐しない。\")\n}\n","plugins/no-user-agent.grit",[239,630,631,636,641],{"__ignoreMap":270},[274,632,633],{"class":276,"line":11},[274,634,635],{},"`$obj.userAgent` as $match where {\n",[274,637,638],{"class":276,"line":26},[274,639,640],{},"  register_diagnostic(span = $match, message = \"navigator.userAgent で分岐しない。\")\n",[274,642,643],{"class":276,"line":289},[274,644,325],{},[214,646,647,650,651,654,655,658,659,662,663,666,667,670],{},[239,648,649],{},".ts"," でも ",[239,652,653],{},".vue"," の ",[239,656,657],{},"\u003Cscript>"," でもちゃんと発火しました。",[239,660,661],{},"overrides"," で当てる範囲も絞れるので、「",[239,664,665],{},"components/"," の中だけ ",[239,668,669],{},"route.params"," を禁止」も書けます。",[214,672,553,673],{},[224,674,675,677],{},[239,676,657],{}," を見るルールは全部移せる",[214,679,680,681,683],{},"ところが ",[239,682,331],{}," の class を見るルールは、うんともすんとも言いません...",[264,685,688],{"className":565,"code":686,"filename":687,"language":567,"meta":270,"style":270},"language html\n\n`class=$value` where { ... }\n","plugins/no-arbitrary.grit",[239,689,690,695,699],{"__ignoreMap":270},[274,691,692],{"class":276,"line":11},[274,693,694],{},"language html\n",[274,696,697],{"class":276,"line":26},[274,698,424],{"emptyLinePlaceholder":423},[274,700,701],{"class":276,"line":289},[274,702,703],{},"`class=$value` where { ... }\n",[214,705,706,650,708,711,712,715,716,718],{},[239,707,653],{},[239,709,710],{},".html"," でも、",[239,713,714],{},"html.experimentalFullSupportEnabled"," を立てても、診断は1件も出ません。",[239,717,339],{}," に至っては CSS 用の入口自体がありませんでした。",[720,721,724],"callout",{"title":722,"type":723},"疑問","help",[214,725,726,727,729],{},"formatter は同じSFCを整形できているのに、なぜ lint からは ",[239,728,331],{}," が見えないのか...?",[214,731,732],{},"パーサが無いわけではなさそうなので、プラグインから触れる形にまだなっていないのだと思います。",[210,734,735],{"id":735},"分かったこと",[214,737,738,739,742],{},"移せるかどうかは、",[224,740,741],{},"そのルールがSFCのどこを見ているか","で決まっていました。",[744,745,746,762,774],"ol",{},[235,747,748,750,751,753,754],{},[239,749,657],{}," / ",[239,752,649],{}," を見るルール\n",[232,755,756],{},[235,757,758,759],{},"import 制限、AST パターン、パス限定 → ",[224,760,761],{},"Biome に移せる",[235,763,764,753,766],{},[239,765,331],{},[232,767,768],{},[235,769,770,771],{},"Tailwind の任意値、未定義コンポーネントの検出 → ",[224,772,773],{},"移せない",[235,775,776,753,778],{},[239,777,339],{},[232,779,780],{},[235,781,782,783],{},"色とサイズの直値 → ",[224,784,773],{},[214,786,787],{},"このブログは 2 と 3 を持っていました。どちらもデザインの一貫性を機械に守らせるためのもので、手放すと目視に戻ります。さすがに速度と引き換えにはできないので、ESLint 続投です！！",[214,789,790],{},"※ 逆に言うと、2 と 3 が1本も無いなら乗り換えの障害は無さそうです。ベンチマークを読む前に、自分の設定を上の3つに仕分けてみてください。",[210,792,793],{"id":793},"おわりに",[214,795,796,797,800],{},"formatter だけ先に移す手もありますね。oxfmt が Prettier と同じ出力を出す以上、",[224,798,799],{},"lint は ESLint のまま、整形だけ oxfmt"," が普通に成立します。",[214,802,803],{},"そのうちやるかもしれません😴",[210,805,806],{"id":806},"参考",[232,808,809,815,820,827],{},[235,810,811],{},[350,812,814],{"href":352,"rel":813},[354],"Oxfmt | The JavaScript Oxidation Compiler",[235,816,817],{},[350,818,487],{"href":485,"rel":819},[354],[235,821,822],{},[350,823,826],{"href":824,"rel":825},"https://biomejs.dev/internals/language-support/",[354],"Biome — Language support",[235,828,829],{},[350,830,833],{"href":831,"rel":832},"https://github.com/uyaaaaaa/personal-blog/blob/main/eslint.config.mjs",[354],"このブログの eslint.config.mjs",[835,836,837],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s83E4, html code.shiki .s83E4{--shiki-default:#B31D28;--shiki-dark:#FDAEB7}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}",{"title":270,"searchDepth":26,"depth":26,"links":839},[840,841,842,843,844,845,846,847],{"id":212,"depth":26,"text":212},{"id":230,"depth":26,"text":230},{"id":343,"depth":26,"text":343},{"id":559,"depth":26,"text":559},{"id":615,"depth":26,"text":616},{"id":735,"depth":26,"text":735},{"id":793,"depth":26,"text":793},{"id":806,"depth":26,"text":806},"速度だけ見て決めると痛い目に遭います","md",{},{"title":99,"description":848},"article/eslint-biome-migration",[44,39,19],"q3t21VlDLz2YXI1hHCcQOZMlK0JsqYOMajbRKQXzPUg",1790473631769]