<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>&lt;![CDATA[TOMILOG]]&gt;</title><description>フリーランス ← フルスタックエンジニア ← Webデザイナー。長野県に移住しフルリモートでアプリやWebサービス開発に携わるエンジニアとして活動中。開発で得た知見を発信していきます。</description><link>https://blog.ryou103.com</link><image><url>https://blog.ryou103.com/icons/icon-144x144.png</url><width>144</width><height>144</height><title>&lt;![CDATA[TOMILOG]]&gt;</title><link>https://blog.ryou103.com</link></image><lastBuildDate>Sun, 25 May 2025 11:00:00 +0000</lastBuildDate><item><title>&lt;![CDATA[Homebrewで管理しているasdfをアップデートしたらエラーが発生したので修正する]]&gt;</title><link>https://blog.ryou103.com/post/asdf-upgrade-homebrew</link><guid>https://blog.ryou103.com/post/asdf-upgrade-homebrew</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/asdf-upgrade-homebrew/asdf-upgrade-homebrew.png" medium="image"></media:content><description>asdfをbrew upgradeコマンドでアップグレードしたらプラグインの利用でNo such file or directoryエラーが発生したので、対処方法をお伝えします。</description><pubDate>Sun, 25 May 2025 11:00:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/asdf-upgrade-homebrew/asdf-upgrade-homebrew.png&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;問題&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;asdf&lt;/code&gt; を &lt;code&gt;brew upgrade asdf&lt;/code&gt; でアップデートした後、インストール済みのプラグインが利用できなくなる問題が発生しました。&lt;/p&gt;
&lt;p&gt;以下のようなエラーメッセージが表示されます:&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;~/.asdf/shims/node: line 6: /opt/homebrew/opt/asdf/libexec/bin/asdf: No such file or directory
~/.asdf/shims/node: line 6: exec: /opt/homebrew/opt/asdf/libexec/bin/asdf: cannot execute: No such file or directory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;v0.14→v0.17（2025.05.25 最新バージョン）にアップデートした際に発生しました。&lt;/p&gt;
&lt;h1&gt;対処方法&lt;/h1&gt;
&lt;p&gt;以下の手順で問題を解決できます。&lt;/p&gt;
&lt;h2&gt;1. &lt;code&gt;asdf&lt;/code&gt; のインストールパスを確認&lt;/h2&gt;
&lt;p&gt;以下のコマンドを実行して、&lt;code&gt;asdf&lt;/code&gt; の新しいインストールパスを確認します:&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;brew --prefix asdf
/opt/homebrew/opt/asdf
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. シムリンクを再生成&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;asdf&lt;/code&gt; のシムリンクを再生成するには、以下のコマンドを実行します:&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;asdf reshim
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これにより、すべてのプラグインのシムリンクが正しいパスで再作成されます。&lt;/p&gt;
&lt;h2&gt;3. 動作確認&lt;/h2&gt;
&lt;p&gt;再度、プラグインが正しく動作するか確認します。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;node --version
v22.14.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;エラーが表示されず、問題なくバージョンが出力されました&lt;/p&gt;
&lt;h1&gt;まとめ&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;brew upgrade asdf&lt;/code&gt; 後にプラグインが動作しなくなる問題は、&lt;code&gt;asdf&lt;/code&gt; のインストールパス変更が原因です。&lt;br /&gt;&lt;code&gt;asdf reshim&lt;/code&gt; コマンドを使用してシムリンクを再生成することで解決できます。&lt;/p&gt;
&lt;p&gt;今後、&lt;code&gt;asdf&lt;/code&gt; をアップデートする際は、この手順を参考にしてください。&lt;/p&gt;
]]&gt;</content:encoded></item><item><title>&lt;![CDATA[Next.jsのSWCコンパイラでPolyfillの自動挿入はできるのか]]&gt;</title><link>https://blog.ryou103.com/post/next-swc-auto-polyfill-survey</link><guid>https://blog.ryou103.com/post/next-swc-auto-polyfill-survey</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/next-swc-auto-polyfill-survey/next-swc-auto-polyfill-survey.png" medium="image"></media:content><description>SWCやBabelを使用すればPolyfillの自動挿入は設定次第で可能です。SWCの設定方法やNext.jsのenv設定の可能性についても探った結果、Next.jsのSWCコンパイラでは設定する方法がありませんでした</description><pubDate>Sat, 18 May 2024 06:44:48 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/next-swc-auto-polyfill-survey/next-swc-auto-polyfill-survey.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;この記事はNext.js@14.2での調査結果になります。&lt;/p&gt;
&lt;h1&gt;Next.jsでPolyfillを自動挿入できるか&lt;/h1&gt;
&lt;p&gt;結論から言うと、SWCを使用している場合はPolyfillを自動挿入する設定はありませんでした。&lt;/p&gt;
&lt;p&gt;Babelを使用している場合は、設定次第でPolyfillを自動挿入することが可能です。&lt;br /&gt;&lt;a href=&quot;https://babeljs.io/docs/babel-preset-env#usebuiltins&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://babeljs.io/docs/babel-preset-env#usebuiltins&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;そもそも、SWCでPolyfillの自動挿入はできる？&lt;/h1&gt;
&lt;p&gt;純正のSWCでもBabel同様の機能を提供しています。&lt;/p&gt;
&lt;p&gt;.swcrcにtargetとmodeを指定すると、トランスパイル結果にPolyfillが挿入されます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-json&quot; data-filename=&quot;.swcrc&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;{
    &quot;env&quot;: {
        &quot;targets&quot;: &quot;Chrome &gt;= 48, Safari &gt; 9&quot;,
        &quot;mode&quot;: &quot;usage&quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下のような、新しめのメソッドを使ったコードで試してみます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-jsx&quot; data-filename=&quot;index.js&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;const hoge = &quot;hoge&quot;

console.log(hoge.replaceAll(&quot;1&quot;, &quot;2&quot;))

const arr = [1, 4, 3, 2].toSorted()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;トランスパイル後のコードは次のようになります。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npx swc ./index.js&lt;/code&gt; の出力結果:&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;import &quot;core-js/modules/es.string.replace.js&quot;;
import &quot;core-js/modules/es.regexp.exec.js&quot;;
import &quot;core-js/modules/es.array.sort.js&quot;;

var hoge = &quot;hoge&quot;;
console.log(hoge.replaceAll(&quot;1&quot;, &quot;2&quot;));
var arr = [1, 4, 3, 2].toSorted();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;文頭にcore-jsのPolyfillコードが注入されました。&lt;/p&gt;
&lt;h1&gt;Next.jsでenvの設定ができないのか&lt;/h1&gt;
&lt;p&gt;Webpackの設定からSWCのオプションであるenvを指定できれば、Polyfillの自動挿入が可能と考えました。&lt;/p&gt;
&lt;p&gt;Next.jsのドキュメントにはSWCローダーに関する記載がなかったため、実装の方を調査しました。&lt;/p&gt;
&lt;h2&gt;next.config.jsの出力結果から探る&lt;/h2&gt;
&lt;p&gt;next.config.jsからWebpackをカスタマイズできるので、ログを仕込んで確認します。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;webpack: (config, options) =&gt; {
    if (options.isServer) {
        return config;
    }

    console.dir(config.module.rules, { depth: 5 });
    
    return config;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;出力された結果から、それらしきものを発見しました。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;{
    test: { or: [/\.(tsx|ts|js|cjs|mjs|jsx)$/, /__barrel_optimize__/] },
    include: [
        &#x27;/myproject/path&#x27;,
        /next[\\/]dist[\\/](esm[\\/])?shared[\\/]lib/,
        /next[\\/]dist[\\/](esm[\\/])?client/,
        /next[\\/]dist[\\/](esm[\\/])?pages/,
        /[\\/](strip-ansi|ansi-regex|styled-jsx)[\\/]/
    ],
    exclude: [Function: exclude],
    use: [
        {
            loader: &#x27;next-swc-loader&#x27;,
            options: {
                isServer: false,
                rootDir: &#x27;/myproject/path&#x27;,
                pagesDir: &#x27;/myproject/path/src/pages&#x27;,
                appDir: undefined,
                hasReactRefresh: false,
                nextConfig: [Object],
                jsConfig: [Object],
                transpilePackages: [Array],
                supportedBrowsers: [Array],
                swcCacheDir: &#x27;/myproject/path/.next/cache/swc&#x27;,
                serverComponents: true,
                esm: false
            }
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;next-swc-loader&lt;/code&gt;がSWCでコードをトランスパイルしてると予想できます。&lt;/p&gt;
&lt;h2&gt;next-swc-loaderの実装を探す&lt;/h2&gt;
&lt;p&gt;GitHubからコードを探します。&lt;/p&gt;
&lt;p&gt;Webpackのオプションを生成している箇所：&lt;br /&gt;&lt;a href=&quot;https://github.com/vercel/next.js/blob/v14.2.3/packages/next/src/build/webpack-config.ts#L426&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/vercel/next.js/blob/v14.2.3/packages/next/src/build/webpack-config.ts#L426&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;next-swc-loader：&lt;br /&gt;&lt;a href=&quot;https://github.com/vercel/next.js/blob/v14.2.3/packages/next/src/build/webpack/loaders/next-swc-loader.ts&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/vercel/next.js/blob/v14.2.3/packages/next/src/build/webpack/loaders/next-swc-loader.ts&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;SWCオプションに変換している箇所：&lt;br /&gt;&lt;a href=&quot;https://github.com/vercel/next.js/blob/v14.2.3/packages/next/src/build/webpack/loaders/next-swc-loader.ts#L115&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/vercel/next.js/blob/v14.2.3/packages/next/src/build/webpack/loaders/next-swc-loader.ts#L115&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;getLoaderSWCOptions：&lt;br /&gt;&lt;a href=&quot;https://github.com/vercel/next.js/blob/v14.2.3/packages/next/src/build/swc/options.ts#L324&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/vercel/next.js/blob/v14.2.3/packages/next/src/build/swc/options.ts#L324&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;next.config.jsから設定できるオプションをSWCオプションに置換している箇所まで辿り着きました。&lt;br /&gt;&lt;a href=&quot;https://github.com/vercel/next.js/blob/v14.2.3/packages/next/src/build/swc/options.ts#L454-L474&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/vercel/next.js/blob/v14.2.3/packages/next/src/build/swc/options.ts#L454-L474&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;そして一番重要なenvの部分が以下のように記述されていました。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;...(supportedBrowsers &amp;#x26;&amp;#x26; supportedBrowsers.length &gt; 0
    ? {
        env: {
            targets: supportedBrowsers
        }
    }
    : {}),
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Polyfillを挿入するには&lt;code&gt;env.mode&lt;/code&gt;を設定する必要がありますが、追加する余地がありませんでした。&lt;/p&gt;
&lt;h1&gt;結論&lt;/h1&gt;
&lt;p&gt;残念ながら、Next.jsのSWCコンパイラではPolyfillの自動挿入を設定することができませんでした。&lt;/p&gt;
&lt;p&gt;SWCのオプションを設定したい要望はいくつか上がってるので、いつかできるようになると良いですね。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/vercel/next.js/discussions/46979&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/vercel/next.js/discussions/46979&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://github.com/vercel/next.js/discussions/30413&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/vercel/next.js/discussions/30413&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://github.com/vercel/next.js/discussions/30940&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/vercel/next.js/discussions/30940&lt;/a&gt;&lt;/p&gt;
]]&gt;</content:encoded><category>Next.js</category></item><item><title>&lt;![CDATA[使われてないiOSシミュレーターを削除してMacbookのストレージを空ける]]&gt;</title><link>https://blog.ryou103.com/post/mac-clean-storage-remove-simulator</link><guid>https://blog.ryou103.com/post/mac-clean-storage-remove-simulator</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/mac-clean-storage-remove-simulator/mac-clean-storage-remove-simulator.png" medium="image"></media:content><description>unavailableと表示される使えなくなったシミュレーターを一発削除できるコマンドを紹介。他にも様々なプロジェクトに携わりストレージがパンパンになってしまったMacbook内のシミュレーターを整理しました。</description><pubDate>Wed, 20 Sep 2023 13:21:48 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/mac-clean-storage-remove-simulator/mac-clean-storage-remove-simulator.png&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;使われてないiOSシミュレーターを削除してストレージを空ける&lt;/h1&gt;
&lt;p&gt;1TBもあるストレージがパンパンに・・・&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/mac-clean-storage-remove-simulator/mac-clean-storage-remove-simulator-1.png&quot; alt=&quot;1TBもあるストレージがパンパン&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;Flutterで開発していると、iOSシミュレーターだったり、Androidエミュレータだったり、XCode, AndroidStudio, etc&lt;/p&gt;
&lt;p&gt;他にも様々なプロジェクトに携わり、ReactNativeやUnityなどのツールも入っていて、、&lt;/p&gt;
&lt;p&gt;あっという間にストレージがいっぱいになってしまいました。&lt;/p&gt;
&lt;p&gt;使っていないiOSシミュレーターが溜まっていることに気づいたので、シミュレーターを削除してストレージを確保しました。&lt;/p&gt;
&lt;h1&gt;使えなくなっているシミュレーターを削除&lt;/h1&gt;
&lt;p&gt;以下のコマンドを叩くと、unavailable と表示されるシミュレーターがある。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ xcrun simctl list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PC上に保存されているシミュレーターがズラーっと表示される。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;== Devices ==
-- iOS 10.3 --
-- iOS 13.7 --
    iPhone 8 (45557ED5-9009-412F-B34E-486217554EB9) (Shutdown)
    iPhone 8 Plus (095C2487-0A98-4F0E-A4D3-633BFE5F2933) (Shutdown)
    iPhone 11 (C26FD654-62C6-46A6-B91E-8640242F02C4) (Shutdown)
    iPhone 11 Pro (B2AD3004-DFFF-4518-8F82-8B5E540C966B) (Shutdown)
    iPhone 11 Pro Max (E61A865B-6B3E-4230-94DF-E76910BACA0A) (Shutdown)
    iPhone SE (2nd generation) (BC6D4CEE-DD96-4E20-9A06-DED430497DE7) (Shutdown)
    iPad Pro (9.7-inch) (3F5CB658-6CEB-4BE5-AD17-7D17E9D31210) (Shutdown)
    iPad (7th generation) (161CEFAA-79DA-4955-992A-278E43D41DA5) (Shutdown)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下の3つはunavailableになったシミュレーター&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;-- Unavailable: com.apple.CoreSimulator.SimRuntime.iOS-14-1 --
    iPhone 6s (C6C43D69-915B-40C9-81FB-52A1EFB03EA1) (Shutdown) (unavailable, runtime profile not found)
    iPhone 8 (3B5B229C-D232-46CE-9D03-640506FDCC17) (Shutdown) (unavailable, runtime profile not found)
    iPhone 8 Plus (3E20A0C5-DFD5-4360-8033-543420F7F771) (Shutdown) (unavailable, runtime profile not found)
     ・
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これらはiOS14.1のシミュレーターで、マイナーバージョンでiOS14.4が出てから使われなくなってしまったシミュレーターだ。&lt;/p&gt;
&lt;p&gt;使うことはなさそうなので、これらを一括削除してくれるコマンドを実行&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ xcrun simctl delete unavailable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このコマンドで unavailable なシミュレーターが削除される&lt;/p&gt;
&lt;h1&gt;無駄に多いシミュレーターを削除&lt;/h1&gt;
&lt;p&gt;また、使っていないシミュレーターをあると思うのでそれも削除する。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/mac-clean-storage-remove-simulator/mac-clean-storage-remove-simulator-2.png&quot; alt=&quot;使っていないシミュレーターを確認&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;上の画面を表示するには、XCodeのデバイス部分をクリックして、&lt;code&gt;Add Additional Simulators&lt;/code&gt;を選択するとウィンドウが表示される。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/mac-clean-storage-remove-simulator/mac-clean-storage-remove-simulator-3.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;無駄にあるiPhone〇〇だったり、Pro Max, Proだとかこんなにいらんわ！ってやつを右クリックしてDeleteで削除してしまいましょう。&lt;/p&gt;
&lt;p&gt;これらで、10GBくらいは容量を確保できました。&lt;/p&gt;
]]&gt;</content:encoded></item><item><title>&lt;![CDATA[Sentryにソースマップが効かない問題を解決]]&gt;</title><link>https://blog.ryou103.com/post/sentry-sourcemaps-not-working</link><guid>https://blog.ryou103.com/post/sentry-sourcemaps-not-working</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/sentry-sourcemaps-not-working/sentry-sourcemaps-not-working.png" medium="image"></media:content><description></description><pubDate>Sun, 30 Jul 2023 05:23:35 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/sentry-sourcemaps-not-working/sentry-sourcemaps-not-working.png&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Sentryにソースマップが効かない&lt;/h1&gt;
&lt;p&gt;該当するソースマップはSentryにアップロードされているのに、Sentry画面では&lt;code&gt;Source Map File doesn’t exist&lt;/code&gt;と表示され、どこでエラーが起こったか検討もつかない状態になってしまっている。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/sentry-sourcemaps-not-working/sentry-sourcemaps-not-working-1.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;アプリケーションの環境は以下である。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Next.js（CSR）&lt;/li&gt;
  &lt;li&gt;CloudStorageにホスティング&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.sentry.io/platforms/javascript/guides/nextjs/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Sentryのドキュメント&lt;/a&gt;通り導入し、エラーのトラッキングは動作している。&lt;/p&gt;
&lt;p&gt;上手く動作しない過程で、next.config.jsにSentryの設定を追加したが効果はなかった。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;/** @type {import(&#x27;@sentry/nextjs/types/config/types&#x27;).UserSentryOptions} */
const userSentryOptions = {
  hideSourceMaps: true,
  widenClientFileUpload: true,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;オプション内容は、下記を確認してください。&lt;br /&gt;&lt;a href=&quot;https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;distオプションを設定することで解決&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;next.config.js&lt;/code&gt;と&lt;code&gt;sentry.client.config.js&lt;/code&gt;の2ファイルにdistの設定を行うことで解決しました。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;diff-highlight language-diff-tsx&quot; data-filename=&quot;next.config.js&quot;&gt;&lt;code class=&quot;language-diff-tsx&quot;&gt; const sentryWebpackPluginOptions = {
   project: process.env.SENTRY_PROJECT,
   authToken: process.env.SENTRY_AUTH_TOKEN,
+  dist: &#x27;MYAPP&#x27;.
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;diff-highlight language-diff-tsx&quot; data-filename=&quot;sentry.client.config.js&quot;&gt;&lt;code class=&quot;language-diff-tsx&quot;&gt; Sentry.init({
+  dist: &#x27;MYAPP&#x27;,
   dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
   // ...
 })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SSR等サーバー側でも動かしている場合は、&lt;code&gt;sentry.server.config.js/sentry.edge.config.js&lt;/code&gt;にも同じ設定を追加する必要があるだろう。&lt;/p&gt;
&lt;p&gt;distの値は同じ名前にしてください。&lt;/p&gt;
&lt;p&gt;ビルドしてエラーを起こすと、無事ソースマップが効いてエラー箇所が分かりやすくなった。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/sentry-sourcemaps-not-working/sentry-sourcemaps-not-working-4.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;なにが原因だったのか&lt;/h1&gt;
&lt;p&gt;ひとつのSentryプロジェクトに複数のアプリケーションを繋げているのが原因だった。&lt;/p&gt;
&lt;p&gt;運用しているサービスでは Next.js のアプリが2つ存在しており、それらが1つのSentryプロジェクトに紐づいている。&lt;/p&gt;
&lt;p&gt;ソースマップはreleaseごとに管理されているが、解決前のソースマップを見ると、同じようなファイルが2つ存在していた。&lt;br /&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/sentry-sourcemaps-not-working/sentry-sourcemaps-not-working-2.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;なぜこのようなことが起こったかというと、&lt;a href=&quot;https://docs.sentry.io/product/cli/releases/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;sentryの&lt;strong&gt;relaseタグはデフォルトでgitのSHA&lt;/strong&gt;になるように設定&lt;/a&gt;されている。&lt;/p&gt;
&lt;p&gt;当アプリケーションでは、モノレポ構成で管理画面・ユーザー画面を管理しており、GitHub Actionsで同時に2つのアプリのビルドを行っている。&lt;br /&gt;そうすると同じreleaseタグかつ、同じSentryプロジェクト上にあるため、ソースマップのバッティングが起こってしまったと考えられる。&lt;/p&gt;
&lt;p&gt;それを解決するために、誰のソースマップかを認識させるためdistオプションを使用した。&lt;/p&gt;
&lt;p&gt;distについて調べたが、&lt;a href=&quot;https://docs.sentry.io/platforms/react-native/sourcemaps/#using-custom-release-and-distribution&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;distオプションを使用している例&lt;/a&gt;はちらほらあるが、詳しい情報を見つけることができなかった。。&lt;/p&gt;
&lt;p&gt;この設定を入れたあとのソースマップは、以下のようになっている。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/sentry-sourcemaps-not-working/sentry-sourcemaps-not-working-3.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;解決前後で比較すると,アプリ毎に識別されたソースマップになっていることがわかる(一部重複してしまっていたファイルもあったみたい)。&lt;/p&gt;
&lt;h1&gt;結論&lt;/h1&gt;
&lt;p&gt;同Sentryプロジェクトに複数のアプリケーションが紐づくときはdistの設定を行いましょう。&lt;/p&gt;
]]&gt;</content:encoded><category>Sentry</category></item><item><title>&lt;![CDATA[最近のWebアプリ開発環境の紹介]]&gt;</title><link>https://blog.ryou103.com/post/my-next-js-project</link><guid>https://blog.ryou103.com/post/my-next-js-project</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/my-next-js-project/my-next-js-project.png" medium="image"></media:content><description> Next.jsで開発する際の設定を紹介。TypeScriptやESLint、Prettier、CIなどどういう設定で開発しているか紹介します。</description><pubDate>Mon, 26 Jun 2023 09:38:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/my-next-js-project/my-next-js-project.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;最近、個人開発などでWebアプリを開発するとなると、Next.js＋Vercelで開発することが多いです。&lt;/p&gt;
&lt;p&gt;その中でも、TypeScriptやESLintなどの設定が固まりつつあるので、自分の開発しやすい設定を紹介します。&lt;/p&gt;
&lt;h1&gt;Create a project&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;create-next-app&lt;/code&gt; でNext.jsを作成する際の設定&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ npx create-next-app@latest
What is your project named? my-app
Would you like to use TypeScript with this project? Yes
Would you like to use ESLint with this project? Yes
Would you like to use Tailwind CSS with this project? No
Would you like to use `src/` directory with this project? Yes
Use App Router (recommended)? Yes
Would you like to customize the default import alias? Yes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TailwindだけNoとしています。&lt;br /&gt;TypeScript、ESLintは当然入れています。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;src/&lt;/code&gt;ディレクトリを使うのは、ルートディレクトリが&lt;code&gt;.eslintrc&lt;/code&gt;, &lt;code&gt;.prettierrc&lt;/code&gt;, &lt;code&gt;tsconfig.json&lt;/code&gt;, &lt;code&gt;codegen.config.js&lt;/code&gt;, &lt;code&gt;pathpida.const.js&lt;/code&gt;, ... と設定ファイルで溢れ返るので開発中に触るファイルは&lt;code&gt;src/&lt;/code&gt;以下にまとまっていた方が見やすくなるので、&lt;code&gt;src/&lt;/code&gt;を使うようにしています。&lt;/p&gt;
&lt;p&gt;App Routerは、今後のスタンダードになっていくと思うので、Yesにしています。&lt;/p&gt;
&lt;p&gt;the default import aliasは&lt;code&gt;import ... from &#x27;@/...&#x27;&lt;/code&gt; のようにエイリアスで参照できる設定を入れてくれます。自分はエイリアス参照が好みなので入れています。&lt;/p&gt;
&lt;p&gt;Tailwindを入れていない理由としては、例えばMUIなど外部コンポーネントライブラリを使う場合などに、ここのスタイルはTailwind、こっちのスタイルはMUIなど、ごちゃごちゃになるので使っていません。&lt;br /&gt;あと、クラス名でコードが埋まってしまい、可読性も落ちるのが好きじゃないので使っていません。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/my-next-js-project/tailwind.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;TypeScriptの設定&lt;/h1&gt;
&lt;p&gt;TypeScriptの設定は、&lt;code&gt;create-next-app&lt;/code&gt;を使って生成されるファイルで満足しているのでほぼいじっていません。&lt;/p&gt;
&lt;p&gt;ひとつだけ設定を追加していて、それは&lt;a href=&quot;https://typescriptbook.jp/reference/tsconfig/nouncheckedindexedaccess&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;noUncheckedIndexedAccess&lt;/a&gt;です&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;compilerOptions&quot;: {
    &quot;noUncheckedIndexedAccess&quot;: true,
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この設定を追加すると、オブジェクトや配列のプロパティにアクセスする際に、undefinedを考慮した書き方にしないとTypeErrorにしてくれる機能です。&lt;/p&gt;
&lt;p&gt;例えば、以下のコードで&lt;code&gt;str&lt;/code&gt;の型が何になるかというと、&lt;code&gt;str: string | undefined&lt;/code&gt;となります。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;const arr: string[] = [&#x27;1&#x27;, &#x27;2&#x27;]
  
const str = arr[0]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/my-next-js-project/noUncheckedIndexedAccess.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;しかし、&lt;code&gt;noUncheckedIndexedAccess&lt;/code&gt;を設定しないと、&lt;code&gt;str: string&lt;/code&gt;となります。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/my-next-js-project/noUncheckedIndexedAccess-2.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;これの何がいけないかというと、&lt;code&gt;arr[3] = undefined&lt;/code&gt;なのに、&lt;code&gt;string&lt;/code&gt;型と認識してしまい。&lt;br /&gt;&lt;code&gt;arr[3].length&lt;/code&gt;なんて書いてもエラーも出ずにコンパイルが通ってしまいます。&lt;/p&gt;
&lt;p&gt;こういったバグの温床を防ぐために、、&lt;code&gt;noUncheckedIndexedAccess&lt;/code&gt;を設定しています。&lt;/p&gt;
&lt;h1&gt;ESLint, Prettier&lt;/h1&gt;
&lt;p&gt;Linterには、ESLint,Prettierを入れています。&lt;/p&gt;
&lt;h2&gt;Prettier&lt;/h2&gt;
&lt;p&gt;フォーマットの設定は、以下のようにしています。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;module.exports = {
  trailingComma: &#x27;all&#x27;,
  semi: false,
  singleQuote: true,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;code&gt;trailingComma: &#x27;all&#x27;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;常にカンマを入れる設定をしています。&lt;br /&gt;理由は、gitの差分を見やすくするためです。&lt;/p&gt;
&lt;p&gt;例えば以下のある型にプロパティを追加したいとき、設定によって差分が変わります。&lt;/p&gt;
&lt;p&gt;デフォルトの&lt;code&gt;es5&lt;/code&gt;の場合&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/my-next-js-project/trailingComma-es5.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;code&gt;trailingComma: &#x27;all&#x27;&lt;/code&gt;の場合&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/my-next-js-project/trailingComma-all.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;画像のようにカンマが追加される行も差分として表示され、若干見づらくなるので、&lt;code&gt;trailingComma: &#x27;all&#x27;&lt;/code&gt;を好んで使っています。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;semi: false&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;行末につける&lt;code&gt;;&lt;/code&gt;を省略しています。&lt;/p&gt;
&lt;p&gt;セミコロンつける必要なくない？てことがほとんどなので、省略しています。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;singleQuote: true&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;文字列を書くときは、&lt;code&gt;&#x27;hoge&#x27;&lt;/code&gt;とシングルクォートで囲います。&lt;/p&gt;
&lt;p&gt;これは好みかもしれません。昔からシングルクォートだったので入れています。&lt;/p&gt;
&lt;h2&gt;ESLint&lt;/h2&gt;
&lt;p&gt;初期設定では、&lt;code&gt;&#x27;next/core-web-vitals&#x27;&lt;/code&gt;しか入っておらず、ほぼLintが効いていない状態なのでカスタマイズしています。&lt;/p&gt;
&lt;p&gt;以下の設定がどのプロジェクトでも使っている設定です。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;/**
 * @type {import(&#x27;eslint&#x27;).ESLint.ConfigData}
 */
module.exports = {
  extends: [&#x27;eslint:recommended&#x27;, &#x27;next/core-web-vitals&#x27;],
  overrides: [
    {
      files: [&#x27;**/*.ts?(x)&#x27;],
      extends: [&#x27;plugin:@typescript-eslint/recommended&#x27;],
      rules: {
        &#x27;@typescript-eslint/no-unused-vars&#x27;: [
          &#x27;warn&#x27;,
          {
            argsIgnorePattern: &#x27;^_&#x27;,
            caughtErrorsIgnorePattern: &#x27;^_&#x27;,
            destructuredArrayIgnorePattern: &#x27;^_&#x27;,
            // Note { ref, ...others } = propsのように不要なrefの取り除くときに、unused-vars警告を無視できるが、refを使っているのかがわからなくなってしまうので使わない
            // 代わりに、varsIgnorePatternで、{ ref: _, ...others } = propsのように回避できるようにする
            // ignoreRestSiblings: true,
            varsIgnorePattern: &#x27;^_&#x27;,
          },
        ],
      },
    },
    { files: [&#x27;*&#x27;], extends: [&#x27;prettier&#x27;] },
  ],
  rules: {
    &#x27;no-duplicate-imports&#x27;: &#x27;error&#x27;,
    curly: &#x27;error&#x27;,
    eqeqeq: &#x27;error&#x27;,
    &#x27;no-nested-ternary&#x27;: &#x27;error&#x27;,
    &#x27;no-param-reassign&#x27;: &#x27;error&#x27;,
    &#x27;no-restricted-imports&#x27;: [
      &#x27;error&#x27;,
      {
        patterns: [&#x27;../*&#x27;],
      },
    ],
    &#x27;no-return-assign&#x27;: &#x27;error&#x27;,
    &#x27;no-return-await&#x27;: &#x27;error&#x27;,
    &#x27;object-shorthand&#x27;: &#x27;error&#x27;,
    &#x27;prefer-const&#x27;: &#x27;error&#x27;,
    yoda: &#x27;error&#x27;,
    &#x27;import/order&#x27;: [
      &#x27;error&#x27;,
      {
        alphabetize: {
          order: &#x27;asc&#x27;,
        },
        distinctGroup: false,
        pathGroups: [
          {
            pattern: &#x27;@/**&#x27;,
            group: &#x27;parent&#x27;,
            position: &#x27;before&#x27;,
          },
        ],
      },
    ],
  },
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;&#x27;eslint:recommended&#x27;&lt;/code&gt;, &lt;code&gt;&#x27;plugin:@typescript-eslint/recommended&#x27;&lt;/code&gt;で、ESLintとTypeScriptのおすすめのLintを入れています。&lt;/p&gt;
&lt;p&gt;rulesには、開発者によって書き方の揺れが発生するのを防いだり、他の人にとって理解しづらいコードにならないようにするためのルールを追加しています。&lt;/p&gt;
&lt;p&gt;その中でも工夫しているルールをピックアップして紹介します。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;@typescript-eslint/no-unused-vars&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;no-unused-vars&lt;/code&gt;は、使ってない変数をかくなよというルールですが、少しルールに手を加えています。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;&#x27;@typescript-eslint/no-unused-vars&#x27;: [
  &#x27;warn&#x27;,
  {
    argsIgnorePattern: &#x27;^_&#x27;,
    caughtErrorsIgnorePattern: &#x27;^_&#x27;,
    destructuredArrayIgnorePattern: &#x27;^_&#x27;,
    varsIgnorePattern: &#x27;^_&#x27;,
  },
],
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例えば以下のように使いたいときに、&lt;code&gt;@typescript-eslint/no-unused-vars&lt;/code&gt;が効いているとアラートが出てしまい困ることがありました。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;const { ref, ...others } = props

return (
  &amp;#x3C;div&gt;
    &amp;#x3C;Component {...others} /&gt;
  &amp;#x3C;/div&gt;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;props&lt;/code&gt;から変数&lt;code&gt;ref&lt;/code&gt;を取り除いて、Componentに&lt;code&gt;props&lt;/code&gt;を流したときに、&lt;code&gt;ref&lt;/code&gt;は使っていないのでアラートが出てしまいます。&lt;/p&gt;
&lt;p&gt;それを解消するために上記の設定をすると以下のように書くことができます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;const { ref: _, ...others } = props

return (
  &amp;#x3C;div&gt;
    &amp;#x3C;Component {...others} /&gt;
  &amp;#x3C;/div&gt;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使っていない&lt;code&gt;ref&lt;/code&gt;を&lt;code&gt;_&lt;/code&gt;という変数にしています。&lt;/p&gt;
&lt;p&gt;このように書くことで、refは使っていないということを他の開発者に伝えることができ、わざわざこの変数を使っていないか確認する必要がありません。&lt;br /&gt;アンダーバーから始まっている変数は使っていないということが明示できているので。&lt;/p&gt;
&lt;h1&gt;GitHub Actions&lt;/h1&gt;
&lt;p&gt;GitHub Actionsを使って、LintやTypeCheckの確認を行うようにしています。&lt;/p&gt;
&lt;p&gt;ワークフローのコードは、少し長いので以下のリンク先でご覧ください。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://codesandbox.io/p/sandbox/lingering-hill-9w2p9v?file=%2F.github%2Fworkflows%2Fci.yaml%3A122%2C1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codesandbox.io/p/sandbox/lingering-hill-9w2p9v?file=%2F.github%2Fworkflows%2Fci.yaml%3A122%2C1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;やっていることは、&lt;code&gt;setup-node&lt;/code&gt;ジョブで環境セットアップを行い、並列でESLint,Prettier,TypeScriptビルドのジョブを走らせています。&lt;/p&gt;
&lt;h2&gt;node_modulesをキャッシュすることで実行時間を削減&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;actions/setup-node@v3&lt;/code&gt;にキャッシュの機能がありますが、それではnode_modulesをキャッシュすることができず、毎回&lt;code&gt;yarn install&lt;/code&gt;が走ってしまい実行時間がかかってしまいます。&lt;/p&gt;
&lt;p&gt;ですので、node_modulesをキャッシュする設定を加えます。それが以下になります。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;jobs:
  setup-node:
    steps:
      - uses: actions/cache@v3
        id: node_modules_cache_id
        with:
          path: ${{ github.workspace }}/node_modules
          key: ${{ runner.os }}-node${{ env.NODE_BUILD_VERSION }}-yarn-${{ hashFiles(&#x27;**/yarn.lock&#x27;) }}

      - name: Package install
        if: steps.node_modules_cache_id.outputs.cache-hit != &#x27;true&#x27;
        run: yarn install --frozen-lock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;actions/cache@v3&lt;/code&gt;でnode_modulesディレクトリをキャッシュするように設定して、キャッシュが見つかった場合は&lt;code&gt;yarn install&lt;/code&gt;を省略しています。&lt;/p&gt;
&lt;p&gt;キャッシュキーに各パラメータを持たせることで、nodeバージョンやyarn.lockに変更があったらキャッシュを更新するようにしています。&lt;/p&gt;
&lt;p&gt;キャッシュを使う側のジョブでは、このキャッシュをリストアする設定を追加します。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;jobs:
  setup-node:
    steps:
      - name: Restore cache node_modules
        id: restore_cache_node_modules
        uses: actions/cache/restore@v3
        with:
          path: ${{ github.workspace }}/node_modules
          key: ${{ runner.os }}-node${{ env.NODE_BUILD_VERSION }}-yarn-${{ hashFiles(&#x27;**/yarn.lock&#x27;) }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;actions/cache/restore@v3&lt;/code&gt;を使って、先程のキーからキャッシュを取得します。&lt;/p&gt;
&lt;h1&gt;まとめ&lt;/h1&gt;
&lt;p&gt;Next.jsで開発する際の設定を、なぜその設定なのか、どう工夫しているかを紹介しました。&lt;/p&gt;
&lt;p&gt;プロジェクトによってzustand, zod, storybook, jestなどのツールを導入するので、それに合わせた設定を追加していますが、今回紹介したものはどのプロジェクトにも共通して入れています。&lt;/p&gt;
&lt;p&gt;説明したコードは以下においていますので参考にしてみてください。&lt;br /&gt;&lt;a href=&quot;https://codesandbox.io/p/sandbox/lingering-hill-9w2p9v?f&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codesandbox.io/p/sandbox/lingering-hill-9w2p9v?f&lt;/a&gt;&lt;/p&gt;
]]&gt;</content:encoded><category>Next.js</category><category>Prettier</category><category>TypeScript</category></item><item><title>&lt;![CDATA[AdonisJSのOpaque Tokenの期限を更新する]]&gt;</title><link>https://blog.ryou103.com/post/adonis-upgrade-refresh-oat-token</link><guid>https://blog.ryou103.com/post/adonis-upgrade-refresh-oat-token</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-refresh-oat-token/adonis-upgrade-refresh-oat-token.png" medium="image"></media:content><description>Adonis5をAPIサーバーとして扱う場合の認証はAPI tokensというOAT認証方式のトークンが採用されています。AdonisにはOpaqueトークンを更新する機能が用意されていないので、トークンの期限を更新をどうしたかを紹介します。</description><pubDate>Fri, 25 Nov 2022 09:00:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-refresh-oat-token/adonis-upgrade-refresh-oat-token.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Adonis5をAPIサーバーとして扱う場合の認証は&lt;a href=&quot;https://docs.adonisjs.com/guides/auth/api-tokens-guard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;API tokens&lt;/a&gt;というOAT認証方式のトークンが採用されています。&lt;/p&gt;
&lt;p&gt;Opaqueトークンは、JWTトークンのように有効期限を更新するためのリフレッシュトークンがない。そのため期限をすぎると再認証が必要になります。&lt;/p&gt;
&lt;p&gt;しかし、AdonisにはOpaqueトークンを更新する機能が用意されていないので、トークンの期限を更新をどうしたかを紹介します。&lt;/p&gt;
&lt;h1&gt;Opaqueトークンの更新タイミング&lt;/h1&gt;
&lt;p&gt;まずOpaqueトークンの文字列、情報が含まれてないランダムなハッシュデータです。ですから頻繁にネットワーク上に流れてもセキュリティ上問題ありません。&lt;br /&gt;(JWTなんかは30分とか非常に短い期限にするのが一般的です。)&lt;/p&gt;
&lt;p&gt;なので、トークン自体の期限を2週間と長く設定して、APIにアクセスする度に期限をチェックして、10日以下になったときに、トークンの期限日を伸ばす仕様にする。&lt;/p&gt;
&lt;p&gt;クライアントはトークンを更新する必要がない。なぜなら、DBに保存されているexpired_atを更新するだけでハッシュに変更はないからです。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-refresh-oat-token/adonis-upgrade-refresh-oat-token-1.png&quot; alt=&quot;Opaqueトークンの更新タイミング&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;実装方法&lt;/h1&gt;
&lt;p&gt;コードでは以下のようなことを行う。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;if (await auth.use(&#x27;api&#x27;).check()) {
  const expiresAt = auth.use(&#x27;api&#x27;).token?.expiresAt
  const tokenHash = auth.use(&#x27;api&#x27;).token?.tokenHash

  if (!!expiresAt &amp;#x26;&amp;#x26; !!tokenHash) {
    // トークンの期限が10日以下になった場合
    if (DateTime.local().plus({day: 10}) &gt; expiresAt) {
      const dateTimeFormat = Database.query().client.dialect.dateTimeFormat
      const newExpiresAt = DateTime.local().plus({day: 14}).toFormat(dateTimeFormat)
      // DBのapi_tokens.expires_atを更新する
      await Database.from(&#x27;api_tokens&#x27;).where(&#x27;token&#x27;, tokenHash).update({ &#x27;expires_at&#x27;: newExpiresAt })
    }
  }
  return response.accepted(auth.use(&#x27;api&#x27;).user)
} else {
  return response.unauthorized()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;tokenをアップデートする関数が用意されていないので、DatabaseQueryBuilderで直接api_tokensテーブルを指定してupdateを行っている。&lt;/p&gt;
&lt;p&gt;その際に、&lt;strong&gt;日付をクエリに入れるときFormatしないとエラーを吐く。&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&quot;update `api_tokens` set `expires_at` = 2022-10-19 13:20:32.841 +00:00 where `token` = &#x27;a753bc6e3d3aee20afb19d53d56f87bb9b4d1e415cc70ab0ee009387b57f4dd9&#x27; - Unknown column &#x27;_zone&#x27; in &#x27;field list&#x27;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;日時のフォーマットは、Adonisのauthパッケージを参考にdateTimeFormatを取得した。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/adonisjs/auth/blob/master/src/TokenProviders/Database/index.ts#L110&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/adonisjs/auth/blob/master/src/TokenProviders/Database/index.ts#L110&lt;/a&gt;&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;const dateTimeFormat = Database.query().client.dialect.dateTimeFormat
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これで同じトークンを使っていても定期的にサービスを利用していれば、期限切れを起こすことがなくなる認証ができた。&lt;/p&gt;
&lt;p&gt;この他にも、&lt;a href=&quot;/post/adonis-upgrade-to-v5/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AdonisJS v5へのアップグレードで行ったことをまとめました。&lt;/a&gt;&lt;br /&gt;なにか役に立てたら幸いです。&lt;/p&gt;
]]&gt;</content:encoded><category>AdonisJS</category></item><item><title>&lt;![CDATA[AdonisJSでDBから件数検索できるgetCountを追加する]]&gt;</title><link>https://blog.ryou103.com/post/adonis-upgrade-query-get-count</link><guid>https://blog.ryou103.com/post/adonis-upgrade-query-get-count</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-query-get-count/adonis-upgrade-query-get-count.png" medium="image"></media:content><description>検索件数が知りたいときに数値を返すgetCountを作成する。AdonisのORMにはcount関数が存在するのだが、通常のSQL同様の結果が返ってきて不便なので、このcount関数を拡張して便利な関数をQueryBuilderに自作します。</description><pubDate>Thu, 24 Nov 2022 09:00:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-query-get-count/adonis-upgrade-query-get-count.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;検索件数が知りたいときに数値を返すgetCountを作成する。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;await User.query().getCount();
// =&gt; 23
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;いちおうcount()は存在する。&lt;/h1&gt;
&lt;p&gt;AdonisのORMにはcount関数が存在するのだが、通常のSQL同様の結果が返ってきて不便&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;await Database.query().from(&#x27;users&#x27;).count(&#x27;* as total&#x27;)
// =&gt; [ { total: 23 } ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配列だし、オブジェクトだし、、扱いにくい。&lt;/p&gt;
&lt;p&gt;なので、このcount関数をマクロという機能を使い、便利な関数をQueryBuilderに自作しようと思います。&lt;/p&gt;
&lt;h1&gt;getCountを作成する&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;providers/AppProvider.ts&lt;/code&gt;のbootに以下のコードを追加します。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot; data-filename=&quot;providers/AppProvider.ts&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;export default class AppProvider {
  public async boot() {
    const {
      DatabaseQueryBuilder
    } = this.app.container.use(&#x27;Adonis/Lucid/Database&#x27;)

    DatabaseQueryBuilder.macro(&#x27;getCount&#x27;, async function () {
      const result = await this.count(&#x27;* as total&#x27;)
      return BigInt(result[0].total)
    })

		const {
		  ModelQueryBuilder
		} = this.app.container.use(&#x27;Adonis/Lucid/Database&#x27;)
		
		ModelQueryBuilder.macro(&#x27;getCount&#x27;, async function () {
		  const result = await this.count(&#x27;* as total&#x27;)
		  return BigInt(result[0].$extras.total)
		})
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Database.query(&#x27;users&#x27;)...&lt;/code&gt;と&lt;code&gt;User.query()...&lt;/code&gt;の2パターンで実行できるように、DatebaseQueryBuilderとModelQueryBuilderに同じgetCountを作成しています。&lt;/p&gt;
&lt;p&gt;これを使おうとするとgetCountなんで関数ねぇよとTypeScriptに怒られるので、型を追加します。&lt;br /&gt;Adonisではcontracts/というディレクトリにすでにいくつかの型が作成されていますので、contracts以下に追加します。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;database.ts&lt;/code&gt;と&lt;code&gt;lucid.ts&lt;/code&gt;という2ファイルを作成しました。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot; data-filename=&quot;contracts/database.ts&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;declare module &#x27;@ioc:Adonis/Lucid/Database&#x27; {
  interface DatabaseQueryBuilderContract&amp;#x3C;Result&gt; {
    getCount(): Promise&amp;#x3C;BigInt&gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot; data-filename=&quot;contracts/lucid.ts&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;declare module &#x27;@ioc:Adonis/Lucid/Orm&#x27; {
  interface ModelQueryBuilderContract&amp;#x3C;
    Model extends LucidModel,
    Result = InstanceType&amp;#x3C;Model&gt;
  &gt; {
    getCount(): Promise&amp;#x3C;BigInt&gt;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;大きな値も扱えるように&lt;a href=&quot;https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BigInt&lt;/a&gt;にしています。&lt;/p&gt;
&lt;p&gt;これで以下のように実行することができます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;await User.query().getCount()
// もしくは
await Database.query().from(&#x27;users&#x27;).getCount()
// =&gt; 23n    BigIntにはnがつく
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上になります。&lt;/p&gt;
&lt;p&gt;この他にも、&lt;a href=&quot;/post/adonis-upgrade-to-v5/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AdonisJS v5へのアップグレードで行ったことをまとめました。&lt;/a&gt;&lt;br /&gt;なにか役に立てたら幸いです。&lt;/p&gt;
]]&gt;</content:encoded><category>AdonisJS</category></item><item><title>&lt;![CDATA[AdonisJSのSQLクエリをデバッグする]]&gt;</title><link>https://blog.ryou103.com/post/adonis-upgrade-debug-sql</link><guid>https://blog.ryou103.com/post/adonis-upgrade-debug-sql</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-debug-sql/adonis-upgrade-debug-sql.png" medium="image"></media:content><description>AdonisではORMを使ってDBへのアクセスを行うので、実際に発行されているSQLクエリがわからない。そのため、N+1問題とかを見つけにくくなっている。今回は裏で発行されているSQLクエリをログに出力できる設定を紹介します。</description><pubDate>Wed, 23 Nov 2022 09:03:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-debug-sql/adonis-upgrade-debug-sql.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;AdonisではORMを使ってDBへのアクセスを行うので、実際に発行されているSQLクエリがわからない。&lt;br /&gt;そのため、N+1問題とかを見つけにくくなっている。&lt;/p&gt;
&lt;p&gt;シンプルな宣言だったらクエリのイメージはつく&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;const project = await Project.query().where(&#x27;id&#x27;, id).first()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;related, preloadなのリレーションも検索するとなると実際どういったクエリが動いているかが想像しにくい、、&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;const projects = await user
      .related(&#x27;projects&#x27;)
      .query()
      .whereNot(&#x27;status&#x27;, &#x27;preview&#x27;)
      .preload(&#x27;items&#x27;)
      .preload(&#x27;image&#x27;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;今回は裏で発行されているSQLクエリをログに出力できる設定を紹介します。&lt;br /&gt;2パターンあって、すべてのクエリが出力と確認したいクエリだけを出力する方法があります。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-debug-sql/adonis-upgrade-debug-sql-1.png&quot; alt=&quot;AdonisのSQLクエリをログに出力&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;データベースの設定でデバッグを有効にする&lt;/h1&gt;
&lt;p&gt;DB設定でデータベースのデバッグをonにすると、上記で設定したEvent db:queryが発行されるようになる。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot; data-filename=&quot;config/database.ts&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;const databaseConfig: DatabaseConfig = {
  connections: {
    mysql: {
      debug: true,
    },
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;SQLクエリをログに出力する設定をする&lt;/h1&gt;
&lt;h2&gt;全てのクエリを出力する&lt;/h2&gt;
&lt;p&gt;AdonisJSの機能のひとつEventを使ってSQLクエリを検知してログ出力する&lt;/p&gt;
&lt;p&gt;まずはEventのファイルを用意する。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ node ace make:prldfile events
❯ Select the environment(s) in which you want to load this file · No items were selected

CREATE: start/events.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;make:prldfile&lt;/code&gt;とはpreloadsと呼ばれる機能用のファイルを作成するコマンドで、preloadsはアプリケーション起動時に一度だけ読み込み実行されます。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.adonisrc.json&lt;/code&gt;に先程作成したファイルが追加されていると思います。&lt;/p&gt;
&lt;p&gt;ここにEvent機能を使ってクエリを検知したらログを出力するコードを書きます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot; data-filename=&quot;start/events.ts&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;import Event from &#x27;@ioc:Adonis/Core/Event&#x27;
import Logger from &#x27;@ioc:Adonis/Core/Logger&#x27;
import Database from &#x27;@ioc:Adonis/Lucid/Database&#x27;
import Application from &#x27;@ioc:Adonis/Core/Application&#x27;

Event.on(&#x27;db:query&#x27;, (query) =&gt; {
  if (Application.inProduction) {
    Logger.debug(query)
  } else {
    Database.prettyPrint(query)
  }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;アプリを再起動するとコンソールにログが出力されます。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-debug-sql/adonis-upgrade-debug-sql-1.png&quot; alt=&quot;AdonisのSQLクエリをログに出力&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;特定のクエリのみを確認する&lt;/h2&gt;
&lt;p&gt;上記ですべてのクエリが確認できるようにはなるのですが、複雑なプログラミングになると一度で10個以上のクエリが動いたりと自分の確認したいクエリがどれなのかがわかりづらくなってしまうので、特定のクエリのみログを出力する方法も紹介します。&lt;/p&gt;
&lt;p&gt;方法はシンプルで出力したいQueryBuilderに&lt;code&gt;.debug(true)&lt;/code&gt;を追加するだけ。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;Database
  .query()
  .select(&#x27;*&#x27;)
  .debug(true) // 👈
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最初のconfigファイルで設定を忘れていなければログが出力されるかと思います。&lt;/p&gt;
&lt;p&gt;以上になります。&lt;/p&gt;
&lt;p&gt;この他にも、&lt;a href=&quot;/post/adonis-upgrade-to-v5/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AdonisJS v5へのアップグレードで行ったことをまとめました。&lt;/a&gt;&lt;br /&gt;なにか役に立てたら幸いです。&lt;/p&gt;
]]&gt;</content:encoded><category>AdonisJS</category></item><item><title>&lt;![CDATA[AdonisJSの開発環境をSSLで起動する]]&gt;</title><link>https://blog.ryou103.com/post/adonis-upgrade-ssl</link><guid>https://blog.ryou103.com/post/adonis-upgrade-ssl</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-ssl/adonis-upgrade-ssl.png" medium="image"></media:content><description>Adonisで開発環境でもHTTPSで起動できるようにしました。Facebookのログイン認証を行うのにSSLである必要があるため開発環境でもHTTPSにする必要がありました。</description><pubDate>Wed, 09 Nov 2022 09:00:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-ssl/adonis-upgrade-ssl.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Facebookのログイン認証を行うのにSSLである必要があるので、開発環境でもHTTPSで起動できるようにする。&lt;/p&gt;
&lt;p&gt;証明書は&lt;code&gt;$ mkcert localhost&lt;/code&gt;などで各自で用意してください。&lt;br /&gt;&lt;a href=&quot;/post/nuxtjs-ssl/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;mkcertの使い方はこちらの記事&lt;/a&gt;で紹介しています。&lt;/p&gt;
&lt;p&gt;HttpsServerパッケージをインストール&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ yarn add https
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;server.tsを編集&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot; data-filename=&quot;server.ts&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;const httpServer = new Ignitor(__dirname).httpServer()

// Env.get(&#x27;NODE_ENV&#x27;)はserver.tsではまだ使えないため以下のように書く
if (process.env.NODE_ENV !== &#x27;production&#x27;) {
  const options = {
    key: fs.readFileSync(path.join(__dirname, &#x27;../cert/localhost+2-key.pem&#x27;)),
    cert: fs.readFileSync(path.join(__dirname, &#x27;../cert/localhost+2.pem&#x27;)),
  }

  httpServer.start((handler) =&gt; https.createServer(options, handler))
} else {
  httpServer.start()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SSLが効いた状態で起動できた。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-ssl/adonis-upgrade-ssl-1.png&quot; alt=&quot;Adonis develop環境でSSL起動&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;この他にも、&lt;a href=&quot;/post/adonis-upgrade-to-v5/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AdonisJS v5へのアップグレードで行ったことをまとめました。&lt;/a&gt;&lt;br /&gt;なにか役に立てたら幸いです。&lt;/p&gt;
]]&gt;</content:encoded><category>AdonisJS</category></item><item><title>&lt;![CDATA[AdonisJSにテストを導入する]]&gt;</title><link>https://blog.ryou103.com/post/adonis-upgrade-test</link><guid>https://blog.ryou103.com/post/adonis-upgrade-test</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-test/adonis-upgrade-test.png" medium="image"></media:content><description>Adonisでのテスト導入手順と、旧Adonis v4との違いを紹介します。</description><pubDate>Tue, 08 Nov 2022 09:00:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-test/adonis-upgrade-test.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;こちらは、現時点で最新のAdonisJS v5の記事になります。&lt;/p&gt;
&lt;p&gt;テストの導入手順とAdonis v4との変更点を紹介します。&lt;/p&gt;
&lt;p&gt;公式ドキュメント&lt;br /&gt;&lt;a href=&quot;https://docs.adonisjs.com/guides/testing/introduction&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.adonisjs.com/guides/testing/introduction&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;テストはVowという内製？のテストツールからJapaに変更されています。&lt;br /&gt;全体的に大きな変更はなく、メソッド名が違ったりの小さな変化でした。&lt;/p&gt;
&lt;h1&gt;Test機能を追加する&lt;/h1&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ node ace configure tests

[ wait ]  installing @japa/runner, @japa/preset-adonis  ..
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;なぜかインストールが進まないので、一旦停止してpackageを追加します。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ yarn add -D @japa/runner @japa/preset-adonis
$ node ace configure tests
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;TestDB設定&lt;/h2&gt;
&lt;p&gt;test用のDBを用意する。&lt;/p&gt;
&lt;p&gt;テスト環境でのみ読み込まれる環境変数.env.testを作成します。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot; data-filename=&quot;.env.test&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;DB_DATABASE=adonis-test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;テスト実行前にDBのマイグレーションを実行する設定を追加&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;diff-highlight language-diff-tsx&quot; data-filename=&quot;tests/bootstrap.ts&quot;&gt;&lt;code class=&quot;language-diff-tsx&quot;&gt;  export const runnerHooks: Required&amp;#x3C;Pick&amp;#x3C;Config, &#x27;setup&#x27; | &#x27;teardown&#x27;&gt;&gt; = {
    setup: [
      () =&gt; TestUtils.ace().loadCommands(),
+     () =&gt; TestUtils.db().migrate(),
    ],
    teardown: [],
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Drive設定の追加&lt;/h2&gt;
&lt;p&gt;自分のサービスではAdonisで画像ファイルを作成する機能があるため、テストで作成されたファイルはテスト終了後に削除する設定が必要。&lt;/p&gt;
&lt;p&gt;テストで作成されたファイルを削除する。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot; data-filename=&quot;tests/bootstrap.ts&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;export const runnerHooks: Required&amp;#x3C;Pick&amp;#x3C;Config, &#x27;setup&#x27; | &#x27;teardown&#x27;&gt;&gt; = {
  teardown: [
    async () =&gt; {
      const list = await Drive.list(&#x27;./&#x27;).recursive().toArray()
      await Promise.all(await list.map((item) =&gt; Drive.delete(item.location)))
    }
  ],
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このままだと全環境のファイルを削除してしまうので、テスト環境でのファイルの保存先を変更する。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot; data-filename=&quot;config/drive.ts&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;export default driveConfig({
  disks: {
    local: {
			root: Env.get(&#x27;NODE_ENV&#x27;) === &#x27;test&#x27; ? Application.tmpPath(&#x27;upload&#x27;) : Application.publicPath(&#x27;upload&#x27;),
		}
	}
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Testの書き方&lt;/h1&gt;
&lt;h2&gt;テストを追加する&lt;/h2&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ node ace make:test functional user
CREATE: tests/functional/user.spec.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;作成されたファイルにテスト内容を書いていく。&lt;/p&gt;
&lt;p&gt;テストの書き方はJapaなので公式サイトを参考に書いてください。&lt;br /&gt;&lt;a href=&quot;https://japa.dev/docs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://japa.dev/docs&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Factoryを作成する&lt;/h2&gt;
&lt;p&gt;モデルのモックデータとなるFactoryを作成する&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ node ace make:factory user
CREATE: database/factories/UserFactory.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot; data-filename=&quot;database/factories/UserFactory.ts&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;import user from &#x27;App/Models/user&#x27;
import Factory from &#x27;@ioc:Adonis/Lucid/Factory&#x27;

export default Factory.define(user, ({ faker }) =&gt; {
  return {
    email: faker.internet.email(),
    password: faker.internet.password(),
  }
}).build()
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Adonis v4との違い&lt;/h1&gt;
&lt;p&gt;テストツールをAdonis v4ではassertにchaiを使っていたが、Adonis v5からjapaというものに変わり、書き方が変わっているので変更点だけ紹介する。&lt;/p&gt;
&lt;h2&gt;全体的な書き方の違い&lt;/h2&gt;
&lt;p&gt;Adonis v4&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;const { test } = use(&#x27;Test/Suite&#x27;)(&#x27;User&#x27;)
const Factory = use(&#x27;Factory&#x27;)
const Mail = use(&#x27;Mail&#x27;)

const UserFactory = Factory.model(&#x27;App/Models/User&#x27;)

test(&#x27;can create user if valid data&#x27;, async ({ assert }) =&gt; {
  // some tests
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adonis v5&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { test } from &#x27;@japa/runner&#x27;
import Mail from &#x27;@ioc:Adonis/Addons/Mail&#x27;
import UserFactory from &#x27;Database/factories/UserFactory&#x27;

// test関数の似た感じ
test.group(&#x27;User&#x27;, () =&gt; {
  test(&#x27;can create user if valid data&#x27;, async ({ assert, client }) =&gt; {
     // some tests
  })
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Database Transaction&lt;/h2&gt;
&lt;p&gt;テスト間でデータが影響しないように、テスト毎にトランザクションを貼り、終了後にロールバックします。&lt;/p&gt;
&lt;p&gt;Adonis v4&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;const { test, trait } = use(&#x27;Test/Suite&#x27;)(&#x27;User registration&#x27;)

trait(&#x27;DatabaseTransactions&#x27;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adonis v5&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;import Database from &#x27;@ioc:Adonis/Lucid/Database&#x27;

test.group(&#x27;Group name&#x27;, (group) =&gt; {
  group.each.setup(async () =&gt; {
    await Database.beginGlobalTransaction()
    return () =&gt; Database.rollbackGlobalTransaction()
  })
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;ApiClient&lt;/h2&gt;
&lt;p&gt;Adonis v4&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;const response = await client.post(&#x27;/api/v1/user&#x27;).send(data).end()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adonis v5&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;const response = await client.post(&#x27;/api/v1/user&#x27;).fields(data)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Response Assert&lt;/h2&gt;
&lt;p&gt;ApiClientのレスポンスのアサーション&lt;/p&gt;
&lt;p&gt;Adonis v4&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;response.assertJSONSubset([
  {
    message: &#x27;会員登録完了しました&#x27;,
  },
])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adonis v5&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;response.assertBodyContains({
  message: &#x27;会員登録完了しました&#x27;,
})

// error response
response.assertBodyContains({
  errors: [{
    message: &#x27;メールアドレスが正しくありません&#x27;,
    field: &#x27;email&#x27;,
    rule: &#x27;email&#x27;,
  }]
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;メール&lt;/h2&gt;
&lt;p&gt;Adonis v4&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-jsx&quot;&gt;&lt;code class=&quot;language-jsx&quot;&gt;test(&#x27;hoge&#x27;, async ({}) =&gt; {
  Mail.fake()
  const recentEmail = Mail.pullRecent()
  assert.equal(recentEmail.message.to[0].address, email)
  assert.equal(recentEmail.message.subject, &#x27;会員登録いただきありがとうございます。&#x27;)

  Mail.restore()
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adonis v5&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;test(&#x27;hoge&#x27;, async ({}) =&gt; {
  const mailer = Mail.fake()
  mailer.exists((mail) =&gt; {
    const sameAddress = mail.to?.some(to =&gt; to.address == email) || false
    const sameSubject = mail.subject === &#x27;会員登録いただきありがとうございます。&#x27;
    return sameAddress &amp;#x26;&amp;#x26; sameSubject
  })
  Mail.restore()
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Factory&lt;/h2&gt;
&lt;p&gt;Adonis v4&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;const user = await UserFactory.create({
  password: &#x27;password&#x27;,
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adonis v5&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;const user = await UserFactory.merge({
  password: &#x27;password&#x27;,
}).create()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この他にも、&lt;a href=&quot;/post/adonis-upgrade-to-v5/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AdonisJS v5へのアップグレードで行ったことをまとめました。&lt;/a&gt;&lt;br /&gt;なにか役に立てたら幸いです。&lt;/p&gt;
]]&gt;</content:encoded><category>AdonisJS</category></item><item><title>&lt;![CDATA[AdonisJSにValidator機能を追加する]]&gt;</title><link>https://blog.ryou103.com/post/adonis-upgrade-validator</link><guid>https://blog.ryou103.com/post/adonis-upgrade-validator</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-validator/adonis-upgrade-validator.png" medium="image"></media:content><description>Validatorとは、リクエストで送られてきたデータを解析・検証して異常があれば、エラーメッセージをレスポンスすることができる機能です。Validatorの導入手順とAdonis v4との違いについて紹介します。</description><pubDate>Mon, 07 Nov 2022 05:49:33 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-validator/adonis-upgrade-validator.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;こちらは、現時点で最新のAdonisJS v5の記事になります。&lt;/p&gt;
&lt;p&gt;Validatorの導入手順とAdonis v4との違いについて紹介します。&lt;/p&gt;
&lt;p&gt;Validatorとは、リクエストで送られてきたデータを解析・検証して異常があれば、エラーメッセージをレスポンスすることができる機能です。&lt;/p&gt;
&lt;p&gt;公式リファレンス&lt;br /&gt;&lt;a href=&quot;https://docs.adonisjs.com/guides/validator/introduction&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.adonisjs.com/guides/validator/introduction&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Adonis v4ではRouteにvalidatorというメソッドがありましたが、v5からはController内で行います。&lt;/p&gt;
&lt;p&gt;Adonis v4&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Route.post(&#x27;users&#x27;, &#x27;UserController.store&#x27;).validator(&#x27;User/CreateUser&#x27;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Adonis v5&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;export default class UsersController {
  public async store({ auth, request, response }: HttpContextContract) {
    const payload = await request.validate(StoreUserValidator)
    const user = await User.create(payload)
...
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;ValidatorClassを作っていく&lt;/h1&gt;
&lt;p&gt;リクエストで送られてくるデータの検証をするスキーマ、エラーの場合のエラーメッセージを定義するClassを作ります。&lt;/p&gt;
&lt;p&gt;例に上記で書いたStoreUserValidatorを作っていきます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ node ace make:validator User/StoreUser
CREATE: app/Validators/User/StoreUserValidator.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot; data-filename=&quot;app/Validators/User/StoreUserValidator.ts&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;import { schema, CustomMessages } from &#x27;@ioc:Adonis/Core/Validator&#x27;
import type { HttpContextContract } from &#x27;@ioc:Adonis/Core/HttpContext&#x27;

export default class StoreUserValidator {
  constructor(protected ctx: HttpContextContract) {}

  public schema = schema.create({})

  public messages: CustomMessages = {}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ValidatorClassが生成されたので、schemaとmessagesを編集していく。&lt;/p&gt;
&lt;h2&gt;Validator Schemaの書き方&lt;/h2&gt;
&lt;p&gt;schemaでは、リクエストで送られてくるデータを検証するためのValidationRuleを決めます。&lt;/p&gt;
&lt;p&gt;リクエストで、メールアドレスとパスワードを送って来てほしい場合は以下のようになります。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;export default class StoreUserValidator {
  public schema = schema.create({
    email: schema.string([
      rules.required(),
      rules.email(),
      rules.unique({ table: &#x27;users&#x27;, column: &#x27;email&#x27; })
    ]),
    password: schema.string([rules.required()]),
  })
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;emailは、string型で必須&lt;code&gt;required&lt;/code&gt;, メール形式&lt;code&gt;email&lt;/code&gt;, DB上のusersテーブルで重複がないか&lt;code&gt;unique&lt;/code&gt;をルールにしています。&lt;/p&gt;
&lt;p&gt;passwordは、string型で必須&lt;code&gt;required&lt;/code&gt;をルールにしています。&lt;/p&gt;
&lt;p&gt;他にも様々なルールが用意されているので、以下のリンクの左メニューから探しください。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.adonisjs.com/reference/validator/rules/alpha&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.adonisjs.com/reference/validator/rules/alpha&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Validationメッセージの書き方&lt;/h2&gt;
&lt;p&gt;続いてはルールに違反したときのメッセージについてですが、これは定義しなくてもデフォルトで設定されているのでエラーがでない心配は無いのですが、英語です。。。&lt;/p&gt;
&lt;p&gt;ですので、日本語で返せるようにだったり、「メールアドレスは必須です」や「パスワードを入力してください」のように同じrequiredルールでも表示を変えたい場合はカスタムメッセージを定義する必要があります。&lt;/p&gt;
&lt;p&gt;簡単に書くと以下のようになります。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;export default class StoreUserValidator {
	...
  public messages: CustomMessages = {
    required: &#x27;The {{ field }} is required to create a new account&#x27;,
    &#x27;email.required&#x27;: &#x27;メールアドレスは必須です&#x27;,
    &#x27;password.required&#x27;: &#x27;パスワードを入力してください&#x27;,
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これでも動作はするのですが、StoreUserValidatorだったりLoginUserValidator, UpdateUserValidatorだったり、Validatorが増えたときにそれぞれで同じメッセージを書く必要があり、メール, メールアドレス, Emailのような表記のゆれや、変更があったときに修正漏れの可能性が高くなります。&lt;/p&gt;
&lt;p&gt;なので、メッセージを共通化をしていきます。&lt;/p&gt;
&lt;h3&gt;Validatorメッセージの共通化&lt;/h3&gt;
&lt;p&gt;やり方としては、&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;各ルールのエラーメッセージを一つのファイルにまとめる。&lt;/li&gt;
  &lt;li&gt;email, passwordといった項目がメールアドレス、パスワードといった表示名を一つのファイルにまとめる&lt;/li&gt;
  &lt;li&gt;カスタムメッセージでそこから参照できるように設定する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これを実現するために少しトリッキーかもしれませんが多言語サポートで使われるi18nを使おうと思います。&lt;/p&gt;
&lt;p&gt;@adonisjs/i18nを導入。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ yarn add @adonisjs/i18n
$ node ace configure @adonisjs/i18n
&lt;/code&gt;&lt;/pre&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot; data-filename=&quot;start/kernel.ts&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;Server.middleware.register([
  () =&gt; import(&#x27;App/Middleware/DetectUserLocale&#x27;)
])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;日本語しか使わないから、設定を日本語のみに変更&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot; data-filename=&quot;config/i18n.ts&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;defaultLocale: &#x27;ja&#x27;,
supportedLocales: [&#x27;ja&#x27;],
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;翻訳データはjsonでもyamlでも書ける。&lt;br /&gt;yaml推しなのでyamlで書いていく。&lt;/p&gt;
&lt;p&gt;i18nはValidatorMessageに対応しています。&lt;br /&gt;&lt;a href=&quot;https://docs.adonisjs.com/guides/i18n#validator-messages&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.adonisjs.com/guides/i18n#validator-messages&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;validator.yamlのsharedに書くと自動的にValidationMessageに紐づきます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-yaml&quot; data-filename=&quot;resources/lang/ja/validator.yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;shared:
	maxLength: &quot;最大{maxLength}文字までです&quot;
  required: &quot;{field}は必須です&quot;
  email: &quot;{field}が正しくありません&quot;
  unique: &quot;{field}は既に存在しています&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ただこのままだと、fieldが翻訳されずに&lt;code&gt;emailが正しくありません&lt;/code&gt;のようにemailと表示されてしまうので、&lt;code&gt;メールアドレスが正しくありません&lt;/code&gt;と日本語名で表示できるように修正していきます。&lt;/p&gt;
&lt;p&gt;fieldの翻訳データを用意する。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-yaml&quot; data-filename=&quot;resources/lang/ja/user.yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;email: &#x27;メールアドレス&#x27;
password: &#x27;パスワード&#x27;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Validatorをカスタマイズする。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot; data-filename=&quot;app/Validators/User/StoreUserValidator.ts&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;export default class StoreUserValidator {
	// 追加
  get fields() {
    return {
      email: this.ctx.i18n.formatMessage(&#x27;user.email&#x27;),
      password: this.ctx.i18n.formatMessage(&#x27;user.password&#x27;),
    }
  }
  public messages: CustomMessages = {
    &#x27;*&#x27;: (field, rule, arrayExpressionPointer, options) =&gt; {
      try {
        return this.ctx.i18n.formatMessage(`validator.shared.${rule}`, { field: this.fields[field] })
      } catch (_) {
        return this.ctx.i18n.validatorMessages()[&#x27;*&#x27;](field, rule, arrayExpressionPointer, options)
      }
    },
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;i18n.formatMessage(`validator.shared.${rule}`, { field: this.fields[field] })&lt;/code&gt;が、validator.yamlで定義したValidationメッセージに翻訳されたfieldを渡して、fieldも翻訳されたエラーメッセージを出力している。&lt;/p&gt;
&lt;p&gt;これでemailフィールドはresouces/locale/*/user.yamlから翻訳データを取得してValidatorメッセージのfieldに出力でき、&lt;code&gt;メールアドレスが正しくありません&lt;/code&gt;と表示される。&lt;/p&gt;
&lt;p&gt;もしfieldが定義されていなかったりのエラーの可能性も考慮してtry-catchで通常の&lt;code&gt;i18n.validatorMessages()&lt;/code&gt;に渡している。&lt;/p&gt;
&lt;p&gt;以上にて、AdonisでValidator設定が完了。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/post/adonis-upgrade-to-v5/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AdonisJS v5へのアップグレードが大変だったのでこちらにまとめました。&lt;/a&gt;&lt;/p&gt;
]]&gt;</content:encoded><category>AdonisJS</category></item><item><title>&lt;![CDATA[AdonisJSに認証機能を追加する]]&gt;</title><link>https://blog.ryou103.com/post/adonis-upgrade-authentication</link><guid>https://blog.ryou103.com/post/adonis-upgrade-authentication</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-authentication/adonis-upgrade-authentication.png" medium="image"></media:content><description>AdonisJSでユーザーログイン認証設定の紹介と旧AdonisJS v4から移行する際の修正箇所を紹介します。</description><pubDate>Tue, 01 Nov 2022 13:30:03 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-authentication/adonis-upgrade-authentication.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;こちらは、現時点で最新のAdonisJS v5の記事になります。&lt;/p&gt;
&lt;p&gt;前回データベースに接続できたので、次は認証機能を追加してユーザーをDBに保存できるようにする。&lt;br /&gt;v4ではJWT認証で実装していたが、v5では代わってOpaqueAccessTokenを使っての認証になるので、実装方法を変える必要があったので、修正箇所も説明します。&lt;/p&gt;
&lt;p&gt;公式リファレンス&lt;br /&gt;&lt;a href=&quot;https://docs.adonisjs.com/guides/auth/introduction&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.adonisjs.com/guides/auth/introduction&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/post/adonis-upgrade-to-v5/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AdonisJS v5へのアップグレードで他にやったことはこちらにまとめています。&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Authパッケージと設定の追加&lt;/h1&gt;
&lt;p&gt;設定追加時に認証方式をsessions, basic auth, API tokensから選ぶのだが、apiサーバーとして使ってるので&lt;strong&gt;API tokensを使っていく。&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ yarn add @adonisjs/auth
$ node ace configure @adonisjs/auth
&lt;/code&gt;&lt;/pre&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;# いくつか入力を求められる
❯ Select provider for finding users · lucid
❯ Select which guard you need for authentication (select using space) · api

❯ Enter model name to be used for authentication · User
# すでにtableはあるからmigration fileは生成しない
❯ Create migration for the users table? (y/N) · false
❯ Select the provider for storing API tokens · database
# v4ではtokensテーブルだったので、api_tokensのmigration fileは生成する
❯ Create migration for the api_tokens table? (y/N) › true

CREATE: app/Models/User.ts
CREATE: database/migrations/1664350142101_api_tokens.ts
CREATE: contracts/auth.ts
CREATE: config/auth.ts
CREATE: app/Middleware/Auth.ts
CREATE: app/Middleware/SilentAuth.ts
UPDATE: tsconfig.json { types += &quot;@adonisjs/auth&quot; }
UPDATE: .adonisrc.json { providers += &quot;@adonisjs/auth&quot; }
CREATE: ace-manifest.json file
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;api_tokensテーブルを作成&lt;/h1&gt;
&lt;p&gt;トークン情報が保存されるapi_tokensテーブルを新たに作るためにマイグレーションを実行する。&lt;br /&gt;&lt;code&gt;—dry-run&lt;/code&gt;を付けてテスト実行してみたらエラーがでた。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ node ace migration:run --dry-run

  Exception 

 Migration completed, but unable to release database lock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;adonis_schema_versionsテーブルがAdonis v4にはなかったためエラーになったようで、再度&lt;code&gt;—dry-run&lt;/code&gt;すると問題なかった。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ node ace migration:run --dry-run
------------- database/migrations/1664350142101_api_tokens -------------

CREATE TABLE `api_tokens` (`id` INT unsigned NOT NULL auto_increment PRIMARY KEY, `user_id` INT unsigned, `name` VARCHAR(255) NOT NULL, `type` VARCHAR(255) NOT NULL, `token` VARCHAR(64) NOT NULL, `expires_at` TIMESTAMP NULL, `created_at` TIMESTAMP NOT NULL);
ALTER TABLE `api_tokens` ADD CONSTRAINT `api_tokens_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE;
ALTER TABLE `api_tokens` ADD UNIQUE `api_tokens_token_unique`(`token`)

------------- END -------------
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;マイグレーションを実行して、テーブル作成する。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ node ace migration:run
❯ error database/migrations/1664350142101_api_tokens
[ error ]  create table `api_tokens` (`id` int unsigned not null auto_increment primary key, `user_id` int unsigned, `name` varchar(255) not null, `type` varchar(255) not null, `token` varchar(64) not null, `expires_at` timestamp null, `created_at` timestamp not null) - Invalid default value for &#x27;created_at&#x27;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;エラーでた。。。&lt;/p&gt;
&lt;p&gt;created_atのデフォルト値を指定する必要がありそうだ。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://knexjs.org/guide/schema-builder.html#timestamp&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://knexjs.org/guide/schema-builder.html#timestamp&lt;/a&gt;&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;diff-highlight language-diff-ts&quot; data-filename=&quot;1664350142101_api_tokens.ts&quot;&gt;&lt;code class=&quot;language-diff-ts&quot;&gt;-      table.timestamp(&#x27;created_at&#x27;, { useTz: true }).notNullable()
+      table.timestamp(&#x27;created_at&#x27;, { useTz: true }).notNullable().defaultTo(this.now())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再実行したら、作成できた。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ node ace migration:run
❯ migrated database/migrations/1664350142101_api_tokens

Migrated in 296 ms
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Social認証のためにallyパッケージと設定の追加&lt;/h1&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ yarn add @adonisjs/ally
$ node ace configure @adonisjs/ally
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;各ソーシャルのログイン認証に必要な環境変数を追加する。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot; data-filename=&quot;env.ts&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;export default Env.rules({
  // ### Variables for Twitter provider
  TWITTER_CLIENT_ID: Env.schema.string(),
  TWITTER_CLIENT_SECRET: Env.schema.string(),
  // ### Variables for Facebook provider
  FACEBOOK_CLIENT_ID: Env.schema.string(),
  FACEBOOK_CLIENT_SECRET: Env.schema.string(),
})
&lt;/code&gt;&lt;/pre&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot; data-filename=&quot;.env&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;TWITTER_CLIENT_ID=clientId
TWITTER_CLIENT_SECRET=clientSecret
FACEBOOK_CLIENT_ID=clientId
FACEBOOK_CLIENT_SECRET=clientSecret
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Auth middlewareを登録&lt;/h1&gt;
&lt;p&gt;リクエストに対して、認証されているかを確認するためにAuth Middlewareを使用する。&lt;/p&gt;
&lt;p&gt;Middlewareを以下のように設定すると、HTTPリクエストがルートハンドラに到達する前にMiddlewareに設定した内容が実行され、リクエストを終了させたり次の関数に転送したりする。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Route.get(&#x27;mypage&#x27;, &#x27;MypageController.index&#x27;).middleware([&#x27;auth&#x27;])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;node ace configure @adonisjs/auth&lt;/code&gt;実行時に、&lt;code&gt;app/Middleware/Auth.ts&lt;/code&gt;という認証チェックするものが作成されているので、それをauthというミドルウェアとして登録する。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot; data-filename=&quot;start/kernel.ts&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;Server.middleware.registerNamed({
  auth: () =&gt; import(&#x27;App/Middleware/Auth&#x27;),
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;ログインRouteを作成&lt;/h1&gt;
&lt;p&gt;ログイン認証するAPIを作成する。&lt;/p&gt;
&lt;p&gt;例としてTwitterログインをできるようにしていく。&lt;br /&gt;&lt;code&gt;/api/v1/user/twitter&lt;/code&gt;に認証周りのAPIを作成していく。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot; data-filename=&quot;/start/routes.ts&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;Route.group(() =&gt; {
  Route.get(&#x27;user/login/twitter&#x27;, &#x27;UserController.loginTwitter&#x27;)
  Route.get(&#x27;user/login/twitter/callback&#x27;, &#x27;UserController.loginTwitterCallback&#x27;)
}).prefix(&#x27;api/v1&#x27;).namespace(&#x27;App/Controllers/Http/Api/v1&#x27;)
// namespaceがrootからのパスに変わってる。
// v4では、namespace(&#x27;Api/v1&#x27;)だった
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;処理はUserControllerに書いていく。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ node ace make:controller Api/v1/UserController

CREATE: app/Controllers/Http/Api/v1/UserController.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot; data-filename=&quot;app/Controllers/Http/Api/v1/UserController.ts&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;export default class UsersController {
	// ここにアクセスしてきたら、Twitter上の認証画面にリダイレクト
  public async loginTwitter({ ally }: HttpContextContract) {
    return ally.use(&#x27;twitter&#x27;).redirect()
  }
　// 認証画面からのコールバック
  public async loginTwitterCallback({ ally, auth }: HttpContextContract) {
    try {
      const twitter = ally.use(&#x27;twitter&#x27;)
			Logger.info(await twitter.user())
			// 認証後の処理
      // DBにユーザーを追加したり、トークンを発行したりして
      // クライアント側に戻す
   } catch (error) {
      Logger.error(error)
      return &#x27;Unable to authenticate. Try again later&#x27;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;http://127.0.0.1:3333/api/v1/user/login/twitter&lt;/code&gt;にアクセスしてログインができるか確認しておく。&lt;/p&gt;
&lt;h1&gt;Callback用のViewを作っていく&lt;/h1&gt;
&lt;p&gt;自分の場合は、ログインボタン押下→別ウィンドウが開く→ログインが完了したらウィンドウを閉じる→元ウィンドウをマイページに遷移させる といった動作にしている。&lt;/p&gt;
&lt;p&gt;なので、元ウィンドウにトークン情報を渡すために、Adonis側で一部Viewを用意している。&lt;/p&gt;
&lt;p&gt;viewパッケージと設定の追加&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ yarn add @adonisjs/view
$ node ace configure @adonisjs/view
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Callback用のViewテンプレートを生成&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ node ace make:view user/social-callback
CREATE: resources/views/user/social_callback.edge
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;social-callback.edge、ハイフンで指定していたが、Adonis v5ではsocial_callback.edge、アンダーバーで生成されることに注意&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-html&quot; data-filename=&quot;resources/views/user/social_callback.edge&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;#x3C;p&gt;Authenticated successfully.&amp;#x3C;/p&gt;
&amp;#x3C;script type=&quot;text/javascript&quot;&gt;
  // 元ウィンドウにトークンを送る
  window.opener.postMessage({{{toJSON(token)}}}, &#x27;{{targetOrigin}}&#x27;)
  window.close();
&amp;#x3C;/script&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;UserControllerは以下のようにした。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot; data-filename=&quot;app/Controllers/Http/Api/v1/UserController.ts&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;  public async loginTwitterCallback({ ally, auth, view }: HttpContextContract) {
    try {
      const twitter = ally.use(&#x27;twitter&#x27;)

      if (twitter.accessDenied()) {
        return &#x27;Access was denied&#x27;
      }
      if (twitter.stateMisMatch()) {
        return &#x27;Request expired. Retry again&#x27;
      }
      if (twitter.hasError()) {
        return twitter.getError()
      }

      const twUser = await twitter.user()

      if (!twUser.email) {
        return &#x27;Cannot get email.&#x27;
      }

      // user details to be saved
      const userDetails = {
        email: twUser.email,
        source: &#x27;twitter&#x27;,
      }
      const whereClause = {
        email: twUser.email,
        source: &#x27;twitter&#x27;,
      }
      const user = await User.firstOrCreate(whereClause, userDetails)

      const token = await auth.use(&#x27;api&#x27;).generate(user, {
        expiresIn: &#x27;14 days&#x27;,
      })
      Logger.info(JSON.stringify(token))

      view.share({
        token,
        targetOrigin: Env.get(&#x27;URL&#x27;),
      })

      return view.render(&#x27;user/social_callback&#x27;)
    } catch (error) {
      Logger.error(error)
      return &#x27;Unable to authenticate. Try again later&#x27;
    }
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;renderの書き方がv4と少し変わっていた。&lt;br /&gt;viewを指定するときドットで繋いでいたのがDeprecatedされて、スラッシュ/で繫ぐようになった。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;diff-highlight language-diff-ts&quot;&gt;&lt;code class=&quot;language-diff-ts&quot;&gt;- return view.render(&#x27;user.social-callback&#x27;)
+ return view.render(&#x27;user/social_callback&#x27;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上にて、Adonisでログイン認証設定が完了。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/post/adonis-upgrade-to-v5/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AdonisJS v5へのアップグレードが大変だったのでこちらにまとめました。&lt;/a&gt;&lt;/p&gt;
]]&gt;</content:encoded><category>AdonisJS</category></item><item><title>&lt;![CDATA[AdonisJSでデータベース接続設定]]&gt;</title><link>https://blog.ryou103.com/post/adonis-upgrade-database</link><guid>https://blog.ryou103.com/post/adonis-upgrade-database</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-database/adonis-upgrade-database.png" medium="image"></media:content><description>AdonisJSでデータベースへの接続設定の紹介と旧AdonisJS v4から移行する際の修正箇所を紹介します。</description><pubDate>Sat, 29 Oct 2022 07:49:14 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-database/adonis-upgrade-database.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;こちらは、現時点で最新のAdonisJS v5の記事になります。&lt;/p&gt;
&lt;p&gt;AdonisJSでデータベースへの接続設定の紹介と旧AdonisJS v4から移行する際の修正箇所を紹介。&lt;/p&gt;
&lt;p&gt;公式リファレンス&lt;br /&gt;&lt;a href=&quot;https://docs.adonisjs.com/guides/database/introduction&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.adonisjs.com/guides/database/introduction&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Lucidパッケージと設定の追加&lt;/h1&gt;
&lt;p&gt;DB接続に必要なAdonisのパッケージを追加と設定ファイルを生成します。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ yarn @adonisjs/lucid
$ node ace configure @adonisjs/lucid
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;DB接続情報の環境変数を追加&lt;/h1&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot; data-filename=&quot;env.ts&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;export default Env.rules({
  DB_HOST: Env.schema.string({ format: &#x27;host&#x27; }),
  DB_PORT: Env.schema.number(),
  DB_USER: Env.schema.string(),
  DB_PASSWORD: Env.schema.string.optional(),
  DB_DATABASE: Env.schema.string(),
})
&lt;/code&gt;&lt;/pre&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot; data-filename=&quot;.env&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_USER=adonis
DB_PASSWORD=adonis
DB_DATABASE=adonis
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;DB接続設定の変更&lt;/h1&gt;
&lt;p&gt;初期ではPostgreSQLのpg設定しかないので、MySQLの設定に変更する。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;diff-highlight language-diff-tsx&quot; data-filename=&quot;config/database.ts&quot;&gt;&lt;code class=&quot;language-diff-tsx&quot;&gt;connections: {
-    pg: {
-      client: &#x27;pg&#x27;,
+    mysql: {
+      client: &#x27;mysql2&#x27;,
       connection: {
-        host: Env.get(&#x27;PG_HOST&#x27;),
-        port: Env.get(&#x27;PG_PORT&#x27;),
-        user: Env.get(&#x27;PG_USER&#x27;),
-        password: Env.get(&#x27;PG_PASSWORD&#x27;, &#x27;&#x27;),
-        database: Env.get(&#x27;PG_DB_NAME&#x27;),
+        host: Env.get(&#x27;DB_HOST&#x27;),
+        port: Env.get(&#x27;DB_PORT&#x27;),
+        user: Env.get(&#x27;DB_USER&#x27;),
+        password: Env.get(&#x27;DB_PASSWORD&#x27;, &#x27;&#x27;),
+        database: Env.get(&#x27;DB_DATABASE&#x27;),
       },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;clientにmysql2をつかっているので、Packageをインストール&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ yarn add mysql2
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;HealthCheckで接続できているか確認&lt;/h1&gt;
&lt;p&gt;AdonisJSにHealthCheckという設定に問題がないかを確認できる機能があるので、それを使ってDB接続できているか確認する。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot; data-filename=&quot;start/routes.ts&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;if (Env.get(&#x27;NODE_ENV&#x27;) !== &#x27;production&#x27;) {
  Route.get(&#x27;health&#x27;, async ({ response }) =&gt; {
    const report = await HealthCheck.getReport()

    response.type(&#x27;.json&#x27;)

    return report.healthy
      ? response.ok(report)
      : response.badRequest(report)
  })
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-ts&quot; data-filename=&quot;config/database.ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;mysql: {
  healthCheck: true,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;http://127.0.0.1:8080/health&lt;/code&gt;にアクセスすると、以下のように情報が表示される。&lt;/p&gt;
&lt;p&gt;接続失敗例&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;{
    &quot;healthy&quot;: false,
    &quot;report&quot;: {
        &quot;lucid&quot;: {
            &quot;displayName&quot;: &quot;Database&quot;,
            &quot;health&quot;: {
                &quot;healthy&quot;: false,
                &quot;message&quot;: &quot;One or more connections are not healthy&quot;
            },
            &quot;meta&quot;: [
                {
                    &quot;connection&quot;: &quot;mysql&quot;,
                    &quot;message&quot;: &quot;Unable to reach the database server&quot;,
                    &quot;error&quot;: {
                        &quot;errno&quot;: -3008,
                        &quot;code&quot;: &quot;ENOTFOUND&quot;,
                        &quot;syscall&quot;: &quot;getaddrinfo&quot;,
                        &quot;hostname&quot;: &quot;mysql&quot;,
                        &quot;fatal&quot;: true
                    }
                }
            ]
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;環境変数が間違っていたので修正して、再度確認したら解決。&lt;br /&gt;.envを変更したら自動で再起動してくれるの便利&lt;/p&gt;
&lt;p&gt;接続成功例&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;{
    &quot;healthy&quot;: true,
    &quot;report&quot;: {
        &quot;lucid&quot;: {
            &quot;displayName&quot;: &quot;Database&quot;,
            &quot;health&quot;: {
                &quot;healthy&quot;: true,
                &quot;message&quot;: &quot;All connections are healthy&quot;
            },
            &quot;meta&quot;: [
                {
                    &quot;connection&quot;: &quot;mysql&quot;,
                    &quot;message&quot;: &quot;Connection is healthy&quot;,
                    &quot;error&quot;: null
                }
            ]
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Migrationファイルを移す&lt;/h1&gt;
&lt;p&gt;旧&lt;code&gt;database/migrations/&lt;/code&gt;以下にあるmigrationファイルを移していく。&lt;/p&gt;
&lt;p&gt;ファイルをコピペ&lt;br /&gt;TypeScriptに書き直す必要があるので、拡張子をjs→tsに書き換える。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-database/adonis-upgrade-database-1.png&quot; alt=&quot;旧migrationファイル&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;KnexのSchemaBuilderの書き方はほぼ変わってなかった。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;diff-highlight language-diff-tsx&quot;&gt;&lt;code class=&quot;language-diff-tsx&quot;&gt;-&#x27;use strict&#x27;
+import BaseSchema from &#x27;@ioc:Adonis/Lucid/Schema&#x27;
 
-/** @type {import(&#x27;@adonisjs/lucid/src/Schema&#x27;)} */
-const Schema = use(&#x27;Schema&#x27;)
+export default class UserSchema extends BaseSchema {
+  protected tableName = &#x27;users&#x27;
 
-class UserSchema extends Schema {
-  up() {
-    this.createIfNotExists(&#x27;users&#x27;, (table) =&gt; {
+  public async up() {
+    this.schema.createTableIfNotExists(this.tableName, (table) =&gt; {
       table.increments()
       table.string(&#x27;email&#x27;, 254).notNullable().unique()
       table.string(&#x27;password&#x27;, 60).notNullable()
@@ -13,9 +12,7 @@ class UserSchema extends Schema {
     })
   }
 
-  down() {
-    this.dropIfExists(&#x27;users&#x27;)
+  public async down() {
+    this.schema.dropTableIfExists(this.tableName)
   }
 }
-
-module.exports = UserSchema
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;変わってた部分&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;// ❌
table.unique(&#x27;email&#x27;)
// ✅
table.unique([&#x27;email&#x27;])
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Migrationステータスを修正していく&lt;/h1&gt;
&lt;p&gt;現状のmigrationステータスを確認するとpendingになっており、マイグレーションが正常な状態でないことがわかる。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ node ace migration:status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-database/adonis-upgrade-database-2.png&quot; alt=&quot;マイグレーションが正常な状態でない&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;Adonis v4ではDBに&lt;code&gt;1503250034279_user&lt;/code&gt;で保存されており、v5では&lt;code&gt;database/migrations/1503250034279_user&lt;/code&gt;のようにディレクトリ名も含めて保存されるようになったため。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-database/adonis-upgrade-database-3.png&quot; alt=&quot;ディレクトリ名も含めて保存されるようになった&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;この差を更新するSQLを実行して、DBを更新する&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-sql&quot;&gt;&lt;code class=&quot;language-sql&quot;&gt;UPDATE adonis_schema
SET name = CONCAT(&#x27;database/migrations/&#x27;, name);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再度Migrationステータスを確認するとcompletedになった。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ node ace migration:status
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-database/adonis-upgrade-database-4.png&quot; alt=&quot;Migrationステータスを確認するとcompletedになった&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;Migrationファイルの移行完了。&lt;/p&gt;
&lt;p&gt;以上にて、Adonisでデータベースに接続作業完了。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/post/adonis-upgrade-to-v5/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AdonisJS v5へのアップグレードが大変だったのでこちらにまとめました。&lt;/a&gt;&lt;/p&gt;
]]&gt;</content:encoded><category>AdonisJS</category></item><item><title>&lt;![CDATA[AdonisJS v5へのアップグレードが大変だったのでまとめた]]&gt;</title><link>https://blog.ryou103.com/post/adonis-upgrade-to-v5</link><guid>https://blog.ryou103.com/post/adonis-upgrade-to-v5</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-to-v5/adonis-upgrade-to-v5.png" medium="image"></media:content><description>AdonisJSをv4→v5へのアップグレードを行ったのだが、あまりにも大変だったので、いくつかの記事に分けて紹介します。テスト駆動を意識して書いたおかげもあり、不具合を出すことなくアップグレードが完了できた。</description><pubDate>Sat, 29 Oct 2022 07:48:56 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-to-v5/adonis-upgrade-to-v5.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;AdonisJSをv4→v5へのアップグレードを行ったのだが、あまりにも大変だったので、いくつかに分けて紹介。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;現在、絶賛執筆中です。随時記事を追加していきます。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ちなみに、公式ではv4で作成したアプリをアップグレードするのを薦めていない。。&lt;br /&gt;規模が大きいサービスに取り入れていたらと思うとゾッとしますね。。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonis-upgrade-to-v5/adonis-upgrade-to-v5-1.png&quot; alt=&quot;公式ではv4で作成したアプリをアップグレードするのを薦めていない&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.adonisjs.com/releases/out-of-preview&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.adonisjs.com/releases/out-of-preview&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;AdonisJSは、「&lt;a href=&quot;https://qrar.bonos.work/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;QRコードから飛び出すARをカンタンに作成できるサービス QRAR&lt;/a&gt;」のAPIサーバーで利用しています。&lt;br /&gt;当時、テスト駆動を意識して書いたおかげもあり、不具合を出すことなくアップグレードが完了できた。&lt;/p&gt;
&lt;h1&gt;どうやるか&lt;/h1&gt;
&lt;p&gt;通常アップグレードしていく場合、パッケージのバージョンを上げてエラー箇所を修正していくかと思うが、v5では根本的に書き方や構造が変更されているので、新しく別プロジェクトとして作成していく。&lt;/p&gt;
&lt;p&gt;機能的に廃止されたとかがなかったので、そこはまだマシだったかな。。&lt;/p&gt;
&lt;h1&gt;やったこと&lt;/h1&gt;
&lt;h2&gt;新プロジェクトの作成&lt;/h2&gt;
&lt;p&gt;こちらは何かしたわけではないので、記事としては省略。&lt;/p&gt;
&lt;p&gt;以下のコマンドで別プロジェクトを作成する。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ yarn create adonis-ts-app server2
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;a href=&quot;/post/adonis-upgrade-database/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;データベースに接続できるようにする&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;データベース機能の追加と旧データベースに接続する際にマイグレーションファイルの修正が必要だったので、その手順を紹介。&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;/post/adonis-upgrade-authentication/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;認証機能を追加する&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;認証機能の追加手順を紹介。&lt;br /&gt;以前はJWT認証で実装していたが、v5では代わってOpaqueAccessTokenを使っての認証になるので、実装方法を変える必要がありました。&lt;br /&gt;また、ソーシャル認証も取り入れています。&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;/post/adonis-upgrade-validator/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Validatorの実装&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;AdonisJSでリクエストボディをバリデーションできるValidator機能の導入天順を紹介。&lt;/p&gt;
&lt;h2&gt;&lt;a href=&quot;/post/adonis-upgrade-test/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;テスト&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;テストの導入手順と変更点を紹介&lt;br /&gt;テストはVowという内製？のテストツールからJapaに変更されている。&lt;br /&gt;全体的に大きな変更はなく、メソッド名が違ったりの小さな変化だった。&lt;/p&gt;
&lt;h1&gt;機能拡張した点や小技を紹介&lt;/h1&gt;
&lt;h2&gt;&lt;a href=&quot;/post/adonis-upgrade-ssl/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;開発環境をSSLで起動する&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Facebookで認証を行うのにSSLである必要があるので、開発環境でもHTTPSで起動できるようにする。&lt;/p&gt;
&lt;h2&gt;SQLをデバッグする&lt;/h2&gt;
&lt;p&gt;ORMを使っていると内部で実際にどういったSQLが叩かれているかがわからない。&lt;br /&gt;正常に動作しているか、ログにSQL文を表示させる。&lt;/p&gt;
&lt;h2&gt;件数検索できるようにDatabase,ModelにgetCountを追加する&lt;/h2&gt;
&lt;p&gt;v4には検索件数を取得できるgetCountがあったのだが、v5ではなくなってしまったので、QueryBuilderのマクロにgetCountを追加する&lt;/p&gt;
&lt;h2&gt;OAT Tokenの期限を更新する&lt;/h2&gt;
&lt;p&gt;以前のJWTトークンではリフレッシュトークンを使ってログイン期限を更新するようにしていたが、OATトークンはリフレッシュトークンがないので、初回認証から一定時間経つと再認証が必要になる。&lt;/p&gt;
&lt;p&gt;最終認証からN日間認証を許可するという仕様になるようにOAT Tokenの期限を更新する。&lt;/p&gt;
]]&gt;</content:encoded><category>AdonisJS</category></item><item><title>&lt;![CDATA[PrismJSのAutoloaderを使ってシンタックスハイライトを行う]]&gt;</title><link>https://blog.ryou103.com/post/prismjs-autoloader</link><guid>https://blog.ryou103.com/post/prismjs-autoloader</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/prismjs-autoloader/prismjs-autoloader.png" medium="image"></media:content><description>PrismJSのAutoloaderを使うとシンタックスハイライト言語を自動追加してくれる便利なプラグインがあるにも関わらず、npmでパッケージ管理しているプロジェクトでPrismJSとAutoloaderを使ったHowto記事が見つからなかったので、挑戦してみました。</description><pubDate>Thu, 22 Sep 2022 09:23:20 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/prismjs-autoloader/prismjs-autoloader.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Autoloaderを使ってシンタックスハイライト言語を自動追加してくれる便利なプラグインがあるにも関わらず、npmでパッケージ管理しているプロジェクトでPrismJSとAutoloaderを使ったHowto記事が見つからなかったので、挑戦してみました。&lt;/p&gt;
&lt;h1&gt;Autoloaderを使う&lt;/h1&gt;
&lt;p&gt;シンタックスハイライトしたい言語だけのファイルを自動で読み込めるように、プラグインのAutoloaderを導入する。&lt;/p&gt;
&lt;h2&gt;インストール&lt;/h2&gt;
&lt;p&gt;AutoloaderはPrismJSのパッケージの中に入っているので、PrismJSだけのインストールで済む。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;$ yarn add prismjs
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;コードの追加&lt;/h2&gt;
&lt;p&gt;今回はReactで説明していく。&lt;/p&gt;
&lt;p&gt;PrismJSをいつも通り使うのに加えて、autoloaderをimportしておく。&lt;br /&gt;読み込むファイルはブログなら記事部分が対象になるでしょうから、Content.tsxなど記事のコンポーネントに追加すれば良い。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;import &#x27;prismjs/plugins/autoloader/prism-autoloader&#x27;
import Prism from &quot;prismjs&quot;

const Component: React.FC = () =&gt;  {
  useEffect(() =&gt; {
    Prism.highlightAll();
  }, []);
  
  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;実行すると、Autoloaderが動いてシンタックスハイライト対象言語を読み込みに行こうとしているが、404エラーが返ってきている。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/prismjs-autoloader/prismjs-autoloader-1.png&quot; alt=&quot;404エラー&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;対象言語用のコードの取得先を変更する必要がある。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;
import &#x27;prismjs/plugins/autoloader/prism-autoloader&#x27;
import Prism from &quot;prismjs&quot;

if (prism.plugins.autoloader) {
	// npmで管理しているバージョンと合わせる
  prism.plugins.autoloader.languages_path =
    &#x27;https://unpkg.com/prismjs@1.28.0/components/&#x27;
}

const Component: React.FC = () =&gt;  {
  useEffect(() =&gt; {
    Prism.highlightAll();
  }, []);
  
  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これでシンタックスハイライトが効くようになる。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/prismjs-autoloader/prismjs-autoloader-2.png&quot; alt=&quot;シンタックスハイライトが効く&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;なぜAutoloaderを使いたかったのか&lt;/h1&gt;
&lt;p&gt;ReactやVueなどでPrismJSを使ってシンタックスハイライトを行う際、PrismJSもnpmでパッケージ管理したい。&lt;/p&gt;
&lt;p&gt;Reactを例にあげると、以下のように書くと思う。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;import Prism from &quot;prismjs&quot;;

const Component: React.FC = () =&gt;  {
  useEffect(() =&gt; {
    Prism.highlightAll();
  }, []);
  
  // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;シンタックスハイライトの対象言語を追加するとなると、&lt;code&gt;import&lt;/code&gt;を行うか、babelを使っているなら&lt;code&gt;babel-plugin-prismjs&lt;/code&gt; を使うと思う。&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;パターン１：importを使って対象言語を読み込む&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;import Prism from &quot;prismjs&quot;;
// bashを対象言語に追加する場合
import &#x27;prismjs/components/prism-bash&#x27;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
  &lt;li&gt;パターン２：bebel-plugin-prismjsで対象言語を読み込む&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;.babelrc&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;plugins&quot;: [
    [
      &quot;prismjs&quot;,
      {
        &quot;languages&quot;: [&quot;bash&quot;]
      }
    ]
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上記のように書けば問題なく動作する。&lt;/p&gt;
&lt;p&gt;しかし例えばブログで使う際に、あるページではbash, tsx, jsonを使い、とあるページでは、go, graphql, yamlを使うとすると、記事ページでは6言語分のシンタックスハイライト用のコードを読み込んでおかなければならない。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使っていない対象言語用のコードを読み込んでしまうし、何十種類も対象言語を読み込むとなるとパフォーマンス的に悪影響になる&lt;/strong&gt;のは間違いない。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/prismjs-autoloader/prismjs-autoloader-3.png&quot; alt=&quot;１言語ごとのファイルサイズは小さいが全ファイル読み込むと・・・&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;なので、シンタックスハイライトする言語だけのファイルを読み込めるAutoloaderを使いたかった。&lt;/p&gt;
]]&gt;</content:encoded><category>JavaScript</category></item><item><title>&lt;![CDATA[FVMで管理しているFlutterアプリをアップグレード]]&gt;</title><link>https://blog.ryou103.com/post/flutter-upgrade</link><guid>https://blog.ryou103.com/post/flutter-upgrade</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/flutter-upgrade/flutter-upgrade.png" medium="image"></media:content><description>Flutterをv3.13にアップグレードを行いました。FVMを使っていると公式サイトとは手順が違った。また修正作業が必要だった箇所もいくつかあったので書き出しました。</description><pubDate>Tue, 20 Sep 2022 09:00:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/flutter-upgrade/flutter-upgrade.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;現在2.10.5を使用していて、3.13.6にアップグレードを行う。&lt;/p&gt;
&lt;p&gt;公式サイトだと、以下のように実行すると書いてある。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ flutter channel stable
$ flutter upgrade
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;しかし、実行してみると、&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ fvm flutter channel stable
$ fvm flutter upgrade

You should not upgrade a release version. Please install a channel instead to upgrade it.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;エラーが表示されアップグレードできない。&lt;/p&gt;
&lt;p&gt;fvmでバージョン管理している場合は、fvmコマンドで新しいバージョンをインストールしていく。&lt;/p&gt;
&lt;h1&gt;Flutter 3.13.6にアップグレードする&lt;/h1&gt;
&lt;h2&gt;fvmでFlutter 3.13.6をインストール&lt;/h2&gt;
&lt;p&gt;リリースされているバージョンを確認&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ fvm releases

Sep 13 23  │ 3.13.3           
Sep 13 23  │ 3.13.4           
Sep 15 23  │ 3.15.0-15.1.pre  
Sep 20 23  │ 3.13.5           
--------------------------------------
Sep 21 23  │ 3.15.0-15.2.pre   beta
--------------------------------------
--------------------------------------
Sep 27 23  │ 3.13.6            stable
--------------------------------------
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3.13.6がstableバージョンなので、3.13.6にアップグッレードする&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ fvm use 3.13.6

Installing version: 3.13.6...
...
Flutter 3.13.6 • channel stable • https://github.com/flutter/flutter.git
Framework • revision ead455963c (12 days ago) • 2023-09-26 18:28:17 -0700
Engine • revision a794cf2681
Tools • Dart 3.1.3 • DevTools 2.25.0
Project now uses Flutter [3.13.6]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;ついでにfvmも2.4.1にアップグレード&lt;/h2&gt;
&lt;p&gt;fvmのバージョンも2.4.1に上げれるみたいなので、アップグレードしておく。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ pub global activate fvm
Installed executable fvm.
Activated fvm 2.3.1.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;しかし、メッセージ通りのコマンドを実行しても上がらない。&lt;/p&gt;
&lt;p&gt;正解はfvm内の &lt;code&gt;dart pub&lt;/code&gt; 使う。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ fvm dart pub global activate fvm
Installed executable fvm.
Activated fvm 2.4.1.
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;dartパッケージをアップグレード&lt;/h2&gt;
&lt;p&gt;Flutterのバージョンだけでなく、dartパッケージをアップグレードしていく。&lt;/p&gt;
&lt;p&gt;まずは、マイナーバージョンをアップグレード&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ fvm flutter pub upgrade
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;メジャーバージョンのアップグレードを確認&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ fvm flutter pub outdated
Package Name                         Current   Upgradable  Resolvable  Latest   

direct dependencies:                
bonsoir                              *1.0.1+2  *1.0.1+2    2.0.0       2.0.0    
device_info_plus                     *3.2.4    *3.2.4      4.1.2       4.1.2    
file_picker                          *4.6.1    *4.6.1      5.0.1       5.0.1    
flutter_launcher_icons               *0.9.3    *0.9.3      0.10.0      0.10.0   
freezed_annotation                   *1.1.0    *1.1.0      2.1.0       2.1.0    
package_info_plus                    *1.4.2    *1.4.2      1.4.3+1     1.4.3+1  
permission_handler                   *9.2.0    *9.2.0      10.0.0      10.0.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;メジャーバージョンの変更は破壊的変更があるため、ひとつずつ確認していく。&lt;/p&gt;
&lt;p&gt;パッケージのpubspec.yamlを見て、flutterのバージョンが3.13に対応しているか確認する。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/flutter-upgrade/flutter-upgrade-1.png&quot; alt=&quot;pubパッケージのバージョンを確認&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;それからCHANGELOG.mdから破壊的変更を見て、修正箇所を事前に確認しておく。&lt;/p&gt;
&lt;p&gt;例えば、&lt;br /&gt;device_info_plusを3.2.4→4.1.2にアップグレードできる。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/fluttercommunity/plus_plugins/blob/main/packages/device_info_plus/device_info_plus/CHANGELOG.md#400&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CHANGELOG.md&lt;/a&gt;を確認すると、4.0.0でAndroidIdという項目が削除されると書いてあり、自分のコードで利用しているのでここはあとで修正が必要だなと確認しておく。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/flutter-upgrade/flutter-upgrade-2.png&quot; alt=&quot;修正が必要な箇所&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;他に、package_info_plusを10.0.0にアップグレードする際に、&lt;a href=&quot;https://github.com/Baseflow/flutter-permission-handler/blob/master/permission_handler/CHANGELOG.md#1000&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;CHANGELOG.md&lt;/a&gt;に、compileSdkVersionを33に書き換える必要があると書いてあった。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;When updating to version 10.0.0 make sure to update the android/app/build.gradle file and set the compileSdkVersion to 33.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;なので、android/app/build.gradle内のcompileSdkVersionを書き換える。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-gradle&quot;&gt;&lt;code class=&quot;language-gradle&quot;&gt;android {
    compileSdkVersion 33
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;問題なければ、自分のpubspec.yamlを書き換えていく。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;-  device_info_plus: ^3.2.4
+  device_info_plus: ^4.1.2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;またFlutterのバージョンも最新だけに対応していれば良いので、書き換えておく。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;environment:
  flutter: &quot;&gt;=3.13.6&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;VSCodeが勝手にpub getしてくれているが、念の為手動でpub getを実行する。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ fvm flutter pub get
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;VSCodeを再起動&lt;/h2&gt;
&lt;p&gt;VSCodeが認識しているFlutterのバージョンが変わらなかった。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/flutter-upgrade/flutter-upgrade-4.png&quot; alt=&quot;VSCodeが認識しているFlutterのバージョンが変わらなかった&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;setting.jsonでも、&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&quot;dart.flutterSdkPath&quot;: &quot;.fvm/flutter_sdk&quot;,
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;sdkの位置を指定しており、そのファイルを確認すると、3.13.6に変更されていた。&lt;/p&gt;
&lt;p&gt;どうしようもなく一旦VSCodeを再起動してみたら、無事3.13.6に変わった。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/flutter-upgrade/flutter-upgrade-5.png&quot; alt=&quot;VSCodeを再起動してみたら、無事3.13.6に変わった。&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;コードの修正&lt;/h2&gt;
&lt;p&gt;dartパッケージをアップグレードするといくらかエラーが出たので修正していく。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/flutter-upgrade/flutter-upgrade-5.png&quot; alt=&quot;エラー箇所を確認&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;動作確認＆修正&lt;/h1&gt;
&lt;h2&gt;Androidでの動作確認&lt;/h2&gt;
&lt;p&gt;Androidデバイスで起動を試みる&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ fvm flutter run -d ANDROID_DEVICE
e: Incompatible classes were found in dependencies. Remove them from the classpath or use &#x27;-Xskip-metadata-version-check&#x27; to suppress errors
e: /Users/.../.gradle/caches/transforms-3/3eb6c3f03955a7bda7310b4d29667dba/transformed/jetified-kotlin-stdlib-jdk7-1.7.10.jar!/META-INF/kotlin-stdlib-jdk7.kotlin_module: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.7.1, expected version is 1.5.1.
...
エラー文が大量に出力
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;見た感じkotlinのバージョンが不適切。&lt;/p&gt;
&lt;p&gt;kotlinをリリースされている最新のバージョン1.7.10にする。&lt;br /&gt;&lt;a href=&quot;https://kotlinlang.org/docs/releases.html#release-details&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://kotlinlang.org/docs/releases.html#release-details&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;android/app/build.gradle&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;buildscript {
    ext.kotlin_version = &#x27;1.7.10&#x27;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;無事起動は完了した。&lt;/p&gt;
&lt;h3&gt;iOSでの動作確認&lt;/h3&gt;
&lt;p&gt;iOSデバイスで起動を試みる&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ fvm flutter run -d IOS_DEVICE
[!] CocoaPods could not find compatible versions for pod &quot;Firebase/CoreOnly&quot;:
      In snapshot (Podfile.lock):
        Firebase/CoreOnly (= 8.15.0)

      In Podfile:
        firebase_core (from `.symlinks/plugins/firebase_core/ios`) was resolved to 1.22.0, which depends on
          Firebase/CoreOnly (= 9.5.0)

    Specs satisfying the `Firebase/CoreOnly (= 8.15.0), Firebase/CoreOnly (= 9.5.0)` dependency were found, but they required a higher minimum deployment target.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;エラーがでた。&lt;/p&gt;
&lt;p&gt;firebase_coreパッケージをpubでアップグレードしたせいで、Firebase/CoreOnlyが9.5.0に依存するはずなのに8.15.0になっている。&lt;/p&gt;
&lt;p&gt;Podfileを再インストールする。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ cd ios
$ rm -f Podfile.lock
$ pod repo update
$ pod install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完了したら、再度起動を試みる。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ fvm flutter run -d IOS_DEVICE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;起動できた！&lt;/p&gt;
]]&gt;</content:encoded><category>Flutter</category><category>Docker</category></item><item><title>&lt;![CDATA[React v18からchildrenを明示的に定義する必要がある]]&gt;</title><link>https://blog.ryou103.com/post/react-18-fcx</link><guid>https://blog.ryou103.com/post/react-18-fcx</guid><description>React v18では、React.FCを使う際にchildren明示する仕様になったので、独自の型React.FCXを用意する</description><pubDate>Sat, 10 Sep 2022 09:00:00 +0000</pubDate><content:encoded>&lt;![CDATA[
&lt;p&gt;以前までは、&lt;code&gt;React.FunctionComponent&lt;/code&gt; (FC), &lt;code&gt;React.Component&lt;/code&gt; に暗黙的に含まれていたので以下のようにchildrenを使うことができた。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;const Layout: React.FC = ({ children }) =&gt; {
  return (
    &amp;#x3C;div&gt;{children}&amp;#x3C;/div&gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;しかし、React v18からはTypeErrorがでてしまう。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/react-18-fcx/react-18-fcx-1.png&quot; alt=&quot;React v18からはTypeError&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;Propsでchildrenを定義する必要がでてくる。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;const Layout: React.FC&amp;#x3C;{ children: React.ReactNode }&gt; = ({ children }) =&gt; {
  return (
    &amp;#x3C;div&gt;{children}&amp;#x3C;/div&gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ただ、childrenを使うときに毎回定義するの面倒だよね、だから&lt;strong&gt;childrenをPropsに持つReact.FCX&lt;/strong&gt;みたいな型を用意してあげようって作戦。&lt;/p&gt;
&lt;h1&gt;React.FCXを定義する&lt;/h1&gt;
&lt;p&gt;Reactのnamespace内に新たにFCXというPropsにchildrenを持つ型を作成する&lt;/p&gt;
&lt;p&gt;react-custom.d.tsを作成する&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-ts&quot;&gt;&lt;code class=&quot;language-ts&quot;&gt;declare namespace React {
  type FCX&amp;#x3C;P = {}&gt; = FC&amp;#x3C;{ children?: ReactNode } &amp;#x26; P　　&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;d.tsがうまく読み込まれない場合は、tsconfig.jsonを確認。&lt;/p&gt;
&lt;p&gt;自分はtypesディレクトリ内のd.tsを読み込むように設定している。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;compilerOptions&quot;: {
    ...
  },
  &quot;include&quot;: [&quot;types/**/*.d.ts&quot;, &quot;**/*.ts&quot;, &quot;**/*.tsx&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;React.FCXの使い方&lt;/h1&gt;
&lt;p&gt;先程React.FCを使ってエラーがでていたものをFCXに書き換えるとエラーが消える&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;const Layout: React.FCX = ({ children }) =&gt; {
  return &amp;#x3C;div&gt;{children}&amp;#x3C;/div&gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;参照&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#updates-to-typescript-definitions&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#updates-to-typescript-definitions&lt;/a&gt;&lt;/p&gt;
]]&gt;</content:encoded><category>React</category></item><item><title>&lt;![CDATA[ブログをNext.js+Markdownにリプレイス]]&gt;</title><link>https://blog.ryou103.com/post/blog-renewal-next-js</link><guid>https://blog.ryou103.com/post/blog-renewal-next-js</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/blog-renewal-next-js/blog-renewal-next-js.png" medium="image"></media:content><description>Next.js, GraphQL, Markdown, Golangを使って当ブログをリニューアルしました。以前はGatsbyを使っていましたが、3年も前に作成しバージョン（v2.18）も古くなってしまったので、そろそろメンテナンスしないとなと思い、どうせならデザインも変えたいし、記事もGitで管理したいし、とか色々考えてフルリニューアルしてしまえってことで１から構成を見直しました。</description><pubDate>Fri, 09 Sep 2022 06:10:12 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/blog-renewal-next-js/blog-renewal-next-js.png&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;はじめに&lt;/h1&gt;
&lt;p&gt;Next.js, GraphQL, Markdown, Golangを使って当ブログをリニューアルしました。&lt;/p&gt;
&lt;p&gt;以前はGatsbyを使っていましたが、3年も前に作成しバージョン（v2.18）も古くなってしまったので、そろそろメンテナンスしないとなと思い、どうせならデザインも変えたいし、記事もGitで管理したいし、とか色々考えてフルリニューアルしてしまえってことで１から構成を見直しました。&lt;/p&gt;
&lt;h1&gt;サイト構成&lt;/h1&gt;
&lt;p&gt;最終的にサイト用と記事用の２レポジトリを作成し、VercelでGraphQLサーバーとホスティングサーバーを用意する形をとりました。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/blog-renewal-next-js/blog-renewal-next-js-1.png&quot; alt=&quot;サイト構成&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;Webサイト側は、Next.jsを利用しISRで配信しています。&lt;/p&gt;
&lt;p&gt;記事側は、Markdownで書いた記事をGolangを使ってGraphqlサーバーとして配信しています。&lt;/p&gt;
&lt;h1&gt;技術選定について&lt;/h1&gt;
&lt;p&gt;書くフレームワークを選んだわけ&lt;/p&gt;
&lt;h2&gt;Markdown&lt;/h2&gt;
&lt;p&gt;こんなに時間を掛けて記事を書いているのに、草が生えない、、草を生やしたい！とずっと思っていました。&lt;/p&gt;
&lt;p&gt;草を生やしたいのでGitHubで管理したいというのと、HeadlessCMSを使うとき乗り換えが簡単ということで、Markdownで記事を書くようにしました。&lt;/p&gt;
&lt;p&gt;GitベースのHeadlessCMSは多いので、いつかは外部サービスで管理するかもしれません。&lt;/p&gt;
&lt;h1&gt;GraphQL&lt;/h1&gt;
&lt;p&gt;RestではなくGraphQLを選んだのは、RestAPIは実務でもよく使うので、新しい学びはないなと思いGraphQLを選びました。&lt;/p&gt;
&lt;p&gt;またGatsbyでもGraphQLクライアントは使っていたので、サーバーサイドも作ってみたいと思いGraphQLを選択しました。&lt;/p&gt;
&lt;h1&gt;Golang&lt;/h1&gt;
&lt;p&gt;GraphQLサーバーを実装するのにGolangを選択しました。&lt;/p&gt;
&lt;p&gt;GraphQLというとAppoloが有名だし、VercelのServerlessFunctionsを使うことを考えるとNode.jsを使った方が実装はラクでしょう。&lt;/p&gt;
&lt;p&gt;しかし、今後記事が増えていくことを考えると、NodeでMarkdownファイルを検索していくことに速度面で不安を覚えました。&lt;/p&gt;
&lt;p&gt;Golangなら速度的な心配がないのが、実務からも分かっていたので今回はGolangを採用しました。&lt;/p&gt;
&lt;p&gt;（Rustも速いとのことなので、次はRustを使ってみたい）&lt;/p&gt;
&lt;h2&gt;Next.js&lt;/h2&gt;
&lt;p&gt;今回作り直すにあたって静的サイトジェネレーターで候補に上がったのはHugo, Next.js, Gatsbyを考えました。&lt;/p&gt;
&lt;p&gt;Hugoが速度的にも開発速度的にも早く、ラクという印象でした。&lt;/p&gt;
&lt;p&gt;しかし、モダンではないので不採用としました。&lt;/p&gt;
&lt;p&gt;理由としてReact, Vueといったコンポーネントという部品単位の考え方に慣れ、ページ単位での作成に管理上の不安を覚えてしまいました。&lt;br /&gt;さらに、CSSModulesやCSSinJSが使えないのでクラス名が干渉しないように考慮しなければならないというデメリットがあります。&lt;/p&gt;
&lt;p&gt;HugoはTailwindなどを取り入れている方には有用な選択肢かもしれません。&lt;/p&gt;
&lt;p&gt;GatsbyかNext.jsかどちらを取るかについては、ぶっちゃけどっちでも良かったです。&lt;/p&gt;
&lt;p&gt;Gatsby v4ではNext.jsのISRに似たDSGというものもあるし。。&lt;/p&gt;
&lt;p&gt;ただ、Next.jsの勢いは凄いし、３年前に比べるとSSG対応も安定してきたので、使ってみたいというのが一番の理由かなとおもいます。&lt;/p&gt;
&lt;p&gt;（作っていてGatsbyの方が良かったと後悔している。。。）&lt;/p&gt;
&lt;h1&gt;リニューアルサイトが完成してみて&lt;/h1&gt;
&lt;h2&gt;パフォーマンスを改善できた&lt;/h2&gt;
&lt;p&gt;せっかくのリニューアルだからパフォーマンス面でも良くしたいと思いチューニングした結果、無事Lighthouseで100点満点をいただきました！&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/blog-renewal-next-js/blog-renewal-next-js-3.png&quot; alt=&quot;Lighthouse結果&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;100点満点取ると、花びらが舞ってくれます。&lt;/p&gt;
&lt;h2&gt;デザインの改善&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;記事の閲覧性を高めるために、タグを左に持ってきました。&lt;/li&gt;
  &lt;li&gt;記事一覧は1カラムの方が視認性が高いので維持&lt;/li&gt;
  &lt;li&gt;ダークモード対応しました。ロゴの右のアイコンをクリックで変更可能&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Next.jsに変更してみて&lt;/h1&gt;
&lt;p&gt;GatsbyからNext.jsに変更して感じたこと&lt;/p&gt;
&lt;h2&gt;良かった点&lt;/h2&gt;
&lt;h3&gt;ISRはブログとの相性が良い&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;revalidate&lt;/code&gt;というプロパティで設定した時間が経つと、ページが再生成される仕組みで、これを利用すると予約投稿をWebhookなどを使わずにできるのでとても便利です。&lt;/p&gt;
&lt;p&gt;またビルドも1分程で終わるし、記事数が増えてもビルド時間に影響しないメリットもあります。&lt;/p&gt;
&lt;h3&gt;学習コストが小さい&lt;/h3&gt;
&lt;p&gt;Reactを一通り触ることができれば、特に苦労することなく、Next.jsは使えます。&lt;/p&gt;
&lt;p&gt;Gatsbyはgatsby-node.jsなどお作法があるので、Next.jsに比べてGatsbyの方が難しく感じました。&lt;/p&gt;
&lt;h2&gt;悪かった点&lt;/h2&gt;
&lt;h3&gt;サイトマップなどを自前で実装しなければならない&lt;/h3&gt;
&lt;p&gt;サイトマップだったり、PWAといったWebサイトに必要な機能を自分で追加する必要があるのが面倒でした。&lt;/p&gt;
&lt;p&gt;next-sitemapといったパッケージは存在していますが、ものによっては更新されていなかったりで、今後Next.jsのアップデートで対応しなくなるのではと不安を覚えるものが多かったです。&lt;/p&gt;
&lt;p&gt;その点、Gatsbyでは公式がそういったプラグインを配信してくれていたり、コミュニティがプラグインを管理してくれていたので、安心感がありました。&lt;/p&gt;
&lt;h1&gt;リニューアル前の構成&lt;/h1&gt;
&lt;p&gt;どこにも以前のサイト構成を記してなかったので残しておきます。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/blog-renewal-next-js/blog-renewal-next-js-2.png&quot; alt=&quot;旧サイト構成&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;HeadelessCMSとしてghostを使っていました。&lt;/p&gt;
&lt;p&gt;そしてGitHub ActionsでビルドをしてSSGで静的ファイルを生成し、VPSサーバーにそのファイルを転送していました。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/blog-renewal-next-js/blog-renewal-next-js-4.png&quot; alt=&quot;旧サイトのLighouse&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;Lighthouseの結果です。&lt;/p&gt;
&lt;p&gt;特に意識することなくこの数値がでてるので、Gatsbyのパフォーマンスは良いと思います。&lt;/p&gt;
]]&gt;</content:encoded><category>Next.js</category><category>React</category></item><item><title>&lt;![CDATA[Next.js+Markdownでの画像を@next/imageを使って表示する]]&gt;</title><link>https://blog.ryou103.com/post/next-js-markdown-image</link><guid>https://blog.ryou103.com/post/next-js-markdown-image</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/next-js-markdown-image/next-js-markdown-image.png" medium="image"></media:content><description>Markdownでの執筆がラクになるように同階層に画像を配置したい。そうした場合にNext.jsの@next/imageを使って表示するには一工夫が必要でした。</description><pubDate>Mon, 05 Sep 2022 13:37:05 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/next-js-markdown-image/next-js-markdown-image.png&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Markdownと画像ファイルを同じディレクトリで管理したい&lt;/h1&gt;
&lt;p&gt;以下のようなディレクトリ構造にしたい。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;.
├── _posts/
│   ├── dynamic-routing/
│   │   └── index.md
│   │   └── image1.png
│   │   └── image2.png
│   ├── hello-world/
│   │   └── index.md
│   │   └── image1.png
│   │   └── image2.png
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;理由としては、記事を書いているときに画像を確認できること。&lt;/p&gt;
&lt;p&gt;さらにVSCodeなら、画像をshiftを押しながらドラッグ＆ドロップするとmdに挿入することができるので執筆がラクになる。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/next-js-markdown-image/next-js-markdown-image-1.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;デフォルトのpublicからの参照だと、画像が表示されない&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;![](/assets/blog/hello-world/cover.jpg)
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;作業&lt;/h1&gt;
&lt;p&gt;実現するためにやることとしては、&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Markdown→ReactElementに変換&lt;/li&gt;
  &lt;li&gt;変換する際に画像部分は@next/imageを使うようにする&lt;/li&gt;
  &lt;li&gt;画像読み込みのためのWebpackの処理を修正&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;プロジェクト作成&lt;/h2&gt;
&lt;p&gt;公式のサンプルであるブログスターターを例に進める&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/vercel/next.js/tree/canary/examples/blog-starter&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/vercel/next.js/tree/canary/examples/blog-starter&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;このサンプルはMarkdownでの記事の管理が行える&lt;/p&gt;
&lt;p&gt;プロジェクトの作成&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ npx create-next-app --example blog-starter blog-starter-app
&lt;/code&gt;&lt;/pre&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ cd blog-starter-app
$ yarn dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:3000/posts/hello-world&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://localhost:3000/posts/hello-world&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/next-js-markdown-image/next-js-markdown-image-2.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;Markdown→Reactに変換する&lt;/h2&gt;
&lt;p&gt;現状、./lib/markdownToHtml.tsでremark-htmlというモジュールを使って、Markdown → HTMLにしている。&lt;/p&gt;
&lt;p&gt;post-body.tsxでdangerouslySetInnerHTMLを使って出力している。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;&amp;#x3C;div
  className={markdownStyles[&#x27;markdown&#x27;]}
  dangerouslySetInnerHTML={{ __html: content }}
/&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;しかし、HTMLのままでは@next/imageのImageコンポーネントが使用できないのでReactに変換する必要がある。&lt;/p&gt;
&lt;p&gt;Reactに変換するために、Markdown → MDAST → HAST → React といった手順を踏む。&lt;/p&gt;
&lt;p&gt;そのために必要なモジュールは、&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;unified
    &lt;ul&gt;
      &lt;li&gt;テキストを変換するインターフェイス。remarkもこれで動いている&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;remark-parse
    &lt;ul&gt;
      &lt;li&gt;MDASTというmarkdownを構造木に変換する。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;remark-rehype
    &lt;ul&gt;
      &lt;li&gt;MDASTをHASTに変換する。HASTはHTMLを構造木で表現したもの&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;reype-react
    &lt;ul&gt;
      &lt;li&gt;HASTをRectに変換する&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;（&lt;a href=&quot;https://github.com/remarkjs/remark-react&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;remark-react&lt;/a&gt;というMarkdownをReactに変換するモジュールの方が簡単にReactに変換できるが、廃止予定なので使わない方が良い）&lt;/p&gt;
&lt;p&gt;コードを修正していく。&lt;/p&gt;
&lt;p&gt;パッケージのインストール&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ yarn add unified remark-parse remark-rehype rehype-react
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;markdownToHtml.tsを修正。ファイル名はmarkdownToReact.tsに変更。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;-import { remark } from &#x27;remark&#x27;
-import html from &#x27;remark-html&#x27;
+import { createElement, Fragment } from &#x27;react&#x27;
+import rehypeReact from &#x27;rehype-react&#x27;
+import remarkParse from &#x27;remark-parse&#x27;
+import remarkRehype from &#x27;remark-rehype&#x27;
+import { unified } from &#x27;unified&#x27;
 
 export default async function markdownToReact(markdown: string) {
-  const result = await remark().use(html).process(markdown)
-  return result.toString()
+  const result = (await unified()
+    .use(remarkParse)
+    .use(remarkRehype)
+    .use(rehypeReact, { createElement, Fragment })
+    .process(markdown)).result
+  return result
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;出力部分であるpost-body.tsxを修正&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;const PostBody = ({ content }: Props) =&gt; {
   return (
     &amp;#x3C;div className=&quot;max-w-2xl mx-auto&quot;&gt;
       &amp;#x3C;div
         className={markdownStyles[&#x27;markdown&#x27;]}
-        dangerouslySetInnerHTML={{ __html: content }}
-      /&gt;
+      &gt;
+      {content}
			 &amp;#x3C;/div&gt;
     &amp;#x3C;/div&gt;
   )
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;しかし、このまま実行してもエラーがでる&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/next-js-markdown-image/next-js-markdown-image-3.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;getStaticPropsの戻り値はJSON.parseできる型でないといけない。contentがReactElementになっているのでエラーが発生した。&lt;/p&gt;
&lt;p&gt;getStaicProps内のconetentはMarkdownの文字列を渡して、post-body.tsxでReactに変換するように変更。&lt;/p&gt;
&lt;p&gt;[slug].tsx&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;-import markdownToHtml from &#x27;../../lib/markdownToHtml&#x27;

...

     &#x27;coverImage&#x27;,
     &#x27;ogImage&#x27;,
     &#x27;coverImage&#x27;,
   ])
-  const content = await markdownToHtml(post.content || &#x27;&#x27;)
 
   return {
     props: {
-      post: {
-        ...post,
-        content,
-      },
+      post,
     },
   }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;post-body.tsx&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;+import { Fragment, useEffect, useState } from &#x27;react&#x27;
+import markdownToReact from &#x27;../lib/markdownToReact&#x27;

...

const PostBody = ({ content }: Props) =&gt; {
+  const [component, setComponent] = useState(&amp;#x3C;Fragment /&gt;)
+  useEffect(() =&gt; {
+    (async () =&gt; {
+      const contentComponent = await markdownToReact(content)
+      setComponent(contentComponent)
+    })()
+    return () =&gt; {}
+  }, [content])
   return (
     &amp;#x3C;div className=&quot;max-w-2xl mx-auto&quot;&gt;
       &amp;#x3C;div
         className={markdownStyles[&#x27;markdown&#x27;]}
-        dangerouslySetInnerHTML={{ __html: content }}
-      /&gt;
+      &gt;{component}&amp;#x3C;/div&gt;
     &amp;#x3C;/div&gt;
   )
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;yarn dev&lt;/code&gt; すると無事Markdownの内容がReactElementで表示できた。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/next-js-markdown-image/next-js-markdown-image-4.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;@next/imageで画像が表示されるようにする&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/rehypejs/rehype-react#optionscomponents&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;rehype-reactのオプションcomponents&lt;/a&gt;でimgコンポーネントをカスタマイズする&lt;/p&gt;
&lt;p&gt;markdownToReact.ts&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;+import PostImage from &#x27;../components/post-image&#x27;

...
 
 export default async function markdownToReact(markdown: string) {
   const result = (await unified()
     .use(remarkParse)
     .use(remarkRehype)
-    .use(rehypeReact, { createElement, Fragment })
+    .use(rehypeReact, {
+      createElement,
+      Fragment,
+      components: {
+        img: PostImage
+      }
+    })
     .process(markdown)).result
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;./components/post-image.tsx&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-tsx&quot;&gt;&lt;code class=&quot;language-tsx&quot;&gt;import Image from &#x27;next/image&#x27;
import React from &#x27;react&#x27;

const PostImage: React.FC&amp;#x3C;JSX.IntrinsicElements[&#x27;img&#x27;]&gt; = ({
  src,
  alt,
  title,
}) =&gt; {
  return &amp;#x3C;Image
    src={src}
    alt={alt}
    title={title}
    height={100}
    width={100}
  /&gt;
}
export default PostImage
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;publicディレクトリの画像を正しく表示するのが目的ではないので、height/widthは適当に入れておく。&lt;/p&gt;
&lt;p&gt;Markdownにpublicの画像を試しに追加する。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;![](/assets/blog/hello-world/cover.jpg)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;無事、@next/imageでMarkdown内の画像を表示できた。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/next-js-markdown-image/next-js-markdown-image-5.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;画像をrequireを使って参照する&lt;/h2&gt;
&lt;p&gt;最終段階ですが、ここが手こずりました。。&lt;/p&gt;
&lt;p&gt;まずは、ディレクトリ構造を記事の最初に示したように、Markdownと画像を同じ階層におけるように修正していく。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;.
├── _posts/
│   ├── dynamic-routing/
│   │   └── index.md
│   │   └── image1.png
│   │   └── image2.png
│   ├── hello-world/
│   │   └── index.md
│   │   └── image1.png
│   │   └── image2.png
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;./_posts/hello-world.mdを./_posts/hello-world/index.mdに変更する。&lt;br /&gt;他のmdファイルも同様に変更しておく。&lt;/p&gt;
&lt;p&gt;Markdownのパスが変わったので、当然エラーが発生する。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/next-js-markdown-image/next-js-markdown-image-6.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;読み込めるように修正する。&lt;/p&gt;
&lt;p&gt;./lib/api/ts&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;export function getPostBySlug(slug: string, fields: string[] = []) {
-  const realSlug = slug.replace(/\.md$/, &#x27;&#x27;)
-  const fullPath = join(postsDirectory, `${realSlug}.md`)
+  const fullPath = join(postsDirectory, `${slug}/index.md`)
   const fileContents = fs.readFileSync(fullPath, &#x27;utf8&#x27;)
   const { data, content } = matter(fileContents)
 
@@ -23,7 +22,7 @@ export function getPostBySlug(slug: string, fields: string[] = []) {
   // Ensure only the minimal needed data is exposed
   fields.forEach((field) =&gt; {
     if (field === &#x27;slug&#x27;) {
-      items[field] = realSlug
+      items[field] = slug
     }
     if (field === &#x27;content&#x27;) {
       items[field] = content
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これでエラーは出ずに表示されるようになった。&lt;/p&gt;
&lt;p&gt;次に以下の画像[cover2.jpg]を用意して、./_posts/hello-world/に配置&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/next-js-markdown-image/next-js-markdown-image-7.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;先程は、publicから画像を読み込んでいたが、同階層から読み込むようにMarkdownを変更する。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;-![](/assets/blog/hello-world/cover.jpg)
+![](cover2.jpg)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/next-js-markdown-image/next-js-markdown-image-8.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;このままでは、publicの画像ではないのでrequireして画像を取得するように修正&lt;/p&gt;
&lt;p&gt;一旦画像をrequireして表示できるかをテストしてみる&lt;/p&gt;
&lt;p&gt;post-image.tsx&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;  const image = require(&#x27;../_posts/hello-world/cover2.jpg&#x27;).default
  
  return &amp;#x3C;Image
    src={image}
    alt={alt}
    title={title}
  /&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これだと上手くいくのですが、hello-worldとcover2.jpgを変数に変更してみると・・・&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;  const slug = &#x27;hello-world&#x27;
  const imagePath = &#x27;cover2.jpg&#x27;
  const image = require(`../_posts/${slug}/${imagePath}`).default
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/next-js-markdown-image/next-js-markdown-image-9.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;Markdownのloaderがないよと怒られる・・&lt;/p&gt;
&lt;p&gt;これは推測なのだが、requireする際に../_posts/以下が代入されるまで不明だから、../posts/内のファイルを参照できるように保持しているのかなと。&lt;br /&gt;その際にwebpackが.mdファイルを読み込むloaderが設定されていないためにエラーが起こるのではと考えました。&lt;/p&gt;
&lt;p&gt;webpackにmdファイルを読み込むloaderを追加する&lt;/p&gt;
&lt;p&gt;./next.config.js：まだない人は作成してください。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;/** @type {import(&#x27;next&#x27;).NextConfig} */
module.exports = {
  module: &#x27;es5&#x27;,
  reactStrictMode: true,
  webpack: ( config ) =&gt; {
    config.module.rules.push({
      test: /\.(md|markdown)$/,
      type: &#x27;asset/source&#x27;,
    })

    return config
  },
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これで再度&lt;code&gt;yarn dev&lt;/code&gt;をすれば表示されます。&lt;br /&gt;next.config.jsを修正した場合は再実行しないと変更が反映されないので注意。&lt;/p&gt;
&lt;p&gt;画像をrequireして表示できることができたので、slugとimagePathを受け取るように修正していく。&lt;/p&gt;
&lt;p&gt;post-image.tsx&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;+type PostImageFunc = (slug: string) =&gt; React.FC&amp;#x3C;JSX.IntrinsicElements[&#x27;
img&#x27;]&gt;
+
+const PostImage: PostImageFunc = (slug) =&gt; ({
   src,
   alt,
   title,
 }) =&gt; {
+  const image = require(&#x27;../_posts/&#x27; + slug + &#x27;/&#x27; + src).default
+
   return &amp;#x3C;Image
-    src={src}
+    src={image}
     alt={alt}
     title={title}
-    height={100}
-    width={100}
   /&gt;
 }
 export default PostImage
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;slugを渡せるように各ファイルを修正&lt;/p&gt;
&lt;p&gt;./pages/posts/[slug].tsx&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;                 author={post.author}
               /&gt;
-              &amp;#x3C;PostBody content={post.content} /&gt;
+              &amp;#x3C;PostBody content={post.content} slug={post.slug} /&gt;
             &amp;#x3C;/article&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;./components/post-body.tsx&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt; type Props = {
   content: string
+  slug: string
 }

-const PostBody = ({ content }: Props) =&gt; {
+const PostBody = ({ content, slug }: Props) =&gt; {
   const [component, setComponent] = useState(&amp;#x3C;Fragment /&gt;)
   useEffect(() =&gt; {
     (async () =&gt; {
-      const contentComponent = await markdownToReact(content)
+      const contentComponent = await markdownToReact(content, slug)
       setComponent(contentComponent)
     })()
     return () =&gt; {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;./lib/markdownToReact.ts&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt;       components: {
-        img: PostImage
+        img: PostImage(slug)
       }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これで画像を同階層から@next/imageを使って表示することができました。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/next-js-markdown-image/next-js-markdown-image-10.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;ちょっと手直し&lt;/h2&gt;
&lt;p&gt;このままだと、URLで画像を参照した、publicディレクトリからの取得ができなくなってしまう。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markdown&quot;&gt;&lt;code class=&quot;language-markdown&quot;&gt;![](https://placehold.jp/150x150.png)
![](/assets/blog/hello-world/cover.jpg)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/next-js-markdown-image/next-js-markdown-image-11.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;ので、それに対する対応を付け加える。&lt;/p&gt;
&lt;p&gt;post-image.tsx&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-diff&quot;&gt;&lt;code class=&quot;language-diff&quot;&gt; import Image from &#x27;next/image&#x27;
-import React from &#x27;react&#x27;
+import React, { createElement } from &#x27;react&#x27;
 
 type PostImageFunc = (slug: string) =&gt; React.FC&amp;#x3C;JSX.IntrinsicElements[&#x27;img&#x27;]&gt;
 
-const PostImage: PostImageFunc = (slug) =&gt; ({
-  src,
-  alt,
-  title,
-}) =&gt; {
-  const image = require(&#x27;../_posts/&#x27; + slug + &#x27;/&#x27; + src).default
+const PostImage: PostImageFunc = (slug) =&gt; (props) =&gt; {
+  try {
+    const { src, alt, title } = props
+    const image = require(&#x27;../_posts/&#x27; + slug + &#x27;/&#x27; + src).default
+  
+    return &amp;#x3C;Image
+      src={image}
+      alt={alt}
+      title={title}
+    /&gt;
 
-  return &amp;#x3C;Image
-    src={image}
-    alt={alt}
-    title={title}
-  /&gt;
+  } catch (e) {
+    return createElement(&#x27;img&#x27;, props)
+  }
 }
 export default PostImage
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;requireして表示する方でエラーが発生したら、通常のimgタグを使った表示にするようにした。&lt;/p&gt;
&lt;p&gt;これで外部URL等の画像も表示できる。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/next-js-markdown-image/next-js-markdown-image-12.png&quot; alt=&quot;&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;まとめ&lt;/h1&gt;
&lt;p&gt;かなーり長くなってしまったが、やったことはMarkdownをReactに変換して、その際にImageは@next/imageを使うということ。&lt;/p&gt;
&lt;p&gt;今回作ったものはGitHubに置いているので覗いてみてください。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/RyoheiTomiyama/nextjs-blog-starter-image-path.git&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/RyoheiTomiyama/nextjs-blog-starter-image-path.git&lt;/a&gt;&lt;/p&gt;
]]&gt;</content:encoded><category>Next.js</category><category>React</category></item><item><title>&lt;![CDATA[NetlifyCMSを試そうとしたら管理画面に入れなかった]]&gt;</title><link>https://blog.ryou103.com/post/netlify-cms-starter-bug</link><guid>https://blog.ryou103.com/post/netlify-cms-starter-bug</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/netlify-cms-starter-bug/netlify-cms-starter-bug.png" medium="image"></media:content><description>NetlifyCMSを体験しようと、ドキュメントの通りに進めたのだが、Git Gateway Errorというエラーが表示され管理画面に入ることができなかった。どうやらmainブランチだとStarterで作成されたレポジトリに修正を加える必要がありそう。</description><pubDate>Tue, 31 May 2022 11:35:03 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/netlify-cms-starter-bug/netlify-cms-starter-bug.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;NetlifyCMSを体験しようと、ドキュメントの通りに進めたのだが&lt;br /&gt;以下のようなエラーが表示され管理画面に入ることができなかった。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;Git Gateway Error: Please ask your site administrator to reissue the Git Gateway token.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/netlify-cms-starter-bug/netlify-cms-starter-bug-1@2x.png&quot; alt=&quot;Git Gateway Error: Please ask your site administrator to reissue the Git Gateway token.&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;解決方法&lt;/h1&gt;
&lt;p&gt;原因はGitHubのデフォルトブランチ名がmainとなっている（masterではない）からでした。&lt;br /&gt;/site/static/admin/config.ymlのbackend項目にbranchを設定すれば解決しました。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;backend:
  name: git-gateway
  branch: main   # ここを追加
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;エラーまでの経緯&lt;/h1&gt;
&lt;p&gt;とりあえずNetlifyのドキュメントにある&lt;a href=&quot;https://www.netlifycms.org/docs/start-with-a-template/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Start with a Template&lt;/a&gt;からジェネレーターはHugoを選択してGithubにデプロイしてみました。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/netlify-cms-starter-bug/netlify-cms-starter-bug-2@2x.png&quot; alt=&quot;Deploy to Netlify&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;手順通り進めていくと、Netlifyに新しいサイト・GitHubにレポジトリが作成されました。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/netlify-cms-starter-bug/netlify-cms-starter-bug-3@2x.png&quot; alt=&quot;Netlify管理画面&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;CMS用のアカウント作成のための招待メールが届いているので、メール内の[Accept the invite]をクリックして、ブラウザが開いてポップアップが表示されるので、そこに適当なパスワードを入力してアカウントが作成されます。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/netlify-cms-starter-bug/netlify-cms-starter-bug-4@2x.png&quot; alt=&quot;CMS用のアカウント作成のための招待メール&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;その後URLに/adminをつけるとCMS管理画面に入れるとのことだったので、アクセスしてみると、、、&lt;/p&gt;
&lt;p&gt;エラーが表示され管理画面に入れない結果に。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-xml&quot;&gt;&lt;code class=&quot;language-xml&quot;&gt;Git Gateway Error: Please ask your site administrator to reissue the Git Gateway token.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/netlify-cms-starter-bug/netlify-cms-starter-bug-1@2x.png&quot; alt=&quot;Git Gateway Error: Please ask your site administrator to reissue the Git Gateway token.&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;原因究明&lt;/h1&gt;
&lt;p&gt;Netlifyの機能であるGit Gatewayで問題が発生しているみたい。&lt;br /&gt;Netlify設定画面のsite settgin → identityにあるGit Gatewayの項目を再設定してみたり、アカウントを作り直したりしたが解消されず・・&lt;/p&gt;
&lt;p&gt;エラー文で検索したら同じ現象の方もいたが、はっきりせず・・&lt;br /&gt;&lt;a href=&quot;https://answers.netlify.com/t/git-gateway-error/12220/17&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://answers.netlify.com/t/git-gateway-error/12220/17&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;どうやらconfig.ymlにGit Gatewayの設定があるようなので、レポジトリを見てみる。&lt;/p&gt;
&lt;p&gt;場所は、/site/static/admin/config.yml&lt;br /&gt;以下の行がGit Gatewayの設定のようだ。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;backend:
  name: git-gateway
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;特になにか設定されているわけではなかった。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.netlifycms.org/docs/backends-overview#backend-configuration&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;NetlifyCMSのconfigurationのドキュメント&lt;/a&gt;を見るとデフォルトのブランチがmasterになっていた。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/netlify-cms-starter-bug/netlify-cms-starter-bug-5@2x.png&quot; alt=&quot;NetlifyCMSのconfigurationのドキュメント&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;自分のGithubでは作成時のデフォルトブランチはmainとなっているのbranchオプションを追加してあげれば良さそうだ。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;backend:
  name: git-gateway
  branch: main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この変更をGitHubにプッシュするとNetlify側でデプロイされるので完了するまで待つ。&lt;/p&gt;
&lt;p&gt;改めて/adminにアクセスすると・・&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/netlify-cms-starter-bug/netlify-cms-starter-bug-6@2x.png&quot; alt=&quot;NetlifyCMS管理画面&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;無事、管理画面に入ることができました！&lt;/p&gt;
]]&gt;</content:encoded><category>Netlify</category><category>HeadlessCMS</category></item><item><title>&lt;![CDATA[Gatsby v2をM1 Macでコンパイルエラーを解消]]&gt;</title><link>https://blog.ryou103.com/post/gatsby-v2-sharp-error</link><guid>https://blog.ryou103.com/post/gatsby-v2-sharp-error</guid><description>M1 MacでGatsby v2がコンパイルエラーが起こった。Error: Something went wrong installing the &#x27;sharp&#x27; module. package.jsonのresolutionsオプションを使ってsharpのバージョンを指定することで解決できそう。
</description><pubDate>Sun, 24 Oct 2021 00:47:42 +0000</pubDate><content:encoded>&lt;![CDATA[
&lt;h1&gt;M1 Mac で Gatsby v2 がコンパイルエラー&lt;/h1&gt;
&lt;p&gt;M1 Mac で起動しようとすると、以下のようなエラーが起こった。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;    Error: Something went wrong installing the &quot;sharp&quot; module

    Error relocating /usr/lib/libpango-1.0.so.0: g_memdup2: symbol not found
    - Remove the &quot;node_modules/sharp&quot; directory then run
      &quot;npm install --ignore-scripts=false --verbose&quot; and look for errors
    - Consult the installation documentation at https://sharp.pixelplumbing.com/in  stall
    - Search for this error at https://github.com/lovell/sharp/issues
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;解決策&lt;/h1&gt;
&lt;p&gt;resolutions で sharp モジュールのバージョンを指定する&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;    &quot;dependencies&quot;: {
      ...
      &quot;gatsby&quot;: &quot;^2.21.37&quot;,
      &quot;gatsby-image&quot;: &quot;^2.4.4&quot;,
      &quot;gatsby-plugin-sharp&quot;: &quot;^2.6.4&quot;,
      &quot;gatsby-transformer-sharp&quot;: &quot;^2.5.2&quot;,
      ...
    },
    # ↓以下を追加
    &quot;resolutions&quot;: {
      &quot;sharp&quot;: &quot;^0.29.1&quot;
    },
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;解決までの流れ&lt;/h1&gt;
&lt;p&gt;Gatsby のサイトには、この sharp というモジュールを使っている gatsby-plugin-*をアップデートしてと書いてある&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.gatsbyjs.com/plugins/gatsby-transformer-sharp/#incompatible-library-version-sharpnode-requires-version-x-or-later-but-z-provides-version-y&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.gatsbyjs.com/plugins/gatsby-transformer-sharp/#incompatible-library-version-sharpnode-requires-version-x-or-later-but-z-provides-version-y&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;しかし、プラグインをアップデートすると、Gatsby v2 で上手く動かない。&lt;/p&gt;
&lt;p&gt;調べていくと、sharp モジュールは 0.29.0 から M1 Mac に対応しているようだった&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://sharp.pixelplumbing.com/install#apple-m1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sharp.pixelplumbing.com/install#apple-m1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;sharp のバージョンだけ指定すれば動かないかな〜と resolutions を使ってみたら動いた！&lt;/p&gt;
&lt;p&gt;package.json に以下を追記&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;  &quot;resolutions&quot;: {
    &quot;sharp&quot;: &quot;^0.29.1&quot;
  },
&lt;/code&gt;&lt;/pre&gt;
]]&gt;</content:encoded><category>Gatsby</category><category>React</category></item><item><title>&lt;![CDATA[WAVASHAREの電子ペーパーとラズパイでサンプルを動かす]]&gt;</title><link>https://blog.ryou103.com/post/e-paper-raspi</link><guid>https://blog.ryou103.com/post/e-paper-raspi</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/e-paper-raspi/e-paper-raspi.png" medium="image"></media:content><description>電子ペーパーに興味を買ったのでまずはサンプルを動かしてみる。買ったのはWAVESHAREの7.5inch E-Paper E-Ink Display HAT(B)。動作チェックのためにラズパイを使ってWAVESHAREのWikiにあるサンプルを動かしてみた。</description><pubDate>Mon, 09 Aug 2021 06:09:40 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/e-paper-raspi/e-paper-raspi.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ラズパイに接続できる電子ペーパーを買ったのでサンプルを実行して動作確認したいと思います。&lt;/p&gt;
&lt;p&gt;購入したのは、&lt;a href=&quot;https://amzn.to/3AkJrEy&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;WAVESHAREの7.5inch E-Paper E-Ink Display HAT (B) For Raspberry Pi&lt;/a&gt;&lt;/p&gt;&lt;iframe style=&quot;width:120px;height:240px;&quot; marginwidth=&quot;0&quot; marginheight=&quot;0&quot; scrolling=&quot;no&quot; frameborder=&quot;0&quot; src=&quot;https://rcm-fe.amazon-adsystem.com/e/cm?ref=tf_til&amp;t=bonos-22&amp;m=amazon&amp;o=9&amp;p=8&amp;l=as1&amp;IS2=1&amp;detail=1&amp;asins=B08H8R6TQG&amp;linkId=007d082f8dc6e29c692c2c44c37fee43&amp;bc1=ffffff&amp;amp;lt1=_blank&amp;fc1=333333&amp;lc1=0066c0&amp;bg1=ffffff&amp;f=ifr&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;こちらと、以前に購入していた&lt;a href=&quot;https://a.r10.to/hyr7lN&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Raspberry Pi Zero WH&lt;/a&gt;を使います。&lt;/p&gt;
&lt;p&gt;WAVESHAREのwiki &lt;a href=&quot;https://www.waveshare.com/wiki/7.5inch_e-Paper_HAT_(B)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.waveshare.com/wiki/7.5inch_e-Paper_HAT_(B)&lt;/a&gt; を元に実行していきます。&lt;/p&gt;
&lt;h1&gt;ラズパイでSPI通信をできるように設定&lt;/h1&gt;
&lt;p&gt;ラズパイに接続。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ ssh raspi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;設定画面を開く。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ sudo raspi-config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下のような画面が開くので、5 Interfacing Optionsを選択。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/e-paper-raspi/e-paper-raspi-5@2x.png&quot; alt=&quot;ラズパイでSPI通信設定手順画面1&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;次にSPIを有効にするために、P4 SPIを選択して有効にする。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/e-paper-raspi/e-paper-raspi-6@2x.png&quot; alt=&quot;ラズパイでSPI通信設定手順画面2&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;The SPI interface is enabledとなればOK。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/e-paper-raspi/e-paper-raspi-7@2x.png&quot; alt=&quot;ラズパイでSPI通信設定手順画面3&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;ラズパイを再起動&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ sudo reboot
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;サンプルコードと必要なパッケージのインストール&lt;/h1&gt;
&lt;p&gt;ラズパイに接続。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ ssh raspi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;サンプルコードをダウンロード。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ git clone git@github.com:waveshare/e-Paper.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;実行する前にpythonライブラリをインストール。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ sudo apt-get install python3-pip
$ pip install spidev Pillow
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pillowをインストールするときにエラーが出た。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/e-paper-raspi/e-paper-raspi-8@2x.png&quot; alt=&quot;Pillowインストールエラー&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;Pillow公式のLinuxのインストール手順&lt;br /&gt;&lt;a href=&quot;https://pillow.readthedocs.io/en/latest/installation.html#building-on-linux&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://pillow.readthedocs.io/en/latest/installation.html#building-on-linux&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;必要パッケージをインストール。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt-get install --fix-missing python3-dev python3-setuptools libjpeg-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;--fix-missing&lt;/code&gt;を付けないと&lt;code&gt;Cannot initiate the connection to raspbian.raspberrypi.org&lt;/code&gt; になった。&lt;br /&gt;jpegでも怒られているので、libjpeg-devをインストールした。&lt;/p&gt;
&lt;p&gt;改めてPillowをインストール。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install Pillow
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;今度は上手くインストールできた。&lt;/p&gt;
&lt;h1&gt;ラズパイと電子ペーパーを繋ぐ&lt;/h1&gt;
&lt;p&gt;電子ペーパーに付属している以下のパーツを使ってラズパイに繋ぎます。&lt;br /&gt;今回はピンヘッダ付きのラズパイZeroWHを使うので、左のコードは使いません。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/e-paper-raspi/e-paper-raspi-1@2x.png&quot; alt=&quot;ラズパイと電子ペーパーを繋ぐ1&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;まずはラズパイと電子ペーパのピンヘッダをカチッとはめ込みます。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/e-paper-raspi/e-paper-raspi-2@2x.png&quot; alt=&quot;ラズパイと電子ペーパーを繋ぐ2&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;次に電子ペーパと繋ぐ部分ですが、黒い部分が上下するようになっており、これを上げた状態にします。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/e-paper-raspi/e-paper-raspi-3@2x.png&quot; alt=&quot;ラズパイと電子ペーパーを繋ぐ3&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;電子ペーパー側の線を挿入して黒い部分を下げれば接続完了&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/e-paper-raspi/e-paper-raspi-4@2x.png&quot; alt=&quot;ラズパイと電子ペーパーを繋ぐ4&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;実行してみる&lt;/h1&gt;
&lt;p&gt;examplesにある購入した電子ペーパーに対応するコードを実行する。&lt;br /&gt;間違ったものを使うと表示されなかったり、サイズが違ったりと表示が崩れます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ cd e-Paper/RaspberryPi_JetsonNano/python/examples/
$ python epd_7in5b_V2_test.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;電子ペーパー動いた！&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://user-images.githubusercontent.com/10805686/128626281-d9a2f272-c7e7-4fa1-95d5-0481a1af352c.mp4&quot; data-canonical-src=&quot;https://user-images.githubusercontent.com/10805686/128626281-d9a2f272-c7e7-4fa1-95d5-0481a1af352c.mp4&quot; controls=&quot;controls&quot; muted=&quot;muted&quot; class=&quot;d-block rounded-bottom-2 width-fit&quot; style=&quot;max-height:640px;&quot;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;ちらちらするのが気になります。&lt;br /&gt;kindleとかみたいにスムーズに描画されると期待していましたが仕様のようです。&lt;br /&gt;これは少し残念。&lt;/p&gt;
]]&gt;</content:encoded><category>ラズパイ</category></item><item><title>&lt;![CDATA[透明のアレをCSSで作成]]&gt;</title><link>https://blog.ryou103.com/post/transparent-grid</link><guid>https://blog.ryou103.com/post/transparent-grid</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/transparent-grid/transparent-grid.png" medium="image"></media:content><description>イラレやフォトショなどのデザインツールでみる透過を表すアレ、透明グリッドをCSSで表現する方法紹介します。</description><pubDate>Thu, 31 Dec 2020 12:29:18 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/transparent-grid/transparent-grid.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;イラレやフォトショなどのデザインツールでみる透過を表すアレをCSSで表現する方法紹介します。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/transparent-grid/transparent-grid-1.png&quot; alt=&quot;透明グリッド&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;透明を表す白とグレーの格子のことを透明グリッドというらしいです。&lt;/p&gt;
&lt;p&gt;写真のように、透明であることを表現したくて、CSSで透明グリッドを作成しました。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/transparent-grid/transparent-grid-2.png&quot; alt=&quot;透明グリッド使い方&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;ソースはこちら↓&lt;br /&gt;&lt;a href=&quot;https://codepen.io/RyoheiTomiyama/pen/GRjyyrx&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://codepen.io/RyoheiTomiyama/pen/GRjyyrx&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;CSSは以下のように作成しました。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;line-numbers language-scss&quot;&gt;&lt;code class=&quot;language-scss&quot;&gt;.transparent {
  position: ralative;
	&amp;#x26;::before {
		content: &#x27;&#x27;;
		position: absolute;
		top: 0;
		left: 0;
		width: 100%;
		height: 100%;
		background-position: 0px 0px, 7px 7px;
		background-size: 14px 14px;
		background-repeat: repeat;
		background-image:
			linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff 100%),
			linear-gradient(45deg, #fff 25%, #e3e3e3 25%, #e3e3e3 75%, #fff 75%, #fff 100%);
	}
}
&lt;/code&gt;&lt;/pre&gt;
]]&gt;</content:encoded><category>CSS</category></item><item><title>&lt;![CDATA[GatsbyのFileSystemRouteAPIでさらに使いやすくなった]]&gt;</title><link>https://blog.ryou103.com/post/gatsby-file-system-route-api</link><guid>https://blog.ryou103.com/post/gatsby-file-system-route-api</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/gatsby-file-system-route-api/gatsby-file-system-route-api.png" medium="image"></media:content><description>Gatsby.js v2.26で新しい機能として追加された「File System Route API」を試してみました。CMSからデータを取得して動的なページが1ファイルだけで生成できるようになりました。</description><pubDate>Mon, 23 Nov 2020 03:54:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/gatsby-file-system-route-api/gatsby-file-system-route-api.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Gatsby.js v2.26で新しい機能として追加された「File System Route API」を試してみました。&lt;/p&gt;
&lt;p&gt;レポジトリはこちら&lt;br /&gt;&lt;a href=&quot;https://github.com/RyoheiTomiyama/gatsby-file-system-route-api/tree/master/app&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/RyoheiTomiyama/gatsby-file-system-route-api/tree/master/app&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;ContentfulCMSから記事を取得してGatsbyで表示させたので、基本的な使い方とポイントを紹介します。（なお、Gatsbyはgatsby-starter-defaultから作成しています。）&lt;/p&gt;
&lt;h1&gt;File System Route APIについて&lt;/h1&gt;
&lt;p&gt;英語が読める方はこちらに詳しく書かれています。&lt;br /&gt;&lt;a href=&quot;https://www.gatsbyjs.com/blog/fs-route-api&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.gatsbyjs.com/blog/fs-route-api&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;簡単に説明すると、ブログなどの記事ページなどを生成するときに、以前は&lt;code&gt;gatsby-node.js&lt;/code&gt;に設定を何十行も書かなければいけなかったものが、固定ページ同様&lt;code&gt;pages/&lt;/code&gt;フォルダにテンプレートファイルを作成するだけで、動的な記事ページが生成できる機能です。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/gatsby-file-system-route-api/gatsby-file-system-route-api-1.png&quot; alt=&quot;File System Route APIについて&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;ファイル名でルート設定ができるからFile System Route APIなのでしょう。&lt;/p&gt;
&lt;h1&gt;File System Route APIの使い方&lt;/h1&gt;
&lt;p&gt;では、さっそく使い方を紹介します。&lt;/p&gt;
&lt;p&gt;まずはGatsbyの基礎構築。&lt;/p&gt;
&lt;h2&gt;Gatsbyインストール&lt;/h2&gt;
&lt;p&gt;まずはGatsby.jsのgatsby-starter-defaultをインストール&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ gatsby new app https://github.com/gatsbyjs/gatsby-starter-default
$ cd app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(gatsbyコマンドをインストールしていなければ、&lt;code&gt;yarn global add gatsby-cli&lt;/code&gt;)&lt;/p&gt;
&lt;h2&gt;ContentfulCMSから記事を取得するプラグインをインストールと設定&lt;/h2&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ yarn add gatsby-source-contentful
&lt;/code&gt;&lt;/pre&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;line-numbers language-javascript&quot; data-filename=&quot;gatsby-config.js&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;require(&#x27;dotenv&#x27;).config({
  path: `.env.${process.env.NODE_ENV}`,
})

module.exports = {
  plugins: [
    {
      resolve: `gatsby-source-contentful`,
      options: {
        spaceId: process.env.CONTENTFUL_SPACE_ID,
        accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
      },
    },
  ],
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;TypeScript周り&lt;/h2&gt;
&lt;p&gt;TypeScriptを使いたいので、TypeScript周りを整備します。&lt;/p&gt;
&lt;p&gt;(使わない人は省略してください。)&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ yarn add gatsby-plugin-typegen
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;gatsby-configs.js&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;line-numbers language-javascript&quot; data-filename=&quot;gatsby-config.js&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;module.exports = {
  plugins: [
    {
      resolve: &#x27;gatsby-plugin-typegen&#x27;,
      options: {
        outputPath: &#x27;types/graphql-types.d.ts&#x27;,
      },
    },
  ],
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;tsconfig.json&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;line-numbers language-json&quot; data-filename=&quot;tsconfig.json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;compilerOptions&quot;: {
    &quot;baseUrl&quot;: &quot;.&quot;,
    &quot;module&quot;: &quot;commonjs&quot;,
    &quot;target&quot;: &quot;ES5&quot;,
    &quot;jsx&quot;: &quot;preserve&quot;,
    &quot;lib&quot;: [&quot;dom&quot;, &quot;es2015&quot;, &quot;es2017&quot;, &quot;esnext&quot;],
    &quot;strict&quot;: true,
    &quot;noEmit&quot;: true,
    &quot;isolatedModules&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;skipLibCheck&quot;: false,
    &quot;noUnusedLocals&quot;: true,
    &quot;noUnusedParameters&quot;: true,
    &quot;removeComments&quot;: false,
    &quot;paths&quot;: {
      &quot;@/*&quot;: [&quot;src/*&quot;]
    },
    &quot;preserveConstEnums&quot;: true,
    &quot;typeRoots&quot;: [&quot;./types&quot;, &quot;node_modules/@types&quot;]
  },
  &quot;include&quot;: [&quot;./src/**/*&quot;, &quot;./types/**/*&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;ここからがFile System Route API&lt;/h2&gt;
&lt;p&gt;Gatsbyの基礎構築が以上で完了です。&lt;/p&gt;
&lt;p&gt;（実際にサービス開発で使うときは、ESLint, Prettier, Manifestなどの設定もしましょう。）&lt;/p&gt;
&lt;p&gt;&lt;code&gt;gatsby-source-contentful&lt;/code&gt;でCMSとの連携は済んでいるので、記事ページを作成していきます。&lt;/p&gt;
&lt;p&gt;作成するのは、1ファイルだけです。&lt;/p&gt;
&lt;p&gt;pages/posts/{ContentfulPost.slug}.tsx&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;line-numbers language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;import React from &#x27;react&#x27;
import { graphql } from &#x27;gatsby&#x27;
import { renderRichText } from &quot;gatsby-source-contentful/rich-text&quot;
import Layout from &#x27;../../components/layout&#x27;

const PostPage: React.FC&amp;#x3C;{
  data: GatsbyTypes.PostBySlugQuery
}&gt; = ({ data }) =&gt; {
  return (
    &amp;#x3C;Layout&gt;
      &amp;#x3C;h1&gt;{data.contentfulPost?.title}&amp;#x3C;/h1&gt;
      &amp;#x3C;div&gt;{
        renderRichText({
          raw: data.contentfulPost?.content?.raw || &#x27;&#x27;,
          references: [],
        })
      }&amp;#x3C;/div&gt;
    &amp;#x3C;/Layout&gt;
  )
}

export default PostPage

export const query = graphql`
  query PostBySlug ($id: String) {
    contentfulPost(id: { eq: $id }) {
      title
      slug
      content {
        raw
      }
    }
  }
`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このファイルを作成するだけで、とりあえずContentfulCMSから記事を取得してページの生成ができます。&lt;/p&gt;
&lt;p&gt;例） &lt;a href=&quot;http://localhost:8080/posts/hello-world&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://localhost:8080/posts/hello-world&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/gatsby-file-system-route-api/gatsby-file-system-route-api-2.png&quot; alt=&quot;ContentfulCMSから記事を取得してページの生成&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;作成するポイントは2点。ファイル名とGraphQLの書き方です。&lt;/p&gt;
&lt;h3&gt;ファイル名で、SchemaとFieldを指定する&lt;/h3&gt;
&lt;p&gt;記事ページのファイル名を&lt;code&gt;{ContentfulPost.slug}.tsx&lt;/code&gt;としました。&lt;/p&gt;
&lt;p&gt;この&lt;strong&gt;ファイル名で記事を取得する条件を指定できるのがFile System Route API&lt;/strong&gt;の特徴です。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ContentfulPost&lt;/code&gt;がSchema名で&lt;code&gt;slug&lt;/code&gt;がField名となっています。&lt;/p&gt;
&lt;p&gt;このSchema, Fieldというは、&lt;a href=&quot;http://localhost:8080/__graphql&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://localhost:8080/__graphql&lt;/a&gt;で確認できるもの連動しており、&lt;code&gt;ContentfulPost.slug&lt;/code&gt;で記事を取得できます。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/gatsby-file-system-route-api/gatsby-file-system-route-api-3.png&quot; alt=&quot;GraphQL Playground&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;先の例だと、&lt;a href=&quot;http://localhost:8080/posts/hello-world&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://localhost:8080/posts/hello-world&lt;/a&gt; の場合は、&lt;code&gt;ContentfulPost.slug = hello-world&lt;/code&gt;である記事を取得しています。&lt;br /&gt;idで取得したい、&lt;a href=&quot;http://localhost:8080/posts/103%E3%81%A8%E3%81%97%E3%81%9F%E3%81%84%E5%A0%B4%E5%90%88%E3%81%AF%E3%80%81%60%7BContentfulPost.id%7D.tsx%60%E3%81%A8%E3%81%97%E3%81%BE%E3%81%99%E3%80%82&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://localhost:8080/posts/103としたい場合は、`{ContentfulPost.id}.tsx`とします。&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Wordpressの場合は、&lt;code&gt;{WpPost.slug}.tsx&lt;/code&gt;とします。&lt;br /&gt;Markdownの場合は、&lt;code&gt;{MarkdownRemark.parent__(File)__name}.tsx&lt;/code&gt;とします。&lt;/p&gt;
&lt;p&gt;（この指定方法については、下記補足で説明します。）&lt;/p&gt;
&lt;h3&gt;GraphQLのqueryの書き方&lt;/h3&gt;
&lt;p&gt;今回は、&lt;code&gt;ContentfulPost&lt;/code&gt;の&lt;code&gt;slug&lt;/code&gt;から記事を取得します。その場合の書き方がこちら。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;line-numbers language-typescript&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;export const query = graphql`
  query PostBySlug ($slug: String) {
    contentfulPost(slug: { eq: $slug }) {
      title
      slug
      content {
        raw
      }
    }
  }
`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ファイル名で指定したFieldで検索することができます。&lt;br /&gt;もしくは、以下のように&lt;code&gt;id&lt;/code&gt; での検索もできます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;line-numbers language-graphql&quot;&gt;&lt;code class=&quot;language-graphql&quot;&gt;query PostBySlug ($id: String) {
  contentfulPost(id: { eq: $id }) {
     // something fields...
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;{MarkdownRemark.parent__(File)__name}.tsx&lt;/code&gt; のような場合は、&lt;code&gt;id&lt;/code&gt; を使ったほうがシンプルになりそうですね。&lt;/p&gt;
&lt;h1&gt;まとめ&lt;/h1&gt;
&lt;p&gt;Gatsby.js v2.26からは、&lt;code&gt;{ContentfulPost.id}.tsx&lt;/code&gt; のようなファイル名にすれば、&lt;code&gt;gatsby-node.js&lt;/code&gt; に&lt;code&gt;createPages&lt;/code&gt;メソッドで、記事データを取得して、テンプレート指定して、といった煩わしい設定をせずに生成できるようになりました。&lt;/p&gt;
&lt;p&gt;しかし、一覧ページでページネーションをしたり、2つ以上のSchemaを使う場合には、まだ対応していないので、&lt;code&gt;gatsby-node.js&lt;/code&gt; で生成する必要があるみたいです。&lt;/p&gt;
&lt;h1&gt;補足&lt;/h1&gt;
&lt;h2&gt;今回のContentfulの設定&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ContentfulPost&lt;/code&gt; というSchemaを使いましたが、これはContentfuleにてpostというSchemaContentを作成しています。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/gatsby-file-system-route-api/gatsby-file-system-route-api-4.png&quot; alt=&quot;Contentfulの設定&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;様々なファイル名での指定&lt;/h2&gt;
&lt;p&gt;シンプルな&lt;code&gt;{ContentfulPost.id}.tsx&lt;/code&gt; 以外に、&lt;code&gt;{Product.fields__sku}.tsx&lt;/code&gt;、&lt;code&gt;{MarkdownRemark.parent__(File)__name}.tsx&lt;/code&gt; のように少し複雑な指定もできます。&lt;/p&gt;
&lt;p&gt;それぞれのqueryを見るとすぐ理解できると思います。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;{Product.fields__sku}.tsx&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;line-numbers language-graphql&quot;&gt;&lt;code class=&quot;language-graphql&quot;&gt;allProduct {
  nodes {
    id # Gatsby always queries for id
    fields {
      sku
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;{MarkdownRemark.parent__(File)__name}.tsx&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;line-numbers language-graphql&quot;&gt;&lt;code class=&quot;language-graphql&quot;&gt;allMarkdownRemark {
  nodes {
    id # Gatsby always queries for id
    parent {
      … on File {
        name
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;{Product.fields__sku}.tsx&lt;/code&gt; のように、ネストされたfieldの場合は、&lt;code&gt;__&lt;/code&gt;で繋げます。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;{MarkdownRemark.parent__(File)__name}.tsx&lt;/code&gt; は、マークダウンのファイル名で生成する場合です。&lt;/p&gt;
&lt;p&gt;また、今回の指定ではSSGとなり、&lt;code&gt;gatsby build&lt;/code&gt;で静的なHTMLファイルが生成されますが、SPAにしたい場合にも対応しているようです。&lt;/p&gt;
&lt;p&gt;詳しくはこちらをご覧ください。&lt;br /&gt;&lt;a href=&quot;https://www.gatsbyjs.com/docs/file-system-route-api/#creating-client-only-routes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.gatsbyjs.com/docs/file-system-route-api/#creating-client-only-routes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;以上になります。&lt;/p&gt;
&lt;p&gt;今回作成したリポジトリはこちらになります。&lt;br /&gt;&lt;a href=&quot;https://github.com/RyoheiTomiyama/gatsby-file-system-route-api/tree/master/app&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/RyoheiTomiyama/gatsby-file-system-route-api/tree/master/app&lt;/a&gt;&lt;/p&gt;
]]&gt;</content:encoded><category>Gatsby</category><category>React</category></item><item><title>&lt;![CDATA[Gatsby.jsでのWebフォントの読み込み方]]&gt;</title><link>https://blog.ryou103.com/post/gatsby-plugin-web-font-loader</link><guid>https://blog.ryou103.com/post/gatsby-plugin-web-font-loader</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/gatsby-plugin-web-font-loader/gatsby-plugin-webfonts-4.png" medium="image"></media:content><description>Gatsby.jsでWebフォントを読み込み方としてTypegraphy.jsとFontSourceの2パターンを紹介します。公式ドキュメントにあるgatsby-plugin-web-font-loaderのやり方とtypefacesのやり方は古くなっているので注意が必要です。</description><pubDate>Wed, 07 Oct 2020 12:33:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/gatsby-plugin-web-font-loader/gatsby-plugin-webfonts-4.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Gatsby.jsでWebフォントを読み込むプラグインgatsby-plugin-web-font-loaderがアーカイブされていたので、フォントの読み込み方を変更しようと調べた情報をまとめました。&lt;/p&gt;
&lt;h1&gt;Gatsby.jsでWebフォントを読み込む&lt;/h1&gt;
&lt;p&gt;gatsby-plugin-web-font-loader以外にGatsby.jsが紹介している方法が2つあります。&lt;/p&gt;
&lt;p&gt;ひとつは、Typography.jsを使ったやり方。&lt;br /&gt;もうひとつは、Typefacesを使ったやり方です。&lt;/p&gt;
&lt;p&gt;それぞれのやり方でのGoogleフォントの読み込み方を説明します。&lt;/p&gt;
&lt;h2&gt;Typography.jsの使ったWebフォントの読み込み&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://kyleamathews.github.io/typography.js/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Typography.js&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://www.gatsbyjs.com/docs/using-typography-js/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Gatsby.jsのドキュメント&lt;/a&gt;には、Googleフォントの読み込み方は書かれてなかったので、&lt;a href=&quot;https://kyleamathews.github.io/typography.js/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Typography.js&lt;/a&gt;のドキュメントを参考に実装しました。&lt;/p&gt;
&lt;p&gt;必要なパッケージをインストール&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ gatsby-plugin-typography react-typography typography
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;gatsby-config.js&lt;/code&gt;にプラグインを追加&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;line-numbers language-js&quot; data-filename=&quot;gatsby-config.js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;plugins: [
  {
    resolve: `gatsby-plugin-typography`,
    options: {
      pathToConfigModule: `src/utils/typography`,
    },
  },
],
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pathToConfigModule&lt;/code&gt;で指定したファイルを作成してtypographyの設定を書きます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;line-numbers language-js&quot; data-filename=&quot;typography.js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;import Typography from &#x27;typography&#x27;

const typography = new Typography({
  googleFonts: [
    {
      name: &#x27;Noto Sans JP&#x27;,
      styles: [
        &#x27;500&#x27;,
        &#x27;700&#x27;,
        &#x27;900&#x27;,
      ],
    },
    {
      name: &#x27;Roboto&#x27;,
      styles: [&#x27;700&#x27;],
    },
  ],
})

export default typography
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;斜体フォントを読み込む場合は、&#x27;500i&#x27;と指定します。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/gatsby-plugin-web-font-loader/gatsby-plugin-webfonts-4-1.png&quot; alt=&quot;GoogleフォントからCSSの読み込みを確認&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;GoogleフォントからCSSの読み込みを確認できました。&lt;/p&gt;
&lt;p&gt;このTypography.jsはフォントを読み込むためだけのツールではなくて、サイト全体のフォントサイズやウェイトなどの書体を整えるためのツールです。&lt;/p&gt;
&lt;p&gt;ですので、フォントを読み込むだけに使うのは、ちょっと太り過ぎかなと感じます。&lt;/p&gt;
&lt;h2&gt;&lt;del&gt;Typefacesを使ったWebフォントの読み込み&lt;/del&gt;&lt;/h2&gt;
&lt;p&gt;&lt;del&gt;&lt;a href=&quot;https://github.com/KyleAMathews/typefaces&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Typefaces&lt;/a&gt;&lt;/del&gt;&lt;br /&gt;&lt;a href=&quot;https://www.gatsbyjs.com/docs/using-web-fonts/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Gatsbyドキュメント&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;紹介しようと記事を書いていたら、Deprecatedされてました。&lt;br /&gt;&lt;a href=&quot;https://github.com/fontsource/fontsource&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;FontSource&lt;/a&gt;というプロジェクトに引き継がれたようですので、こちらを紹介します。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;FontSourceを使ったWebフォントの読み込み&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/fontsource/fontsource&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;FontSource&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;使い方は、まったく同じです。&lt;/p&gt;
&lt;p&gt;TypefacesにはなかったNotoSansJPなどがFontSourceにはあり、最近のGoogleフォントにも対応しています。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/fontsource/fontsource/tree/master/packages&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;packagesフォルダ&lt;/a&gt;がフォント一覧となっているので、使うフォントをインストールします。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/gatsby-plugin-web-font-loader/gatsby-plugin-webfonts-5.png&quot; alt=&quot;FontSource フォント一覧&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;今回は、noto-sans-jpを使うとした場合、フォルダ名の前に&lt;code&gt;fontsource-&lt;/code&gt;を付けて以下のようにインストールします。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ yarn add fontsource-noto-sans-jp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;あとは、app.jsやlayout.jsなどベースとなるファイルの中でimportするだけです。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot; data-filename=&quot;layout.js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;import &#x27;fontsource-noto-sans-jp&#x27;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/gatsby-plugin-web-font-loader/gatsby-plugin-webfonts-6.png&quot; alt=&quot;読み込まれたことを確認&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;読み込まれたことが確認できました。&lt;/p&gt;
&lt;p&gt;FontSourceの場合、サイズごとに読み込む場合は、以下のようになります。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot; data-filename=&quot;layout.js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;import &#x27;fontsource-noto-sans-jp/500-normal.css&#x27;
import &#x27;fontsource-noto-sans-jp/700-normal.css&#x27;
import &#x27;fontsource-noto-sans-jp/900-normal.css&#x27;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;フォントによってファイル名が異なるので確認して読み込みましょう。&lt;/p&gt;
&lt;p&gt;ちょっと面倒ですね。。&lt;/p&gt;
&lt;h1&gt;結局Googleフォントは普通に読み込むのが良い&lt;/h1&gt;
&lt;p&gt;便利なプラグインやパッケージを紹介しましたが、結局自分は、純粋にGoogleフォントを読み込むやり方にしました。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;#x3C;link href=&quot;https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@500;700;900&amp;#x26;family=Roboto:wght@500&amp;#x26;display=swap&quot; rel=&quot;stylesheet&quot;&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;参考に&lt;/h1&gt;
&lt;h2&gt;gatsby-plugin-web-font-loaderはもうメンテナンスされていません。&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/escaladesports/legacy-gatsby-plugin-web-font-loader&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;gatsby-plugin-web-font-loaderのリポジトリ&lt;/a&gt;を見ると&lt;strong&gt;すでにアーカイブされています。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/gatsby-plugin-web-font-loader/gatsby-plugin-webfonts-1.png&quot; alt=&quot;DO NOT USE　gatsby-plugin-web-font-loader&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;DO NOT USE の文字も・・・&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.gatsbyjs.com/plugins/gatsby-plugin-web-font-loader/?=font&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Gatsbyのページでwebfontのプラグインを探す&lt;/a&gt;と、一番上に出てきますが使うのはやめましょう。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/gatsby-plugin-web-font-loader/gatsby-plugin-webfonts-2.png&quot; alt=&quot;Gatsbyのページでwebfontのプラグインを探す&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;実際すでに弊害もでてきていて、&lt;strong&gt;GoogleFontを使う場合は、urlが古いので設定を加えなければならないし、複数のWebフォントを設定するとエラーになるバグも発生しています。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/gatsby-plugin-web-font-loader/gatsby-plugin-webfonts-3.png&quot; alt=&quot;GoogleFontを使う場合は、urlが古いので設定を加えなければならないし、複数のWebフォントを設定するとエラー&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;gatsby-plugin-webfontsというプラグインも試しましたが、こちらも同様に古くなっていたので使うのはオススメしません！&lt;/p&gt;
]]&gt;</content:encoded><category>Gatsby</category><category>React</category><category>JavaScript</category></item><item><title>&lt;![CDATA[DockerでPuppeteerが動かなときはchromiumのバージョンを疑え]]&gt;</title><link>https://blog.ryou103.com/post/puppeteer-apk</link><guid>https://blog.ryou103.com/post/puppeteer-apk</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/puppeteer-apk/puppeteer-apk.png" medium="image"></media:content><description>Dockerイメージ node:10-alpine でPuppeteerを起動しようとしたが、puppeteer.launch()で止まったままエラーも吐かずにTimeoutしてしまう現象が起きた。原因はapkレポジトリのバージョンが古く、古いchromiumがインストールされていたのが原因だったので。apkレポジトリのアップデートして改善した。</description><pubDate>Tue, 22 Sep 2020 09:12:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/puppeteer-apk/puppeteer-apk.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Dockerイメージ node:10-alpine でPuppeteerを起動しようとしたが、&lt;code&gt;await puppeteer.launch()&lt;/code&gt; で止まったままエラーも吐かずにTimeoutしてしまう現象が起きた。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#running-on-alpine&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Puppeteerのトラブルシューティング&lt;/a&gt;のRunning on Alpineに従って設定した。&lt;/p&gt;
&lt;h2&gt;原因はChromiumのバージョンが古い&lt;/h2&gt;
&lt;p&gt;node:10-alpineではapkのリポジトリが3.8で、&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-sh&quot; data-filename=&quot;/etc/apk/repositories&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;http://dl-cdn.alpinelinux.org/alpine/v3.8/main
http://dl-cdn.alpinelinux.org/alpine/v3.8/community
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;そのバージョンでインストールできるChromiumは、68.0.3440.75-r0だった。&lt;/p&gt;
&lt;p&gt;そして、トラブルシューティングのページにコメントアウトで&lt;code&gt;# Installs latest Chromium (77) package.&lt;/code&gt;と書いてある。&lt;/p&gt;
&lt;p&gt;これはChromiumがバージョン77以上じゃないと動かないよ〜という意味かはわからないが、とりあえず最新版(chromium-83.0.4103.116-r0)をインストールしたら、無事Puppeteerが動いた。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/puppeteer-apk/puppeteer-apk-1.png&quot; alt=&quot;node:10-alpineではapkのリポジトリが3.8で、Chrominiumのバージョンが古い&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;Chromiumの最新版をインストール&lt;/h2&gt;
&lt;p&gt;Chromiumの最新版をインストールするため、apkのリポジトリを最新版にしてインストールをする。&lt;/p&gt;
&lt;p&gt;ただ&lt;code&gt;apk update&lt;/code&gt;をしてもリポジトリが変わらないので、リポジトリを指定してupgradeする。&lt;/p&gt;
&lt;p&gt;Dockerを使ってない人は&lt;code&gt;setup-apkrepos&lt;/code&gt;でレポジトリを最新版に書き換えて&lt;code&gt;apk upgrade&lt;/code&gt;すればよい。&lt;/p&gt;
&lt;p&gt;Dockerを使っている人は、&lt;code&gt;-X|--repository&lt;/code&gt;オプションで最新のレポジトリを指定してアップグレードすればよい。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker-compose exec server sh

$ apk upgrade --no-cache \
  -X http://dl-cdn.alpinelinux.org/alpine/v3.12/main \
  -X http://dl-cdn.alpinelinux.org/alpine/v3.12/community \
  chromium
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;レポジトリの最新バージョンは、&lt;a href=&quot;https://pkgs.alpinelinux.org/packages&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;こちら&lt;/a&gt;のページから確認できる&lt;/p&gt;
&lt;p&gt;インストールしたchromiumのバージョンは以下で確認。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ apk info chromium
chromium-83.0.4103.116-r0 description:
Chromium web browser

chromium-83.0.4103.116-r0 webpage:
https://www.chromium.org/Home

chromium-83.0.4103.116-r0 installed size:
247402496
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;そもそもnode:10-alineが古いのが原因だった&lt;/h2&gt;
&lt;p&gt;サーバーにnode:10-alpineイメージをインストールしたのが1年前で、いろいろ古いのが原因だった。&lt;br /&gt;nodeイメージをアップグレードすれば、alpineのバージョンも3.11がインストールされているので解消できた。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker pull node:10-alpine
$ docker-compose build server
$ docker-compose up -d server
&lt;/code&gt;&lt;/pre&gt;
]]&gt;</content:encoded><category>Docker</category><category>Puppeteer</category></item><item><title>&lt;![CDATA[フリーランスがAdobeCCを3万円以上安く手に入れる方法]]&gt;</title><link>https://blog.ryou103.com/post/adobe-discount</link><guid>https://blog.ryou103.com/post/adobe-discount</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/adobe-discount/adobe-discount.png" medium="image"></media:content><description>個人開発をするのに必ずといって必要になるのがAdobe CC。Adobe Creative Cloudのコンプリートプランを公式サイトから購入しようとすると、年間で72,336円もかかるんですよね。それがデジハリのAdobeマスター講座を受講することで、39,980円税込でAdobeCCを利用できるようになります。</description><pubDate>Sat, 19 Sep 2020 13:55:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adobe-discount/adobe-discount.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;こんにちは、フリーランスエンジニアのトミーです。&lt;/p&gt;
&lt;p&gt;開発をするのに必ずといって必要になるのが、Adobeのソフトですよね。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;年間ライセンスを39,980円で購入する方法を紹介します。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;会社員時代は、会社がライセンスを購入していたから特に意識することはなかったのですが、Adobe Creative Cloudのコンプリートプランを公式サイトから購入しようとすると、年間で72,336円もかかるんですよね。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adobe-discount/adobe-discount-1.png&quot; alt=&quot;公式サイトから購入しようとすると、年間で72,336円&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;公式サイトよりも半額近く安く購入する方法があります。&lt;/h2&gt;
&lt;p&gt;それをとある方法で購入すると、&lt;strong&gt;年間ライセンスが39,980円と半額近い3万円も安くなる&lt;/strong&gt;んです。&lt;/p&gt;
&lt;p&gt;それは&lt;a href=&quot;//af.moshimo.com/af/c/click?a_id=2198895&amp;p_id=2842&amp;pc_id=6482&amp;pl_id=36115&amp;guid=ON&quot; rel=&quot;nofollow&quot;&gt;&lt;strong&gt;デジハリのAdobeマスター講座&lt;/strong&gt;&lt;/a&gt;&lt;img src=&quot;//i.moshimo.com/af/i/impression?a_id=2198895&amp;p_id=2842&amp;pc_id=6482&amp;pl_id=36115&quot; width=&quot;1&quot; height=&quot;1&quot; style=&quot;border:none;&quot;&gt;を受講すること。&lt;/p&gt;
&lt;p&gt;こちらの講座は1年分のAdobe Creative Cloudのコンプリートプランがセットで付いて、39,980円税込なんです！&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adobe-discount/adobe-discount-2.png&quot; alt=&quot;1年分のAdobe Creative Cloudのコンプリートプランがセットで付いて、39,980円税込&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;授業を受けなければならないとかの&lt;strong&gt;縛りもありません。&lt;/strong&gt;&lt;br /&gt;むしろAdobeのソフトが安く手に入って学習教材も付いてくるという、得しかありません。&lt;/p&gt;
&lt;p&gt;3万円以上も安くAdobeを使えてしまうのです。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adobe-discount/adobe-discount-3.png&quot; alt=&quot;Adobe Creative Cloudのコンプリートプラン&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;毎年購入できるので、毎年安くAdobeCCが手に入る&lt;/h2&gt;
&lt;p&gt;今年は安く手に入るけど、来年以降は高くなるんじゃないの？と思った方、安心してください。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Adobeマスター講座は何度でも受講できるので、毎年半額近い値段でAdobeCCを使うことができます。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;私も今回で3度目の購入になります。&lt;/p&gt;
&lt;h2&gt;購入方法&lt;/h2&gt;
&lt;p&gt;さっそく購入方法をご紹介します。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;//af.moshimo.com/af/c/click?a_id=2198895&amp;p_id=2842&amp;pc_id=6482&amp;pl_id=36115&amp;guid=ON&quot; rel=&quot;nofollow&quot;&gt;こちらのデジハリのAdobeマスター講座のサイト&lt;/a&gt;&lt;img src=&quot;//i.moshimo.com/af/i/impression?a_id=2198895&amp;p_id=2842&amp;pc_id=6482&amp;pl_id=36115&quot; width=&quot;1&quot; height=&quot;1&quot; style=&quot;border:none;&quot;&gt;より購入することができます。&lt;/p&gt;
&lt;p&gt;ページ下部のAdobeマスター講座にお申し込みをクリック。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adobe-discount/adobe-discount-4.png&quot; alt=&quot;ページ下部のAdobeマスター講座にお申し込み&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;購入画面に進みますので、個人情報を入力して購入しましょう。&lt;/p&gt;
&lt;p&gt;購入が完了すると、メールアドレスの方に「＜重要＞Adobe Creative Cloud シリアルコード発行のご案内【デジハリ・オンラインスクール】」というメールが届いているはずです。&lt;br /&gt;そのメールにはAdobeCCを契約するための引き換えコードが書いてあります。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adobe-discount/adobe-discount-5.png&quot; alt=&quot;＜重要＞Adobe Creative Cloud シリアルコード発行のご案内【デジハリ・オンラインスクール】&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;そのメールに「■専用サイトにアクセス　&gt;&gt;&gt;　&lt;a href=&quot;https://creative.adobe.com/ja/educard&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://creative.adobe.com/ja/educard&lt;/a&gt; 」とありますので、そちらのリンクをクリック。&lt;br /&gt;Adobeのサイトに飛びますので、すでにアカウントを持っている方はログインを、まだの方はフォームからアカウントを作成しましょう。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adobe-discount/adobe-discount-6.png&quot; alt=&quot;専用サイトにアクセス&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;ログインできたら、所属する教育機関情報を入力します。&lt;/p&gt;
&lt;p&gt;デジハリ学生としてAdobeを利用することになるので、&lt;br /&gt;学校名に「デジハリ・オンラインスクール」&lt;br /&gt;卒業予定は、受講期間が3ヶ月となりますので、3ヶ月後の年月を選択してください。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adobe-discount/adobe-discount-7.png&quot; alt=&quot;所属する教育機関情報を入力&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;次に引き換えコードを入力します。&lt;br /&gt;（入力欄が6つに分かれていますが、引き換えコードをすべてまるっとコピーして、最初の入力欄にペーストすると、一気に入力できます。）&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adobe-discount/adobe-discount-8.png&quot; alt=&quot;引き換えコードを入力&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;引き換えコードに問題がなければ、以下の画像のようにCreative Cloudを使用開始と表示されますので、「今すぐ開始」をクリック。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adobe-discount/adobe-discount-9.png&quot; alt=&quot;Creative Cloudを使用開始と表示&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;以上でAdobe CCの契約完了となります。&lt;/p&gt;
&lt;p&gt;公式サイトとの違いといえば、&lt;br /&gt;公式はサブスクなので毎年自動更新ですが、こちらの方法では、毎年&lt;a href=&quot;//af.moshimo.com/af/c/click?a_id=2198895&amp;p_id=2842&amp;pc_id=6482&amp;pl_id=36115&amp;guid=ON&quot; rel=&quot;nofollow&quot;&gt;デジハリのAdobeマスター講座&lt;/a&gt;&lt;img src=&quot;//i.moshimo.com/af/i/impression?a_id=2198895&amp;p_id=2842&amp;pc_id=6482&amp;pl_id=36115&quot; width=&quot;1&quot; height=&quot;1&quot; style=&quot;border:none;&quot;&gt;を購入する必要があることくらいです。&lt;/p&gt;
&lt;p&gt;10分もかからない作業で3万円以上も安くなるのであれば、こちらを使わない手はないですよね！&lt;/p&gt;
&lt;h2&gt;さらに自己アフィリエイトで2,200円程返ってくる！&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;//af.moshimo.com/af/c/click?a_id=2198895&amp;p_id=2842&amp;pc_id=6482&amp;pl_id=36115&amp;guid=ON&quot; rel=&quot;nofollow&quot;&gt;デジハリのAdobeマスター講座&lt;/a&gt;&lt;img src=&quot;//i.moshimo.com/af/i/impression?a_id=2198895&amp;p_id=2842&amp;pc_id=6482&amp;pl_id=36115&quot; width=&quot;1&quot; height=&quot;1&quot; style=&quot;border:none;&quot;&gt;は、&lt;a href=&quot;//af.moshimo.com/af/c/click?a_id=2198989&amp;p_id=1&amp;pc_id=1&amp;pl_id=1319&amp;url=https%3A%2F%2Faf.moshimo.com%2F%3Futm_source%3Dmoshimo%26utm_medium%3Daffiliate%26utm_campaign%3Dregister_maf_so&quot; rel=&quot;nofollow&quot;&gt;もしもアフィリエイト&lt;/a&gt;&lt;img src=&quot;//i.moshimo.com/af/i/impression?a_id=2198989&amp;p_id=1&amp;pc_id=1&amp;pl_id=1319&quot; width=&quot;1&quot; height=&quot;1&quot; style=&quot;border:none;&quot;&gt;から広告を出しており、&lt;strong&gt;本人申し込みOKの成果報酬5.5%なので、約2,200円のアフィリエイト報酬をもらうことができます！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;手間がかかっても少しでも安くしたい！という方は、ぜひ&lt;a href=&quot;//af.moshimo.com/af/c/click?a_id=2198989&amp;p_id=1&amp;pc_id=1&amp;pl_id=1319&amp;url=https%3A%2F%2Faf.moshimo.com%2F%3Futm_source%3Dmoshimo%26utm_medium%3Daffiliate%26utm_campaign%3Dregister_maf_so&quot; rel=&quot;nofollow&quot;&gt;もしもアフィリエイト&lt;/a&gt;&lt;img src=&quot;//i.moshimo.com/af/i/impression?a_id=2198989&amp;p_id=1&amp;pc_id=1&amp;pl_id=1319&quot; width=&quot;1&quot; height=&quot;1&quot; style=&quot;border:none;&quot;&gt;に登録して、自分の広告から申し込みましょう！&lt;/p&gt;
&lt;p&gt;少しでもこの記事が役に立ったのであれば、&lt;a href=&quot;//af.moshimo.com/af/c/click?a_id=2198895&amp;p_id=2842&amp;pc_id=6482&amp;pl_id=36115&amp;guid=ON&quot; rel=&quot;nofollow&quot;&gt;&lt;strong&gt;こちらのリンク&lt;/strong&gt;&lt;/a&gt;&lt;img src=&quot;//i.moshimo.com/af/i/impression?a_id=2198895&amp;p_id=2842&amp;pc_id=6482&amp;pl_id=36115&quot; width=&quot;1&quot; height=&quot;1&quot; style=&quot;border:none;&quot;&gt;からAdobeマスター講座に申し込んでいただけると嬉しいです！&lt;/p&gt;
&lt;h2&gt;更新日より早く申し込んでも、更新日から1年間有効になります。&lt;/h2&gt;
&lt;p&gt;余談ですが、更新の場合のAdobeの有効期限が、申し込み日から1年か更新日から1年か心配になりましたが、&lt;br /&gt;&lt;strong&gt;Adobeの更新日から1年間有効になります&lt;/strong&gt;ので、ご安心を。&lt;/p&gt;
&lt;p&gt;その証拠に、Adobeの有効期限が9月24日までだったので、9月19日に申し込んでも来年の9月24日までに有効期限がなりました。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adobe-discount/adobe-discount-10.png&quot; alt=&quot;Adobeの更新日から1年間有効&quot; /&gt;
&lt;/p&gt;
]]&gt;</content:encoded><category>Adobe</category></item><item><title>&lt;![CDATA[AntdのTooltipをツールチップにホバーしたときは非表示にする]]&gt;</title><link>https://blog.ryou103.com/post/antd-tooltip</link><guid>https://blog.ryou103.com/post/antd-tooltip</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/antd-tooltip/antd-tooltip.png" medium="image"></media:content><description>AntdのTooltipのデフォルト動作は、Tooltipにカーソルが移動しても表示されたままになる。ただ、この動作だとTooltipが邪魔になる場合があるので、対象の要素の外にカーソルが移動したらTooltipが非表示になってほしい。そのためには、mouseLeaveDelayオプションを0に設定することで実現できる。</description><pubDate>Sun, 30 Aug 2020 07:14:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/antd-tooltip/antd-tooltip.png&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Actual&lt;/h2&gt;
&lt;p&gt;デフォルトの動作は、Tooltipにカーソルが移動しても表示されたままになる&lt;br /&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/antd-tooltip/antd-tooltip-1.gif&quot; alt=&quot;antd-tooltip-デフォルトの動き&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;Expected&lt;/h2&gt;
&lt;p&gt;ただ、この動作だとTooltipが邪魔になる場合があるので、ターゲット外にカーソルが移動したらTooltipが非表示になってほしい&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/antd-tooltip/antd-tooltip-2.gif&quot; alt=&quot;antd-tooltip-理想の動き&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;やりかた&lt;/h2&gt;
&lt;p&gt;visibleEnterTooltipみたいなツールチップの上にマウスが乘ったときの動作オプションがあるかな〜と思ったけど見つからず。。。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mouseLeaveDelay = 0&lt;/code&gt; を設定することで理想の動きを実現しました。&lt;/p&gt;
&lt;p&gt;mouseLeaveDelayは、マウスが離れてからツールチップが非表示になるまでの遅延時間のオプション。&lt;br /&gt;これを0にすると、対象の要素とツールチップの隙間をマウスが通ることでツールチップにマウスが乗る前に消えることになる。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-pug&quot;&gt;&lt;code class=&quot;language-pug&quot;&gt;a-tooltip(placement=&quot;right&quot;, :mouseLeaveDelay=&quot;0&quot;)
  span(slot=&quot;title&quot;) ソーシャルアイコンを配置します
  div(:class=&quot;$style.iconBlock&quot;)
    social-icon
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（AntdVueを使っているが、Antdでも同様のオプションがあるので実装可能です。）&lt;/p&gt;
]]&gt;</content:encoded><category>Vue</category><category>Antd</category></item><item><title>&lt;![CDATA[React Native Debugger 2つのバージョンを共存させる]]&gt;</title><link>https://blog.ryou103.com/post/react-native-debugger-versions</link><guid>https://blog.ryou103.com/post/react-native-debugger-versions</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/react-native-debugger-versions/react-native-debugger-versions.png" medium="image"></media:content><description>React Nativeのバージョンが0.61以下の場合、最新のReact Native Debuggerだと動かない。逆も同様に、React Native v0.62以上の場合、古いReact Native Debuggerだと動かない。なので、既存のプロジェクトと新しいプロジェクトで異なるバージョンのReact Nativeを使っている場合、React Native Debuggerもそれぞれに合わせる必要がある。2つのバージョンのReact Native Debuggerが共存できるインストール方法を紹介します。</description><pubDate>Mon, 27 Jul 2020 03:15:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/react-native-debugger-versions/react-native-debugger-versions.png&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;なぜ2つのバージョンを使いたいのか&lt;/h2&gt;
&lt;p&gt;どうしてReact Native Debuggerをバージョン違いで使おうと思ったのかというと、React Nativeのバージョンが0.61以下の場合、最新のReact Native Debuggerだと動かない。&lt;br /&gt;逆も同様に、React Native v0.62以上の場合、古いReact Native Debuggerだと動かない。&lt;/p&gt;
&lt;p&gt;なので、既存のプロジェクトと新しいプロジェクトで異なるバージョンのReact Nativeを使っている場合、React Native Debuggerもそれぞれに合わせる必要がある。&lt;/p&gt;
&lt;p&gt;異なる2つのバージョンのReact Native Debuggerのインストール方法は、ドキュメントには記載されていなかったので紹介します。&lt;br /&gt;当たり前すぎて、記載するほどのものでなかったのかも・・・&lt;/p&gt;
&lt;p&gt;brew caskを使ってインストールする方法とGitHubからダウンロードする方法の2パターンあります。&lt;/p&gt;
&lt;p&gt;既に新古どちらかのバージョンのReact Native Debuggerはインストールしているものとします。&lt;br /&gt;まだの場合は、&lt;a href=&quot;https://blog.ryou103.com/post/react-native-debugger/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;「Expoのデバッグ環境を整える（React Native Debugger）」&lt;/a&gt;を参考にインストールしてみてください。&lt;/p&gt;
&lt;h2&gt;簡単なGitHubからダウンロードする方法&lt;/h2&gt;
&lt;p&gt;既にインストール済みのReact Native Debuggerのアプリの名前を変更します。&lt;br /&gt;バージョンを名前につけると判別しやすいかも。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/react-native-debugger-versions/react-native-debugger-versions-1.png&quot; alt=&quot;React Native Debuggerのアプリの名前を変更&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;次に&lt;a href=&quot;https://github.com/jhen0409/react-native-debugger/releases&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;GitHubのリリースページ&lt;/a&gt;から、既にインストールしているものを別バージョンのReact Native Debuggerをダウンロードしましょう。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/react-native-debugger-versions/react-native-debugger-versions-2.png&quot; alt=&quot;GitHubのリリースページ&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;ダウンロードしたものをインストールすれば、2つ存在する状態になります。&lt;br /&gt;以上です！&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/react-native-debugger-versions/react-native-debugger-versions-3.png&quot; alt=&quot;2つ存在する状態&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;brew caskを使ってインストールする方法&lt;/h2&gt;
&lt;p&gt;Macユーザーの多くは&lt;code&gt;brew cask&lt;/code&gt;でインストールしているのではないでしょうか。&lt;br /&gt;こちらの場合は、アプリの名前を変更しただけではインストールできません。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;brew cask&lt;/code&gt;でインストールされたファイルの保存先 &lt;code&gt;/usr/local/Caskroom/&lt;/code&gt; でも名前を変更する必要があります。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ cd /usr/local/Caskroom/
$ ls
ngrok    react-native-debugger
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;React Native Debuggerをリネームします。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;mv react-native-debugger react-native-debugger-v0.10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;で、GitHubからダウンロードと同様にアプリケーションフォルダにあるアプリの名前も変更します。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/react-native-debugger-versions/react-native-debugger-versions-1.png&quot; alt=&quot;React Native Debuggerのアプリの名前を変更&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;次にインストール済みのものと異なるバージョンをインストール。&lt;/p&gt;
&lt;p&gt;最新版をインストールする場合は、&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;$ brew update &amp;#x26;&amp;#x26; brew cask install react-native-debugger
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;バージョン0.10のReact Native Debugger をインストールする場合は、&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;$ brew update &amp;#x26;&amp;#x26; brew cask install https://raw.githubusercontent.com/Homebrew/homebrew-cask/b6ac3795c1df9f97242481c0817b1165e3e6306a/Casks/react-native-debugger.rb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;インストールが完了すれば、2つ存在する状態になります。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/react-native-debugger-versions/react-native-debugger-versions-1.png&quot; alt=&quot;2つ存在する状態&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;以上になります。&lt;br /&gt;お疲れさまでした🙌&lt;/p&gt;
]]&gt;</content:encoded><category>ReactNative</category></item><item><title>&lt;![CDATA[Expoのデバッグ環境を整える（React Native Debugger）]]&gt;</title><link>https://blog.ryou103.com/post/react-native-debugger</link><guid>https://blog.ryou103.com/post/react-native-debugger</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/react-native-debugger/react-native-debugger.png" medium="image"></media:content><description>Reat Nativeの開発ツール Expoの検証（デバッグ）できるツール React Native Debugger の導入方法を紹介。インストール後ひと手間加えて起動をラクにします。</description><pubDate>Sun, 26 Jul 2020 11:00:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/react-native-debugger/react-native-debugger.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Reat Nativeの開発ツール &lt;a href=&quot;https://expo.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Expo&lt;/a&gt;を、Chromeのデベロッパーツールみたいに検証（デバッグ）できるツール React Native Debugger の導入方法を紹介。&lt;br /&gt;インストール後ひと手間加えて起動をラクにします。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;やること&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;React Native Debuggerをインストール&lt;/li&gt;
  &lt;li&gt;React Native Debuggerの設定を変更&lt;/li&gt;
  &lt;li&gt;Expoのデバッグを有効にする&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;React Native Debuggerでできること&lt;/h2&gt;
&lt;p&gt;React Native Debugger&lt;br /&gt;&lt;a href=&quot;https://github.com/jhen0409/react-native-debugger&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/jhen0409/react-native-debugger&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;React Native Debuggerの内部ではChromeの拡張機能にもある&lt;a href=&quot;https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;react-devtools&lt;/a&gt;や&lt;a href=&quot;https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;redux-devtools-extension&lt;/a&gt;が動いています。&lt;/p&gt;
&lt;p&gt;下の画像のとおり、Chromeでのデバッグ同様の使い方ができます。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/react-native-debugger/react-native-debugger-1.png&quot; alt=&quot;React Native Debugger&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;React Native Debuggerをインストール&lt;/h2&gt;
&lt;p&gt;インスール方法は、&lt;a href=&quot;https://github.com/jhen0409/react-native-debugger/releases&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;GitHubのリリースページ&lt;/a&gt;からファイルをダウンロードしてきても良いですし、Macでしたら&lt;code&gt;brew cask&lt;/code&gt;コマンドでもインストールできます。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;バージョンに注意が必要で、React Nativeのバージョンが0.61以下の場合は、React Native Debuggerは0.10をインストールしましょう。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;React Nativeのバージョンはpackage.jsonで確認。&lt;/p&gt;
&lt;h3&gt;ダウンロードの場合&lt;/h3&gt;
&lt;p&gt;こちらのURLから、それぞれのOS環境用のファイルをダウンロードしてインストールできます。&lt;br /&gt;&lt;a href=&quot;https://github.com/jhen0409/react-native-debugger/releases&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/jhen0409/react-native-debugger/releases&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/react-native-debugger/react-native-debugger-2.png&quot; alt=&quot;OS環境用のファイルをダウンロード&quot; /&gt;
&lt;/p&gt;
&lt;h3&gt;コマンドからインストール for Mac&lt;/h3&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ brew update &amp;#x26;&amp;#x26; brew cask install react-native-debugger
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;React Native Debugger 0.10をインストールする場合は、こちら&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ brew update &amp;#x26;&amp;#x26; brew cask install https://raw.githubusercontent.com/Homebrew/homebrew-cask/b6ac3795c1df9f97242481c0817b1165e3e6306a/Casks/react-native-debugger.rb
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;React Native Debuggerの設定を加える&lt;/h2&gt;
&lt;p&gt;このまま起動しても画像のように接続待ち状態のままで、Expoのデバッグはできません。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/react-native-debugger/react-native-debugger-3.png&quot; alt=&quot;起動しても接続待ち状態のまま&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;理由は、ただ起動しただけではPort 8081で起動しているReactNativeのアプリケーションをデバッグしようとするためです。&lt;br /&gt;ExpoアプリはデフォルトでPort 19001で起動しています。&lt;/p&gt;
&lt;p&gt;ドキュメントには、コマンドでportを指定して起動するよう書いてありますが、面倒ですよね・・・&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;open &quot;rndebugger://set-debugger-loc?port=19001&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;なので、React Native Debuggerの設定ファイルを修正します。&lt;/p&gt;
&lt;p&gt;React Native Debuggerを起動してメニューからDebugger → Open Config Fileを選択すると、テキストエディタが開きます。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/react-native-debugger/react-native-debugger-4.png&quot; alt=&quot;React Native Debuggerを起動してメニューからDebugger → Open Config Fileを選択&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;このファイルにPort番号 8081が書かれている場所を探します。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/react-native-debugger/react-native-debugger-5.png&quot; alt=&quot;Port番号 8081が書かれている場所を探す&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;そこにExpo用のPort番号を追記してあげればOKです。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;  defaultRNPackagerPorts: [8081],
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;を以下のように。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;  defaultRNPackagerPorts: [8081, 19001],
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;もし、React Nativeで開発する予定がない方は、8081の記述は消してしまっても良いかもしれません。&lt;/p&gt;
&lt;p&gt;設定ファイルに追記したら保存して、React Native Debuggerを再起動しましょう。&lt;br /&gt;8081番と19001番用のウィンドウ2つが立ち上がります。&lt;/p&gt;
&lt;p&gt;Expoでは19001の方だけを使うので、8081のウィンドウは閉じてしまって構いません。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/react-native-debugger/react-native-debugger-7.png&quot; alt=&quot;P8081番と19001番用のウィンドウ2つが立ち上がります。&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;Expoのデバッグを有効にする&lt;/h2&gt;
&lt;p&gt;デバッグ用のウィンドウは開けたので、Expoで起動したアプリからDebugを有効にします。&lt;/p&gt;
&lt;p&gt;アプリを開いた状態で、&lt;code&gt;Cmd+Ctrl+Z&lt;/code&gt;を押してExpoのメニューを開きます。&lt;br /&gt;メニューにある「Debug RemoteJS」をタップ。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://ghost.blog.ryou103.com/content/images/2020/07/react-native-debugger-6.png&quot; alt=&quot;Expoアプリ&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;すると、React Native DebuggerのウィンドウがWainting for React to connect ...という表示が変わって、コンポーネントの階層が表示されていると思います。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://ghost.blog.ryou103.com/content/images/2020/07/react-native-debugger-8.png&quot; alt=&quot;React Native Debuggerのウィンドウ&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;これっでデバッグ環境が整いました。&lt;/p&gt;
&lt;h2&gt;おまけ&lt;/h2&gt;
&lt;p&gt;使い方を軽く紹介します。&lt;br /&gt;右下のコンポーネントの階層が深すぎて、デバッグしたい目的のコンポーネントを探すのも大変です。&lt;/p&gt;
&lt;p&gt;ですので、Chromeの要素検証のようにアプリの画面から目的の場所をクリックしてコンポーネントを指定するのが良いかと思います。&lt;/p&gt;
&lt;p&gt;そのためには、&lt;code&gt;Cmd+Ctrl+Z&lt;/code&gt;でExpoのメニューを開いて、「Show Element Inspector」をタップ。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://ghost.blog.ryou103.com/content/images/2020/07/react-native-debugger-9.png&quot; alt=&quot;Show Element Inspector&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;調べたい要素の場所をタップすると、青く塗られてReact Native Debuggerのウィンドウも指定されたコンポーネントが表示され調べることができます。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://ghost.blog.ryou103.com/content/images/2020/07/react-native-debugger-10.png&quot; alt=&quot;調べたい要素の場所をタップ&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;以上で、React Native DebuggerでExpoのデバッグ環境を整えることができました。&lt;br /&gt;お疲れさまでした。&lt;/p&gt;
]]&gt;</content:encoded><category>ReactNative</category><category>Expo</category></item><item><title>&lt;![CDATA[DockerComposeで生成したコンテナ・イメージ・ボリューム・ネットワークを一括削除]]&gt;</title><link>https://blog.ryou103.com/post/docker-compose-down</link><guid>https://blog.ryou103.com/post/docker-compose-down</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/docker-compose-down/docker-compose-down.png" medium="image"></media:content><description>Dockerの開発環境を初期化して、開発し直したいときに役立つdownコマンドの紹介。docker-compose down --rmi local -v  だけで、関係するコンテナ・イメージ・ボリューム・ネットワークを一括削除することができる。デフォルトでは、コンテナとネットワークしか削除してくれない。--rmiはイメージを削除するオプション。-vはボリュームを削除するオプションとなっている。</description><pubDate>Sat, 27 Jun 2020 01:00:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/docker-compose-down/docker-compose-down.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Dockerの開発環境を初期化して、開発し直したいときに役立つdownコマンドの紹介。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker-compose down --rmi local -v  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上のコマンドだけで、関係するコンテナ・イメージ・ボリューム・ネットワークを一括削除することができる。&lt;/p&gt;
&lt;h1&gt;想定する状況&lt;/h1&gt;
&lt;p&gt;例で示すと、&lt;br /&gt;クライアントサイドはNodeのフレームワークGatsby、記事管理にはGhostCMS+MySQLを使ったブログサービスがDockerで動いているとする。&lt;br /&gt;ゴミファイルなどが溜まってしまったDBなど、すべて白紙に戻して開発し直したい。&lt;/p&gt;
&lt;p&gt;Dockerの構成は以下のようになっている。&lt;/p&gt;
&lt;p&gt;コンテナ&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;app: Gatsby&lt;/li&gt;
  &lt;li&gt;db: MySQL&lt;/li&gt;
  &lt;li&gt;ghost: Ghost&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
af50ab24edc5        blog-sample_app     &quot;docker-entrypoint.s…&quot;   2 minutes ago       Up About a minute   0.0.0.0:8080-&gt;8080/tcp   blog-sample_app_1
624ee466b8d8        blog-sample_db      &quot;docker-entrypoint.s…&quot;   2 minutes ago       Up 2 minutes        3306/tcp, 33060/tcp      blog-sample_db_1
f6f41484bf81        ghost:3.7-alpine    &quot;docker-entrypoint.s…&quot;   2 minutes ago       Up 2 minutes        0.0.0.0:2368-&gt;2368/tcp   blog-sample_ghost_1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;イメージ&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;node&lt;/li&gt;
  &lt;li&gt;mysql&lt;/li&gt;
  &lt;li&gt;ghost&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker images
REPOSITORY                 TAG                 IMAGE ID            CREATED             SIZE
blog-sample_app            latest              de2c50cbc00e        4 minutes ago       1.67GB
node                       12-alpine           7a48db49edbf        8 weeks ago         88.7MB
mysql                      5.7                 1e4405fe1ea9        7 months ago        437MB
ghost                      3.7-alpine          1320b5ea0bea        4 months ago        328MB
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ボリューム&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;node_modules&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker volume list
DRIVER              VOLUME NAME
local               blog-sample_node_modules
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ネットワーク&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;default&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker network list
NETWORK ID          NAME                       DRIVER              SCOPE
1d5ce36ca401        blog-sample_default        bridge              local
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;docker-composeの一括削除コマンド down&lt;/h1&gt;
&lt;p&gt;「一度白紙に戻して、再度開発環境を整えたい！」となったときに、一括削除してくれる down コマンドが存在する&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker-compose down
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
  &lt;p&gt;コンテナを停止し、 up で作成したコンテナ・ネットワーク・ボリューム・イメージを削除します。デフォルトではコンテナとネットワークのみ削除します。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;オプションをつけずに実行して削除されるのは、&lt;/p&gt;
&lt;p&gt;Compose ファイル内で定義したサービス用のコンテナ&lt;br /&gt;Compose ファイルの network セクションで定義したネットワーク&lt;br /&gt;default ネットワーク（を使っている場合）&lt;/p&gt;
&lt;p&gt;のコンテナとネットワークだけで、イメージとボリュームが削除されないので、完全に白紙の状態には戻らない。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/docker-compose-down/docker-compose-down-1.png&quot; alt=&quot;イメージとボリュームが削除されない&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;オプションを付けて、イメージとボリュームも削除ができる。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker-compose down --rmi local -v  
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;--rmi&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;--rmi&lt;/code&gt; つまり「remove image」の略で、イメージを削除するオプション。&lt;/p&gt;
&lt;p&gt;このオプションでは、削除対象を選択することができ、&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;&#x27;all&#x27;: あらゆるサービスで使う全イメージを削除&lt;br /&gt;&#x27;local&#x27;: image フィールドにカスタム・タグのないイメージだけ削除&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;allを指定した場合、例にあるイメージでは、blog-sample_app, node, mysql, ghostが削除される。&lt;br /&gt;他のプロジェクトで、同じmysqlのイメージを使っていた場合、動かなくなる可能性があるので、全削除は注意が必要になる。&lt;/p&gt;
&lt;p&gt;node, mysql, ghostのイメージは何度削除しても、インストールするものは結局同じイメージなので、あえて削除する必要がない。&lt;/p&gt;
&lt;p&gt;だから、自分は&lt;code&gt;--rmi local&lt;/code&gt; でblog-sample_appイメージだけ削除するようにしている。&lt;/p&gt;
&lt;h2&gt;-v&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;-v&lt;/code&gt; オプションは、「volume」 ボリュームを削除するオプション。&lt;/p&gt;
&lt;h1&gt;実行結果&lt;/h1&gt;
&lt;p&gt;これらのオプションを使い削除することで、コンテナ・イメージ・ボリューム・ネットワークの一括削除ができる&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker-compose down --rmi local -v  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/docker-compose-down/docker-compose-down-2.png&quot; alt=&quot;コンテナ・イメージ・ボリューム・ネットワークの一括削除ができる&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;実際に削除してみると・・&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker-compose down --rmi local -v  
Stopping blog-sample_app_1   ... done
Stopping blog-sample_db_1    ... done
Stopping blog-sample_ghost_1 ... done
Removing blog-sample_app_1   ... done
Removing blog-sample_db_1    ... done
Removing blog-sample_ghost_1 ... done
Removing network blog-sample_default
Removing volume blog-sample_node_modules
Removing image blog-sample_app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;コンテナが停止され、コンテナ・イメージ・ボリューム・ネットワークを削除していることがわかる。&lt;br /&gt;nodeやmysqlイメージは削除されずに残っている。&lt;/p&gt;
&lt;p&gt;以上で、開発環境を完全削除ができたので、&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker-compose build
$ docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;とすれば、白紙に戻った開発環境の出来上がりです。&lt;/p&gt;
]]&gt;</content:encoded><category>Docker</category></item><item><title>&lt;![CDATA[お名前メールを外部ネームサーバーから利用する裏技]]&gt;</title><link>https://blog.ryou103.com/post/onamae-nameserver</link><guid>https://blog.ryou103.com/post/onamae-nameserver</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/onamae-nameserver/onamae-nameserver.png" medium="image"></media:content><description>お名前メールをCloudflareなどの外部ネームサーバーから利用する方法です。月77円で20ドメインのメールアドレスが管理できるメール専用サーバーのお名前メールをMXレコード・TXTレコードを設定することで外部ネームサーバーから利用できたので紹介します。</description><pubDate>Fri, 22 May 2020 14:55:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/onamae-nameserver/onamae-nameserver.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;お名前メールをCloudflareなどの外部ネームサーバーで利用する方法です。&lt;br /&gt;お名前.comの公式の方法ではありませんので、ご了承の上お試しください。&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;各サーバーサービス指定のネームサーバー（DNS）をご設定いただかなければご利用ドメイン名で弊社サーバーをご利用いただけません。&lt;br /&gt;&lt;a href=&quot;https://help.onamae.com/answer/8793&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://help.onamae.com/answer/8793&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;とあるように、お名前メールも同様、指定のネームサーバーを設定しないと利用できません。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;通常は。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;試行錯誤した結果、やっと！&lt;strong&gt;Cloudflareをネームサーバーに指定して、お名前メールを利用できる方法&lt;/strong&gt;を見つけ出したのでご紹介！&lt;/p&gt;
&lt;p&gt;これは、全個人開発者におすすめしたい！月77円で20ドメインのメールアドレスが管理できるんです！&lt;/p&gt;
&lt;h1&gt;準備するもの&lt;/h1&gt;
&lt;ul&gt;
  &lt;li&gt;運用していないドメイン
    &lt;ul&gt;
      &lt;li&gt;お名前.comで管理していること&lt;/li&gt;
      &lt;li&gt;ネームサーバーを変更するので、サブドメも使っていないこと&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;使いたいドメイン
    &lt;ul&gt;
      &lt;li&gt;運用していないドメインと同じでも良い&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Cloudflare
    &lt;ul&gt;
      &lt;li&gt;外部ネームサーバー&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;DNSレコード取得ツール
    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;http://webadmin.jp/tooldns/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://webadmin.jp/tooldns/&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;お名前メール
    &lt;ul&gt;
      &lt;li&gt;２ドメイン以上使う場合は、スタンダードプラン&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;おおまかな流れ&lt;/h1&gt;
&lt;ul&gt;
  &lt;li&gt;運用していないドメインにお名前メールのネームサーバーを設定&lt;/li&gt;
  &lt;li&gt;DNSレコード取得ツールでMX,SPFレコードを取得&lt;/li&gt;
  &lt;li&gt;使いたいドメインにMX,SPFレコードを設定&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;お名前メールを契約してドメインにネームサーバーを設定する&lt;/h1&gt;
&lt;h2&gt;お名前メール契約&lt;/h2&gt;
&lt;p&gt;お名前メールを契約するためには、すでにお名前.comでドメインを所持しているか、契約するときにドメインを新規取得する必要があります。&lt;/p&gt;
&lt;p&gt;新規取得の場合は、&lt;a href=&quot;https://www.onamae.com/service/mail/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;こちらのお名前メールサービスページ&lt;/a&gt;から申し込みを進めてください。&lt;/p&gt;
&lt;p&gt;既存のドメインでお名前メールを契約する場合はサービスページからは契約できないので、管理画面のドメインのページから契約します。&lt;br /&gt;&lt;a href=&quot;https://navi.onamae.com/domain&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://navi.onamae.com/domain&lt;/a&gt;&lt;br /&gt;レンタルサーバー「申し込み」をクリック。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/onamae-nameserver/onamae_1.png&quot; alt=&quot;レンタルサーバー「申し込み」をクリック。&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;おすすめサービスにお名前メールがあるので、こちらから契約を進めてください。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/onamae-nameserver/onamae_2.png&quot; alt=&quot;おすすめサービスにお名前メールがあるので、こちらから契約&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;ネームサーバー設定&lt;/h2&gt;
&lt;p&gt;お名前メールのネームサーバーからDNSレコードを取得するために、ドメインのネームサーバーを一度お名前メールに向けます。&lt;br /&gt;（お名前メールの契約時に、「ネームサーバの変更をするか否か」を聞かれます。そのときに変更した方は、この手順は飛ばしてください。）&lt;/p&gt;
&lt;p&gt;ドメインページの「ネームサーバー」の項目がクリックできるので、対象のドメインのところをクリック。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/onamae-nameserver/onamae_3.png&quot; alt=&quot;ドメインページの「ネームサーバー」の項目&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;2.ネームサーバーの選択から「レンタルサーバー SDプラン、お名前メールのネームサーバーを使う」を選択し確認に進んでOK。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/onamae-nameserver/onamae_4.png&quot; alt=&quot;「レンタルサーバー SDプラン、お名前メールのネームサーバーを使う」を選択&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;これでネームサーバーの設定が完了。&lt;br /&gt;ネームサーバーが浸透するまでしばらく待ちます。&lt;/p&gt;
&lt;p&gt;こちらのサイトで、浸透したかをチェックできます。&lt;br /&gt;&lt;a href=&quot;https://www.whatsmydns.net/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.whatsmydns.net/&lt;/a&gt;&lt;br /&gt;フォームに、ドメインを入力して、NSを選択してSearch。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/onamae-nameserver/onamae_5.png&quot; alt=&quot;ドメインを入力して、NSを選択してSearch。&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;大体浸透してたら大丈夫です！&lt;/p&gt;
&lt;h1&gt;DNSレコード取得ツールでMX,SPFレコードを取得&lt;/h1&gt;
&lt;p&gt;浸透したら、ドメインのDNSレコードを調べます。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://webadmin.jp/tooldns/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://webadmin.jp/tooldns/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;ドメインを入力して「取得」。&lt;/p&gt;
&lt;p&gt;表示されるMXとTXTの欄をメモしておく。&lt;/p&gt;
&lt;p&gt;MX: ****.gmoserver.jp&lt;br /&gt;TXT: spf**.gmoserver.jpが含まれているもの&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/onamae-nameserver/onamae_6.png&quot; alt=&quot;表示されるMXとTXTの欄をメモ&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;使いたいドメインにMX,SPFレコードを設定&lt;/h1&gt;
&lt;p&gt;ここから、メールアドレスとして使いたいドメインの操作になります。（先程のドメインでも問題ないと思います。）&lt;/p&gt;
&lt;p&gt;使いたいドメインの、ネームサーバーをCloudflareなどの外部ネームサーバーに設定してください。&lt;br /&gt;私は、Cloudflareを使っているのでそちらの画面で説明していきます。&lt;/p&gt;
&lt;p&gt;外部ネームサーバーに設定が完了したら、DNSの設定ページからMXとTXTを追加します。&lt;/p&gt;
&lt;p&gt;Type： MX&lt;br /&gt;Name: 使いたいドメイン名&lt;br /&gt;Mail server： ****.gmoserver.jp（さきほどメモしたもの）&lt;/p&gt;
&lt;p&gt;Type： TXT&lt;br /&gt;Name: 使いたいドメイン名&lt;br /&gt;Content: v=spf1 include:****.gmoserver.jp ~all（さきほどメモしたもの）&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/onamae-nameserver/onamae_8.png&quot; alt=&quot;DNSの設定ページからMXとTXTを追加&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;これで設定完了となります。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/onamae-nameserver/onamae_7.png&quot; alt=&quot;設定完了&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;お名前メールに使いたいドメインを追加する&lt;/h1&gt;
&lt;p&gt;ドメインが1つだけのときは、この手順はスキップ。&lt;/p&gt;
&lt;p&gt;お名前メールのページから「ドメインを追加」をクリック。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/onamae-nameserver/onamae_9.png&quot; alt=&quot;お名前メールのページから「ドメインを追加」&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;対象の追加方法を選択。&lt;br /&gt;自分は、「お名前.comで契約済みのドメイン」を選択。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/onamae-nameserver/onamae_10.png&quot; alt=&quot;「お名前.comで契約済みのドメイン」を選択&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;1.ドメイン選択で対象のドメインを選択。&lt;br /&gt;2.ネームサーバーを変更するは、「変更しない」を選択。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/onamae-nameserver/onamae_11.png&quot; alt=&quot;ネームサーバーを変更するは、「変更しない」を選択&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;請求が0であることを確認。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/onamae-nameserver/onamae_12.png&quot; alt=&quot;請求が0であることを確認。&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;これでお名前メールにドメインの追加完了。&lt;/p&gt;
&lt;h1&gt;メールアドレスの作成&lt;/h1&gt;
&lt;p&gt;最後に、使いたいドメインのメールアドレスを発行します。&lt;br /&gt;ここからは通常のお名前メールの設定と変わりません。&lt;/p&gt;
&lt;p&gt;お名前.comの管理画面からお名前メールのコントロールパネルにログイン。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/onamae-nameserver/onamae_13.png&quot; alt=&quot;お名前メールのコントロールパネルにログイン&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;メール設定の「メールアカウント」を選択。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/onamae-nameserver/onamae-14.png&quot; alt=&quot;メール設定の「メールアカウント」を選択&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;作成したいメールアドレスのドメインの選択とアドレス名の入力をして、「新規作成」。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/onamae-nameserver/onamae-15.png&quot; alt=&quot;ドメインの選択とアドレス名の入力をして、「新規作成」&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;これで作成完了です。&lt;br /&gt;最後に表示されたメールアドレス/パスワードと、サーバー設定の「サーバー情報」からSMTPサーバーとPOPサーバーのURLが取得できるので、メールクライアントに設定すれば送受信できるようになります！&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/onamae-nameserver/onamae-17.png&quot; alt=&quot;サーバー設定の「サーバー情報」&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/onamae-nameserver/onamae-18.png&quot; alt=&quot;SMTPサーバーとPOPサーバーのURLが取得できる&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;私は、色々試行錯誤した結果、この方法でできました。&lt;br /&gt;ドメインは2つ使ったのですが、理論上は１つのドメインでもできる気がします。&lt;/p&gt;
&lt;p&gt;そしてさらに気がついたのですが、コンパネにあるサーバー情報に、stmp[&lt;strong&gt;数字&lt;/strong&gt;]@gmoserver.jpとありますが、この数字とMX，TXTレコードに記入する数字の部分が同じであることに気づきました。&lt;/p&gt;
&lt;p&gt;MX: mx[&lt;strong&gt;数字&lt;/strong&gt;].gmoserver.jp&lt;br /&gt;TXT: v=spf1 include:spf[&lt;strong&gt;数字&lt;/strong&gt;].gmoserver.jp ~all&lt;/p&gt;
&lt;p&gt;もしかしたら、こんなに長い手順を踏まなくとも、数字を確認してネームサーバーのDNSレコードを追加するだけでできるかもしれません！&lt;br /&gt;誰か試して欲しいです！&lt;/p&gt;
&lt;h1&gt;個人開発者にお名前メールを薦めたい&lt;/h1&gt;
&lt;p&gt;最後に、なんでこんなにもお名前メールに執着したかというと、&lt;strong&gt;格安でマルチドメイン対応のメール専用サーバー&lt;/strong&gt;だったからです。&lt;/p&gt;
&lt;p&gt;月額77円です！&lt;/p&gt;
&lt;p&gt;以前は、さくらサーバーの&lt;a href=&quot;https://www.sakura.ne.jp/mail/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;さくらのメールボックス&lt;/a&gt;を使っていました。こちらは価格には満足していました。&lt;br /&gt;ですが、、&lt;strong&gt;マルチドメインに対応という表記に罠があって&lt;/strong&gt;、&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;同じメールアカウント名で異なるドメイン毎のメールアドレスの運用は利用できません。&lt;br /&gt;（例：「info@◯◯◯.com」と「info@△△△.net」の場合、同じ「info」のメールボックスに配信されます）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;さくらのメールボックスでは、確かに複数のドメインを設定できますが、@の前が同じメール扱いされてしまうのです。つまりドメイン毎に分けた運用はできません。（これでマルチドメイン対応とか言っていいのか・・・？）&lt;/p&gt;
&lt;p&gt;他のメール専用サーバーは、少し高かったり、マルチドメイン非対応だったりで、&lt;strong&gt;お名前メールかムームーメール&lt;/strong&gt;くらいしか条件を満たすものがありませんでした。&lt;/p&gt;
&lt;p&gt;月契約もできるし、年契約にしたらムームーメールより安くなる、お名前メールを選びました。&lt;/p&gt;
&lt;p&gt;結果、&lt;strong&gt;月額77円で20個のドメインを運用できる満足いくメールサーバー&lt;/strong&gt;を手に入れました！&lt;/p&gt;
]]&gt;</content:encoded></item><item><title>&lt;![CDATA[Nuxt.jsで開発環境を超カンタンにSSL化]]&gt;</title><link>https://blog.ryou103.com/post/nuxtjs-ssl</link><guid>https://blog.ryou103.com/post/nuxtjs-ssl</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/nuxtjs-ssl/nuxtjs-ssl.png" medium="image"></media:content><description>ローカル開発環境でのSSL認証が必要な時ってありますよね。mkcertというツールを使えば簡単にhttps://localhostでアクセスできるようになります。今回は、Nuxt.jsへの実装例を紹介。</description><pubDate>Mon, 18 May 2020 09:00:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/nuxtjs-ssl/nuxtjs-ssl.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;開発環境で、SSL接続が必要なときってありますよね。ServiceWorkerだったり、WebARだったり。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/FiloSottile/mkcert&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;mkcert&lt;/a&gt;を使って認証キーを用意すれば、nuxt.config.jsに数行の設定を追記するだけで、Nuxt.jsでは簡単にブラウザの警告も出さずにSSL化ができます。&lt;/p&gt;
&lt;h1&gt;mkcertでSSL認証キーの準備&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/FiloSottile/mkcert&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;mkcert&lt;/a&gt;とは、設定なしに開発環境でSSL認証をできるようにしてくれるツールです。&lt;/p&gt;
&lt;h2&gt;mkcertをインストール&lt;/h2&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ brew install mkcert
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(ドキュメントには、Linuxt, Windowsでのインストール方法も記載されています。)&lt;/p&gt;
&lt;h2&gt;認証局作成&lt;/h2&gt;
&lt;p&gt;SSL認証キーが正しいかを検査してくれる認証局というものをローカルに準備します。準備といっても一行実行するだけ。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ mkcert -install
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;SSL証明書と認証キーを作成&lt;/h2&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ cd /my/app/path/cert
$ mkcert localhost 127.0.0.1
Using the local CA at &quot;/Users/****/Library/Application Support/mkcert&quot; ✨

Created a new certificate valid for the following names 📜
 - &quot;localhost&quot;
 - &quot;127.0.0.1&quot;

The certificate is at &quot;./localhost+1.pem&quot; and the key at &quot;./localhost+1-key.pem&quot; ✅
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;適当なフォルダに証明書と認証キーを作成します。&lt;br /&gt;私は、nuxtのフォルダと並列のディレクトリにcertフォルダを用意して、その中に作成しました。&lt;/p&gt;
&lt;p&gt;また、他のドメイン・IPからもアクセスしたい場合は、引数に追加してください。（&lt;code&gt;mkcert localhost 127.0.0.1 0.0.0.0 192.168.43.41&lt;/code&gt;）&lt;/p&gt;
&lt;p&gt;以上で認証キーの準備完了です。&lt;/p&gt;
&lt;p&gt;余談ですが、opensslを使って&lt;a href=&quot;https://qiita.com/ll_kuma_ll/items/13c962a6a74874af39c6#%E7%99%BA%E8%A1%8C%E8%A6%81%E6%B1%82csr%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E4%BD%9C%E6%88%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;こちらの記事&lt;/a&gt;のようなことをしてました。&lt;br /&gt;これをみたらmkcertのありがたみが100倍になります。。&lt;/p&gt;
&lt;h1&gt;Nuxt.jsの設定&lt;/h1&gt;
&lt;p&gt;こちらも非常に簡単！nuxt.config.jsに以下を追記。&lt;/p&gt;
&lt;p&gt;nuxt.config.js&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;line-numbers language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;// fsとpathを読み込んでない場合は先頭に追記
import fs from &#x27;fs&#x27;
import path from &#x27;path&#x27;

export default {
  // ここから
  server: {
    https: {
      key: fs.readFileSync(path.join(__dirname, &#x27;../cert/localhost+1-key.pem&#x27;)),
      cert: fs.readFileSync(path.join(__dirname, &#x27;../cert/localhost+1.pem&#x27;)),
    },
  },
  // ここまで
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;はい、これで起動すれば、もうSSL化されています。&lt;br /&gt;&lt;a href=&quot;https://localhost:3000&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://localhost:3000&lt;/a&gt; でアクセスできるはず。&lt;/p&gt;
&lt;h2&gt;ひとくふう&lt;/h2&gt;
&lt;p&gt;ただし、このままでは本番環境で実行するときも、認証キーを読み込みにいってしまうので、開発環境のときのみローカルSSL認証されるようにしましょう。&lt;/p&gt;
&lt;p&gt;ということで、こちら。&lt;/p&gt;
&lt;p&gt;nuxt.config.js&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;line-numbers language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;const server = {}
if (process.env.NODE_ENV === &#x27;development&#x27;) {
  server.https = {
    key: fs.readFileSync(path.join(__dirname, &#x27;../cert/localhost+1-key.pem&#x27;)),
    cert: fs.readFileSync(path.join(__dirname, &#x27;../cert/localhost+1.pem&#x27;)),
  }
}

export default {
  server,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これで、開発環境のときのみSSL化に成功しました。&lt;/p&gt;
&lt;h1&gt;まとめ&lt;/h1&gt;
&lt;p&gt;開発環境での認証局・認証ファイルの作成からSSLの設定まで、たったこれだけの作業で可能となります。&lt;/p&gt;
&lt;p&gt;mkcertによって開発環境用の認証キーの作成が簡単に行えたわけですから、応用すればwebpack-dev-serverなど他でも活用できます。&lt;/p&gt;
]]&gt;</content:encoded><category>JavaScript</category><category>Nuxt.js</category><category>Vue</category></item><item><title>&lt;![CDATA[Nuxt.jsで@nuxtjs/gtmを使ったGAの設定]]&gt;</title><link>https://blog.ryou103.com/post/nuxtjs-gtm-ga</link><guid>https://blog.ryou103.com/post/nuxtjs-gtm-ga</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/nuxtjs-gtm-ga/nuxtjs-gtm.png" medium="image"></media:content><description>Nuxt.js SPA・SSR対応のGTMからGAのページトラッキングの設定方法です。nuxt-community/gtm-moduleリポジトリの@nuxtjs/gtmを使った2020年最新の設定方法になります。@nuxtjs/google-tag-managerは廃止されメンテナンスされなくなるので、@nuxtjs/gtmを使いましょう。</description><pubDate>Sun, 17 May 2020 09:00:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/nuxtjs-gtm-ga/nuxtjs-gtm.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Nuxt.jsでは公式からGoogleタグマネージャ（以下、GTM）用のモジュール&lt;a href=&quot;https://www.npmjs.com/package/@nuxtjs/gtm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;@nuxtjs/gtm&lt;/a&gt;を用意してくれているのですが、GTMを使ったGoogleアナリティクス（以下、GA）の設定が通常の設定と異なるので紹介します。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.npmjs.com/package/@nuxtjs/google-tag-manager&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;@nuxtjs/google-tag-manager&lt;/a&gt;を使った古いやり方を紹介しているサイトはあったのですが、このモジュールはDeprecated廃止されていたので、新しいモジュール&lt;a href=&quot;https://www.npmjs.com/package/@nuxtjs/gtm&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;@nuxtjs/gtm&lt;/a&gt;を使ったGAのページトラッキングの設定方法となります。&lt;/p&gt;
&lt;p&gt;リポジトリは、&lt;a href=&quot;https://github.com/nuxt-community/gtm-module&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;nuxt-community/gtm-module&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;（@nuxtjs/google-tag-managerは廃止となり、メンテナンスされなくなるので、@nuxtjs/gtmを使いましょう！）&lt;/p&gt;
&lt;h1&gt;Nuxt.jsの設定&lt;/h1&gt;
&lt;p&gt;@nuxtjs/gtmをインストール&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ yarn add -D @nuxtjs/gtm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;設定ファイルの編集&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;line-numbers language-js&quot; data-filename=&quot;nuxt.config.js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;// 以下を追記
require(&#x27;dotenv&#x27;).config() // GTMのIDを.envから取得するため

export default: {
  buildModules: [
    &#x27;@nuxtjs/gtm&#x27;,
  ],
  gtm: {
    id: process.env.GTM_ID,
    pageTracking: true,
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash:.env&quot;&gt;&lt;code class=&quot;language-bash:.env&quot;&gt;GTM_ID=GTM-*******
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nuxtのバージョンがv2.9より低い場合は、buildModulesではなくmodulesに書いてください。&lt;/p&gt;
&lt;p&gt;コード上の設定は以上。&lt;/p&gt;
&lt;h1&gt;GTMの設定&lt;/h1&gt;
&lt;p&gt;ここからは、GTMの画面上の設定になります。&lt;/p&gt;
&lt;h2&gt;GA_TRACKING_ID用ユーザー定義変数を作成&lt;/h2&gt;
&lt;p&gt;事前にGAのトラッキングIDをメモしておいてください。&lt;/p&gt;
&lt;p&gt;左メニューから「変数」を選択し、ユーザー定義変数の「新規」をクリック。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/nuxtjs-gtm-ga/nuxt-gtm-1.png&quot; alt=&quot;GTMの画面上の設定1&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;変数のタイプ： Googleアナリティクス設定&lt;br /&gt;トラッキングID： UA-*******-*　（GAのトラッキングID）&lt;br /&gt;を設定。&lt;br /&gt;保存名は、GA_TRACKING_IDとか分かれば何でも良い&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/nuxtjs-gtm-ga/nuxt-gtm-1.png&quot; alt=&quot;GTMの画面上の設定2&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;タグを作成&lt;/h2&gt;
&lt;p&gt;左メニューから「タグ」を選択し、「新規」をクリック。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/nuxtjs-gtm-ga/nuxt-gtm-1.png&quot; alt=&quot;GTMの画面上の設定3&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;「タグタイプを選択して設定を開始...」をクリックして、「Google アナリティクス: ユニバーサル アナリティクス」を選択。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/nuxtjs-gtm-ga/nuxt-gtm-1.png&quot; alt=&quot;GTMの画面上の設定4&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;Google アナリティクス設定に、先程作成したユーザー定義変数を指定する。&lt;br /&gt;次に、このタグを発火させるトリガーを作成するために、「トリガーを選択してこのタグを配信...」をクリック。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/nuxtjs-gtm-ga/nuxt-gtm-1.png&quot; alt=&quot;GTMの画面上の設定5&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;トリガーを作成&lt;/h2&gt;
&lt;p&gt;静的サイトであれば、トリガーはAllPagesを選択すれば良いのですが、SPA/SSRでは動かないので、カスタムイベントを作成する必要があります。&lt;/p&gt;
&lt;p&gt;右上の＋をクリックして、新規作成。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/nuxtjs-gtm-ga/nuxt-gtm-1.png&quot; alt=&quot;GTMの画面上の設定6&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;「トリガーのタイプを選択して設定を開始...」をクリックして、「カスタム イベント」を選択。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/nuxtjs-gtm-ga/nuxt-gtm-1.png&quot; alt=&quot;GTMの画面上の設定7&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;イベント名： nuxtRoute&lt;br /&gt;とし、保存名はGA_PageView_Triggerとしました。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/nuxtjs-gtm-ga/nuxt-gtm-1.png&quot; alt=&quot;GTMの画面上の設定8&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;このnuxtRouteというのは、@nuxtjs/gtmがデフォルトでSPA/SSR上でページ遷移があったときに、GTMに送信するイベント名です。&lt;/p&gt;
&lt;p&gt;イベント名は、nuxt.config.jsのgtm設定に以下を追記することで変更できます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;pageViewEventName: &#x27;myCustomEvent&#x27;,
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;設定は以上になります。&lt;br /&gt;下の画像のようになっていれば、問題ないでしょう。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/nuxtjs-gtm-ga/nuxt-gtm-1.png&quot; alt=&quot;GTMの画面上の設定9&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;動作確認&lt;/h1&gt;
&lt;p&gt;GTMの設定したタグが問題なく動作するか、GTMのプレビュー機能を使って確認します。&lt;/p&gt;
&lt;p&gt;ワークスペースの右上に「プレビュー」というボタンがあるのでクリックすると、プレビューモードになります。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/nuxtjs-gtm-ga/nuxt-gtm-1.png&quot; alt=&quot;GTMのプレビュー機能1&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;対象のサイトを見に行くと、ページ下部にGTMのコンソール画面的なものが現れます。&lt;br /&gt;適当にページ遷移すると、左側にnuxtRouteという表示が現れます。これがGTMに送信されているイベントです。&lt;/p&gt;
&lt;p&gt;左側のnuxtRouteをクリックすると、nuxtRouteイベントで発火したタグが表示されます。&lt;br /&gt;ここに先程作成したタグが表示されていれば成功です！&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/nuxtjs-gtm-ga/nuxt-gtm-1.png&quot; alt=&quot;GTMのプレビュー機能2&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;問題なければ、プレビューを終了して、公開ボタンから公開しましょう。&lt;/p&gt;
&lt;p&gt;お疲れ様でした！&lt;br /&gt;念の為、GA上でも確認しておきましょうね。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://offers.jp/a/f67f1c0099ec9313b7c708f12172170a0c7cf76c2cd11641c0480c0916f1b6c1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/offers-pr.png&quot; alt=&quot;offers-pr&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
]]&gt;</content:encoded><category>JavaScript</category><category>Nuxt.js</category><category>Vue</category></item><item><title>&lt;![CDATA[Dockerで動いているGhostをv2→v3にアップデートする]]&gt;</title><link>https://blog.ryou103.com/post/ghost-update-v3-in-docker</link><guid>https://blog.ryou103.com/post/ghost-update-v3-in-docker</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/ghost-update-v3-in-docker/ghost-update-in-docker.png" medium="image"></media:content><description>Ghostのv3がでていたので、Docker上で動いているGhostをドキュメント通りにアップデートしようとしたが、エラーがでたので、Docker上のGhostのアップデート方法を紹介</description><pubDate>Sat, 18 Apr 2020 10:01:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/ghost-update-v3-in-docker/ghost-update-in-docker.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Docker上で動いているGhostのアップデートをしようとしたら、エラーが出たので解決方法をお伝えします。&lt;/p&gt;
&lt;p&gt;結論からいうと、&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;$ docker pull ghost:3-alipine
$ docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;で解決できました。&lt;/p&gt;
&lt;p&gt;Ghostのバージョン3がでていたので、v2.15からv3にアップデートを試みる。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://ghost.org/update/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Ghostのアップデート方法&lt;/a&gt;は公式に載っている。&lt;br /&gt;バックアップをとって、&lt;code&gt;ghost update&lt;/code&gt;すれば良いようです。&lt;/p&gt;
&lt;h1&gt;発生したエラー&lt;/h1&gt;
&lt;p&gt;さっそくアップデートしようとしたら、&lt;code&gt;You can&#x27;t run commands as the &#x27;root&#x27; user.&lt;/code&gt;とエラーがでた。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/ghost-update-v3-in-docker/ghost-update-failed.png&quot; alt=&quot;You can&amp;#x27;t run commands as the &amp;#x27;root&amp;#x27; user.&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;解決方法&lt;/h1&gt;
&lt;p&gt;どうやら、rootユーザーには権限がないようだ。。。&lt;br /&gt;&lt;code&gt;/home&lt;/code&gt; ディレクトリを確認すると、&lt;code&gt;node&lt;/code&gt;というユーザーが存在していた。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;bash-4.4# ls /home/
node
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;nodeユーザーからアップデートするが失敗。&lt;/h2&gt;
&lt;p&gt;nodeユーザーでログインして&lt;code&gt;ghost update&lt;/code&gt;してみる。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker-compose exec --user node ghost ghost update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/ghost-update-v3-in-docker/ghost-update-user-node.png&quot; alt=&quot;ghost update エラー&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;失敗。エラーを見てみると「&lt;strong&gt;v2.0にアップグレードしようとしてるけど、あんたv1.0じゃないよ&lt;/strong&gt;」って言われている。&lt;br /&gt;いや、、v3.0にアップグレードしたいんですが。。&lt;/p&gt;
&lt;p&gt;何が問題かというと&lt;strong&gt;Ghost CLIのバージョンが古かったために、v3.0に対応していなかった&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Ghost CLIをアップグレードして、&lt;code&gt;ghost update&lt;/code&gt;しても良いのですが、Dockerのイメージをv3.0のものにした方がラクなのでそちらの方法を紹介します。&lt;/p&gt;
&lt;h1&gt;Docker上のGhostの正しいアップデート方法&lt;/h1&gt;
&lt;p&gt;Dockerイメージファイルをダウンロード&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker pull ghost:3-alpine
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;docker-composeのimageのバージョンを変更&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-yaml&quot; data-filename=&quot;docker-compose.yml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;version: &quot;3&quot;
services:
  ghost:
    restart: always
    image: ghost:3-alpine
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;コンテナ生成&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;バージョン確認&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;[root@v133-18-171-226 ghost.blog]# docker-compose exec ghost ghost version
Ghost-CLI version: 1.13.1
Ghost version: 3.13.3 (at /var/lib/ghost)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;管理画面もv3.0のUIに変わったことが確認できました！&lt;br /&gt;データがなくなることもなかったです。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/ghost-update-v3-in-docker/ghost-update-completed.png&quot; alt=&quot;管理画面もv3.0のUIに変わったことが確認&quot; /&gt;
&lt;/p&gt;
]]&gt;</content:encoded><category>Ghost</category><category>Docker</category></item><item><title>&lt;![CDATA[npmパッケージの公開&amp;自動化の手順]]&gt;</title><link>https://blog.ryou103.com/post/npm-publish-automatically</link><guid>https://blog.ryou103.com/post/npm-publish-automatically</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/npm-publish-automatically/npm-publish-automatically.png" medium="image"></media:content><description>npmパッケージ公開にあたって調べた、公開の仕方とパッケージの準備、それとGithubのレポジトリにpushしたとき、CircleCIでビルド〜npm公開されるところまでを自動化について説明します。</description><pubDate>Fri, 17 Apr 2020 12:13:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/npm-publish-automatically/npm-publish-automatically.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;はじめて&lt;a href=&quot;https://www.npmjs.com/package/vue-add-event-listener&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;npmパッケージを公開&lt;/a&gt;しました！&lt;br /&gt;パッケージ公開にあたって調べた、公開の仕方とパッケージの準備、それとCircleCIを使った自動化の手順をまとめます。&lt;/p&gt;
&lt;h1&gt;公開の手順&lt;/h1&gt;
&lt;p&gt;npmパッケージの公開までのおおまかな手順なこんな感じ&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;公開するパッケージの準備（ビルドされたファイル）&lt;/li&gt;
  &lt;li&gt;パッケージ情報を入力（packege.json）&lt;/li&gt;
  &lt;li&gt;パッケージの使い方説明(README.md など）&lt;/li&gt;
  &lt;li&gt;npmアカウントを作成&lt;/li&gt;
  &lt;li&gt;npmに公開！（Githubにpushしたら自動的にビルド&amp;#x26;公開されるように）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;使用者の環境に合わせるために3種類の規格に沿ったファイルを用意すること、package.jsonの書き方、CircleCIでの自動化&lt;/strong&gt;&lt;br /&gt;主にここらへんをまとめていきます。&lt;/p&gt;
&lt;h1&gt;公開するパッケージの準備&lt;/h1&gt;
&lt;p&gt;公開するにあたって、パッケージとなるソースコードを書いて完成！というわけにはいきません。&lt;br /&gt;Javascriptといっても様々な規格があり、パッケージをインストールしてくれる人によって使っている規格が異なります。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;require()&lt;/code&gt;を使うCommonJS&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;const VueAddEventListener = require(&#x27;vue-add-event-listener&#x27;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;import&lt;/code&gt;を使うECMAScript&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;import VueAddEventListener from &#x27;vue-add-event-listener&#x27;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ブラウザから直接利用&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&amp;#x3C;script src=&quot;https://unpkg.com/vue-add-event-listener&quot;&gt;&amp;#x3C;/script&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最低限、この3つを用意すると良いでしょう。&lt;/p&gt;
&lt;h2&gt;3つの規格をビルドする&lt;/h2&gt;
&lt;p&gt;好きなように書いた&lt;code&gt;index.js&lt;/code&gt;を&lt;code&gt;yarn build&lt;/code&gt;で、これら3つのJS規格がビルドされるようにします。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;./index.js&lt;/code&gt;をビルドして、&lt;br /&gt;&lt;code&gt;./dist/index.cjs.js&lt;/code&gt;, &lt;code&gt;./dist/index.esm.js&lt;/code&gt;, &lt;code&gt;./dist/index.umd.js&lt;/code&gt;を出力します。&lt;/p&gt;
&lt;p&gt;CommonJS =&gt; &lt;code&gt;./dist/index.cjs.js&lt;/code&gt;&lt;br /&gt;ECMAScript =&gt; &lt;code&gt;./dist/index.esm.js&lt;/code&gt;&lt;br /&gt;ブラウザ用　=&gt; &lt;code&gt;./dist/index.umd.js&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;今回は、手軽にビルドできる&lt;a href=&quot;https://rollupjs.org/guide/en/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Rollup&lt;/a&gt;を用います。（Webpackやparcelを使っても構いません。）&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ yarn add -D rollup @rollup/plugin-buble
&lt;/code&gt;&lt;/pre&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;line-numbers language-js&quot; data-filename=&quot;build/rollup.config.js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;import buble from &#x27;@rollup/plugin-buble&#x27; // 適切にブラウザをサポートするトランスパイラおよびポリフィル

export default {
    input: &#x27;index.js&#x27;, // Path relative to package.json
    output: [
      {
        file: &#x27;dist/index.umd.js&#x27;,
        format: &#x27;umd&#x27;,
        name: &#x27;VueAddEventListener&#x27;,
      },
      {
        file: &#x27;dist/index.esm.js&#x27;,
        format: &#x27;es&#x27;,
      },
      {
        file: &#x27;dist/index.cjs.js&#x27;,
        format: &#x27;cjs&#x27;,
      },
    ],
    // 依存モジュールを含めたくない場合に設定する
    // external: [&#x27;vue&#x27;],
    plugins: [
        buble(), // ES5 へトランスパイルする
    ],
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ビルドのための設定はこれだけです。&lt;/p&gt;
&lt;p&gt;そして、package.jsonに&lt;code&gt;yarn build&lt;/code&gt;で実行できるように追記します。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;rollup --config build/rollup.config.js&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上でビルドは完了です。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ yarn build
yarn run v1.22.4
$ rollup --config build/rollup.config.js

index.js → dist/index.umd.js, dist/index.esm.js, dist/index.cjs.js...
created dist/index.umd.js, dist/index.esm.js, dist/index.cjs.js in 31ms
✨  Done in 0.67s.
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;package.jsonの書き方&lt;/h1&gt;
&lt;p&gt;npmに公開される情報は、package.jsonに書かれた内容とREADME.mdの内容で構成されます。&lt;br /&gt;README.mdについての説明は省きます。（英文がんばってください。。。）&lt;/p&gt;
&lt;p&gt;今回公開したpackage.jsonの中身はこちら。&lt;br /&gt;それぞれ見ていきましょう。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;line-numbers language-json&quot; data-filename=&quot;package.json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;name&quot;: &quot;vue-add-event-listener&quot;,
  &quot;version&quot;: &quot;1.0.3&quot;,
  &quot;description&quot;: &quot;AddEventListener plugin for Vue&quot;,
  &quot;main&quot;: &quot;dist/index.cjs.js&quot;,
  &quot;module&quot;: &quot;dist/index.esm.js&quot;,
  &quot;unpkg&quot;: &quot;dist/index.umd.js&quot;,
  &quot;scripts&quot;: {
    &quot;build&quot;: &quot;rollup --config build/rollup.config.js&quot;
  },
  &quot;repository&quot;: {
    &quot;type&quot;: &quot;git&quot;,
    &quot;url&quot;: &quot;git+https://github.com/bonos103/vue-add-event-listener.git&quot;
  },
  &quot;keywords&quot;: [
    &quot;vue&quot;,
    &quot;addEventListener&quot;
  ],
  &quot;author&quot;: &quot;Ryohei Tomiyama&quot;,
  &quot;license&quot;: &quot;MIT&quot;,
  &quot;bugs&quot;: {
    &quot;url&quot;: &quot;https://github.com/bonos103/vue-event-listener/issues&quot;
  },
  &quot;homepage&quot;: &quot;https://github.com/bonos103/vue-event-listener#readme&quot;,
  &quot;devDependencies&quot;: {
    &quot;@rollup/plugin-buble&quot;: &quot;^0.21.2&quot;,
    &quot;rollup&quot;: &quot;^2.6.0&quot;,
    &quot;vue&quot;: &quot;^2.6.11&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;name&lt;/h2&gt;
&lt;p&gt;まずは、name。こちらがパッケージ名になる重要な部分です。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm install vue-add-event-listener&lt;/code&gt; などで使われる名前になります。&lt;/p&gt;
&lt;p&gt;注意するべき点は、すでに公開されているパッケージと名前が類似していると、公開する際にエラーになります。&lt;/p&gt;
&lt;p&gt;具体的には、&lt;strong&gt;&lt;code&gt;-&lt;/code&gt; &lt;code&gt;.&lt;/code&gt; &lt;code&gt;_&lt;/code&gt; を無視した名前が重複するパッケージ名は使用できません。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;vue-add-event-listener&lt;/code&gt; &lt;code&gt;vue-addeventlistener&lt;/code&gt; &lt;code&gt;vue_add_event_listener&lt;/code&gt; &lt;code&gt;vue.add-event-listener&lt;/code&gt; は同じ名前とみなされます。&lt;/p&gt;
&lt;h2&gt;description&lt;/h2&gt;
&lt;p&gt;npmサイトのパッケージ一覧に表示される説明文&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/npm-publish-automatically/npm_description.png&quot; alt=&quot;npmサイトのパッケージ一覧に表示される説明文&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;version&lt;/h2&gt;
&lt;p&gt;パッケージのバージョン&lt;br /&gt;&lt;strong&gt;npmパッケージを更新したいときに、このバージョンも変更しないと、npm上に反映されません。&lt;/strong&gt;&lt;br /&gt;どんなに小さい修正だろうと、1.0.1 → 1.0.2 のように変更してください。&lt;/p&gt;
&lt;h2&gt;main, module, unpkg&lt;/h2&gt;
&lt;p&gt;ビルドした3つのファイルを指定します。&lt;br /&gt;これらに指定することで、各環境に合わせて使われるファイルが自動で変わります。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&quot;main&quot;: &quot;dist/index.cjs.js&quot;,
&quot;module&quot;: &quot;dist/index.esm.js&quot;,
&quot;unpkg&quot;: &quot;dist/index.umd.js&quot;,
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;main&lt;/h3&gt;
&lt;p&gt;CommonJSの環境で使うファイルを指定。&lt;/p&gt;
&lt;h3&gt;module&lt;/h3&gt;
&lt;p&gt;ECMAScriptの環境で使うファイルを指定。&lt;/p&gt;
&lt;h3&gt;unpkg&lt;/h3&gt;
&lt;p&gt;ブラウザから使うファイルを指定。&lt;br /&gt;これを指定すると、unpkg.comのサイトからファイルにアクセスできるようになります。&lt;br /&gt;&lt;a href=&quot;https://unpkg.com/vue-add-event-listener&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://unpkg.com/vue-add-event-listener&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;ここで注意が必要なのが、unpkgに指定したファイルはGithubのリポジトリ入れておく必要があります。&lt;br /&gt;&lt;code&gt;.gitignore&lt;/code&gt;でdistディレクトリを無視する設定をしている場合は、&lt;code&gt;!dist/index.umd.js&lt;/code&gt;を追記してあげてください。&lt;/p&gt;
&lt;h2&gt;repository&lt;/h2&gt;
&lt;p&gt;npmサイトの右側にRepositoryとして、表示されます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;  &quot;repository&quot;: {
    &quot;type&quot;: &quot;git&quot;,
    &quot;url&quot;: &quot;git+https://github.com/bonos103/vue-add-event-listener.git&quot;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/npm-publish-automatically/npm_repository.png&quot; alt=&quot;npmサイトの右側にRepositoryとして、表示されます。&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;homepage&lt;/h2&gt;
&lt;p&gt;npmサイトの右側にHomepageとして、表示されます。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/npm-publish-automatically/npm_homepage.png&quot; alt=&quot;npmサイトの右側にHomepageとして、表示されます。&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;keywords&lt;/h2&gt;
&lt;p&gt;npmサイトのパッケージ一覧に表示されます。&lt;br /&gt;検索などにも引っかかるものなので、しっかりと指定しましょう。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/npm-publish-automatically/npm_keywords.png&quot; alt=&quot;npmサイトのパッケージ一覧に表示されます。  &quot; /&gt;
&lt;/p&gt;
&lt;p&gt;とりあえず、これだけ指定しておけばnpmに公開するのに支障ないと思われます。&lt;/p&gt;
&lt;h1&gt;npmの公開方法&lt;/h1&gt;
&lt;p&gt;公開自体には難しいことはなく、npmアカウントを作成して、&lt;code&gt;npm login&lt;/code&gt; して &lt;code&gt;npm publish&lt;/code&gt;すれば公開できます。&lt;br /&gt;自動化せずにコマンドから公開する手順は、&lt;a href=&quot;https://qiita.com/hoshimado/items/c6f1484297d974f44f19&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;こちらの記事&lt;/a&gt;が参考になります。&lt;/p&gt;
&lt;h1&gt;CircleCIでビルド〜公開までを自動化する&lt;/h1&gt;
&lt;p&gt;ここまで読めばnpmを公開すること自体はできますが、今回はGithubのレポジトリにmasterブランチをpushしたとき、CircleCIで3つのファイルを生成するビルドが行われて、npmに公開されるところまでを自動化したいと思います。&lt;/p&gt;
&lt;p&gt;CircleCIの実行ファイルはこちら。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;line-numbers language-yaml&quot; data-filename=&quot;.circleci/config.yml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;version: 2
defaults: &amp;#x26;defaults
  docker:
    - image: circleci/node:12.14.1
  working_directory: ~/repo
jobs:
  install:
    &amp;#x3C;&amp;#x3C;: *defaults
    steps:
      - checkout
      - restore_cache:
          keys:
            - v1-dependencies-{{ .Branch }}-{{ checksum &quot;yarn.lock&quot; }}
            - v1-dependencies-{{ .Branch }}-
            - v1-dependencies-
      - run: yarn
      - save_cache:
          key: v1-dependencies-{{ .Branch }}-{{ checksum &quot;yarn.lock&quot; }}
          paths:
              - node_modules/
      - persist_to_workspace:
          root: ~/repo
          paths:
            - .
  build:
    &amp;#x3C;&amp;#x3C;: *defaults
    steps:
      - attach_workspace:
          at: ~/repo
      - run: yarn build
  
  deploy:
    &amp;#x3C;&amp;#x3C;: *defaults
    steps:
      - attach_workspace:
          at: ~/repo
      - run: echo &quot;//registry.npmjs.org/:_authToken=$NPM_TOKEN&quot; &gt; ~/repo/.npmrc
      - run: npm publish

workflows:
  version: 2
  deploy-vue-event-listener:
    jobs:
      - install:
          filters:
            branches:
              only: /^master/
      - build:
          requires:
            - install
          filters:
            branches:
              only: /^master/
      - deploy:
          requires:
            - install
            - build
          filters:
            branches:
              only: /^master/
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;実行ファイルの説明&lt;/h2&gt;
&lt;h3&gt;jobs&lt;/h3&gt;
&lt;p&gt;実行するジョブは３つ。&lt;br /&gt;node_modulesをインストールするinstall&lt;br /&gt;3つのファイルを生成するbuild&lt;br /&gt;npmに公開するdeploy&lt;/p&gt;
&lt;h3&gt;workflows&lt;/h3&gt;
&lt;p&gt;これらのジョブをworkflowsを使ってinstall → build → deployの順番でジョブが実行されるようにしています。&lt;/p&gt;
&lt;p&gt;workflows内でrequiresを指定すると、指定されたジョブが実行完了されたあとに実行するようにしてくれます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;requires:
  - install
  - build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;filtersの設定で、branchがmasterのときだけ実行するようにしています。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;filters:
  branches:
    only: /^master/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;また、workflowsを使うときに重要なのが、persist_to_workspaceとattach_workspaceです。&lt;/p&gt;
&lt;p&gt;persist_to_workspaceでディレクトリを共有して、とattach_workspaceでそのディレクトリを参照することができます。&lt;/p&gt;
&lt;p&gt;この設定によって、installで生成された、node_modules/がその後に続く、build, deployのジョブから参照できるようになります。&lt;/p&gt;
&lt;p&gt;もしこの設定がなかったら、ジョブはそれぞれ別のコンテナ（空間）で実行されるので、buildを実行しても、node_modulesをインストールできていないので、失敗します。&lt;/p&gt;
&lt;h2&gt;npm publishの準備&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;npm publish&lt;/code&gt; をする前にnpmのログイン情報が入っている、&lt;code&gt;.npmrc&lt;/code&gt;を作成しています。&lt;br /&gt;このときに必要なTokenの取得方法とCircleCIへの設定方法を説明します。&lt;/p&gt;
&lt;p&gt;まずは、&lt;a href=&quot;https://www.npmjs.com/signup&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;npmの会員登録&lt;/a&gt;を行ってください。&lt;/p&gt;
&lt;p&gt;会員登録が完了したら、パソコンのターミナルから&lt;code&gt;npm login&lt;/code&gt; します。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/npm-publish-automatically/npm_login.png&quot; alt=&quot;パソコンのターミナルからnpm login&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;ログインが完了しました。&lt;/p&gt;
&lt;p&gt;そしたら、Tokenを取得するために、&lt;code&gt;cat ~/.npmrc&lt;/code&gt; します。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/npm-publish-automatically/npm_token.png&quot; alt=&quot;Tokenを取得&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;ここに表示される06451a9f….がTokenになります。&lt;/p&gt;
&lt;p&gt;続いて、CircleCIからこのTokenが使えるように環境変数に設定します。&lt;br /&gt;（まだCircleCIとレポジトリを連携してない場合はしてください。）&lt;/p&gt;
&lt;p&gt;プロジェクトの設定画面に進んでください。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/npm-publish-automatically/npm_circleci_setting.png&quot; alt=&quot;CircleCIからこのTokenが使えるように環境変数に設定&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;設定画面の「Environment Variables」を開き、「Add Variable」ボタンを押して、環境変数を追加します。&lt;/p&gt;
&lt;p&gt;Name: NPM_TOKEN&lt;br /&gt;Value: { 先程取得したToken }&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/npm-publish-automatically/npm_environment.png&quot; alt=&quot;環境変数を追加&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;以上の設定で、ビルド〜公開までを自動化が完了になります。&lt;br /&gt;あとは、masterブランチをpushすれば、ジョブが実行されてnpmに公開されます！&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/npm-publish-automatically/npm_jobs.png&quot; alt=&quot;ジョブが実行&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;1回のpushで3つのjobが動きました！&lt;br /&gt;そして、npmにも公開されました！🎉&lt;br /&gt;&lt;a href=&quot;https://www.npmjs.com/package/vue-add-event-listener&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.npmjs.com/package/vue-add-event-listener&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/npm-publish-automatically/npm_publish.png&quot; alt=&quot;npmにも公開&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;結構長い道のりになってしまいましたが、良い経験になりました。&lt;br /&gt;おつかれさまでした。&lt;/p&gt;
]]&gt;</content:encoded><category>npm</category></item><item><title>&lt;![CDATA[GoogleMapsのMarkerClusterのアイコンをカスタマイズ]]&gt;</title><link>https://blog.ryou103.com/post/marker-cluster-customize</link><guid>https://blog.ryou103.com/post/marker-cluster-customize</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/marker-cluster-customize/marker-cluster-customize.png" medium="image"></media:content><description>MarkerClustererPlusというライブラリを使うと、Google Maps 上のマーカーを地図のスケールによってクラスタリング（統合）することができます。導入の仕方は、Maps JavaScript APIのドキュメントに書かれていますが、細かいオプションについては説明されていないので、各オプションについて解説していきます。</description><pubDate>Mon, 30 Mar 2020 13:10:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/marker-cluster-customize/marker-cluster-customize.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/googlemaps/v3-utility-library/tree/master/packages/markerclustererplus&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MarkerClustererPlus&lt;/a&gt;というライブラリを使うと、Google Maps 上のマーカーを地図のスケールによってクラスタリング（統合）することができます。&lt;/p&gt;
&lt;p&gt;導入の仕方は、&lt;a href=&quot;https://developers.google.com/maps/documentation/javascript/marker-clustering&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Maps JavaScript APIのドキュメント&lt;/a&gt;に書かれていますが、細かいオプションについては説明されていないので、各オプションについて解説していきます。&lt;/p&gt;
&lt;p&gt;（すべてのオプションを網羅していないので、もっと詳しく知りたい場合はコードに書かれたコメント見てください。）&lt;/p&gt;
&lt;h1&gt;画像を使ったクラスタアイコン&lt;/h1&gt;
&lt;h2&gt;imagePath&lt;/h2&gt;
&lt;p&gt;ドキュメントでは、&lt;code&gt;imagePath&lt;/code&gt;を使ってクラスタアイコンを表示しています。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;new MarkerClusterer(
  map,
  markers,
  {
    imagePath: &#x27;https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m&#x27;,
  },
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;imagePath&lt;/code&gt;に指定するのは、画像のフルパスではない点に注意。&lt;br /&gt;上記例で説明すると、&lt;code&gt;https://develop...lusterer/m&lt;/code&gt; とmで終わっています。&lt;/p&gt;
&lt;p&gt;上記で指定した&lt;code&gt;imagePath&lt;/code&gt;にアクセスしていただいたらわかりますが、404となります。&lt;br /&gt;このパスに1.pngや2.pngを終わりに追加すると、画像が確認できます。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m1.png&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m1.png&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m2.png&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m2.png&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;imagePathと数字の大きさが、クラスタの大きさに比例します。&lt;br /&gt;以下の画像でいう、青色のクラスタはm1.png。黄色のクラスタはm2.pngとなります。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/marker-cluster-customize/marker-cluster-1.png&quot; alt=&quot;imagePathと数字の大きさが、クラスタの大きさに比例&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;オリジナルの画像を使う場合は、hoge1.png, hoge2.png ...と、画像名＋連番で作成します。&lt;/p&gt;
&lt;h2&gt;imageExtension&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;imagePath&lt;/code&gt; と合わせて使います。&lt;br /&gt;画像の拡張子を設定します。&lt;/p&gt;
&lt;p&gt;前述したとおり、&lt;code&gt;imagePath&lt;/code&gt; + &lt;code&gt;1.png&lt;/code&gt; が画像名となりますが、画像をjpgやgifで作成した場合は、こちらのオプションで拡張子を変更しましょう。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;{
  imagePath: &#x27;https://your_custom_image_path/m&#x27;,
  imageExtension: &#x27;jpg&#x27;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;imageSizes&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;imagePath&lt;/code&gt; と合わせて使います。&lt;br /&gt;画像のサイズをpxで指定します。&lt;br /&gt;オリジナル画像を使う場合は、指定しましょう。&lt;/p&gt;
&lt;p&gt;画像の表示サイズを設定できるのではなく、使う画像のサイズを指定する点に注意。&lt;br /&gt;画像サイズと合っていないと、画像が欠けてしまったり、位置がズレたりしてしまいます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;{
  imagePath: &#x27;https://your_custom_image_path/m&#x27;,
  imageExtension: &#x27;jpg&#x27;,
  imageSizes: [30, 40, 50],
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;enableRetinaIcons&lt;/h2&gt;
&lt;p&gt;アイコン画像と表示サイズが異なるときに設定すると幸せになります。&lt;/p&gt;
&lt;p&gt;デフォルトでは、imageSizesで述べた通り、画像サイズと指定サイズが異なると画像が欠けますが、そちらを指定するだけで、画像サイズを指定サイズにリサイズしてくれます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;{
  enableRetinaIcons: true,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/marker-cluster-customize/marker-cluster-enable-resolution-icons.png&quot; alt=&quot;enableRetinaIcons&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;stylesを使ったクラスタアイコンのカスタマイズ&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;styles&lt;/code&gt;オプションを使うと、各サイズのクラスタに対して細かいカスタマイズが可能になります。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;{
  styles: [
    {
      url: &#x27;https://your_custom_image_path/m1.png&#x27;,
      className: &#x27;cluster-1&#x27;,
      height: &#x27;30&#x27;,
      width: &#x27;30&#x27;,
      anchorText: [0, 0],
      anchorIcon: [0, 0],
    },
    {
      url: &#x27;https://your_custom_image_path/m2.png&#x27;,
      className: &#x27;cluster-2&#x27;,
      height: &#x27;35&#x27;,
      width: &#x27;35&#x27;,
      anchorText: [0, 0],
      anchorIcon: [0, 0],
    },
    {
      url: &#x27;https://your_custom_image_path/m3.png&#x27;,
      className: &#x27;cluster-3&#x27;,
      height: &#x27;40&#x27;,
      width: &#x27;40&#x27;,
      anchorText: [0, 0],
      anchorIcon: [0, 0],
    },
  ],
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;こんな感じでArray型で各クラスタのオプションを指定します。&lt;/p&gt;
&lt;h2&gt;styles[&#x27;url&#x27;]&lt;/h2&gt;
&lt;p&gt;クラスタアイコンのファイルパスをしてします。&lt;br /&gt;&lt;code&gt;imagePath&lt;/code&gt; と違いパスの最後まで入力します。&lt;/p&gt;
&lt;h2&gt;styles[&#x27;className&#x27;]&lt;/h2&gt;
&lt;p&gt;クラスタアイコンに対して、クラス名を指定できます。&lt;br /&gt;このクラス名に対してCSSでクラスタアイコンをカスタマイズできるので、画像を使わずにCSSのみでクラスタアイコンを作成することも可能です。&lt;/p&gt;
&lt;h2&gt;styles[&#x27;height&#x27;] / styles[&#x27;width&#x27;]&lt;/h2&gt;
&lt;p&gt;こちらのオプションは必須です。&lt;br /&gt;名前の通り、高さと幅を指定します。&lt;/p&gt;
&lt;h2&gt;styles[&#x27;anchorText&#x27;] / styles[&#x27;anchorIcon&#x27;]&lt;/h2&gt;
&lt;p&gt;文字と画像の位置を中心地よりずらすことができます。&lt;br /&gt;例えば以下の画像のようなピンアイコンは、ピンの先を中心にしたいので、これらのオプションが有効になってきます。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/marker-cluster-customize/marker-cluster-2.png&quot; alt=&quot;文字と画像の位置を中心地よりずらすことができます&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;他にも、textColor, textSize, textLineHeight などスタイルを変更できるオプションがありますが、基本的にclassNameで指定したクラス名から設定できるので、割愛します。&lt;/p&gt;
&lt;p&gt;詳しくはこちらを御覧ください↓&lt;br /&gt;&lt;a href=&quot;https://github.com/googlemaps/v3-utility-library/blob/master/packages/markerclustererplus/src/cluster-icon.ts#L48&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/googlemaps/v3-utility-library/blob/master/packages/markerclustererplus/src/cluster-icon.ts#L48&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;その他オプション&lt;/h1&gt;
&lt;h2&gt;gridSize&lt;/h2&gt;
&lt;p&gt;マーカーをクラスタリングする範囲を指定できます。&lt;br /&gt;値を小さくするとクラスタにまとまりやすく、大きくするとクラスタにまとまりにくくなります。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;{
  gridSize: 60,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;gridSize: 20&lt;/code&gt;と&lt;code&gt;gridSize: 60&lt;/code&gt;で比較するとまとまり方が違うことがわかると思います。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/marker-cluster-customize/marker-cluster-3.png&quot; alt=&quot;マーカーをクラスタリングする範囲を指定できます&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;maxZoom&lt;/h2&gt;
&lt;p&gt;マーカーをクラスタリングする最大の拡大値を指定できます。&lt;br /&gt;指定した値以上マップで拡大をすると、クラスタで表示されなくなります。&lt;br /&gt;値は、GoogleMapsAPIのzoomと同じです。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;{
  maxZoom: 10,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;zoomOnClick&lt;/h2&gt;
&lt;p&gt;クラスタをクリックしたときに、クラスタの範囲にズームするかを設定できます。&lt;br /&gt;デフォルトでは、trueになってり、クラスタをクリックすると拡大されます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;{
  zoomOnClick: true,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;averageCenter&lt;/h2&gt;
&lt;p&gt;クラスタにまとめられたマーカーの中心地にクラスタアイコンを表示するかを設定できます。&lt;br /&gt;falseの場合は、クラスタの中の最初のマーカーの位置に表示されます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;{
  averageCenter: true,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;minimumClusterSize&lt;/h2&gt;
&lt;p&gt;クラスタの範囲内に&lt;code&gt;minimumClusterSize&lt;/code&gt; の値よりマーカーの数が多いとき、クラスタリングされます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;{
  minimumClusterSize: 4,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;まだまだ説明しきれていないオプションが存在しますが、以上のオプションで大抵のことはできると思います。&lt;/p&gt;
&lt;p&gt;気になる方は、コードを読み解いてみてください。&lt;/p&gt;
]]&gt;</content:encoded></item><item><title>&lt;![CDATA[RailsでTime.nowが日本時間にならないときはTZを確認しましょう]]&gt;</title><link>https://blog.ryou103.com/post/rails-docker-timezone</link><guid>https://blog.ryou103.com/post/rails-docker-timezone</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/rails-docker-timezone/rails-docker-timezone.png" medium="image"></media:content><description>DockerでRailsの開発を作成した際に、Time.nowが日本時間になってくれず困ったので備忘録。結論、サーバーのタイムゾーンがUTCのままだったので環境変数TZを設定することで解決しました。</description><pubDate>Sat, 29 Feb 2020 14:32:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/rails-docker-timezone/rails-docker-timezone.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;DockerでRailsの開発を作成した際に、Time.nowが日本時間になってくれず困ったので備忘録。&lt;/p&gt;
&lt;p&gt;結論、サーバーのタイムゾーンがUTCのままだったので環境変数TZを設定することで解決しました。&lt;/p&gt;
&lt;h1&gt;Time.nowが日本時間になってくれない！？&lt;/h1&gt;
&lt;p&gt;現在の時刻が22:38ですが、rails上でUTC時刻になってしまいます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&gt; Time.now
=&gt; 2020-02-29 13:38:34 +0000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;config/application.rb&lt;/code&gt; に &lt;code&gt;config.time_zone = &#x27;Tokyo&#x27;&lt;/code&gt; を記載していても、Time.nowには反映されません。&lt;/p&gt;
&lt;p&gt;なぜなら、&lt;code&gt;config/application.rb&lt;/code&gt;の設定は&lt;a href=&quot;https://api.rubyonrails.org/v6.0.2.1/classes/ActiveSupport/TimeWithZone.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;TimeWithZone&lt;/a&gt;というメソッド（Time.zone.nowなど）に効果があり、Time自体はRailsの設定に影響されないためです。&lt;/p&gt;
&lt;p&gt;なので、&lt;code&gt;config/application.rb&lt;/code&gt; の設定だけでは、以下のように&lt;code&gt;Time.zone.now&lt;/code&gt;と&lt;code&gt;Time.now&lt;/code&gt;で結果が変わってしまう。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&gt; Time.zone.now
=&gt; Sat, 29 Feb 2020 22:40:13 JST +09:00

&gt; Time.now
=&gt; 2020-02-29 13:40:20 +0000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Time.now&lt;/code&gt;はサーバーのタイムゾーンを参照しており、&lt;code&gt;Time.zone.now&lt;/code&gt;はRailsのタイムゾーンを参照しているためである。&lt;/p&gt;
&lt;p&gt;このままでは、不具合が出る可能性が高くなってしまうので、サーバーのタイムゾーンもRailsのタイムゾーンに合わせましょう。&lt;/p&gt;
&lt;h1&gt;サーバーのタイムゾーンを日本時間に変更&lt;/h1&gt;
&lt;p&gt;Dockerfileに環境変数を設定しました。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;ENV TZ Asia/Tokyo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DockerComposeを使っている場合は、docker-compose.ymlに環境変数を設定してもよいでしょう。ただ一度設定したら変えるものでもないので、Dockerfileに記載する方がスッキリすると思います。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;services:
  rails:
    environment:
      TZ: Asia/Tokyo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;コンテナをビルド＆再起動するのを忘れずに。&lt;/p&gt;
&lt;h1&gt;チェック&lt;/h1&gt;
&lt;p&gt;無事、&lt;code&gt;Time.zone.now&lt;/code&gt;と&lt;code&gt;Time.now&lt;/code&gt;で同じ結果となりました。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&gt; Time.zone.now
=&gt; Sat, 29 Feb 2020 23:08:52 JST +09:00
&gt; Time.now
=&gt; 2020-02-29 23:08:55 +0900
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;P.S.&lt;br /&gt;RailsではTimeWithZoneを使った方がRailsの設定上で動くので良いそうです。&lt;/p&gt;
]]&gt;</content:encoded><category>Rails</category><category>Docker</category></item><item><title>&lt;![CDATA[毎日スノーボード、毎日シゴト。という暮らし]]&gt;</title><link>https://blog.ryou103.com/post/winter-life-2020</link><guid>https://blog.ryou103.com/post/winter-life-2020</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/winter-life-2020/winter-life-2020.png" medium="image"></media:content><description>1月からちょっと特殊な働き方を実践していて、午前はスノーボード（約4時間）、午後は仕事（約6時間）、土日関係なく週7日稼働しています。なので、週5日8時間勤務と変わらない労働時間確保しつつ、毎日スノーボードができるという、夢のようなライフスタイル。このような暮らしを始めて、1ヶ月が経ったので感じたことをまとめました。</description><pubDate>Tue, 18 Feb 2020 13:32:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/winter-life-2020/winter-life-2020.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;1月からちょっと特殊な働き方を実践していて、午前はスノーボード（約4時間）、午後は仕事（約6時間）、土日関係なく週7日稼働しています。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;週5日8時間勤務と変わらない労働時間確保しつつ、毎日スノーボードができるという、夢のようなライフスタイル。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ちなみに、仕事はフルリモートで行っていて、キャンピングカーで生活しています。ゲレンデ徒歩0分、勤務地徒歩0分。&lt;/p&gt;
&lt;p&gt;このような暮らしを始めて、1ヶ月が経ったので感じたことをまとめました。&lt;/p&gt;
&lt;h1&gt;なぜこのような暮らしを選択したのか&lt;/h1&gt;
&lt;p&gt;一言で言えば、&lt;strong&gt;「スノーボードのため」&lt;/strong&gt; です。&lt;/p&gt;
&lt;p&gt;今まで通りの働き方をしていたら、土日の週2日がスノーボードに当てれる時間になります。&lt;br /&gt;土日は混雑でリフト待ちが発生するし、大学生の頃のように、1日フルで滑るのはしんどいです。。身体が。。&lt;/p&gt;
&lt;p&gt;さらに、天気が良いとも限りません。強風でリフトが止まるかも。大雪でパークが開かないかも。&lt;/p&gt;
&lt;p&gt;そしてなにより、好きなことは毎日したい！じゃないですか。&lt;/p&gt;
&lt;p&gt;これらの課題を解決する方法が &lt;strong&gt;「毎日スノーボード、毎日シゴト。という暮らし」&lt;/strong&gt; だったのです。&lt;br /&gt;仕事する時間は減らさずに、スノーボードの時間を最大化する方法として、このような働き方をしています。&lt;/p&gt;
&lt;h1&gt;メリット&lt;/h1&gt;
&lt;p&gt;実際に挑戦してみて良かったこと。&lt;/p&gt;
&lt;h2&gt;生活の軸が趣味&lt;/h2&gt;
&lt;p&gt;天気が良いから、スノーボード多め。&lt;br /&gt;天気が悪いから、仕事多め。&lt;br /&gt;午前はアイスバーンだから、午前中は仕事しよう。&lt;br /&gt;みたく、趣味を軸に1日の行動を決めれます。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/winter-life-2020/winter-life-2020-1.png&quot; alt=&quot;スノーボードの様子&quot; /&gt;&lt;br /&gt;（天気が良い日は、多めに滑っています。）
&lt;/p&gt;
&lt;p&gt;会社員の頃は、平日は仕事を軸に動かざるを得なかった（勤務時間も決まってるし、出勤しなくちゃいけないし）ので、今の暮らしは &lt;strong&gt;「1日を行動をコントロール ≒ 自由に生きる」&lt;/strong&gt; ような感覚です。&lt;/p&gt;
&lt;h2&gt;仕事のモチベーション・効率アップ&lt;/h2&gt;
&lt;p&gt;「今日まだあと4時間もあるじゃん」とか、「明日も仕事か・・・」「まだ水曜日・・・」「明日は月曜日・・・」みたいな負のモチベーションを持つことはありません。&lt;/p&gt;
&lt;p&gt;むしろ、明日も仕事か・・・よりも、明日も滑れる！のワクワクが大きいからかも。&lt;/p&gt;
&lt;p&gt;効率が良くなったかは、測っているわけではないのですが、土日は仕事仲間からの連絡が来ないので、もくもくと作業ができるし、休みがないので「先週は何してたっけ？」みたいなブランクもなく仕事に取り組めます。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/winter-life-2020/winter-life-2020-2_2.png&quot; alt=&quot;キャンピングカー内での仕事風景&quot; /&gt;&lt;br /&gt;キャンピングカーの中で仕事しています。
&lt;/p&gt;
&lt;h2&gt;毎日温泉&lt;/h2&gt;
&lt;p&gt;毎日、温泉入ってます。&lt;br /&gt;&lt;a href=&quot;http://hakuba-happo-onsen.jp/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;八方温泉&lt;/a&gt;の冬季温泉パスポート（30,000円）で4ヶ所の温泉入り放題。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/winter-life-2020/winter-life-2020-3-1.png&quot; alt=&quot;八方温泉冬季パスポート&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;同伴者100円引と温泉まんじゅう5％割引が特典で付いています。&lt;/p&gt;
&lt;h1&gt;デメリット&lt;/h1&gt;
&lt;h2&gt;土日は、メンバーが稼働していないから質問できない。&lt;/h2&gt;
&lt;p&gt;これは仕方のないことですね。&lt;br /&gt;しっかりとテキストに残して週明け質問できるようにしています。&lt;/p&gt;
&lt;h2&gt;身体のダメージが残る&lt;/h2&gt;
&lt;p&gt;遊び疲れとかではなく、関節痛や炎症などの症状が治らなくなってしまいました。&lt;br /&gt;毎日痛みを我慢しながら滑っているため、ダメージが蓄積してしまっています。&lt;br /&gt;身体の老いを感じます。。&lt;/p&gt;
&lt;h2&gt;食生活の乱れ&lt;/h2&gt;
&lt;p&gt;昼飯はカップラーメン。夕飯はお惣菜。&lt;/p&gt;
&lt;p&gt;オフシーズンは3食とも自炊。しかも、畑もやっているので野菜たっぷりですので、明らかに食事が偏っています。。どうしたものか、、&lt;/p&gt;
&lt;h1&gt;まとめ&lt;/h1&gt;
&lt;p&gt;こんな感じで1ヶ月間の暮らしを振り返ってみました。&lt;br /&gt;考えを文章にまとめてみて思ったのは、1日の行動をコントロールできるのが今の暮らしに満足している1番の理由かなと思いました。&lt;br /&gt;「行動をコントロールできているか」は今後も判断軸として持ち続けたいなと思います。&lt;/p&gt;
]]&gt;</content:encoded></item><item><title>&lt;![CDATA[vue-svg-loaderとfile-loaderを使い分ける]]&gt;</title><link>https://blog.ryou103.com/post/webpack-oneof</link><guid>https://blog.ryou103.com/post/webpack-oneof</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/webpack-oneof/webpack-oneof.png" medium="image"></media:content><description>Vue内でSVGを読み込む際に、アイコンは色や塗り・線などのスタイルを変更するためにvue-svg-loaderで読み込みたいし、ロゴはimgタグで出力するためにfile-loaderで読み込みたい。WebpackのoneOfを使えば、vue-svg-loaderとfile-loaderの使い分けができる。</description><pubDate>Tue, 21 Jan 2020 12:32:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/webpack-oneof/webpack-oneof.png&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;やりたいこと&lt;/h2&gt;
&lt;p&gt;SVGをVueComponentとして読み込む場合と、ファイルパスとして読み込む場合のどちらにも対応したい。&lt;/p&gt;
&lt;p&gt;例えば、アイコンなどは、vue-svg-loaderを使ってVueComponentとして読み込んで、色や塗り、線などをスタイルの変更をしたい。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;&amp;#x3C;template lang=&quot;pug&quot;&gt;
  download-icon.icon
&amp;#x3C;/template&gt;
&amp;#x3C;script&gt;
import DownloadIcon from &#x27;@/assets/images/icons/download.svg&#x27;

export default {
  components: {
    DownloadIcon,
  },
}
&amp;#x3C;/script&gt;
&amp;#x3C;style&gt;
  .icon {
    fill: #000;
  }
&amp;#x3C;/style&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ロゴ画像の場合は、ファイルパスとして読み込んで画像として出力したい。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;&amp;#x3C;template lang=&quot;pug&quot;&gt;
  img(src=&quot;@/assets/images/logo.svg&quot;)
&amp;#x3C;/template&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;しかし、VueCLIのデフォルトの設定やvue-svg-loaderのリファレンスの設定だと、&lt;strong&gt;どちらか一方の方法でしか読み込めない&lt;/strong&gt;ので、設定方法を変更する必要がある。&lt;/p&gt;
&lt;h2&gt;実現方法&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://webpack.js.org/configuration/module/#ruleoneof&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;strong&gt;WebpackにoneOf&lt;/strong&gt;という設定&lt;/a&gt;がある。&lt;/p&gt;
&lt;p&gt;oneOfを使うと、一つの条件（今回の場合は、&lt;code&gt;test: /\.svg/&lt;/code&gt; ）に対して、どのLoaderを使うかを指定できる。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://webpack.js.org/configuration/module/#ruleoneof&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;公式リファレンス&lt;/a&gt;では、cssに対して、&lt;code&gt;hoge.css?inline&lt;/code&gt;と&lt;code&gt;?inline&lt;/code&gt;を付けた場合は、url-loaderで読み込み、&lt;code&gt;hoge.css?file&lt;/code&gt;とした場合は、file-loaderで読み込む設定となっている。&lt;/p&gt;
&lt;p&gt;今回紹介するのは、SVGファイルに対して、デフォルトではfile-loaderで読み込み、&lt;code&gt;@/assets/images/icons/download.svg?component&lt;/code&gt;と&lt;code&gt;?component&lt;/code&gt;と付けた場合は、vue-svg-loaderで読み込みVueComponentになるように設定する。&lt;/p&gt;
&lt;h2&gt;実装&lt;/h2&gt;
&lt;p&gt;Webpackの設定は以下のようになる。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;module.exports = {
  //...
  module: {
    rules: [
      {
        test: /\.svg$/,
        oneOf: [
          // VueComponent
          {
            resourceQuery: /component/,
            use: &#x27;vue-svg-loader&#x27;,
          },
          // デフォルト
          {
            use: &#x27;file-loader&#x27;,
          },
        ],
      },
    ],
  },
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;VueCLIを使っている場合&lt;/h3&gt;
&lt;p&gt;VueCLIの場合は、すでにsvgに対するルールが設定されているので、それを書き換える必要がある。&lt;/p&gt;
&lt;p&gt;vue.config.jsに以下の設定を追加する。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;module.exports = {
  //...
  chainWebpack: (config) =&gt; {
    const svgRule = config.module.rule(&#x27;svg&#x27;)
    svgRule
      .oneOf(&#x27;component&#x27;)
      .resourceQuery(/component/)
      .use(&#x27;vue-svg-loader&#x27;)
      .loader(&#x27;vue-svg-loader&#x27;)
      .end()
      .end()
    svgRule.oneOf(&#x27;normal&#x27;).uses.merge(svgRule.uses.entries())
    svgRule.uses.clear()
  },
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上の設定で、VueComponentとして読み込むこともできるし、ファイルパスとして読み込むことができる。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;以下のように、VueComponentとファイルパスの両方で、SVGを読み込むことができる。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;&amp;#x3C;template lang=&quot;pug&quot;&gt;
  div
    download-icon.icon
    img(src=&quot;@/assets/images/logo.svg&quot;)
&amp;#x3C;/template&gt;
&amp;#x3C;script&gt;
import DownloadIcon from &#x27;@/assets/images/icons/download.svg?component&#x27;

export default {
  components: {
    DownloadIcon,
  },
}
&amp;#x3C;/script&gt;
&amp;#x3C;style&gt;
  .icon {
    fill: #000;
  }
&amp;#x3C;/style&gt;
&lt;/code&gt;&lt;/pre&gt;
]]&gt;</content:encoded><category>Webpack</category><category>Vue</category></item><item><title>&lt;![CDATA[CSSだけでノート風の罫線を表現する]]&gt;</title><link>https://blog.ryou103.com/post/css-ruled-line</link><guid>https://blog.ryou103.com/post/css-ruled-line</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/css-ruled-line/ruled_line_eyecatch.png" medium="image"></media:content><description>ノートのような一行ごとに罫線があるデザインをCSSのみで作成しました。CodePenでサンプルを確認してください。background-image: linear-gradient(#ccc 1px, transparent 1px);background-size: 100% 3em;</description><pubDate>Mon, 06 Jan 2020 14:26:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/css-ruled-line/ruled_line_eyecatch.png&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;完成サンプル&lt;/h2&gt;
&lt;p&gt;ノートのような一行ごとに罫線があるデザインをCSSのみで作成しました。&lt;/p&gt;&lt;iframe height=&quot;370&quot; style=&quot;width: 100%;&quot; scrolling=&quot;no&quot; title=&quot;ノート風の罫線&quot; src=&quot;https://codepen.io/RyoheiTomiyama/embed/dyPJZLy?height=265&amp;theme-id=default&amp;default-tab=css,result&quot; frameborder=&quot;no&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot;&gt;
  See the Pen &lt;a href=&#x27;https://codepen.io/RyoheiTomiyama/pen/dyPJZLy&#x27;&gt;ノート風の罫線&lt;/a&gt; by RyoheiTomiyama
  (&lt;a href=&#x27;https://codepen.io/RyoheiTomiyama&#x27;&gt;@RyoheiTomiyama&lt;/a&gt;) on &lt;a href=&#x27;https://codepen.io&#x27;&gt;CodePen&lt;/a&gt;.
&lt;/iframe&gt;
&lt;h2&gt;ポイント&lt;/h2&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;.note {
  background-image: linear-gradient(#ccc 1px, transparent 1px);
  background-size: 100% 3em;
  line-height: 3;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;罫線は、背景にグラデーションを指定することで表現できる。&lt;br /&gt;最初の1pxを線である#cccに。1px目以降を透明（transparent）なグラデーションにして、背景を繰り返すと、1行ごとに罫線が引かれます。&lt;/p&gt;
&lt;p&gt;行間を&lt;code&gt;line-height: 3&lt;/code&gt;としているため、背景も3emごとに繰り返しています。&lt;br /&gt;行間を狭めたい場合は、&lt;code&gt;line-height&lt;/code&gt;と&lt;code&gt;background-size&lt;/code&gt;の数値を小さくすれば可能です。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/css-ruled-line/ruled_line@2x.png&quot; alt=&quot;ruled_line&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;このままでは、文末に罫線が表示されない状態です。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/css-ruled-line/ruled_line_2@2x.png&quot; alt=&quot;ruled_line_2&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;文末にも線を引くには、&lt;code&gt;padding-bottom&lt;/code&gt;で調整して、文末にもグラデーションの線を表示させあげましょう。&lt;/p&gt;
&lt;p&gt;完成形はこちら。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;.note {
  background-image: linear-gradient(#ccc 1px, transparent 1px);
  background-size: 100% 3em;
  line-height: 3;
+ padding-bottom: 1px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/css-ruled-line/ruled_line_3@2x.png&quot; alt=&quot;ruled_line_3&quot; /&gt;
&lt;/p&gt;
]]&gt;</content:encoded><category>CSS</category></item><item><title>&lt;![CDATA[Lodashのファイルサイズを削減する]]&gt;</title><link>https://blog.ryou103.com/post/lodash-reduce-bundle</link><guid>https://blog.ryou103.com/post/lodash-reduce-bundle</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/lodash-reduce-bundle/lodash-reduce-bundle.jpg" medium="image"></media:content><description>Lodashをマニュアル通り読み込んでしまうと、315個もの関数を読み込むことになるので、Lodashだけで69.02KBものサイズになってしまう。そこで必要な関数だけを読み込むようにコードを変更すると、Lodashにかかるファイルサイズは16.46KBまで抑えることができた。また、babelを使っている場合は、babel-plugin-lodashを読み込めば、コードを修正することなく、ファイルサイズを削減することができます。</description><pubDate>Sat, 17 Aug 2019 23:00:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/lodash-reduce-bundle/lodash-reduce-bundle.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Lodashを&lt;code&gt;import _ from &#x27;lodash&#x27;;&lt;/code&gt;と読み込んでしまうと、315個もの関数を読み込むことになる。&lt;br /&gt;Lodashだけで、69.02KBものサイズになってしまう。&lt;/p&gt;
&lt;p&gt;そこで必要な関数だけを読み込むようにコードを以下のように変更したら、今回の検証では、Lodashにかかるファイルサイズは16.46KBまで抑えることができた。&lt;/p&gt;
&lt;p&gt;約７５%削減することになる。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;// 必要な関数だけを読み込む
import compact from &#x27;lodash/compact&#x27;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ちなみに、babelを使っている場合は、&lt;a href=&quot;https://github.com/lodash/babel-plugin-lodash&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;babel-plugin-lodash&lt;/a&gt;をpluginに設定するだけで、必要な関数だけをバンドルしてくれるようになります。&lt;/p&gt;
&lt;h1&gt;検証&lt;/h1&gt;
&lt;p&gt;3パターンの読み込み方で検証しました。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bonos103/webpack-lodash&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;検証用リポジトリ&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;パターン1&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot; data-filename=&quot;index1.js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;import _ from &#x27;lodash&#x27;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;パターン2&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot; data-filename=&quot;index2.js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;import { join, compact, cloneDeep } from &#x27;lodash&#x27;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;パターン3&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot; data-filename=&quot;index3.js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;import join from &#x27;lodash/join&#x27;;
import compact from &#x27;lodash/compact&#x27;;
import cloneDeep from &#x27;lodash/cloneDeep&#x27;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;webpackの設定は以下。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;const BundleAnalyzerPlugin = require(&#x27;webpack-bundle-analyzer&#x27;).BundleAnalyzerPlugin;

module.exports = {
  mode: &#x27;production&#x27;,
  entry: {
    index1: &#x27;./src/index1.js&#x27;,
    index2: &#x27;./src/index2.js&#x27;,
    index3: &#x27;./src/index3.js&#x27;,
  },
  output: {
    filename: &#x27;[name].js&#x27;,
    path: __dirname + &#x27;/dist&#x27;,
  },
  plugins: [
    new BundleAnalyzerPlugin(),
  ],
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://www.npmjs.com/package/webpack-bundle-analyzer&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;webpack-bundle-analyzer&lt;/a&gt;は、各バンドルで何がファイルサイズを食っているのかを可視化できるツールです。便利。&lt;/p&gt;
&lt;h2&gt;結果&lt;/h2&gt;
&lt;h3&gt;各ファイルサイズ&lt;/h3&gt;
&lt;p&gt;パターン1 70.7KB&lt;br /&gt;パターン2 70.7KB&lt;br /&gt;パターン3 18.1KB&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/lodash-reduce-bundle/webpack-compile.png&quot; alt=&quot;webpack-compile&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/lodash-reduce-bundle/webpack-analyzer.png&quot; alt=&quot;webpack-analyzer&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;パターン1とパターン2は同じファイルサイズとなった。&lt;code&gt;from &#x27;lodash&#x27;&lt;/code&gt;といった感じで読み込んでいる場合は、export defaultを取得しようが、複数のexportを取得しようが、ファイルサイズは変わらないようだ。&lt;/p&gt;
&lt;p&gt;パターン3のように使う関数だけを読み込む&lt;code&gt;import compact from &#x27;lodash/compact&#x27;;&lt;/code&gt;なら不要なコードはバンドルされないので、結果として軽くなった。&lt;/p&gt;
&lt;h1&gt;Babelを使っている場合&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/lodash/babel-plugin-lodash&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;babel-plugin-lodash&lt;/a&gt;&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot; data-filename=&quot;.babelrc&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;# 以下を追加
&quot;plugins&quot;: [&quot;lodash&quot;],
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;pluginを設定するだけOKです！&lt;br /&gt;ソース内のLodash部分を全て書き換えるのは骨が折れそうですから、babelを使っているならこちらをお勧めします。&lt;/p&gt;
]]&gt;</content:encoded><category>Lodash</category></item><item><title>&lt;![CDATA[DockerComposeには必ずログの設定をしよう]]&gt;</title><link>https://blog.ryou103.com/post/docker-compose-logging</link><guid>https://blog.ryou103.com/post/docker-compose-logging</guid><description>DockerComposeを使って起動したDockerコンテナのログは、docker-compose logs SERVICE_NAMEで確認できる。しかし、このログはコンテナを削除しない限りずっと残ってしまう。ログが溜まれば、ディスクを圧迫してしまうことにもなるし、何万行もあるログを確認することになると、最新のログが表示されるまで何十秒もかかってしまう。ログの設定を行い、快適なDockerライフを送りましょう。</description><pubDate>Wed, 14 Aug 2019 13:42:00 +0000</pubDate><content:encoded>&lt;![CDATA[
&lt;p&gt;DockerComposeを使って起動したDockerコンテナのログは、&lt;code&gt;docker-compose logs SERVICE_NAME&lt;/code&gt;で確認できる。&lt;/p&gt;
&lt;p&gt;しかし、このログはコンテナを削除しない限りずっと残ってしまう。&lt;/p&gt;
&lt;p&gt;ログが溜まれば、ディスクを圧迫してしまうことにもなるし、何万行もあるログを確認することになると、最新のログが表示されるまで何十秒もかかってしまう。&lt;/p&gt;
&lt;p&gt;ログの設定を行い、快適なDockerライフを送りましょう。&lt;/p&gt;
&lt;h1&gt;DockerComposeでのログの設定&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;http://docs.docker.jp/compose/compose-file.html#logging&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;公式ドキュメント&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;私が開発環境で行っているログ設定はこちら。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;services:
  app:
    logging:
      driver: json-file
      options:
        max-file: &#x27;1&#x27;
        max-size: 3m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ログを最新の3MBだけ記録しています。&lt;/p&gt;
&lt;h1&gt;設定について&lt;/h1&gt;
&lt;h2&gt;driver&lt;/h2&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;driver: json-file
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ここでは、ログの保存方式を指定しています。&lt;br /&gt;&lt;code&gt;json-file&lt;/code&gt;はデフォルトの設定で、その名の通りJSONファイルとして保存します。&lt;br /&gt;他にも、&lt;code&gt;syslog, awslogs, gcplogs&lt;/code&gt; など設定できます。&lt;/p&gt;
&lt;h2&gt;options&lt;/h2&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;options:
  max-file: &#x27;1&#x27;
  max-size: 3m
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1ログファイルmax-sizeまで保存され、max-file数のログファイルを残しておくことができます。&lt;/p&gt;
&lt;p&gt;開発環境では、古いログは不要なので、最大3MBまで保存していれば十分でしょう。&lt;/p&gt;
]]&gt;</content:encoded><category>Docker</category></item><item><title>&lt;![CDATA[nsurlsessiond-stop]]&gt;</title><link>https://blog.ryou103.com/post/nsurlsessiond-stop</link><guid>https://blog.ryou103.com/post/nsurlsessiond-stop</guid><description>nsurlsessiondというプロセスがネットワークを占めていて他の通信ができない状態になることがありました。MacOSソフトウェア・アップデートの「新しいアップデートがある場合はダウンロード」からチェックを外すことで、nsurlsessiondが通信することがなくなりました。</description><pubDate>Tue, 23 Jul 2019 03:44:25 +0000</pubDate><content:encoded>&lt;![CDATA[
&lt;p&gt;ある日突然、インターネット接続がとても重くなり、なかなかWebページが表示されなくなった。&lt;br /&gt;モバイルWifiの電波の入りが悪くなったのかなと確認しても電波は３本立っていて問題ない。&lt;/p&gt;
&lt;p&gt;アクティビティモニターでネットワークを確認すると、nsurlsessiondというプロセスが圧倒的に通信量を占めていることがわかりました。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/nsurlsessiond-stop/nsurlsessiond--1-.png&quot; alt=&quot;nsurlsessiondの通信量暴走中&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;ほんの少しの時間確認しただけで、あっという間にnsurlsessiondが１GBも消費。&lt;/p&gt;
&lt;p&gt;通信してそうなアプリを落としても止まらない。再起動しても止まらない。。&lt;br /&gt;色々調査した結果、&lt;strong&gt;MacOSのソフトウェア・アップデートが原因&lt;/strong&gt;でした。&lt;/p&gt;
&lt;h1&gt;nsurlsessiondを止める&lt;/h1&gt;
&lt;p&gt;結論から書くと、ソフトウェア・アップデートの「新しいアップデートがある場合はダウンロード」からチェックを外すことで解消しました。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/nsurlsessiond-stop/system.png&quot; alt=&quot;system1&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;環境設定からソフトウェアアップデートをクリック。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/nsurlsessiond-stop/system2.png&quot; alt=&quot;system2&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;右下の詳細をクリック。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/nsurlsessiond-stop/system3.png&quot; alt=&quot;system3&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;出てきたポップアップ画面の、「新しいアップデートがある場合はダウンロード」からチェックを外す。&lt;/p&gt;
&lt;p&gt;これでnsurlsessiondの通信が止まりました。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/nsurlsessiond-stop/nsurlsessiond-stop-no-show.png&quot; alt=&quot;nsurlsessiondが表示されなくなった&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;新しいソフトウェア・アップデートがあると、自動でダウンロードしていたために知らないところで通信されていたみたいです。&lt;/p&gt;
&lt;h1&gt;iCloudが原因という説も・・・&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://book.mynavi.jp/macfan/detail_summary/id=103019&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://book.mynavi.jp/macfan/detail_summary/id=103019&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;こちらの記事では、iCloudへの同期が原因で通信量がかかってしまっていると言っています。&lt;/p&gt;
&lt;p&gt;たしかに、この可能性もあるので、ソフトウェア・アップデートの方で改善されない場合は試してみてください。&lt;/p&gt;
&lt;p&gt;nsurlsessiondというのは、Mac自体のプロセスのようなので、外部アプリが原因という可能性は低く、appleに関係のあるサービスが原因になっていると考えられます。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/nsurlsessiond-stop/nsurlsessiond_log.png&quot; alt=&quot;nsurlsessiond_log&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;モバイルWiFi利用者にとっては痛い通信量&lt;/h1&gt;
&lt;p&gt;勝手に１GBとかの通信が行われてしまうわけですから、YMobileのPocketWiFiやUQ Wimaxを利用している人からしたら、あっという間に通信制限に引っかかってします。&lt;br /&gt;テザリングをしているときに、この現象が起こったときは悲惨ですね。&lt;/p&gt;
&lt;h1&gt;まとめ&lt;/h1&gt;
&lt;p&gt;今回のnsurlsessiondが通信量を占める現象は、&lt;a href=&quot;https://px.a8.net/svt/ejp?a8mat=35DJ1D+7FX4JM+3KKM+BWVTE&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;&lt;strong&gt;どんなときもWiFi&lt;/strong&gt;&lt;/a&gt;&lt;img border=&quot;0&quot; width=&quot;1&quot; height=&quot;1&quot; src=&quot;https://www12.a8.net/0.gif?a8mat=35DJ1D+7FX4JM+3KKM+BWVTE&quot; alt=&quot;&quot;&gt;を利用していて起こりました。&lt;/p&gt;
&lt;p&gt;どんなときもWiFiは、通信量制限がない代わりに、最大通信速度が低いのです。（他のモバイルルーターと比較すると1/4以下）&lt;br /&gt;ですので、ソフトウェア・アップデートのデータをダウンロードするのに、帯域のほとんどを使ってしまい、他の通信ができなくなっていたのが原因でした。&lt;/p&gt;
]]&gt;</content:encoded></item><item><title>&lt;![CDATA[Adonis入門 #2（DB操作編）]]&gt;</title><link>https://blog.ryou103.com/post/adonisjs-second</link><guid>https://blog.ryou103.com/post/adonisjs-second</guid><description>AdonisJsでDB（mysql）に接続して、マイグレーションを使ったテーブルの作成します。そして、AdonisJsからデータの挿入・取得・削除の方法を紹介します。</description><pubDate>Thu, 18 Jul 2019 22:36:00 +0000</pubDate><content:encoded>&lt;![CDATA[
&lt;p&gt;前回は、&lt;a href=&quot;https://blog.ryou103.com/post/adonisjs-first/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Adonis入門（導入編）&lt;/a&gt;ということで、AdonisJsを立ち上げてRouteを操作する方法を学びました。&lt;/p&gt;
&lt;p&gt;今回は、DB（mysql）に接続してテーブルの作成からデータの挿入・取得・削除をしてみようと思います。&lt;/p&gt;
&lt;h1&gt;事前に&lt;/h1&gt;
&lt;p&gt;mysqlを使うので、起動しておいてください。&lt;/p&gt;
&lt;h1&gt;DBへの接続設定&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://adonisjs.com/docs/4.1/database&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;公式リファレンス&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/config/database.js&lt;/code&gt;がDBの設定ファイルになっています。&lt;/p&gt;
&lt;p&gt;すでにmysqlへの接続設定は書かれているため、環境変数で接続先を設定するだけで接続することができます。&lt;/p&gt;
&lt;p&gt;AdonisJsでは、環境変数の設定にdotenvを採用しています。ですので、ルートディレクトリにある&lt;code&gt;.env&lt;/code&gt;の以下の部分を編集します。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot; data-filename=&quot;.env&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;DB_CONNECTION=mysql  # ここで接続するDBにmysqlを選択
# DBの設定に合わせて値を変えてください
DB_HOST=127.0.0.1
DB_PORT=3336
DB_USER=adonis
DB_PASSWORD=adonis
DB_DATABASE=adonis
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;DBへの接続&lt;/h1&gt;
&lt;p&gt;AdonisからDBにアクセスするために、mysqlパッケージをインストール&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ yarn add mysql
もしくは
$ yarn add mysql2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;もし、mysql2を使う場合は、&lt;code&gt;config/database.js&lt;/code&gt;の修正する必要があります。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot; data-filename=&quot;config/database.js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;...
mysql: {
    client: &#x27;mysql2&#x27;,   // ⇐ ここを変更
    connection: {
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DBに接続ができているか確認してみます。&lt;br /&gt;&lt;code&gt;start/routes.js&lt;/code&gt;に以下のrouteを追加&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;Route.get(&#x27;/db&#x27;, async () =&gt; {
  return await Database.table(&#x27;users&#x27;).select(&#x27;*&#x27;)
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;もちろん、データベースの中身が空っぽなので、以下のように表示されます。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonisjs-second/adonis1-1.jpg&quot; alt=&quot;adonis1-1&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;別のエラーが起こるようであれば、DBの接続設定が間違っている可能性があるので、見直してください。&lt;/p&gt;
&lt;h1&gt;Modelを使ったテーブルの作成&lt;/h1&gt;
&lt;p&gt;続いては、TODOアプリを作りたいので、まずはTODO用のテーブルを作成します。&lt;/p&gt;
&lt;h2&gt;ModelとMigrationファイルの作成&lt;/h2&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ adonis make:model Todo --migration
✔ create  app/Models/Todo.js
✔ create  database/migrations/1561644221251_todo_schema.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;--migration&lt;/code&gt;を付けると、モデルの作成と一緒にマイグレーションファイルも作成してくれます。&lt;/p&gt;
&lt;p&gt;作成された段階ではこんな感じ。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot; data-filename=&quot;database/migrations/1561644221251_todo_schema.js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&#x27;use strict&#x27;

/** @type {import(&#x27;@adonisjs/lucid/src/Schema&#x27;)} */
const Schema = use(&#x27;Schema&#x27;)

class TodoSchema extends Schema {
  up () {
    this.create(&#x27;todos&#x27;, (table) =&gt; {
      table.increments()
      table.timestamps()
    })
  }

  down () {
    this.drop(&#x27;todos&#x27;)
  }
}

module.exports = TodoSchema
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;すでに&lt;code&gt;increments&lt;/code&gt;と&lt;code&gt;timestamps&lt;/code&gt;が定義されています。&lt;br /&gt;&lt;code&gt;increments&lt;/code&gt;はプライマリキーであるidを自動で加算してくれます。&lt;br /&gt;&lt;code&gt;timestamps&lt;/code&gt;はcreated_atとupdated_atを自動付与してくれます。&lt;/p&gt;
&lt;h2&gt;Migration設定&lt;/h2&gt;
&lt;p&gt;作成されたマイグレーションファイルにTodoテーブルの内容を書いていきます。&lt;/p&gt;
&lt;p&gt;カラムは、タイトル・詳細内容・完了ステータス。&lt;/p&gt;
&lt;p&gt;Migrationの詳しい使い方は、&lt;a href=&quot;https://adonisjs.com/docs/4.1/migrations&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;公式リファレンス&lt;/a&gt;を参照&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot; data-filename=&quot;database/migrations/1561644221251_todo_schema.js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&#x27;use strict&#x27;

/** @type {import(&#x27;@adonisjs/lucid/src/Schema&#x27;)} */
const Schema = use(&#x27;Schema&#x27;)

class TodoSchema extends Schema {
  up () {
    this.create(&#x27;todos&#x27;, (table) =&gt; {
      table.increments()
      table.timestamps()
      table.string(&#x27;title&#x27;).notNullable()
      table.text(&#x27;content&#x27;)
      table.boolean(&#x27;done&#x27;)
    })
  }

  down () {
    this.drop(&#x27;todos&#x27;)
  }
}

module.exports = TodoSchema
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;タイトル（title）は、絶対に入力されている必要があるので、&lt;code&gt;notNullable&lt;/code&gt;を付けてnullの状態を禁止にしました。&lt;br /&gt;詳細内容（content）は、text型、完了ステータス（done）は、boolean型で定義しました。&lt;/p&gt;
&lt;h2&gt;Migration実行&lt;/h2&gt;
&lt;p&gt;TODOテーブルを定義できたので、Migrationを実行してDB上にテーブルを作成します。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ adonis migration:run
migrate: 1503250034279_user.js
migrate: 1503250034280_token.js
migrate: 1561644221251_todo_schema.js
Database migrated successfully in 1.97 s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Migrationが完了しました。&lt;br /&gt;TODO以外にも、デフォルトで作成されていたuserとtokenモデルもMigrationされました。&lt;/p&gt;
&lt;p&gt;テーブルが作成されたかをmysqlから確認します。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonisjs-second/adonis1-2.jpg&quot; alt=&quot;adonis1-2&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;定義した通り、TODOテーブルが作成されていました。&lt;/p&gt;
&lt;h1&gt;データの作成・取得・更新・削除&lt;/h1&gt;
&lt;p&gt;作成したTODOテーブルをAdonisから操作してみます。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;make:model&lt;/code&gt;した際に、TODOモデル（&lt;code&gt;app/Models/Todo.js&lt;/code&gt;）が作成されているので、それを利用して操作していきます。&lt;/p&gt;
&lt;h2&gt;データの作成&lt;/h2&gt;
&lt;p&gt;TODOを作成して、データをjsonを返すルートを作成します。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot; data-filename=&quot;start/routes.js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;const Todo = use(&#x27;App/Models/Todo&#x27;)

Route.get(&#x27;/todo/add&#x27;, async () =&gt; {
  const todo = new Todo()
  todo.title = &#x27;はじめてのTODOアイテム&#x27;
  await todo.save()
  return todo;
})

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;titleが「はじめてのTODOアイテム」であるTodoを作成して保存しているシンプルなコードです。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;http://127.0.0.1:3333/todo/add&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://127.0.0.1:3333/todo/add&lt;/a&gt; にアクセスして、作成されたことが確認できます。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonisjs-second/adonis1-3.jpg&quot; alt=&quot;adonis1-3&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;データの取得&lt;/h2&gt;
&lt;p&gt;作成したTODOの一覧を取得します。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;Route.get(&#x27;/todo&#x27;, async () =&gt; {
  return await Todo.all();
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonisjs-second/adonis1-4.jpg&quot; alt=&quot;adonis1-4&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;TODOの中からidで一つだけ取得するには、&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;Route.get(&#x27;/todo/:id&#x27;, async ({ params }) =&gt; {
  const { id } = params;
  return await Todo.find(id)
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;データの削除&lt;/h2&gt;
&lt;p&gt;id=1のデータを削除してみましょう。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;Route.get(&#x27;/todo/:id/delete&#x27;, async ({ params }) =&gt; {
  const { id } = params;
  const todo = await Todo.find(id)
  return await todo.delete()
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonisjs-second/adonis1-5.jpg&quot; alt=&quot;adonis1-5&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;一覧を取得して確認すると、id: 1がなくなっていますね。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonisjs-second/adonis1-6.jpg&quot; alt=&quot;adonis1-6&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;まとめ&lt;/h1&gt;
&lt;p&gt;DBに接続して、簡単な操作を行いました。&lt;/p&gt;
&lt;p&gt;次回は、モデルを使って実際の運用を想定した、タイトルでの検索であったり、doneカラムで完了・未完了のフィルタリングに挑戦します。&lt;/p&gt;
&lt;p&gt;リレーションを使ってTODOにメッセージを追加できるようにしてみようと思います。&lt;/p&gt;
]]&gt;</content:encoded><category>AdonisJS</category></item><item><title>&lt;![CDATA[（サービス終了）Amazonパントリーで重くてかさばる買い物から開放されよう]]&gt;</title><link>https://blog.ryou103.com/post/amazon-pantry</link><guid>https://blog.ryou103.com/post/amazon-pantry</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/amazon-pantry/amazon-pantry.jpg" medium="image"></media:content><description>はじめてAmazonパントリーというサービスを使って買い物して、あまりにラクだったのでみんなにも知って欲しいのでご紹介！今後は、買う物が決まっている消耗品類はAmazonパントリーで買うことになるでしょう。Amazonパントリーとは、食品・日用品など毎日使うものを必要な分だけお買い求めいただける、Amazonプライム会員向けのサービスです。</description><pubDate>Mon, 15 Jul 2019 09:18:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/amazon-pantry/amazon-pantry.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Amazonパントリーは2021年8月24日23時59分をもって終了しました。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;はじめて&lt;a target=&quot;_blank&quot; href=&quot;https://www.amazon.co.jp/b/?ie=UTF8&amp;amp;node=3485873051&amp;amp;ref_=topnav_storetab_pantry&amp;_encoding=UTF8&amp;tag=blog-ryou103-22&amp;linkCode=ur2&amp;linkId=45bc8925f04d845726a40a84097ad98a&amp;camp=247&amp;creative=1211&quot;&gt;Amazonパントリー&lt;/a&gt;&lt;img src=&quot;//ir-jp.amazon-adsystem.com/e/ir?t=blog-ryou103-22&amp;l=ur2&amp;o=9&quot; width=&quot;1&quot; height=&quot;1&quot; border=&quot;0&quot; alt=&quot;&quot; style=&quot;border:none !important; margin:0px !important;&quot; /&gt;というサービスを使って買い物して、あまりにラクだったのでみんなにも知って欲しいのでご紹介！&lt;/p&gt;
&lt;h1&gt;Amazonパントリーとは&lt;/h1&gt;
&lt;blockquote&gt;
  &lt;p&gt;食品・日用品など毎日使うものを必要な分だけお買い求めいただける、Amazonプライム会員向けのサービスです。&lt;/p&gt;
&lt;/blockquote&gt;&lt;iframe src=&quot;https://rcm-fe.amazon-adsystem.com/e/cm?o=9&amp;p=294&amp;l=ur1&amp;category=pantry&amp;banner=105R21KBWWH18CFGAG82&amp;f=ifr&amp;linkID=8ef6bae1e944354dae053602236819b9&amp;t=blog-ryou103-22&amp;tracking_id=blog-ryou103-22&quot; width=&quot;320&quot; height=&quot;100&quot; scrolling=&quot;no&quot; border=&quot;0&quot; marginwidth=&quot;0&quot; style=&quot;border:none;&quot; frameborder=&quot;0&quot;&gt;&lt;/iframe&gt;
&lt;h2&gt;少量から買える&lt;/h2&gt;
&lt;p&gt;例えば、安いものだと&lt;a target=&quot;_blank&quot; href=&quot;https://www.amazon.co.jp/%E3%81%84%E3%81%AA%E3%81%B0%E9%A3%9F%E5%93%81-%E3%81%84%E3%81%AA%E3%81%B0-%E3%83%A9%E3%82%A4%E3%83%88%E3%83%84%E3%83%8A%E3%83%95%E3%83%AC%E3%83%BC%E3%82%AF-70g/dp/B01N63D3VY/ref=sr_1_18?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&amp;amp;keywords=%E3%83%84%E3%83%8A%E7%BC%B6&amp;amp;qid=1563181811&amp;amp;s=pantry&amp;amp;sr=8-18&amp;amp;srs=3485873051&amp;_encoding=UTF8&amp;tag=blog-ryou103-22&amp;linkCode=ur2&amp;linkId=e18e9ab98d29a52ddb08c9cbfd59dd81&amp;camp=247&amp;creative=1211&quot;&gt;ツナ缶を1缶112円&lt;/a&gt;&lt;img src=&quot;//ir-jp.amazon-adsystem.com/e/ir?t=blog-ryou103-22&amp;l=ur2&amp;o=9&quot; width=&quot;1&quot; height=&quot;1&quot; border=&quot;0&quot; alt=&quot;&quot; style=&quot;border:none !important; margin:0px !important;&quot; /&gt;から購入可能です。&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.amazon.co.jp/%E3%81%84%E3%81%AA%E3%81%B0%E9%A3%9F%E5%93%81-%E3%81%84%E3%81%AA%E3%81%B0-%E3%83%A9%E3%82%A4%E3%83%88%E3%83%84%E3%83%8A%E3%83%95%E3%83%AC%E3%83%BC%E3%82%AF-70g/dp/B01N63D3VY/ref=sr_1_18?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&amp;amp;keywords=%E3%83%84%E3%83%8A%E7%BC%B6&amp;amp;qid=1563181811&amp;amp;s=pantry&amp;amp;sr=8-18&amp;amp;srs=3485873051&amp;_encoding=UTF8&amp;tag=blog-ryou103-22&amp;linkCode=ur2&amp;linkId=e18e9ab98d29a52ddb08c9cbfd59dd81&amp;camp=247&amp;creative=1211&quot;&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/amazon-pantry/amazon-1.jpg&quot; alt=&quot;amazonパントリーだとツナ缶1缶から購入可能&quot; /&gt;&lt;/a&gt;
&lt;/p&gt;
&lt;p&gt;Amazonで普通に買おうとすると24缶まとめてだったりするので、そんなに欲しくないときに3,4缶買うといったことができます。&lt;/p&gt;
&lt;h2&gt;パントリー限定クーポンで安く買える&lt;/h2&gt;
&lt;p&gt;日によって割引商品は違いますが、5円OFFから30%OFFで買うことできます。&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.amazon.co.jp/gp/pantry/info/sale/ref=pantry-wayfinder-1-1-7?&amp;_encoding=UTF8&amp;tag=blog-ryou103-22&amp;linkCode=ur2&amp;linkId=330e6a9ec4c4ee7cb34d71a7b9153339&amp;camp=247&amp;creative=1211&quot;&gt;クーポン対象商品一覧&lt;/a&gt;&lt;img src=&quot;//ir-jp.amazon-adsystem.com/e/ir?t=blog-ryou103-22&amp;l=ur2&amp;o=9&quot; width=&quot;1&quot; height=&quot;1&quot; border=&quot;0&quot; alt=&quot;&quot; style=&quot;border:none !important; margin:0px !important;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;1箱にまとめて&lt;/h2&gt;
&lt;p&gt;たくさんの種類の商品を買っても1箱に納めて発送してくれます。&lt;br /&gt;商品ページに、「パントリーBoxの使用率」という表示があり、購入商品の使用率合計は100%までは1箱に納めて買うことできます。&lt;br /&gt;ティッシュやトイレットペーパーなどは、10,20％とかさばるので注意が必要ですね。&lt;/p&gt;
&lt;h2&gt;1箱390円の手数料がかかる&lt;/h2&gt;
&lt;p&gt;Amazonプライム会員でも、390円の手数料がかかってしまいます。しかし、現在まとめ割引キャンペーンで、対象商品を9点以上買うと390円OFFとなるので実質手数料無料で買うことができます。&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;https://www.amazon.co.jp/gp/pantry/info/pantrypromotion?&amp;_encoding=UTF8&amp;tag=blog-ryou103-22&amp;linkCode=ur2&amp;linkId=d8f2aa26b36e428162852ff72d2b8f6a&amp;camp=247&amp;creative=1211&quot;&gt;Amazonパントリーまとめて割引キャンペーンについて&lt;/a&gt;&lt;img src=&quot;//ir-jp.amazon-adsystem.com/e/ir?t=blog-ryou103-22&amp;l=ur2&amp;o=9&quot; width=&quot;1&quot; height=&quot;1&quot; border=&quot;0&quot; alt=&quot;&quot; style=&quot;border:none !important; margin:0px !important;&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;実際に購入してみた&lt;/h1&gt;
&lt;p&gt;何点か欲しい商品を買ってみました。&lt;br /&gt;届いたダンボールはこちら。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/amazon-pantry/amazon-5.jpg&quot; alt=&quot;amazon-5&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;ちょっとへこみが気になりました。&lt;br /&gt;いつもAmazonから届くダンボールより柔らかいような。。。&lt;/p&gt;
&lt;p&gt;商品はこんな感じに詰まっていました。&lt;br /&gt;こちらの写真でダンボールの大きさは伝わりますかね。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/amazon-pantry/amazon-4.jpg&quot; alt=&quot;amazon-4&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;今回は、商品数が少なかったからか、隙間にはしっかりと緩衝材が入っていました。&lt;/p&gt;
&lt;p&gt;特大サイズの柔軟剤・ティッシュペーパー・お菓子などなど、普段スーパーやホームセンターで買うものが注文した次の日に届きました。ラク。&lt;/p&gt;
&lt;p&gt;この重くてかさばる商品たちが、玄関で受け取ることができるなんて。ラク。&lt;/p&gt;
&lt;p&gt;しかも、実店舗で買うより安い気がします。&lt;/p&gt;
&lt;h1&gt;おすすめの買い方&lt;/h1&gt;
&lt;p&gt;重くてかさばる商品がラクに安く買えるのは、とてもメリットだと思いますが、9点以上買わないと手数料でまあまあ高くなってしまいます。&lt;/p&gt;
&lt;p&gt;ですので、大きい商品と小さい商品を上手く合わせて買うことで、1箱に納めて手数料0で買う必要があります。&lt;/p&gt;
&lt;p&gt;日用品を例に、1箱に収まる商品をチョイスしてみました。それがこちら。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/amazon-pantry/amazon-3.jpg&quot; alt=&quot;amazon-3&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;まずはじめに、ティッシュやトイレットペーパーなどのかさばるもの、シャンプーやコンディショナーなど重い物など、実店舗で買うのが面倒な商品を詰め込んでいきます。&lt;/p&gt;
&lt;p&gt;欲しい商品を一通りカートに入れたら、手数料が無料になる対象商品を検討します。&lt;br /&gt;まずはカートを確認しにいき、あと何点の対象商品を買うべきかをチェックします。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/amazon-pantry/amazon-2.jpg&quot; alt=&quot;amazon-2&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;「あと6個追加」する必要があるみたいです。&lt;/p&gt;
&lt;p&gt;調味料や缶詰など、普段消費するもので、安くてかさばらない商品をカートに入れていきました。&lt;/p&gt;
&lt;p&gt;結果的に、15点5,678円の買い物となりました。&lt;/p&gt;
&lt;p&gt;これらをお店から一度で持ち帰ると思うと、両手が千切れそうになるでしょう。&lt;/p&gt;
&lt;p&gt;それを玄関まで運んでくれ、スーパーで買うより安く、時間もかからない。&lt;/p&gt;
&lt;h1&gt;まとめ&lt;/h1&gt;
&lt;p&gt;こんなに便利でお得なサービスを今まで使っていなかったなんて・・・&lt;/p&gt;
&lt;p&gt;今後は、買うものが決まっている消耗品類はAmazonパントリーで買うことになるでしょう。&lt;/p&gt;&lt;iframe src=&quot;https://rcm-fe.amazon-adsystem.com/e/cm?o=9&amp;p=12&amp;l=ur1&amp;category=pantry&amp;banner=1X7XF3SQTCH3E3NSW9G2&amp;f=ifr&amp;linkID=d423cd3344d2a49497714028bfebee8f&amp;t=blog-ryou103-22&amp;tracking_id=blog-ryou103-22&quot; width=&quot;300&quot; height=&quot;250&quot; scrolling=&quot;no&quot; border=&quot;0&quot; marginwidth=&quot;0&quot; style=&quot;border:none;&quot; frameborder=&quot;0&quot;&gt;&lt;/iframe&gt;
]]&gt;</content:encoded></item><item><title>&lt;![CDATA[アウトドア好きが選んだコーヒー道具3点]]&gt;</title><link>https://blog.ryou103.com/post/outdoor-coffee-goods</link><guid>https://blog.ryou103.com/post/outdoor-coffee-goods</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/outdoor-coffee-goods/outdoor-coffee-goods.jpg" medium="image"></media:content><description>キャンプ・登山・車中泊でコーヒーを飲むために揃えた道具を紹介します。RIVERSのコーヒーミル・TetraDripのコーヒドリッパー・Amazonで格安の小さいコーヒーポットの3点を新しく購入しました。</description><pubDate>Tue, 02 Jul 2019 22:37:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/outdoor-coffee-goods/outdoor-coffee-goods.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;こんにちは、アウトドア大好きエンジニアとみーです。&lt;br /&gt;コーヒー道具は、主にキャンプ・登山・車中泊で使用しています。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;コーヒーセットを一新しましたのでご紹介！&lt;/strong&gt;&lt;br /&gt;さっそく、購入した商品はこちら！&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/outdoor-coffee-goods/coffee-1.jpg&quot; alt=&quot;コーヒードリッパー、コーヒーミル、コーヒーポット&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;左から、コーヒードリッパー、コーヒーミル、コーヒーポット。&lt;/p&gt;
&lt;p&gt;どれも1〜2人用のサイズとなっています。&lt;/p&gt;
&lt;h1&gt;コーヒードリッパー&lt;/h1&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/outdoor-coffee-goods/coffee-2.jpg&quot; alt=&quot;TetraDripのステンレススチール１人用&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;こちらは、&lt;a href=&quot;//af.moshimo.com/af/c/click?a_id=1509971&amp;amp;p_id=54&amp;amp;pc_id=54&amp;amp;pl_id=616&amp;amp;url=https%3A%2F%2Fitem.rakuten.co.jp%2Fikitselect%2Fmnq000ss01%2F&amp;amp;m=http%3A%2F%2Fm.rakuten.co.jp%2Fikitselect%2Fi%2F10005251%2F&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;&lt;strong&gt;TetraDrip&lt;/strong&gt;のステンレススチール素材のもの&lt;/a&gt;&lt;img src=&quot;//i.moshimo.com/af/i/impression?a_id=1509971&amp;amp;p_id=54&amp;amp;pc_id=54&amp;amp;pl_id=616&quot; alt=&quot;&quot; width=&quot;1&quot; height=&quot;1&quot; style=&quot;border: 0px;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;以前は、ポリピロピレン製のものを使っていましたが、今回はこちらをチョイス。&lt;br /&gt;ポリプロピレン製は、1,000円台で購入することができますが、こちらの&lt;strong&gt;ステンレススチール製は、3,000円ちょっと&lt;/strong&gt;と3倍近いお値段、、果たして味に違いが出るのか。（舌が肥えてないから、多分わからない。。）&lt;/p&gt;
&lt;p&gt;TetraDripのコーヒードリッパーの特徴は、&lt;strong&gt;携帯性の良さ&lt;/strong&gt;。&lt;br /&gt;3枚のプレートでできているので、かさばらずに収納ができます。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/outdoor-coffee-goods/coffee-3.jpg&quot; alt=&quot;TetraDripのコーヒードリッパー&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;さらに横からみたらこの薄さ！&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/outdoor-coffee-goods/coffee-4.jpg&quot; alt=&quot;TetraDripの薄さ&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;これ、3枚重ねてますからね。&lt;/p&gt;
&lt;p&gt;ステンレスになったことでちょっと高級感が出て抽出するときの幸福感が上がりそうです！&lt;/p&gt;
&lt;h1&gt;コーヒーミル&lt;/h1&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/outdoor-coffee-goods/coffee-5.jpg&quot; alt=&quot;RIVERSの手挽きコーヒーミル&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;//af.moshimo.com/af/c/click?a_id=1509971&amp;amp;p_id=54&amp;amp;pc_id=54&amp;amp;pl_id=616&amp;amp;url=https%3A%2F%2Fitem.rakuten.co.jp%2Fsmartkitchen%2F10006383%2F&amp;amp;m=http%3A%2F%2Fm.rakuten.co.jp%2Fsmartkitchen%2Fi%2F10006383%2F&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;&lt;strong&gt;RIVERSの手挽きコーヒーミル&lt;/strong&gt;&lt;/a&gt;&lt;img src=&quot;//i.moshimo.com/af/i/impression?a_id=1509971&amp;amp;p_id=54&amp;amp;pc_id=54&amp;amp;pl_id=616&quot; alt=&quot;&quot; width=&quot;1&quot; height=&quot;1&quot; style=&quot;border: 0px;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;以前は、&lt;a href=&quot;//af.moshimo.com/af/c/click?a_id=1509971&amp;amp;p_id=54&amp;amp;pc_id=54&amp;amp;pl_id=616&amp;amp;url=https%3A%2F%2Fitem.rakuten.co.jp%2Fsmartkitchen%2F10003165%2F&amp;amp;m=http%3A%2F%2Fm.rakuten.co.jp%2Fsmartkitchen%2Fi%2F10003165%2F&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;ポーレックスのコーヒーミル&lt;/a&gt;&lt;img src=&quot;//i.moshimo.com/af/i/impression?a_id=1509971&amp;amp;p_id=54&amp;amp;pc_id=54&amp;amp;pl_id=616&quot; alt=&quot;&quot; width=&quot;1&quot; height=&quot;1&quot; style=&quot;border: 0px;&quot; /&gt;を使用していました。&lt;/p&gt;
&lt;p&gt;シルバーのものを使っていたのですが、買い換えるとしたら、**木目か黒がいいな〜**と思い探していました。&lt;/p&gt;
&lt;p&gt;RIVERSか&lt;a href=&quot;//af.moshimo.com/af/c/click?a_id=1509971&amp;amp;p_id=54&amp;amp;pc_id=54&amp;amp;pl_id=616&amp;amp;url=https%3A%2F%2Fitem.rakuten.co.jp%2Fhomeshop%2F5406-kak-0483%2F&amp;amp;m=http%3A%2F%2Fm.rakuten.co.jp%2Fhomeshop%2Fi%2F10265294%2F&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;カリタの木目がおしゃれなコーヒーミル&lt;/a&gt;&lt;img src=&quot;//i.moshimo.com/af/i/impression?a_id=1509971&amp;amp;p_id=54&amp;amp;pc_id=54&amp;amp;pl_id=616&quot; alt=&quot;&quot; width=&quot;1&quot; height=&quot;1&quot; style=&quot;border: 0px;&quot; /&gt;で迷ったのですが、カリタのは刃が鋳鉄でできており、メンテナンス面を考えて&lt;a href=&quot;//af.moshimo.com/af/c/click?a_id=1509971&amp;amp;p_id=54&amp;amp;pc_id=54&amp;amp;pl_id=616&amp;amp;url=https%3A%2F%2Fitem.rakuten.co.jp%2Fsmartkitchen%2F10006383%2F&amp;amp;m=http%3A%2F%2Fm.rakuten.co.jp%2Fsmartkitchen%2Fi%2F10006383%2F&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;RIVERSの手挽きコーヒーミル&lt;/a&gt;&lt;img src=&quot;//i.moshimo.com/af/i/impression?a_id=1509971&amp;amp;p_id=54&amp;amp;pc_id=54&amp;amp;pl_id=616&quot; alt=&quot;&quot; width=&quot;1&quot; height=&quot;1&quot; style=&quot;border: 0px;&quot; /&gt;にしました。&lt;/p&gt;
&lt;h1&gt;コーヒーポット&lt;/h1&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/outdoor-coffee-goods/coffee-6.jpg&quot; alt=&quot;Kslongのコーヒーポット250ml&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;//af.moshimo.com/af/c/click?a_id=1509975&amp;amp;p_id=170&amp;amp;pc_id=185&amp;amp;pl_id=4062&amp;amp;url=https%3A%2F%2Fwww.amazon.co.jp%2F%25E3%2582%25B3%25E3%2583%25BC%25E3%2583%2592%25E3%2583%25BC%25E3%2583%259D%25E3%2583%2583%25E3%2583%2588%25E3%2582%25B3%25E3%2583%25BC%25E3%2583%2592%25E3%2583%25BC-%25E3%2582%25B1%25E3%2583%2588%25E3%2583%25AB%25E3%2582%25B9%25E3%2583%2586%25E3%2583%25B3%25E3%2583%25AC%25E3%2582%25B9-%25E7%25B4%25B0%25E5%258F%25A3%25E3%2583%258F%25E3%2583%25B3%25E3%2583%2589%25E3%2583%2591%25E3%2583%25B3%25E3%2583%2581%25E3%2583%259D%25E3%2583%2583%25E3%2583%2588%25E3%2583%2589%25E3%2583%25AA%25E3%2583%2583%25E3%2583%2597ih%25E5%25AF%25BE%25E5%25BF%259C%25E9%2595%25B7%25E3%2581%2584%25E5%258F%25A3%25E3%2583%259D%25E3%2583%2583%25E3%2583%2588-%25E3%2583%2595%25E3%2582%25A1%25E3%2582%25A4%25E3%2583%25B3%25E5%258F%25A3%25E3%2583%259D%25E3%2583%2583%25E3%2583%2588-%25E3%2582%25B0%25E3%2583%25BC%25E3%2582%25B9%25E3%2583%258D%25E3%2583%2583%25E3%2582%25AF%25E3%2583%259D%25E3%2583%2583%25E3%2583%2588%2Fdp%2FB07KQW6M7W&quot; rel=&quot;nofollow&quot;&gt;&lt;strong&gt;Kslongのコーヒーポット250ml&lt;/strong&gt;&lt;/a&gt;&lt;img src=&quot;//i.moshimo.com/af/i/impression?a_id=1509975&amp;amp;p_id=170&amp;amp;pc_id=185&amp;amp;pl_id=4062&quot; alt=&quot;&quot; width=&quot;1&quot; height=&quot;1&quot; style=&quot;border: 0px;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;こちらは、Amazonで購入しました。&lt;/p&gt;
&lt;p&gt;細口でこのサイズのものはたくさん出回ってますね。違いはほとんどないので、持ち手の形が好みのものを選びました。&lt;/p&gt;
&lt;p&gt;コーヒーを抽出するのに、一人分150cc～180ccなので、大きさも十分です。&lt;/p&gt;
&lt;p&gt;また質感も、マットな感じで傷が目立ちにくいと思います。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/outdoor-coffee-goods/coffee-7.jpg&quot; alt=&quot;マット感がおしゃれなコーヒーポット&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;今回購入したコーヒー道具&lt;/h1&gt;&lt;p&gt;
&lt;!-- START MoshimoAffiliateEasyLink --&gt;
&lt;script type=&quot;text/javascript&quot;&gt;
(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;
b[a]=b[a]||function(){arguments.currentScript=c.currentScript
||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};
c.getElementById(a)||(d=c.createElement(f),d.src=g,
d.id=a,e=c.getElementsByTagName(&quot;body&quot;)[0],e.appendChild(d))})
(window,document,&quot;script&quot;,&quot;//dn.msmstatic.com/site/cardlink/bundle.js&quot;,&quot;msmaflink&quot;);
msmaflink({&quot;n&quot;:&quot;MUNIEQ(ミュニーク) Tetra Drip 01S 09210001000001&quot;,&quot;b&quot;:&quot;MUNIEQ(ミュニーク)&quot;,&quot;t&quot;:&quot;09210001000001&quot;,&quot;d&quot;:&quot;https:\/\/images-fe.ssl-images-amazon.com&quot;,&quot;c_p&quot;:&quot;\/images\/I&quot;,&quot;p&quot;:[&quot;\/41-FaFAoJfL.jpg&quot;,&quot;\/51rPAKs-erL.jpg&quot;,&quot;\/51NYFGavAOL.jpg&quot;,&quot;\/61MInMmYDqL.jpg&quot;,&quot;\/51Y%2BfT5qDqL.jpg&quot;,&quot;\/51duh2zNXzL.jpg&quot;],&quot;u&quot;:{&quot;u&quot;:&quot;https:\/\/www.amazon.co.jp\/MUNIEQ-%E3%83%9F%E3%83%A5%E3%83%8B%E3%83%BC%E3%82%AF-Tetra-Drip-09210001000001\/dp\/B01H7SWFRS&quot;,&quot;t&quot;:&quot;amazon&quot;,&quot;r_v&quot;:&quot;&quot;},&quot;aid&quot;:{&quot;amazon&quot;:&quot;1509975&quot;,&quot;rakuten&quot;:&quot;1509971&quot;},&quot;eid&quot;:&quot;oP7US&quot;});
&lt;/script&gt;
&lt;div id=&quot;msmaflink-oP7US&quot;&gt;TetraDripのコーヒードリッパー&lt;/div&gt;
&lt;!-- MoshimoAffiliateEasyLink END --&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;!-- START MoshimoAffiliateEasyLink --&gt;
&lt;script type=&quot;text/javascript&quot;&gt;
(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;
b[a]=b[a]||function(){arguments.currentScript=c.currentScript
||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};
c.getElementById(a)||(d=c.createElement(f),d.src=g,
d.id=a,e=c.getElementsByTagName(&quot;body&quot;)[0],e.appendChild(d))})
(window,document,&quot;script&quot;,&quot;//dn.msmstatic.com/site/cardlink/bundle.js&quot;,&quot;msmaflink&quot;);
msmaflink({&quot;n&quot;:&quot;(リバーズ) RIVERS コーヒーグラインダーグリット マットブラック&quot;,&quot;b&quot;:&quot;RIVERS(リバーズ)&quot;,&quot;t&quot;:&quot;GRITMBK&quot;,&quot;d&quot;:&quot;https:\/\/images-fe.ssl-images-amazon.com&quot;,&quot;c_p&quot;:&quot;&quot;,&quot;p&quot;:[&quot;\/images\/I\/31uDCyqRKyL.jpg&quot;],&quot;u&quot;:{&quot;u&quot;:&quot;https:\/\/www.amazon.co.jp\/%E3%83%AA%E3%83%90%E3%83%BC%E3%82%BA-RIVERS-%E3%82%B3%E3%83%BC%E3%83%92%E3%83%BC%E3%82%B0%E3%83%A9%E3%82%A4%E3%83%B3%E3%83%80%E3%83%BC%E3%82%B0%E3%83%AA%E3%83%83%E3%83%88-%E3%83%9E%E3%83%83%E3%83%88%E3%83%96%E3%83%A9%E3%83%83%E3%82%AF\/dp\/B076MS5GBX&quot;,&quot;t&quot;:&quot;amazon&quot;,&quot;r_v&quot;:&quot;&quot;},&quot;aid&quot;:{&quot;amazon&quot;:&quot;1509975&quot;,&quot;rakuten&quot;:&quot;1509971&quot;},&quot;eid&quot;:&quot;gGiDL&quot;});
&lt;/script&gt;
&lt;div id=&quot;msmaflink-gGiDL&quot;&gt;RIVERS コーヒーミル&lt;/div&gt;
&lt;!-- MoshimoAffiliateEasyLink END --&gt;
&lt;/p&gt;
&lt;p&gt;
&lt;!-- START MoshimoAffiliateEasyLink --&gt;
&lt;script type=&quot;text/javascript&quot;&gt;
(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;
b[a]=b[a]||function(){arguments.currentScript=c.currentScript
||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};
c.getElementById(a)||(d=c.createElement(f),d.src=g,
d.id=a,e=c.getElementsByTagName(&quot;body&quot;)[0],e.appendChild(d))})
(window,document,&quot;script&quot;,&quot;//dn.msmstatic.com/site/cardlink/bundle.js&quot;,&quot;msmaflink&quot;);
msmaflink({&quot;n&quot;:&quot;Kslong コーヒーポットコーヒー ケトルステンレス 細口ハンドパンチポットドリップih対応長い口ポット ファイン口ポット グースネックポット (ブラック, 250ml)&quot;,&quot;b&quot;:&quot;kslong&quot;,&quot;t&quot;:&quot;KL00319B2&quot;,&quot;d&quot;:&quot;https:\/\/images-fe.ssl-images-amazon.com&quot;,&quot;c_p&quot;:&quot;\/images\/I&quot;,&quot;p&quot;:[&quot;\/41EfWlbJyhL.jpg&quot;,&quot;\/41oB947KgcL.jpg&quot;,&quot;\/419l4Xph%2BoL.jpg&quot;,&quot;\/41yoFNcavrL.jpg&quot;,&quot;\/31%2BWp05FUjL.jpg&quot;,&quot;\/51g4zjHcktL.jpg&quot;],&quot;u&quot;:{&quot;u&quot;:&quot;https:\/\/www.amazon.co.jp\/%E3%82%B3%E3%83%BC%E3%83%92%E3%83%BC%E3%83%9D%E3%83%83%E3%83%88%E3%82%B3%E3%83%BC%E3%83%92%E3%83%BC-%E3%82%B1%E3%83%88%E3%83%AB%E3%82%B9%E3%83%86%E3%83%B3%E3%83%AC%E3%82%B9-%E7%B4%B0%E5%8F%A3%E3%83%8F%E3%83%B3%E3%83%89%E3%83%91%E3%83%B3%E3%83%81%E3%83%9D%E3%83%83%E3%83%88%E3%83%89%E3%83%AA%E3%83%83%E3%83%97ih%E5%AF%BE%E5%BF%9C%E9%95%B7%E3%81%84%E5%8F%A3%E3%83%9D%E3%83%83%E3%83%88-%E3%83%95%E3%82%A1%E3%82%A4%E3%83%B3%E5%8F%A3%E3%83%9D%E3%83%83%E3%83%88-%E3%82%B0%E3%83%BC%E3%82%B9%E3%83%8D%E3%83%83%E3%82%AF%E3%83%9D%E3%83%83%E3%83%88\/dp\/B07KQW6M7W&quot;,&quot;t&quot;:&quot;amazon&quot;,&quot;r_v&quot;:&quot;&quot;},&quot;aid&quot;:{&quot;amazon&quot;:&quot;1509975&quot;,&quot;rakuten&quot;:&quot;1509971&quot;},&quot;eid&quot;:&quot;XcIFm&quot;});
&lt;/script&gt;
&lt;div id=&quot;msmaflink-XcIFm&quot;&gt;Kslong コーヒーポット&lt;/div&gt;
&lt;!-- MoshimoAffiliateEasyLink END --&gt;
&lt;/p&gt;
&lt;p&gt;新しいコーヒー道具を持って出掛けるのが楽しみです！&lt;/p&gt;
]]&gt;</content:encoded></item><item><title>&lt;![CDATA[Git コマンド1つでマージ済みのブランチをまとめて削除]]&gt;</title><link>https://blog.ryou103.com/post/git-delete-merged-brach</link><guid>https://blog.ryou103.com/post/git-delete-merged-brach</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/git-delete-merged-brach/git-branch-merged-delete.jpg" medium="image"></media:content><description>.gitconfigに1行設定を加えるだけで、マージ済みのブランチをまとめて削除することができます。開発を日々続けていると、どんどんローカルブランチが増えて編集したいブランチを探すのが大変に。そんなローカルブランチを1つのコマンドで整理することができます。</description><pubDate>Tue, 02 Jul 2019 13:27:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/git-delete-merged-brach/git-branch-merged-delete.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;開発が完了して使わなくなったブランチがローカルに大量に残ってしまう。&lt;br /&gt;定期的に整理しないと、新しくプッシュしたブランチを探すのに一苦労なんてことも。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/git-delete-merged-brach/git-find-branch.png&quot; alt=&quot;git-find-branch&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;ということで、&lt;strong&gt;コマンド1つでマージ済みのブランチをまとめて削除する方法&lt;/strong&gt;をご紹介。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://qiita.com/hajimeni/items/73d2155fc59e152630c4&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;こちら&lt;/a&gt;のやり方を参考にさせていただきました。（まんまでは、エラーになってしまったので修正しています。）&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git delete-merged-branch develop&lt;/code&gt; でdevelopブランチにマージ済みのブランチを削除できるようにします。&lt;/p&gt;
&lt;h1&gt;設定&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;.gitconfig&lt;/code&gt;に設定を書きます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;$ vim ~/.gitconfig
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下のコードを追記する。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;[alias]
       delete-merged-branch = &quot;!f () { git checkout $1; git branch --merged | egrep -v &#x27;(\\*|develop|master)&#x27; | xargs git branch -d; }; f&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;実行&lt;/h1&gt;
&lt;p&gt;まとめてブランチを削除するコマンドを、gitに登録したので実行させるだけ。&lt;/p&gt;
&lt;p&gt;developブランチにマージ済みのブランチを削除したければ、&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;$ git delete-merged-branch develop
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;masterブランチにマージ済みのブランチを削除したければ、&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;$ git delete-merged-branch master
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;となります。&lt;/p&gt;
&lt;p&gt;以上で、ブランチをまとめて削除することができました。&lt;/p&gt;
&lt;h1&gt;仕組み&lt;/h1&gt;
&lt;p&gt;登録したコマンドを分解して見ていきましょう。&lt;/p&gt;
&lt;h2&gt;checkout&lt;/h2&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;git checkout $1

Switched to branch &#x27;$1&#x27;&#x27;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;こちらは、引数で渡された値にチェックアウトします。&lt;br /&gt;&lt;code&gt;git delete-merged-branch develop&lt;/code&gt;であれば、developブランチにチェックアウトします。&lt;/p&gt;
&lt;h2&gt;branch --merged&lt;/h2&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;git branch --merged

* develop
  fix/permit-status-list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;現在のブランチにマージされているブランチを表示します。&lt;/p&gt;
&lt;h2&gt;egrep&lt;/h2&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;| egrep -v &#x27;(\\*|develop|master)&#x27; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;egrep&lt;/code&gt;は、*, develop, master以外の入力された値を返します。&lt;/p&gt;
&lt;p&gt;ここでは、&lt;code&gt;|&lt;/code&gt;で繋げているので、「現在のブランチにマージされているブランチの中で、*, develop, master以外のブランチを返す」となります。&lt;/p&gt;
&lt;h2&gt;xargs&lt;/h2&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;| xargs git branch -d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;xargs&lt;/code&gt;で前のコマンドで出力された値を、次のコマンドの入力にします。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;$ seq 10 | xargs echo
1 2 3 4 5 6 7 8 9 10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;seq&lt;/code&gt;は数列を出力するコマンドです。&lt;/p&gt;
&lt;p&gt;ですので、「上で出力されたブランチを削除する」となります。&lt;/p&gt;
&lt;p&gt;最後に関数化してコマンドの完成です。&lt;/p&gt;
&lt;h1&gt;まとめ&lt;/h1&gt;
&lt;p&gt;ローカルにブランチが溜まってきたな〜と思ったら、&lt;code&gt;git delete-merged-branch develop &lt;/code&gt;で一発ですっきりです！&lt;/p&gt;
&lt;p&gt;ちまちま削除する作業から開放されました！&lt;/p&gt;
]]&gt;</content:encoded><category>Git</category></item><item><title>&lt;![CDATA[Adonis入門（導入編）]]&gt;</title><link>https://blog.ryou103.com/post/adonisjs-first</link><guid>https://blog.ryou103.com/post/adonisjs-first</guid><description>Node.jsフレームワークにもRailsライクなものはないかなと探して見つけたのが、AdonisJsでした。Node.jsでもORMを使ったモデルの管理ができるAdnisJsで、TODOアプリを作ってみてその所感をまとめていきます。まずは導入編。</description><pubDate>Mon, 24 Jun 2019 22:00:00 +0000</pubDate><content:encoded>&lt;![CDATA[
&lt;h1&gt;AdonisJsを知ったきっかけ&lt;/h1&gt;
&lt;p&gt;お仕事でRailsに増える機会があり、ORMのありがたみをひしひしと感じました。特に、モデル同士の関連付け(アソシエーション)。&lt;/p&gt;
&lt;p&gt;それで私の得意分野であるNode.jsフレームワークにもRailsライクなものはないかなと探して見つけたのが、&lt;a href=&quot;https://adonisjs.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;AdonisJs&lt;/a&gt;でした。&lt;/p&gt;
&lt;p&gt;（調べて分かったのですが、どうやらLaravelライクに作られているそうです。）&lt;/p&gt;
&lt;h1&gt;やりたいこと&lt;/h1&gt;
&lt;p&gt;今までは、MongoDBをmongooseのpopulationを使ってゴリゴリに書いていたので、&lt;strong&gt;ORMを用いるとどれだけラクにModelの管理ができるか&lt;/strong&gt;を感じてみたいなと思います。&lt;/p&gt;
&lt;p&gt;最終的には、AdonisJsをAPIサーバーとして運用して、SPAでTODOアプリを作成してみようと思います！（フロントにはVue.jsを使用予定）&lt;/p&gt;
&lt;p&gt;まずは導入編！&lt;/p&gt;
&lt;h1&gt;セットアップ&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;インストール&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ mkdir ~/www/adonisjs
$ cd adonisjs
$ sudo yarn global add @adonisjs/cli
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;ファイル生成&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;今回は、APIサーバーにするので、&lt;code&gt;--api-only&lt;/code&gt;オプションを付けます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ adonis new server --api-only
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下のテンプレートがcloneされます。&lt;br /&gt;&lt;a href=&quot;https://github.com/adonisjs/adonis-api-app&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/adonisjs/adonis-api-app&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonisjs-first/adonis-install.png&quot; alt=&quot;adonisjsインストール完了&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;起動&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ cd server

$ adonis serve --dev

SERVER STARTED
&gt; Watching files for changes...

info: serving app on http://127.0.0.1:3333
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;http://127.0.0.1:3333&lt;/code&gt;にアクセスして以下のように表示されたら、APIサーバーのセットアップ完了です。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonisjs-first/get-hoge.png&quot; alt=&quot;get-hoge&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;Routeの追加方法&lt;/h1&gt;
&lt;p&gt;rootにアクセスすることはできたので、次に試しに&lt;code&gt;GET /hoge&lt;/code&gt;できるようにrouteを追加してみます。&lt;/p&gt;
&lt;p&gt;以下のコードを&lt;code&gt;start/routes.js&lt;/code&gt;に追記。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot; data-filename=&quot;start/routes.js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;Route.get(&#x27;/hoge&#x27;, () =&gt; {
  return { hoge: &#x27;hogege&#x27; }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;http://127.0.0.1:3333/hoge&lt;/code&gt;にアクセスしてみると、以下のように表示されます。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/adonisjs-first/get-root.png&quot; alt=&quot;get-root&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;しかし、このままでは、routeが増えてくると、&lt;code&gt;routes.js&lt;/code&gt;は膨大なサイズになってしまうので、Controllerを使って、処理部分をファイル分割してみます。&lt;/p&gt;
&lt;h1&gt;Controllerを使ったRoute&lt;/h1&gt;
&lt;h2&gt;Controllerの作成&lt;/h2&gt;
&lt;p&gt;コンソールからHogeというControllerを作成する。&lt;br /&gt;typeを聞かれるので、HTTPの方を選択。（&lt;code&gt;--type http&lt;/code&gt;を付ければ省略できる。）&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ adonis make:controller Hoge
&gt; Select controller type
❯ For HTTP requests
  For Websocket channel
&lt;/code&gt;&lt;/pre&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;✔ create  app/Controllers/Http/HogeController.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HogeControllerが作成された。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot; data-filename=&quot;app/Controllers/Http/HogeController.js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&#x27;use strict&#x27;

class HogeController {
}

module.exports = HogeController
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Controllerを編集&lt;/h2&gt;
&lt;p&gt;HogeController内にindexというメソッドを作成して、先程routes.jsに書いた&lt;code&gt;/hoge&lt;/code&gt;の処理をこちらに持ってくる。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot; data-filename=&quot;app/Controllers/Http/HogeController.js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&#x27;use strict&#x27;

class HogeController {
  index() {
    return { hoge: &#x27;hogege&#x27; }
  }
}

module.exports = HogeController
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;RouteからControllerを指定&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;/hoge&lt;/code&gt;のrouteを編集する。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;Route.get(&#x27;/hoge&#x27;, &#x27;HogeController.index&#x27;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このように書くことで、Controllerを指定することができる。&lt;br /&gt;importとかexportとかいちいち書かなくていいのがラクですね。&lt;/p&gt;
&lt;h1&gt;まとめ&lt;/h1&gt;
&lt;p&gt;一旦ここまで。&lt;/p&gt;
&lt;p&gt;まずは、AdonisJsをセットアップして、RouteとControllerの理解でした。&lt;br /&gt;すでにNode.jsのフレームワークを使っている方は苦労しないと思います。&lt;/p&gt;
&lt;p&gt;なんとなく、名前もRailsライクな気がしますね。&lt;/p&gt;
]]&gt;</content:encoded><category>AdonisJS</category></item><item><title>&lt;![CDATA[Node8.0以上なのにWHATWG URLが使えない理由]]&gt;</title><link>https://blog.ryou103.com/post/whatwg-url-not-a-constructor</link><guid>https://blog.ryou103.com/post/whatwg-url-not-a-constructor</guid><description>Node.jsに標準で備わっているWHATWG URLを使おうとすると、TypeError: url__WEBPACK_IMPORTED_MODULE_0__.URL is not a constructorとエラーを出て使えないことがあったので、原因を調査して対策しました。</description><pubDate>Sun, 23 Jun 2019 07:00:00 +0000</pubDate><content:encoded>&lt;![CDATA[
&lt;p&gt;Nodeに標準で備わっているWHATWG URLを使おうとすると、&lt;code&gt;TypeError: url__WEBPACK_IMPORTED_MODULE_0__.URL is not a constructor&lt;/code&gt;とエラーを出て使えないことがあったので、原因を調査して対策しました。&lt;/p&gt;
&lt;h1&gt;WHATWG URL&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://nodejs.org/api/url.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;WHATWG URL&lt;/a&gt;は、Node.js8.0から正式対応したURL解析モジュールであり、&lt;a href=&quot;https://developer.mozilla.org/ja/docs/Web/API/URL&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;IE・Edgeを除くブラウザにも対応&lt;/a&gt;しています。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;import { URL } from &#x27;url&#x27;;

const url = new URL(&#x27;https://test.jp/hoge&#x27;);
console.log(url);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;URL {
  href: &#x27;https://test.jp/hoge&#x27;,
  origin: &#x27;https://test.jp&#x27;,
  protocol: &#x27;https:&#x27;,
  username: &#x27;&#x27;,
  password: &#x27;&#x27;,
  host: &#x27;test.jp&#x27;,
  hostname: &#x27;test.jp&#x27;,
  port: &#x27;&#x27;,
  pathname: &#x27;/hoge&#x27;,
  search: &#x27;&#x27;,
  searchParams: URLSearchParams {},
  hash: &#x27;&#x27;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このようにURLをprotocolだったり、hostだったり、URLを解析した結果をオブジェクト型で返します。&lt;/p&gt;
&lt;p&gt;これを利用すると簡単にパラメータの操作ができます。&lt;br /&gt;例えば、パラメータでページャーの制御をしているときには、以下のように使えます。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;url.searchParams.set(&#x27;page&#x27;, 2);
console.log(url.href);
// =&gt; https://test.jp/hoge?page=2

url.searchParams.set(&#x27;page&#x27;, 3);
console.log(url.href);
// =&gt; https://test.jp/hoge?page=3
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;エラーになる理由&lt;/h1&gt;
&lt;p&gt;便利で普段から使っていたのですが、あるとき以下のようなエラーが出て使えませんでした。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/whatwg-url-not-a-constructor/whatwg-url-not-a-constructor-1.png&quot; alt=&quot;Uncaught TypeError: url__WEBPACK_IMPORTED_MODULE_0_.URL is not a constructor&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;code&gt;URL&lt;/code&gt;が見つからないようです。&lt;/p&gt;
&lt;p&gt;Nodeのバージョン古かったかな？と思い確認したのですが、&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ node -v
v12.4.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Node8.0以上はクリア。もちろん、&lt;a href=&quot;https://nodejs.org/api/url.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;公式リファレンス&lt;/a&gt;も存在しています。&lt;/p&gt;
&lt;p&gt;コンソール画面でNodeを実行して確認してみても、URLというメソッドが存在しています。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ node
&gt; var url = require(&#x27;url&#x27;);
&gt; url
{
  Url: [Function: Url],
  parse: [Function: urlParse],
  resolve: [Function: urlResolve],
  resolveObject: [Function: urlResolveObject],
  format: [Function: urlFormat],
  URL: [Function: URL],
  URLSearchParams: [Function: URLSearchParams],
  domainToASCII: [Function: domainToASCII],
  domainToUnicode: [Function: domainToUnicode],
  pathToFileURL: [Function: pathToFileURL],
  fileURLToPath: [Function: fileURLToPath]
}
&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;webpackを通しているのが原因かなと思い、&lt;code&gt;console.log(url);&lt;/code&gt;をしてみると、&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/whatwg-url-not-a-constructor/whatwg-url-not-a-constructor-2.png&quot; alt=&quot;ブラウザでのurlモジュール&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;URLが存在しません！&lt;br /&gt;あれ？なんで？webpackのバージョンが古い？configミスった？とか考え、いろいろと試行錯誤した結果原因がわかりました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;サーバーサイドでは動くけど、クライアントサイドでは動かない！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;そうなんです。同じ実行環境で、サーバーサイドとクライアントサイドで&lt;code&gt;URL&lt;/code&gt;を確認すると、なぜかクライアントサイドでは、&lt;code&gt;URL&lt;/code&gt;というメソッドがなくなっていました。&lt;/p&gt;
&lt;h1&gt;クライアントサイドでURLを使うためには&lt;/h1&gt;
&lt;p&gt;ブラウザ上では&lt;code&gt;URL&lt;/code&gt;が使えないのが分かったのですが、どうしても使いたい！&lt;/p&gt;
&lt;p&gt;そんなときは、方法として2パターン考えました。&lt;/p&gt;
&lt;h2&gt;1. ブラウザに標準実装されているURLを使う&lt;/h2&gt;
&lt;p&gt;もし、実行するファイルがクライアントサイドだけで使うものであれば、&lt;code&gt;import { URL}&lt;/code&gt;せずに使うとこができます。その1行をコメントアウトして実行してみてください。&lt;/p&gt;
&lt;p&gt;ただし、&lt;code&gt;URL&lt;/code&gt;はIE・Edgeには対応していませんので、&lt;strong&gt;&lt;a href=&quot;https://github.com/lifaon74/url-polyfill&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;url-polyfill&lt;/a&gt;&lt;/strong&gt; を読み込ませることで、全ブラウザ上でも実行可能になります。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;インストール&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ yarn add url-polyfill
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使い方&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;import &#x27;url-polyfill&#x27;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;読み込ませる位置は、&lt;code&gt;URL&lt;/code&gt;を使うファイル上でも良いですし、&lt;code&gt;app.js&lt;/code&gt;といったメインファイルに読み込ませておいても問題ありません。&lt;/p&gt;
&lt;h2&gt;2. Node上でもブラウザ上でも使えるuniversal-urlを使う&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/stevenvachon/universal-url&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;universal-url&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;importしたurlモジュールをuniversal-urlに置き換えるだけで、サーバーサイド・クライアントサイド両方で使えるコードとなります。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;インストール&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ yarn add universal-url
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使い方&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;import { URL } from &#x27;url&#x27;&lt;/code&gt;と置き換える&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;import { URL } from &#x27;universal-url&#x27;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;私は、UniversalJSの方がラクなので、後者の&lt;code&gt;universal-url&lt;/code&gt;を導入して解決しました。&lt;/p&gt;
]]&gt;</content:encoded></item><item><title>&lt;![CDATA[IE,Edgeでdocument.querySelectorAllをforEachする方法]]&gt;</title><link>https://blog.ryou103.com/post/queryselectorall-foreach</link><guid>https://blog.ryou103.com/post/queryselectorall-foreach</guid><description>document.querySelectorAll(&#x27;.hoge&#x27;).forEach・・・を私はよく使っていましたが、IE,Edgeだとエラーが発生することに気づきました。古いブラウザでは、querySelectorAllが返すNodeListはforEachに対応していないのです。</description><pubDate>Sat, 15 Jun 2019 13:44:00 +0000</pubDate><content:encoded>&lt;![CDATA[
&lt;p&gt;javascriptで複数の要素に対して、なにかしらの処理を行いたいときに、&lt;code&gt;querySelectorAll&lt;/code&gt;を使って、私はよくこのように書いています。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;const hoges = document.querySelectorAll(&#x27;.hoge&#x27;);
hoges.forEach((el) =&gt; {
  // なにかしらの処理
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;.hoge&lt;/code&gt; 要素をすべて取得して、それぞれになにかしらの処理を行う。&lt;br /&gt;至って自然な書き方ですが、実はこれ、、&lt;strong&gt;IE,Edgeだとエラーが発生します。&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;Object doesn&#x27;t support property or method &#x27;forEach&#x27;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/queryselectorall-foreach/querySelectorAllforIE.png&quot; alt=&quot;querySelectorAllforIE&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;「変数&lt;code&gt;hoges&lt;/code&gt;にforEachというメソッドがありません」&lt;/strong&gt; というエラーが出てしまう。&lt;/p&gt;
&lt;h1&gt;原因&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.mozilla.org/ja/docs/Web/API/Document/querySelectorAll&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;querySelectorAll&lt;/a&gt;は&lt;a href=&quot;https://developer.mozilla.org/ja/docs/Web/API/NodeList&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;NodeList&lt;/a&gt;というオブジェクトを返します。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;console.log(document.querySelectorAll(&#x27;.hoge&#x27;));
&gt; NodeList(3) [div.hoge, div.hoge, div.hoge]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MDNのサイト（ &lt;a href=&quot;https://developer.mozilla.org/ja/docs/Web/API/NodeList&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.mozilla.org/ja/docs/Web/API/NodeList&lt;/a&gt; ）に以下のように書いてありました。&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;NodeList は Array とは異なりますが、 forEach() メソッドで処理を反復適用することは可能です。 Array.from() を使うことで Array に変換することができます。&lt;/p&gt;
  &lt;p&gt;ただし、古いブラウザーでは NodeList.forEach() も Array.from() 実装されていない場合があります。これらの制限は Array.prototype.forEach() を使うことで回避することが可能です (この文書に詳しく書かれています)。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;そう、&lt;strong&gt;古いブラウザーでは NodeList.forEach()に対応していなかったのです！&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;解決策&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;Array.from&lt;/code&gt;を使って、配列にしてから&lt;code&gt;forEach&lt;/code&gt;をする。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;const hoges = Array.from(document.querySelectorAll(&#x27;.hoge&#x27;));
hoges.forEach((el) =&gt; {
  // なんらかの処理
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これで、IE,Edgeでも動作するようになります。&lt;/p&gt;
&lt;p&gt;※ ES6を導入していない場合は以下のように書く必要があります。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;var hoges = document.querySelectorAll(&#x27;.hoge&#x27;);
Array.prototype.forEach.call(hoges, function (el) {
  // なんらかの処理
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;まとめ&lt;/h1&gt;
&lt;p&gt;IE11とEdgeという無視することができないブラウザで対応していないとは、、、&lt;br /&gt;(2017年9月26日にリリースされた EdgeHTML 16.16299 以降ではforEachに対応しているそうです。)&lt;/p&gt;
&lt;p&gt;今後は、&lt;code&gt;Array.from(document.querySelectorAll())&lt;/code&gt;で習慣つけていく必要がありそうです。&lt;/p&gt;
]]&gt;</content:encoded></item><item><title>&lt;![CDATA[どんなときもWiFiだけで1ヶ月生活してみた感想。]]&gt;</title><link>https://blog.ryou103.com/post/donnatokimo-wifi-review-a-month</link><guid>https://blog.ryou103.com/post/donnatokimo-wifi-review-a-month</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/donnatokimo-wifi-review-a-month/donnatokimo-wifi-review-a-month.jpg" medium="image"></media:content><description>約1ヶ月前に「どんなときもWiFi」を契約して、1週間ほど試用してみてエンジニアの仕事にも問題ないし、ビデオ会議もしっかり繋がるので、光回線を解約しました。なので今は、楽天モバイル3GB+どんなときもWiFiのみでインターネットを利用しています。光回線なしで、仕事もプライベートもインターネットを使う生活を1ヶ月間してみて、ちょっとスピード遅いなぁと感じることもありますが、仕事に支障もなく生活できております。どんなときWiFiのみで生活した1ヶ月の中で感じた、良いところと悪いところをまとめました。</description><pubDate>Wed, 15 May 2019 10:19:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/donnatokimo-wifi-review-a-month/donnatokimo-wifi-review-a-month.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;約1ヶ月前に「&lt;a href=&quot;https://px.a8.net/svt/ejp?a8mat=35DJ1D+7FX4JM+3KKM+BWVTE&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;&lt;strong&gt;どんなときもWiFi&lt;/strong&gt;&lt;/a&gt;&lt;img border=&quot;0&quot; width=&quot;1&quot; height=&quot;1&quot; src=&quot;https://www12.a8.net/0.gif?a8mat=35DJ1D+7FX4JM+3KKM+BWVTE&quot; alt=&quot;&quot;&gt;」を契約して、1週間ほど試用してみてエンジニアの仕事にも問題ないし、ビデオ会議もしっかり繋がるので、光回線を解約しました。なので今は、楽天モバイル3GB+どんなときもWiFiのみでインターネットを利用しています。&lt;/p&gt;
&lt;p&gt;光回線なしで、仕事もプライベートもインターネットを使う生活を1ヶ月間してみて、ちょっとスピード遅いなぁと感じることもありますが、仕事に支障もなく生活できております。&lt;/p&gt;
&lt;p&gt;どんなときWiFiのみで生活した1ヶ月の中で感じた、良いところと悪いところをまとめました。&lt;/p&gt;
&lt;p&gt;（メインは、悪いところ・不満なところかな）&lt;/p&gt;
&lt;h1&gt;使用頻度&lt;/h1&gt;
&lt;p&gt;まずは私がどのようにインターネットを使っているかなのですが、週5日リモートワークでエンジニアの仕事をしております。また、プライベートでも開発を行っているので、1日平均10時間以上はインターネットを使用しています。&lt;br /&gt;さらに、Spotifyで音楽を常に流しているので、通信量は毎日10GBは超えているのではないでしょうか。&lt;/p&gt;
&lt;p&gt;プライベートでは、Youtubeを見たり、PrimeVideoを見たりもしています。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/donnatokimo-wifi-review-a-month/donnatokimo-wifi-review-a-month-1.png&quot; alt=&quot;通信量は毎日10GBは超えている&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;良かった点&lt;/h1&gt;
&lt;h2&gt;通信費を抑えられた&lt;/h2&gt;
&lt;p&gt;私は、長野県大町市に暮らしており、東京の企業と業務委託という形でお仕事をさせていただいております。なのでときおり、東京にでることもありますし、在宅だけでなくカフェでやることも多いです。となると、通信環境として、光回線+スマホ用SIM+追加データが必要でした。&lt;/p&gt;
&lt;p&gt;しかし、光回線を解約したので現在は、どんなときもWiFi+スマホ用SIMだけで運用しています。どんなときもWiFiが3,542円、スマホ用SIMが1,730円なので、通信費は5,272円/月になります。&lt;/p&gt;
&lt;p&gt;以前は、1万円を超えていたので、半分になり約5,000円の節約になりました。（光回線って意外と高いですよね。）&lt;/p&gt;
&lt;h2&gt;WiFiのあるカフェを探す必要がなくなった&lt;/h2&gt;
&lt;p&gt;これ私にとって、ほんとにありがたいです！&lt;br /&gt;以前でしたら、外出先でやるとなるとWiFiのあるカフェを探さなければなりませんでした。するとちょっと離れたところまで行かなければいけなかったり、WiFiはあるのにスピードが遅くて仕事にならなかったり・・・。&lt;/p&gt;
&lt;p&gt;場所を探すだけで相当労力を費やすことが多々ありました。WiFiカフェマップを作成してしまうほどに。。。&lt;/p&gt;
&lt;p&gt;ですが、今後は場所を気にする必要がありません！最近実際にやったのが車の中。移動中にサーバーが落ちたと連絡が来て、すぐにコンビニに停車してその場で復旧作業にあたりました。&lt;br /&gt;**どこにいてもすぐに作業できるということが、心理的不安をとても和らげてくれました。**ノマドワークが捗りますね！&lt;/p&gt;
&lt;p&gt;副次的に、無料WiFiは暗号化されていないことも多く少し心配で、パスワードを入力する必要がある作業はなるべくしないように気をつけていたのですが、そういった心配もなくなりました。&lt;/p&gt;
&lt;h2&gt;ド田舎でも繋がる&lt;/h2&gt;
&lt;p&gt;家が市街地から山道を15分ほど走ったところにある、ド田舎でもどんなときもWiFiは繋がります。ちなみにPocketWiFiやUQWiMAXは家では繋がらないです。。&lt;/p&gt;
&lt;p&gt;家にいるときも、実家に帰ったときも、旅行先でも、問題なく利用できました。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/donnatokimo-wifi-review-a-month/inaka--1-.png&quot; alt=&quot;家からすぐの場所から見える景色&quot; /&gt;&lt;br /&gt;&lt;em&gt;家からすぐの場所から見える景色&lt;/em&gt;
&lt;/p&gt;
&lt;h1&gt;悪かった点&lt;/h1&gt;
&lt;h2&gt;時間帯によって通信スピード遅くなることがある&lt;/h2&gt;
&lt;p&gt;お昼時と夜の時間帯くらいに、スピード遅いなぁと感じることが結構ありました。YoutubeをHD画質で見ようとすると途切れ途切れになってしまったり、サイトの画像の読み込みが遅かったり、ちょっとストレスを感じました。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.ryou103.com/post/donnatokimo-wifi-review/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;以前のレビュー&lt;/a&gt;のときは、いつでもスイスイ使える印象だったのですが、「どんなときもWiFi」の人気がでてきて利用者が増えたからでしょうか。格安SIMと同様で、使える周波数帯が限られていると思うので、人が増えれば繋がりにくくなってしまうのでしょう。&lt;/p&gt;
&lt;p&gt;ただし、Spotifyを流したり、ビデオ会議をするのには、なんも問題ありませんでした。&lt;/p&gt;
&lt;p&gt;また複数の通信をしようとすると遅くなる気がしました。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/donnatokimo-wifi-review-a-month/u2s.jpg&quot; alt=&quot;u2s&quot; /&gt;&lt;br /&gt;&lt;em&gt;端末の最大通信速度が遅いのが原因でしょう&lt;/em&gt;
&lt;/p&gt;
&lt;h2&gt;アップロードが遅い？&lt;/h2&gt;
&lt;p&gt;動画など大きいデータをクラウドにアップロードするときのは、めちゃくちゃ遅いです。&lt;br /&gt;例えば、522MBの動画をアップロードしようとすると、調子の良いときで2MB/s。アップロードが完了するまでに約2分20秒かかります。実際にはずっとその速度が出ているわけではないので、5分ほどかかりました。時間帯によっては、アップロードする気にもならない速度になります。&lt;/p&gt;
&lt;p&gt;写真をアップロードする分には、ストレスはほとんどないです。ただし一気にアップロードするのは、やはり動画同様時間がかかってしまうので、パソコンを操作しない時間帯に行うのが無難でしょう。&lt;/p&gt;
&lt;h2&gt;バッテリー切れして再起動したあと、たまに繋がらないことがある&lt;/h2&gt;
&lt;p&gt;バッテリーは10時間以上持つので、外出先でも気にせず使っています。そのため充電を忘れ、バッテリー切れを起こしてしまうことがあります。すぐにコンセントに繋いで再起動させるのですが、WiFiマークが点滅したまま通信できなくなってしまうことがあります。原因が不明です。バグでしょうか。&lt;/p&gt;
&lt;p&gt;こうなったときは、電源ボタンを18秒長押しリセットすることで、解消されました。&lt;/p&gt;
&lt;p&gt;18秒も押すのは面倒なので、バッテリー切れには気をつけて、こまめに充電をするようにしたいと思います。&lt;/p&gt;
&lt;h1&gt;まとめ&lt;/h1&gt;
&lt;p&gt;以上が、「&lt;a href=&quot;https://px.a8.net/svt/ejp?a8mat=35DJ1D+7FX4JM+3KKM+BWVTE&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;&lt;strong&gt;どんなときもWiFi&lt;/strong&gt;&lt;/a&gt;&lt;img border=&quot;0&quot; width=&quot;1&quot; height=&quot;1&quot; src=&quot;https://www12.a8.net/0.gif?a8mat=35DJ1D+7FX4JM+3KKM+BWVTE&quot; alt=&quot;&quot;&gt;」だけで1ヶ月間生活してみて気づいた良い点・悪い点でした。&lt;/p&gt;
&lt;p&gt;光回線を使っているときに比べると、少しストレスは感じますが、動画も見れるし、ゲームもできるし、もちろん仕事にも支障はないので、今後も「&lt;a href=&quot;https://px.a8.net/svt/ejp?a8mat=35DJ1D+7FX4JM+3KKM+BWVTE&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;&lt;strong&gt;どんなときもWiFi&lt;/strong&gt;&lt;/a&gt;&lt;img border=&quot;0&quot; width=&quot;1&quot; height=&quot;1&quot; src=&quot;https://www12.a8.net/0.gif?a8mat=35DJ1D+7FX4JM+3KKM+BWVTE&quot; alt=&quot;&quot;&gt;」だけで生活していこうと思います。&lt;/p&gt;
&lt;p&gt;検討中の方の参考になれば幸いです！&lt;/p&gt;&lt;a href=&quot;https://px.a8.net/svt/ejp?a8mat=35DJ1D+7FX4JM+3KKM+BYDTT&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;
&lt;img border=&quot;0&quot; width=&quot;300&quot; height=&quot;250&quot; alt=&quot;&quot; src=&quot;https://www22.a8.net/svt/bgt?aid=190427809450&amp;wid=003&amp;eno=01&amp;mid=s00000016663002008000&amp;mc=1&quot;&gt;&lt;/a&gt;
&lt;img border=&quot;0&quot; width=&quot;1&quot; height=&quot;1&quot; src=&quot;https://www15.a8.net/0.gif?a8mat=35DJ1D+7FX4JM+3KKM+BYDTT&quot; alt=&quot;&quot;&gt;
]]&gt;</content:encoded></item><item><title>&lt;![CDATA[フリーランスが引っ越しをするときに必要な手続きまとめ]]&gt;</title><link>https://blog.ryou103.com/post/freerance-hikkoshi</link><guid>https://blog.ryou103.com/post/freerance-hikkoshi</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/freerance-hikkoshi/freerance-hikkoshi.jpg" medium="image"></media:content><description>フリーランスになって初めて引っ越したので、その際に行った手続きをまとめておきます。国民健康保険や国民年金など自分で管理しているものが増えているので、手続きが面倒になるかと思いきや、会社員のときと比べて１つだけやることが増えただけでした。</description><pubDate>Wed, 08 May 2019 12:12:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/freerance-hikkoshi/freerance-hikkoshi.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;フリーランスになって初めて引っ越したので、その際に行った手続きをまとめておきます。&lt;/p&gt;
&lt;p&gt;国民健康保険や国民年金など自分で管理しているものが増えているので、手続きが面倒になるかと思いきや、会社員のときと比べて１つだけやることが増えただけでした。&lt;/p&gt;
&lt;h1&gt;引越し前の手続き&lt;/h1&gt;
&lt;h2&gt;所得税・消費税の納税地の異動に関する届出書&lt;/h2&gt;
&lt;p&gt;場所： 引っ越す前の税務署&lt;/p&gt;
&lt;p&gt;やること： 「所得税・消費税の納税地の異動に関する届出書」を提出&lt;/p&gt;
&lt;p&gt;必要なもの： マイナンバー・印鑑&lt;/p&gt;
&lt;p&gt;（何度も同じ住所を記入することになるので、パソコンで記入して印刷していくことをお薦めします。）&lt;/p&gt;
&lt;h2&gt;転出届&lt;/h2&gt;
&lt;p&gt;場所： 引っ越す前の市役所&lt;/p&gt;
&lt;p&gt;やること： 「転出届」を提出&lt;/p&gt;
&lt;p&gt;必要なもの： 現住所の分かるもの・印鑑・マイナンバー？&lt;/p&gt;
&lt;p&gt;（国民健康保険に加入している場合、転出すると同時に自動で喪失するそうです。以前は手続きが必要だったみたい。）&lt;/p&gt;
&lt;h2&gt;郵便局の転送手続き&lt;/h2&gt;
&lt;p&gt;Web上の&lt;a href=&quot;https://welcometown.post.japanpost.jp/etn/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;e転居&lt;/a&gt;サービスから手続き&lt;/p&gt;
&lt;p&gt;（転送開始までには、一週間程かかるそうなので、お早めに。）&lt;/p&gt;
&lt;h2&gt;電気・水道・ガスの供給停止・開始［各供給会社］&lt;/h2&gt;
&lt;p&gt;ガスの停止・開始には立会いが必要な場合が多いので、早めの連絡を。&lt;/p&gt;
&lt;h1&gt;引越し後の手続き&lt;/h1&gt;
&lt;h2&gt;転入届&lt;/h2&gt;
&lt;p&gt;場所： 引越し先の市役所&lt;/p&gt;
&lt;p&gt;やること： 「転入届」を提出&lt;/p&gt;
&lt;p&gt;必要なもの： 印鑑・マイナンバー？・転出証明書（引っ越し先の住所が分かるものが必要になる地域もあります。）&lt;/p&gt;
&lt;h2&gt;国民健康保険&lt;/h2&gt;
&lt;p&gt;転入届を出す際に、申し出れば手続きを行ってくれます。&lt;/p&gt;
&lt;h2&gt;免許証の住所変更&lt;/h2&gt;
&lt;p&gt;場所： 引越し先の運転免許試験場、運転免許更新センター、警察署&lt;/p&gt;
&lt;p&gt;必要なもの： 免許証・引越し先の住所が確認できるもの（新しい住民票、新住所の健康保険証、消印付郵便物、住所が確認できる公共料金の領収証など）&lt;/p&gt;
&lt;h2&gt;金融機関の住所変更手続き&lt;/h2&gt;
&lt;p&gt;各金融機関で行いましょう。&lt;/p&gt;
&lt;h2&gt;その他の住所変更手続き&lt;/h2&gt;
&lt;p&gt;上記以外人も例えば、インターネットのプロバイダ・クレジットカード・携帯電話・終身保険など住所登録しているものは変更しましょう。&lt;/p&gt;
]]&gt;</content:encoded></item><item><title>&lt;![CDATA[Swaggerをファイル分割しながら、編集できるものを作成しました。]]&gt;</title><link>https://blog.ryou103.com/post/swagger-multi-file-in-docker</link><guid>https://blog.ryou103.com/post/swagger-multi-file-in-docker</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/swagger-multi-file-in-docker/swagger-multi-file-in-docler-eyecatch.jpg" medium="image"></media:content><description>Swaggerファイルをpathsやcomponentsなどファイル分割して編集でき、かつ画面をリアルタイムで確認できるものを作成しました。やっていることは単純で、分割されたファイルを監視して変更があったら、swagger.ymlというファイルにまとめ、それをswagger-uiで表示しています。</description><pubDate>Mon, 29 Apr 2019 08:25:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/swagger-multi-file-in-docker/swagger-multi-file-in-docler-eyecatch.jpg&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;作成したもの&lt;/h1&gt;
&lt;p&gt;Swaggerファイルをpathsやcomponentsなどファイル分割して編集でき、かつ画面をリアルタイムで確認できるものを作成しました。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/swagger-multi-file-in-docker/swagge-multi-file-screen.png&quot; alt=&quot;swagge-multi-file-screen&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;やっていることは単純で、分割されたファイルを監視して変更があったら、swagger.ymlというファイルにまとめ、それを&lt;a href=&quot;https://swagger.io/tools/swagger-ui/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;swagger-ui&lt;/a&gt;で表示しています。&lt;br /&gt;１ファイルにまとめるのに&lt;a href=&quot;https://www.npmjs.com/package/swagger-merger&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;swagger-merger&lt;/a&gt;という、CLIツールを使用しました。&lt;/p&gt;
&lt;h2&gt;リポジトリ&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/RyoheiTomiyama/swagger-multi-file-docker&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;swagger-multi-file-docker(Github)&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;環境&lt;/h2&gt;
&lt;p&gt;Dockerさえインストールしていれば、環境に依存することなく実行できるようにしました。（APIドキュメントは、編集する人が不特定いると思うので。）&lt;/p&gt;
&lt;h2&gt;実行&lt;/h2&gt;
&lt;p&gt;初回のみ、dockerイメージをビルドする必要があります。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker-compose build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ビルドが完了したら、コンテナを起動します。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ docker-compose up -d
Starting swagger-multi-file-docker_swagger-ui_1      ... done
Recreating swagger-multi-file-docker_swagger-watch_1 ... done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;http://localhost:8000&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://localhost:8000&lt;/a&gt; で、画面の確認ができます。&lt;br /&gt;ファイルは、好きなエディタで編集してください。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/swagger-multi-file-in-docker/swagger-ui-1.jpg&quot; alt=&quot;swagger-ui-1&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;なぜ作成したのか&lt;/h1&gt;
&lt;p&gt;swaggerは、デフォルトのままでもファイル分割が若干できますが、swagger-uiで確認するとスタイルが崩れたり、エラーが起こったり、使い勝手が悪かったです。&lt;/p&gt;
&lt;p&gt;結局、１ファイルにまとめた方が良いと判断したのですが、数千行になるファイルの管理はしたくないので、ファイル分割して編集したものをマージしてくれるものを探しました。&lt;/p&gt;
&lt;p&gt;そこで見つけたのが、&lt;a href=&quot;https://www.npmjs.com/package/swagger-merger&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;swagger-merger&lt;/a&gt;でした。&lt;br /&gt;ですが、swagger-mergerには、コマンドライン上から実行してファイルをまとめる機能しかありませんでした。編集する→コマンドラインでマージ→ブラウザを確認 を繰り返さなければならず、効率が悪いことに変わりはありません。&lt;/p&gt;
&lt;p&gt;ファイルを監視して自動コンパイルしてくれるツールが見つからなかったので、作ることにしました。&lt;/p&gt;
&lt;h1&gt;使い方&lt;/h1&gt;
&lt;h2&gt;ディレクトリ構造&lt;/h2&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;docs/    # swagger-uiファイルとswagger.ymlの出力先
src/     # 編集するswaggerファイル達
 ├ index.yml
 ├ components/
 └ paths/
swagger-watch/   # ファイル監視システム
docker-compose.yml   # swagger-uiとswagger-watchのコンテナ
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Swaggerファイルの編集&lt;/h2&gt;
&lt;p&gt;Swagger.ymlのエントリーポイントとなっているのは、&lt;code&gt;/src/index.yml&lt;/code&gt;です。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-yaml&quot; data-filename=&quot;/src/index.yml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;openapi: &quot;3.0.0&quot;
info:
  version: 1.0.0
  title: Swagger Petstore
  license:
    name: MIT
servers:
  - url: http://petstore.swagger.io/v1
paths:
  $ref: &#x27;./paths/index.yml&#x27;
components:
  $ref: &#x27;./components/index.yml&#x27;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;$ref&lt;/code&gt;を使って、外部ファイルを読み込んでいます。&lt;br /&gt;デフォルトでは、&lt;code&gt;/paths&lt;/code&gt;と&lt;code&gt;/components&lt;/code&gt;を用意していますが、他に分割したい項目がありましたら、同じ書き方で外部ファイルに分割できます。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/src/paths/index.yml&lt;/code&gt;では、パスのエントリーポイントになっており、各パスごとに分割されたファイルを読み込んでいます。（&lt;code&gt;/components&lt;/code&gt;も同様）&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-yaml&quot; data-filename=&quot;/src/paths/index.yml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;/pets:
  $ref: &#x27;./pets.yml&#x27;
/pets/{petId}:
  $ref: &#x27;./pets_petId.yml&#x27;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/swagger-multi-file-in-docker/swagger-multi-file-directory.png&quot; alt=&quot;swagger-multi-file-directory&quot; /&gt;
&lt;/p&gt;
&lt;h1&gt;最後に&lt;/h1&gt;
&lt;p&gt;これで、快適にswaggerを利用することができるようになりました。&lt;br /&gt;さらに&lt;code&gt;swagger.yml&lt;/code&gt;をdocs以下に出力するようにしたので、Githubにリポジトリを置けば、GithubPagesで公開することも可能となっています。&lt;/p&gt;
&lt;p&gt;↓ こんな感じ&lt;br /&gt;&lt;a href=&quot;https://ryoheitomiyama.github.io/swagger-multi-file-docker/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ryoheitomiyama.github.io/swagger-multi-file-docker/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;是非利用してみてください〜。&lt;/p&gt;
]]&gt;</content:encoded><category>Swagger</category></item><item><title>&lt;![CDATA[axiosでGETリクエストのクエリパラメータにObject型を含める方法]]&gt;</title><link>https://blog.ryou103.com/post/axios-send-object-query</link><guid>https://blog.ryou103.com/post/axios-send-object-query</guid><description>axiosで、GETリクエストのパラメータにObject型を含むことができたら便利なのにと考えました。axiosでは、paramsSerializerオプションを設定すると、クエリパラメータをObject型で取得することが可能になるので、その方法をご紹介。</description><pubDate>Mon, 29 Apr 2019 06:53:00 +0000</pubDate><content:encoded>&lt;![CDATA[
&lt;p&gt;&lt;a href=&quot;https://github.com/axios/axios&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;axios&lt;/a&gt;で、GETリクエストのパラメータにObject型を含むことができたら便利なのにと考えました。&lt;/p&gt;
&lt;p&gt;axiosでは、&lt;strong&gt;&lt;code&gt;paramsSerializer&lt;/code&gt;オプションを設定すると、クエリパラメータをObject型で取得することが可能になります。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;オプションを設定せずに、送信すると、、、&lt;/h2&gt;
&lt;p&gt;例えば、記事を検索するAPIで、キーワードを&quot;hoge&quot;、カテゴリーを&quot;interview&quot;, &quot;column&quot;で絞り込みたいときに、以下のようにクエリパラメータを使ってカテゴリーをObject型で指定してリクエストが送ろうとすると・・・&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;import axios from &#x27;axios&#x27;;

const url = &#x27;http://localhost/api/posts/search&#x27;;

const params = {
  word: &#x27;hoge&#x27;,
  category: {
    $in: [&#x27;interview&#x27;, &#x27;column&#x27;],
  },
};

axios.get(url, { params });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;結果： categoryがObject型→String型に変換されてしまう。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;GET /api/posts/search?word=hoge&amp;#x26;category=%7B%22$in%22:[%22interview%22,%22column%22]%7D

&gt; console.log(req.query);
  {
     word: &#x27;hoge&#x27;,
     category: &#x27;{&quot;$in&quot;:[&quot;interview&quot;,&quot;column&quot;]}&#x27;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;こうなると、サーバーサイドで受け取ったあとに、&lt;code&gt;JSON.parse&lt;/code&gt;してObject型に戻さなければならない。&lt;/p&gt;
&lt;p&gt;しかし、そんなことをしなくても、&lt;strong&gt;axiosにはパラメータにObject型を利用できるオプションがある。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;paramsSerializerオプションを設定&lt;/h2&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;import axios from &#x27;axios&#x27;;
import qs from &#x27;qs&#x27;;

const url = &#x27;http://localhost/api/posts/search&#x27;;

const params = {
  word: &#x27;hoge&#x27;,
  category: {
    $in: [&#x27;interview&#x27;, &#x27;column&#x27;],
  },
};

const paramsSerializer = (params) =&gt; qs.stringify(params);

axios.get(url, { params, paramsSerializer });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;クエリパラメータでは、一度JSON文字列に変換する必要があり、&lt;code&gt;paramsSeriailizer&lt;/code&gt;オプションでは、どのような形のJSON文字列に変換するかを設定できます。&lt;/p&gt;
&lt;p&gt;今回はJSON文字列に変換するのに、&lt;a href=&quot;https://www.npmjs.com/package/qs&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;qs&lt;/a&gt;というモジュールを使用しました。&lt;/p&gt;
&lt;p&gt;実行結果は、以下のようになりました。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;GET /api/posts/search?word=hoge&amp;#x26;category%5B%24in%5D%5B0%5D=interview&amp;#x26;category%5B%24in%5D%5B1%5D=column

&gt; console.log(req.query);
  { word: &#x27;hoge&#x27;,
    category: { &#x27;$in&#x27;: [ &#x27;interview&#x27;, &#x27;column&#x27; ] }
  }

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;paramsSeriailizer&lt;/code&gt;を指定したことで、API側ではObject型で取得することができます。&lt;br /&gt;このオプジョンを使えば柔軟なAPIを簡単に作成することができそうです。&lt;/p&gt;
]]&gt;</content:encoded></item><item><title>&lt;![CDATA[元祖 無制限モバイルWiFi これ1台で快適ノマドライフ「どんなときもWiFi」]]&gt;</title><link>https://blog.ryou103.com/post/donnatokimo-wifi-review</link><guid>https://blog.ryou103.com/post/donnatokimo-wifi-review</guid><media:content url="https://blog-next-posts.vercel.app/assets/posts/donnatokimo-wifi-review/donnatokimo-wifi@2x.jpg" medium="image"></media:content><description>通信容量無制限を謳っているモバイルWi-Fi「どんなときもWiFi」。仕事で使ったり、動画を楽しんだりして、実際にはどうなのかを検証しました。</description><pubDate>Thu, 11 Apr 2019 22:30:00 +0000</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;&lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/donnatokimo-wifi-review/donnatokimo-wifi@2x.jpg&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Ymobile!のPocketWiFiやUQWiMAX、または縛りなしを謳っている民泊WiFiを検討しているノマドワーカーは多いのではないでしょうか？&lt;/p&gt;
&lt;p&gt;しかし、これらのモバイルWiFiは3日間で10GBまでだったり、地方に行くと電波が入らなかったり、地下や室内でも電波が入らなかったり・・・&lt;/p&gt;
&lt;p&gt;仕事に使うには少しリスクが高すぎました。（私自身、長野県に移住しフリーランスとして活動していて、UQWiMAXの導入を検討しましたが、電波が入らず泣く泣く諦めました。）&lt;/p&gt;
&lt;p&gt;できれば電波や通信制限を気にすることなく、&lt;strong&gt;好きな場所で自由に働きたいですよね！&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;まさに！&lt;br /&gt;ノマドワーカーのための容量無制限モバイルWiFiを見つけてしまいました。&lt;br /&gt;その名も「&lt;a href=&quot;https://px.a8.net/svt/ejp?a8mat=35DJ1D+7FX4JM+3KKM+BWVTE&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;&lt;strong&gt;どんなときもWiFi&lt;/strong&gt;&lt;/a&gt;&lt;img border=&quot;0&quot; width=&quot;1&quot; height=&quot;1&quot; src=&quot;https://www12.a8.net/0.gif?a8mat=35DJ1D+7FX4JM+3KKM+BWVTE&quot; alt=&quot;&quot;&gt;」！！&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://px.a8.net/svt/ejp?a8mat=35DJ1D+7FX4JM+3KKM+BWVTE&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/donnatokimo-wifi-review/donnatokimo-wifi-review-1.png&quot; alt=&quot;どんなときもWiFi&quot; /&gt;&lt;/a&gt;&lt;img border=&quot;0&quot; width=&quot;1&quot; height=&quot;1&quot; src=&quot;https://www12.a8.net/0.gif?a8mat=35DJ1D+7FX4JM+3KKM+BWVTE&quot; alt=&quot;&quot;&gt;
&lt;/p&gt;
&lt;p&gt;容量無制限！ネット使い放題！どこでも使える！&lt;/p&gt;
&lt;p&gt;はいはい、どうせ注釈でちっさく規制について書いてあるんでしょ。&lt;br /&gt;詳しくみましたよ。&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;※1：国内外の通信会社では、ネットワーク品質の維持および公正な電波利用の観点から、著しくネットワークを占有するレベルの大容量通信をされた場合、該当のお客様に対し通信速度を概ね384Kbpsに制限する場合があります。また、違法ダウンロードなどの不正利用の疑いがある場合、ご利用停止を行う場合があります。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ほら、384kbpsに制限するって。これキロバイトにすると、48kB/s。&lt;br /&gt;まともにネットサーフィンもできない速度です。&lt;/p&gt;
&lt;p&gt;はぁ、やっぱりね。と思いながらも諦めきれない気持ちがあります。「大容量通信をされた・・・」ってどんくらいなんだろう？&lt;br /&gt;調べてみると口コミには、100GB使おうが200GB使おうが制限がかからないと言っているサイトをちらほらと見かけます。&lt;/p&gt;
&lt;p&gt;これは！ついにきたか！&lt;/p&gt;
&lt;p&gt;アフィリエイトサイトみたいのは100%信用ができるわけではないので、公式サイトのチャットで質問してみました。（スクショ撮り忘れました。。）&lt;/p&gt;
&lt;p&gt;「仕事で使う」「自宅でも使う」「月に200GB超えてしまう」「光回線の代わりに使いたい」という要件をサポートの方に伝えました。とても自信があるようで、「全く問題ございません。」との回答が。1つデメリットとしては、「有線で通信することはできません。」とのことでした。&lt;/p&gt;
&lt;p&gt;期待が持てるので、試してみようとすぐに契約しました。（もし通信速度が悪かったり、エリア的に厳しいなどあれば、8日以内に返品しようと思っています。）&lt;/p&gt;
&lt;p&gt;というわけで、契約した次の日に届きました。速い！&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/donnatokimo-wifi-review/donnatokimo-wifi-review-2.jpg&quot; alt=&quot;契約した次の日に届きました&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;いざ開封！&lt;/h2&gt;
&lt;p&gt;中身は少なく、本体・USBケーブル・ストラップのみ&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/donnatokimo-wifi-review/donnatokimo-wifi-review-3.jpg&quot; alt=&quot;どんなときもWiFi開封&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;本体もとてもシンプルで、ディスプレイもなく、あるのは、サイドに電源ボタン1つのみ。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/donnatokimo-wifi-review/donnatokimo-wifi-review-4.jpg&quot; alt=&quot;本体もとてもシンプル&quot; /&gt;
&lt;/p&gt;
&lt;h2&gt;気になる通信速度は？&lt;/h2&gt;
&lt;p&gt;早速起動して、電波状況を確認してみましたが問題なし！（ちなみに我が家は、UQWiMAXが入らないエリアでした。。。）&lt;br /&gt;3キャリアのLTE回線を使っているので、いづれかのキャリアが繋がれば通信可能だとのこと。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/donnatokimo-wifi-review/donnatokimo-wifi-review-5.jpg&quot; alt=&quot;本体&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;さてさて、みなさんが知りたいのは、通信速度ですよね。&lt;/p&gt;
&lt;p&gt;結果は以下のようになりました。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/donnatokimo-wifi-review/donnatokimo-wifi-review-6.png&quot; alt=&quot;通信速度結果&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;下りが26.8Mbps、上りが17.7Mbps&lt;/strong&gt;でした。（計測した時間帯は19時半。まぁまぁ回線が混み合う時間帯ではないでしょうか。）&lt;br /&gt;期待していた以上のスピードがでました。&lt;/p&gt;
&lt;p&gt;このあとも、何度か試しましたが、上りが4Mbpsと低くなることはありましたが、対して問題ない数値ですね。&lt;/p&gt;
&lt;p&gt;ちなみに、フレッツ光回線も計測してみたところ、下りが46.3Mbps、上りが18.2Mbpsでした。&lt;br /&gt;やはり光回線には勝てないですね。ですが、HD動画をストリーミングしてもストレスなく見れる速度はあるといった印象です。&lt;/p&gt;
&lt;h2&gt;体感スピード&lt;/h2&gt;
&lt;p&gt;数値は数値なので、実際のところどうなのよってことで、色々試してみました！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;YouTube&lt;/strong&gt;&lt;br /&gt;動画を開くと、すぐに広告が流れ出しました。光回線と差なし。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Web&lt;/strong&gt;&lt;br /&gt;いろんなサイトをポチポチ。サイト自体はすぐに開くが、画像の読み込みが少し遅い？でもストレスは感じない速度です。&lt;br /&gt;（コンビニのフリーWiFiとかはストレス感じます。）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Googleドライブからファイルをダウンロード（66MB）&lt;/strong&gt;&lt;br /&gt;約4MB/sで20秒とかからずダウンロード完了。光回線と比べたらやはり遅いですが、こちらもストレスなし！&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ビデオ会議&lt;/strong&gt;&lt;br /&gt;ハングアウトとZoomを使って、４，５人でのビデオ通話を行いましたが、一度も途切れることなく利用できました。&lt;/p&gt;
&lt;p&gt;1日仕事とプライベートで利用してみて、スピードに波があるかなと感じました。おそらく格安SIM同様に回線が混雑するタイミングは、やはりどうしても遅くなってしまうのかなと。ただ遅くなったからといって、&lt;strong&gt;仕事に支障がでるとか動画が見れなくなるということはありませんでした。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;どこでも使える！&lt;/h2&gt;
&lt;p&gt;ソフトバンク・ドコモ・au、3キャリア対応で「&lt;strong&gt;どこでも使える！&lt;/strong&gt;」と言っていますが、果たしてどうでしょうか！&lt;/p&gt;
&lt;p&gt;以前、PocketWiFiやUQWiMAXも使うことができなかったマクドナルドに向かいたいと思います！&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/donnatokimo-wifi-review/donnatokimo-wifi-review-7.png&quot; alt=&quot;安曇野市のスワンガーデン内にあるマクドナルド&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;em&gt;安曇野市のスワンガーデン内にあるマクドナルド。周りは田んぼだらけの田舎です。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;道中の車内でもYouTubeでミュージックビデオを流しながら電波チェックしましたが、&lt;strong&gt;一度も途切れることなく&lt;/strong&gt;目的地に到着しました。&lt;/p&gt;
&lt;p&gt;モバイルWiFiは、移動中だと繋がらないという印象を持っていましたが、&lt;strong&gt;どんなときもWiFi&lt;/strong&gt;は凄いですね！&lt;br /&gt;やはりLTE回線を使っているからなのでしょうか？今回は検証していませんが、トンネル内でももしかしたら利用できるかもしれないですね。&lt;/p&gt;
&lt;p&gt;さてさて肝心のマクドナルドでは、いかがでしょうか？&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/donnatokimo-wifi-review/donnatokimo-wifi-review-8.jpg&quot; alt=&quot;電波状況&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;ビンビンです！&lt;br /&gt;もちろん、なにをしても快適に使用することができました！&lt;/p&gt;
&lt;p&gt;本当に、どこでも使える！&lt;/p&gt;
&lt;h2&gt;容量無制限&lt;/h2&gt;
&lt;p&gt;続いて検証したいのは、本当に容量を気にすることなく使えるのか？&lt;/p&gt;
&lt;p&gt;1日で10GBを超えていますが、特に制限されることなく利用することができています。&lt;br /&gt;PocketWiFi・UQWiMAXでしたら、すでに通信制限にかかっているでしょう。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/donnatokimo-wifi-review/donnatokimo-wifi-review-9.png&quot; alt=&quot;管理画面&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;em&gt;（１日ごとにデータ利用量の表示はリセットされるみたい。）&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;まだ2日しか利用していないので、絶対に大丈夫とは言えませんが、従来のモバイルWiFiとはひと味違うようです。&lt;br /&gt;もう少し利用して、また経過報告ができたらと思います。&lt;/p&gt;
&lt;h2&gt;まとめ&lt;/h2&gt;
&lt;p&gt;通信速度・エリア・速度制限。&lt;br /&gt;仕事用のモバイルWiFiとして利用する上で気になる点を見てきましたが、&lt;strong&gt;どんなときもWiFi&lt;/strong&gt;は満足のいく結果となりました。&lt;/p&gt;
&lt;p&gt;自宅でも外出先でも、これ１台で十分なのではないでしょうか。&lt;br /&gt;料金に関しても、どんなときもWiFiは&lt;strong&gt;2年間3,280円&lt;/strong&gt;。2年目以降も3,980円とモバイルWiFiの価格帯とほぼ変わりはありません。&lt;br /&gt;むしろ同じような利用目的を実現しようとすると、今までであれば光回線＋モバイルWiFiの契約が必要となり約8,000円はかかっていたので、とてもお得だと思います。&lt;/p&gt;
&lt;p&gt;長野に移住してから、WiFiのあるカフェを見つけられず、自宅での作業が多くなってしまいましたが、この&lt;a href=&quot;https://px.a8.net/svt/ejp?a8mat=35DJ1D+7FX4JM+3KKM+BWVTE&quot; target=&quot;_blank&quot; rel=&quot;nofollow&quot;&gt;&lt;strong&gt;どんなときもWiFi&lt;/strong&gt;&lt;/a&gt;&lt;img border=&quot;0&quot; width=&quot;1&quot; height=&quot;1&quot; src=&quot;https://www12.a8.net/0.gif?a8mat=35DJ1D+7FX4JM+3KKM+BWVTE&quot; alt=&quot;&quot;&gt;を持っていれば、好きな場所で仕事をすることができそうです！&lt;/p&gt;
&lt;p&gt;いままで電波が無くて諦めていた、キャンプ場でノマドワークしてみようかな。&lt;/p&gt;
]]&gt;</content:encoded></item><item><title>&lt;![CDATA[VeeValidateでcheckboxを必須項目としてバリデーションする方法]]&gt;</title><link>https://blog.ryou103.com/post/vee-validate-checkbox</link><guid>https://blog.ryou103.com/post/vee-validate-checkbox</guid><description></description><pubDate>Tue, 26 Mar 2019 07:12:00 +0000</pubDate><content:encoded>&lt;![CDATA[
&lt;p&gt;&lt;a href=&quot;https://baianat.github.io/vee-validate/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;VeeValidate&lt;/a&gt;を使ってチェックボックスを必須項目としてバリデーションする場合以下のように書くかと思います。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-pug&quot;&gt;&lt;code class=&quot;language-pug&quot;&gt;input(
  type=&quot;checkbox&quot;,
  data-vv-name=&quot;bool&quot;,
  v-validate=&quot;&#x27;required&#x27;&quot;,	
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;予想される動作としては、チェックされていなければエラーを表示する。&lt;/p&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/vee-validate-checkbox/vee-validate-example@2x.jpg&quot; alt=&quot;VeeValidate例&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;しかし、&lt;strong&gt;実際にはエラーを表示してくれない。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;なぜなのか。&lt;/p&gt;
&lt;h2&gt;checkboxでは値がfalseでもtrueでも、バリデーション結果はtrueになる&lt;/h2&gt;
&lt;p&gt;先程作成したチェックボックスでバリデーションの実験を行った。&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;validator.validate(&#x27;bool&#x27;, true).then((result) =&gt; {
	console.log(result)
});
--&gt; true

validator.validate(&#x27;bool&#x27;, false).then((result) =&gt; {
	console.log(result)
});
--&gt; true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;値がtrueでもfalseでもバリデーション結果がtrueになってしまうのである。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;チェックボックスの正しい動作として、チェックがついていなければ、エラーになって欲しい。&lt;br /&gt;でも、このままだとエラーにはならない。&lt;/p&gt;
&lt;p&gt;調べました。&lt;br /&gt;「vee-validate boolean」で検索。&lt;/p&gt;
&lt;p&gt;解決したissueが出てきました。&lt;br /&gt;&lt;a href=&quot;https://github.com/baianat/vee-validate/issues/484&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/baianat/vee-validate/issues/484&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;あれ？ 修正されてるみたい！&lt;br /&gt;なんで動かないの？バージョンも最新にしたのに。。。&lt;/p&gt;
&lt;p&gt;追記：どうやらv2.2.0での話であって、現在の仕様とは違うみたいです。&lt;/p&gt;
&lt;p&gt;requiredルールのソース見てみました。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/baianat/vee-validate/blob/master/src/rules/required.js&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/baianat/vee-validate/blob/master/src/rules/required.js&lt;/a&gt;&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-js&quot; data-filename=&quot;required.js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;  // incase a field considers `false` as an empty value like checkboxes.
  if (value === false &amp;#x26;&amp;#x26; invalidateFalse) {
    return false;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ちゃんと用意してくれていました。&lt;br /&gt;どうやら&lt;code&gt;invalidateFalse&lt;/code&gt;を指定をしてあげればいいのですね^^&lt;/p&gt;
&lt;p&gt;正しいチェックボックスの例&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-pug&quot;&gt;&lt;code class=&quot;language-pug&quot;&gt;input(
	type=&quot;checkbox&quot;,
	data-vv-name=&quot;bool&quot;,
	v-validate=&quot;&#x27;required:invalidateFalse&#x27;&quot;,  // &amp;#x3C;-- ここ	
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
  &lt;img src=&quot;https://blog-next-posts.vercel.app/assets/posts/vee-validate-checkbox/vee-validate-example-2.png&quot; alt=&quot;VeeValidate期待する動作&quot; /&gt;
&lt;/p&gt;
&lt;p&gt;はい！期待した通りに動作してくれました！&lt;/p&gt;
&lt;p&gt;VeeValidateのドキュメントはたまに古いことがあるので、あれ？ってなったらソース見に行くのがいいですね。&lt;br /&gt;とても見やすいソースになっているので、すぐに原因を見つけることができると想いますよ。&lt;/p&gt;
]]&gt;</content:encoded><category>Vue</category></item><item><title>&lt;![CDATA[Dockerで起動しているMongoDBにリストアする]]&gt;</title><link>https://blog.ryou103.com/post/docker-mongo-restore</link><guid>https://blog.ryou103.com/post/docker-mongo-restore</guid><description></description><pubDate>Mon, 25 Mar 2019 21:22:00 +0000</pubDate><content:encoded>&lt;![CDATA[
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;docker run --rm --link mongo_mongodb_1:mongo -v /var/www/mongo/backup/:/backup --network mongo_default mongo:3.2 bash -c &#x27;mongorestore --db ${DATABASE_NAME} --host mongo /backup&#x27;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;mongo:3.2を起動して、mongo_defaultというネットワーク上にある、mongo_mongodb_1というコンテナに接続する。&lt;br /&gt;ホストの/var/www/mongo/backup/ディレクトリにある、リストアデータを/backupにマウントする。&lt;br /&gt;mongorestoreコマンドを実行して、終了したら、今回起動したコンテナを削除する。&lt;/p&gt;
&lt;h2&gt;オプションの説明&lt;/h2&gt;
&lt;h3&gt;--rm&lt;/h3&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;--rm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;今回走らせるdockerコンテナがexitしたときに、コンテナを削除する&lt;/p&gt;
&lt;h3&gt;--link&lt;/h3&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;--link mongo_mongodb_1:mongo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（container_name:ALIAS）&lt;br /&gt;起動中のコンテナmongo_mongodb_1にmongoとして接続できるようにしている&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker ps&lt;/code&gt;したときに表示されるNAMESを指定している&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;$ docker ps
CONTAINER ID        IMAGE                                    COMMAND                  CREATED             STATUS                          PORTS                                      NAMES
79412c85455e        mongo-express                            &quot;tini -- node app&quot;       19 hours ago        Up 19 hours                     8081/tcp                                   mongo_mongo-express_1
d2381e8d539c        jrcs/letsencrypt-nginx-proxy-companion   &quot;/bin/bash /app/entr…&quot;   19 hours ago        Up 19 hours                                                                letsencrypt-nginx
ecf74e818b8e        jwilder/nginx-proxy                      &quot;/app/docker-entrypo…&quot;   19 hours ago        Up 19 hours                     0.0.0.0:80-&gt;80/tcp, 0.0.0.0:443-&gt;443/tcp   nginx-proxy
88085298f1e5        mongo:3.2                                &quot;docker-entrypoint.s…&quot;   41 hours ago        Up 19 hours                     0.0.0.0:27017-&gt;27017/tcp                   mongo_mongodb_1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;-v&lt;/h3&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;-v /var/www/mongo/backup/:/backup
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;今回走らせるdockerコンテナ内の&lt;code&gt;/backup&lt;/code&gt;にホストの&lt;code&gt;/var/www/mongo/backup/&lt;/code&gt;をマウントしている&lt;/p&gt;
&lt;p&gt;backupディレクトリの中身は、以下のようになっている&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;$ ls -l backup/
合計 203496
-rw-r--r-- 1 admin admin     17545  3月 14 10:44 admin-sessions.bson
-rw-r--r-- 1 admin admin       200  3月 14 10:44 admin-sessions.metadata.json
-rw-r--r-- 1 admin admin     12507  3月 14 10:44 authors.bson
.
.
.
-rw-r--r-- 1 admin admin        92  3月 14 10:47 userinfos.metadata.json
-rw-r--r-- 1 admin admin     29198  3月 14 10:47 users.bson
-rw-r--r-- 1 admin admin        88  3月 14 10:47 users.metadata.json
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;--network&lt;/h3&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;--network mongo_default
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;もし、mongoが別ネットワーク上で起動している場合は、このオプションで、mongoが起動しているネットワークに接続する&lt;/p&gt;
&lt;h3&gt;実行コマンド&lt;/h3&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;bash -c &#x27;mongorestore --db ${DATABASE_NAME} --host mongo /backup&#x27;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;--link&lt;/code&gt;で接続した&lt;code&gt;mongo&lt;/code&gt;に、&lt;code&gt;--host mongo&lt;/code&gt;で接続して、&lt;br /&gt;&lt;code&gt;${DATABASE_NAME}&lt;/code&gt;にリストアしている&lt;/p&gt;
&lt;h2&gt;確認&lt;/h2&gt;
&lt;p&gt;バックアップデータをDocker内のMongoDBにリストアできたか確認します。&lt;/p&gt;
&lt;p&gt;まずは、MongoDBコンテナ内に入る&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;$ docker exec -it mongo_mongodb_1 bash
root@88085298f1e5:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;mongoシェルに入って、データベースがあるか確認&lt;/p&gt;
&lt;pre tabindex=&quot;0&quot; class=&quot;language-markup&quot;&gt;&lt;code class=&quot;language-markup&quot;&gt;root@88085298f1e5:/# mongo
MongoDB shell version: 3.2.19
connecting to: test

&gt; show dbs		# データベース一覧を表示する
${DATABASE_NAME}	0.195GB
local         		0.000GB
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;無事、リストアできていることが確認できました。&lt;/p&gt;
&lt;p&gt;あとは、実行コマンドを&lt;code&gt;init.sh&lt;/code&gt;のようなシェルスクリプトにしてしまえば、開発環境の構築がラクになりそうですね！&lt;/p&gt;
]]&gt;</content:encoded><category>Docker</category><category>MongoDB</category></item></channel></rss>