Jekyll2024-03-18T07:47:11+09:00https://deku.posstree.com/feed.xmlDeku『Programming Artist, DeKu』dev.yakuza@gmail.com[GitHub Actions] Set Reviewers Automatically2024-02-03T00:00:00+09:002024-02-23T11:35:27+09:00https://deku.posstree.com/share/github-actions/github-actions-set-reviewers-en<div id="contents_list"> <h2 id="contents">Contents</h2> <ul> <li><a href="#outline">Outline</a></li> <li><a href="#github-actions">GitHub Actions</a></li> <li><a href="#github-actions-for-dependabot">GitHub Actions for Dependabot</a></li> <li><a href="#actionsgithub-script">actions/github-script</a></li> <li><a href="#completed">Completed</a></li> </ul> </div> <h2 id="outline">Outline</h2> <p>When I create a <code class="language-plaintext highlighter-rouge">Pull request</code> on <code class="language-plaintext highlighter-rouge">GitHub</code>, I manually set the <code class="language-plaintext highlighter-rouge">Reviewers</code> as follows.</p> <picture> <source srcset="/assets/images/category/share/2024/set-reviewers/pull_request_reviewers.avif" type="image/avif" /> <source srcset="/assets/images/category/share/2024/set-reviewers/pull_request_reviewers.webp" type="image/webp" /> <img src="/assets/images/category/share/2024/set-reviewers/pull_request_reviewers.png" alt="GitHub Actions - Set reviewers" /> </picture> <p>In this blog post, I will introduce how to set <code class="language-plaintext highlighter-rouge">Reviewers</code> automatically in <code class="language-plaintext highlighter-rouge">Pull request</code> using <code class="language-plaintext highlighter-rouge">GitHub Actions</code>.</p> <h2 id="github-actions">GitHub Actions</h2> <p>To set the reviewers automatically in the <code class="language-plaintext highlighter-rouge">Pull request</code>, create the <code class="language-plaintext highlighter-rouge">.github/workflows/set_reviewers.yml</code> file and modify it as follows.</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Set reviewers</span> <span class="na">on</span><span class="pi">:</span> <span class="na">pull_request</span><span class="pi">:</span> <span class="na">jobs</span><span class="pi">:</span> <span class="na">set-reviewers</span><span class="pi">:</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Set reviewers</span> <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span> <span class="na">timeout-minutes</span><span class="pi">:</span> <span class="m">1</span> <span class="na">steps</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Set reviewers</span> <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span> <span class="s">actor="$"</span> <span class="s">reviewers=("USER_1" "USER_2" "USER_3")</span> <span class="s">reviewers=($(printf "%s\n" "${reviewers[@]}" | grep -v "$actor" | shuf -n 2))</span> <span class="s">reviewers=$(printf '%s\n' "${reviewers[@]}" | jq -R . | jq -s .)</span> <span class="s">curl -X POST \</span> <span class="s">-H "Content-Type: application/json" \</span> <span class="s">-H "Authorization: token $" \</span> <span class="s">-d "{ \"reviewers\": $reviewers }" \</span> <span class="s">"https://api.github.com/repos/$/pulls/$/requested_reviewers"</span> </code></pre></div></div> <p>This <code class="language-plaintext highlighter-rouge">GitHub Actions</code> is designed to randomly set 2 people as <code class="language-plaintext highlighter-rouge">Reviewers</code> by excluding the person who created the <code class="language-plaintext highlighter-rouge">Pull request</code> from <code class="language-plaintext highlighter-rouge">USER_1</code>, <code class="language-plaintext highlighter-rouge">USER_2</code>, and <code class="language-plaintext highlighter-rouge">USER_3</code>.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">actor</span><span class="o">=</span><span class="s2">"$"</span> <span class="nv">reviewers</span><span class="o">=(</span><span class="s2">"USER_1"</span> <span class="s2">"USER_2"</span> <span class="s2">"USER_3"</span><span class="o">)</span> <span class="nv">reviewers</span><span class="o">=(</span><span class="si">$(</span><span class="nb">printf</span> <span class="s2">"%s</span><span class="se">\n</span><span class="s2">"</span> <span class="s2">"</span><span class="k">${</span><span class="nv">reviewers</span><span class="p">[@]</span><span class="k">}</span><span class="s2">"</span> | <span class="nb">grep</span> <span class="nt">-v</span> <span class="s2">"</span><span class="nv">$actor</span><span class="s2">"</span> | <span class="nb">shuf</span> <span class="nt">-n</span> 2<span class="si">)</span><span class="o">)</span> </code></pre></div></div> <p>The reviewers of the <code class="language-plaintext highlighter-rouge">Pull request</code> cannot be set to the person who created the <code class="language-plaintext highlighter-rouge">Pull request</code> by default.</p> <p>When using this <code class="language-plaintext highlighter-rouge">GitHub Actions</code>, you can set reviewers randomly every time the <code class="language-plaintext highlighter-rouge">Pull request</code> is created and reduce the time to select reviewers.</p> <div class="in-feed-ads ads-container"> <div class="ads-block ads-left"> <ins class="adsbygoogle" style="display: block; text-align: center" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-7987914246691031" data-ad-slot="2718813593"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div> <div class="ads-block ads-center"> <ins class="adsbygoogle" style="display: block; text-align: center" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-7987914246691031" data-ad-slot="6492035359"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div> </div> <h2 id="github-actions-for-dependabot">GitHub Actions for Dependabot</h2> <p>The previous <code class="language-plaintext highlighter-rouge">GitHub Actions</code> does not work with the <code class="language-plaintext highlighter-rouge">Pull request</code> that <code class="language-plaintext highlighter-rouge">Dependabot</code> creates. To make it work, you need to modify the <code class="language-plaintext highlighter-rouge">pull_request</code> part to <code class="language-plaintext highlighter-rouge">pull_request_target</code>. Open the <code class="language-plaintext highlighter-rouge">.github/workflows/set_reviewers.yml</code> file and modify it as follows.</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Set reviewers</span> <span class="na">on</span><span class="pi">:</span> <span class="na">pull_request_target</span><span class="pi">:</span> <span class="na">jobs</span><span class="pi">:</span> <span class="na">set-reviewers</span><span class="pi">:</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Set reviewers</span> <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span> <span class="na">timeout-minutes</span><span class="pi">:</span> <span class="m">1</span> <span class="na">steps</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Set reviewers</span> <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span> <span class="s">actor="$"</span> <span class="s">reviewers=("USER_1" "USER_2" "USER_3")</span> <span class="s">reviewers=($(printf "%s\n" "${reviewers[@]}" | grep -v "$actor" | shuf -n 2))</span> <span class="s">reviewers=$(printf '%s\n' "${reviewers[@]}" | jq -R . | jq -s .)</span> <span class="s">curl -X POST \</span> <span class="s">-H "Content-Type: application/json" \</span> <span class="s">-H "Authorization: token $" \</span> <span class="s">-d "{ \"reviewers\": $reviewers }" \</span> <span class="s">"https://api.github.com/repos/$/pulls/$/requested_reviewers"</span> </code></pre></div></div> <p>You can use any <code class="language-plaintext highlighter-rouge">secrets</code> in the <code class="language-plaintext highlighter-rouge">Pull request</code> that <code class="language-plaintext highlighter-rouge">Dependabot</code> creates, and the <code class="language-plaintext highlighter-rouge">Pull request</code> created by <code class="language-plaintext highlighter-rouge">Dependabot</code> will run with read-only permissions, so an error occurs.</p> <ul> <li><a href="https://github.blog/changelog/2021-02-19-github-actions-workflows-triggered-by-dependabot-prs-will-run-with-read-only-permissions/" rel="nofollow noreferrer" target="\_blank">GitHub Actions: Workflows triggered by Dependabot PRs will run with read-only permissions</a></li> <li><a href="https://securitylab.github.com/research/github-actions-preventing-pwn-requests/" rel="nofollow noreferrer" target="\_blank">Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests</a></li> </ul> <p>To fix this, you need to modify the <code class="language-plaintext highlighter-rouge">pull_request</code> to <code class="language-plaintext highlighter-rouge">pull_request_target</code> in the <code class="language-plaintext highlighter-rouge">on</code> condition.</p> <h2 id="actionsgithub-script">actions/github-script</h2> <p>If you use <code class="language-plaintext highlighter-rouge">actions/github-script</code> provided by <code class="language-plaintext highlighter-rouge">GitHub</code>, you can write more readable code.</p> <ul> <li><a href="https://github.com/actions/github-script" rel="nofollow noreferrer" target="\_blank">actions/github-script</a></li> </ul> <p>To use <code class="language-plaintext highlighter-rouge">actions/github-script</code>, you can modify it as follows.</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Set reviewers</span> <span class="na">on</span><span class="pi">:</span> <span class="na">pull_request_target</span><span class="pi">:</span> <span class="na">jobs</span><span class="pi">:</span> <span class="na">set-reviewers</span><span class="pi">:</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Set reviewers</span> <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span> <span class="na">timeout-minutes</span><span class="pi">:</span> <span class="m">1</span> <span class="na">steps</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Set reviewers</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/github-script@v5</span> <span class="na">with</span><span class="pi">:</span> <span class="na">github-token</span><span class="pi">:</span> <span class="s">$</span> <span class="na">script</span><span class="pi">:</span> <span class="pi">|</span> <span class="s">const reviewers = ["USER_1", "USER_2", "USER_3"];</span> <span class="s">const actor = context.payload.pull_request.user.login;</span> <span class="s">const { owner, repo } = context.repo;</span> <span class="s">const prNumber = context.payload.pull_request.number;</span> <span class="s">const { data: assignedReviewers } = await github.rest.pulls.listRequestedReviewers({</span> <span class="s">owner,</span> <span class="s">repo,</span> <span class="s">pull_number: prNumber,</span> <span class="s">});</span> <span class="s">if (assignedReviewers.length === 0) {</span> <span class="s">const filteredReviewers = reviewers.filter(reviewer => reviewer !== actor);</span> <span class="s">const selectedReviewers = filteredReviewers.sort(() => Math.random() - 0.5).slice(0, 2);</span> <span class="s">const prNumber = context.payload.pull_request.number;</span> <span class="s">await github.rest.pulls.requestReviewers({</span> <span class="s">owner,</span> <span class="s">repo,</span> <span class="s">pull_number: prNumber,</span> <span class="s">reviewers: selectedReviewers</span> <span class="s">});</span> <span class="s">}</span> </code></pre></div></div> <h2 id="completed">Completed</h2> <p>Done! We’ve seen how to set reviewers automatically in <code class="language-plaintext highlighter-rouge">Pull request</code> using <code class="language-plaintext highlighter-rouge">GitHub Actions</code>. By using this <code class="language-plaintext highlighter-rouge">GitHub Actions</code>, you can reduce the time to set reviewers and prevent focusing on specific users for review requests.</p>dev.yakuza@gmail.com[GitHub Actions] Set Assignees automatically2024-02-03T00:00:00+09:002024-02-20T19:51:52+09:00https://deku.posstree.com/share/github-actions/github-actions-set-assignees-en<div id="contents_list"> <h2 id="contents">Contents</h2> <ul> <li><a href="#outline">Outline</a></li> <li><a href="#create-github-actions">Create GitHub Actions</a></li> <li><a href="#dependabot-specific-github-actions">Dependabot-specific GitHub Actions</a></li> <li><a href="#actionsgithub-script">actions/github-script</a></li> <li><a href="#completed">Completed</a></li> </ul> </div> <h2 id="outline">Outline</h2> <p>When creating a <code class="language-plaintext highlighter-rouge">Pull request</code> in <code class="language-plaintext highlighter-rouge">GitHub</code>, I was manually setting the creator of the <code class="language-plaintext highlighter-rouge">Pull request</code> to <code class="language-plaintext highlighter-rouge">Assignees</code> as follows.</p> <picture> <source srcset="/assets/images/category/share/2024/set-assignees/pull_request_assignees.avif" type="image/avif" /> <source srcset="/assets/images/category/share/2024/set-assignees/pull_request_assignees.webp" type="image/webp" /> <img src="/assets/images/category/share/2024/set-assignees/pull_request_assignees.png" alt="GitHub Actions - Set assignees" /> </picture> <p>Since the <code class="language-plaintext highlighter-rouge">Assignees</code> of <code class="language-plaintext highlighter-rouge">Pull request</code> is ultimately the person who created the <code class="language-plaintext highlighter-rouge">Pull request</code>, I think it would be good to automatically set the person who created the <code class="language-plaintext highlighter-rouge">Pull request</code> to <code class="language-plaintext highlighter-rouge">Assignees</code> every time a <code class="language-plaintext highlighter-rouge">Pull request</code> is created.</p> <p>In this blog post, I will introduce how to use <code class="language-plaintext highlighter-rouge">GitHub Actions</code> to automatically set the person who created the <code class="language-plaintext highlighter-rouge">Pull request</code> to <code class="language-plaintext highlighter-rouge">Assignees</code> of the <code class="language-plaintext highlighter-rouge">Pull request</code>.</p> <h2 id="create-github-actions">Create GitHub Actions</h2> <p>Then let’s create a <code class="language-plaintext highlighter-rouge">GitHub Actions</code> that automatically sets the person who created the <code class="language-plaintext highlighter-rouge">Pull request</code> to <code class="language-plaintext highlighter-rouge">Assignees</code> of the <code class="language-plaintext highlighter-rouge">Pull request</code>. Create the <code class="language-plaintext highlighter-rouge">.github/workflows/set_assignees.yml</code> file and modify it as follows.</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Set assigness</span> <span class="na">on</span><span class="pi">:</span> <span class="na">pull_request</span><span class="pi">:</span> <span class="na">types</span><span class="pi">:</span> <span class="pi">-</span> <span class="s">opened</span> <span class="na">jobs</span><span class="pi">:</span> <span class="na">set-assignees</span><span class="pi">:</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Set assignees</span> <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span> <span class="na">timeout-minutes</span><span class="pi">:</span> <span class="m">1</span> <span class="na">steps</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Set assignees</span> <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span> <span class="s">OWNER="$"</span> <span class="s">REPOSITORY="$"</span> <span class="s">TOKEN="$"</span> <span class="s">PULL_REQUEST_NUMBER="$"</span> <span class="s">ASSIGNEES=$(curl -s \</span> <span class="s">"https://api.github.com/repos/$OWNER/$REPOSITORY/issues/$PULL_REQUEST_NUMBER" | \</span> <span class="s">jq --raw-output '.assignees // [] | .[].login')</span> <span class="s">if [ -z "$ASSIGNEES" ]; then</span> <span class="s">ASSIGNEE=$</span> <span class="s">curl -X POST \</span> <span class="s">-H "Content-Type: application/json" \</span> <span class="s">-H "Authorization: token $TOKEN" \</span> <span class="s">-d "{ \"assignees\": \"${ASSIGNEE}\" }" \</span> <span class="s">https://api.github.com/repos/$REPOSITORY/issues/$PULL_REQUEST_NUMBER/assignees</span> <span class="s">fi</span> </code></pre></div></div> <p>I made it to use the <code class="language-plaintext highlighter-rouge">API</code> provided by <code class="language-plaintext highlighter-rouge">GitHub</code> to set the <code class="language-plaintext highlighter-rouge">Pull request</code> creator (<code class="language-plaintext highlighter-rouge">github.actor</code>) to the <code class="language-plaintext highlighter-rouge">Assignees</code> of the <code class="language-plaintext highlighter-rouge">Pull request</code>.</p> <ul> <li>Official document: <a href="https://docs.github.com/en/rest/issues/assignees?apiVersion=2022-11-28#list-assignees" rel="nofollow noreferrer" target="\_blank">List assignees</a></li> <li>Official document: <a href="https://docs.github.com/en/rest/issues/assignees?apiVersion=2022-11-28#add-assignees-to-an-issue" rel="nofollow noreferrer" target="\_blank">Add assignees to an issue</a></li> </ul> <div class="in-feed-ads ads-container"> <div class="ads-block ads-left"> <ins class="adsbygoogle" style="display: block; text-align: center" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-7987914246691031" data-ad-slot="2718813593"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div> <div class="ads-block ads-center"> <ins class="adsbygoogle" style="display: block; text-align: center" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-7987914246691031" data-ad-slot="6492035359"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div> </div> <h2 id="dependabot-specific-github-actions">Dependabot-specific GitHub Actions</h2> <p>I use <a href="https://docs.github.com/en/code-security/dependabot/working-with-dependabot" rel="nofollow noreferrer" target="\_blank">Dependabot</a> in my personal project. I wanted to set the project <code class="language-plaintext highlighter-rouge">Owner</code> to <code class="language-plaintext highlighter-rouge">Assignees</code> for the <code class="language-plaintext highlighter-rouge">Pull request</code> created by <code class="language-plaintext highlighter-rouge">Dependabot</code>.</p> <p>For this, I modified the <code class="language-plaintext highlighter-rouge">GitHub Actions</code> as follows.</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Set assigness</span> <span class="na">on</span><span class="pi">:</span> <span class="na">pull_request</span><span class="pi">:</span> <span class="na">types</span><span class="pi">:</span> <span class="pi">-</span> <span class="s">opened</span> <span class="na">jobs</span><span class="pi">:</span> <span class="na">set-assignees</span><span class="pi">:</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Set assignees</span> <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span> <span class="na">timeout-minutes</span><span class="pi">:</span> <span class="m">1</span> <span class="na">steps</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Set assignees</span> <span class="na">run</span><span class="pi">:</span> <span class="pi">|</span> <span class="s">OWNER="$"</span> <span class="s">REPOSITORY="$"</span> <span class="s">TOKEN="$"</span> <span class="s">PULL_REQUEST_NUMBER="$"</span> <span class="s">ASSIGNEES=$(curl -s \</span> <span class="s">"https://api.github.com/repos/$OWNER/$REPOSITORY/issues/$PULL_REQUEST_NUMBER" | \</span> <span class="s">jq --raw-output '.assignees // [] | .[].login')</span> <span class="s">if [ -z "$ASSIGNEES" ]; then</span> <span class="s">ASSIGNEE=$</span> <span class="s">BRANCH_NAME=$</span> <span class="s">if [[ "${BRANCH_NAME}" == "dependabot/"* ]]; then</span> <span class="s">ASSIGNEE=$OWNER</span> <span class="s">fi</span> <span class="s">curl -X POST \</span> <span class="s">-H "Content-Type: application/json" \</span> <span class="s">-H "Authorization: token $TOKEN" \</span> <span class="s">-d "{ \"assignees\": \"${ASSIGNEE}\" }" \</span> <span class="s">https://api.github.com/repos/$REPOSITORY/issues/$PULL_REQUEST_NUMBER/assignees</span> <span class="s">fi</span> </code></pre></div></div> <p>I added <code class="language-plaintext highlighter-rouge">dependabot/</code> to the <code class="language-plaintext highlighter-rouge">branch</code> name and set <code class="language-plaintext highlighter-rouge">Assignees</code> to <code class="language-plaintext highlighter-rouge">github.repository_owner</code> as follows.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">BRANCH_NAME</span><span class="o">=</span><span class="err">$</span> <span class="k">if</span> <span class="o">[[</span> <span class="s2">"</span><span class="k">${</span><span class="nv">BRANCH_NAME</span><span class="k">}</span><span class="s2">"</span> <span class="o">==</span> <span class="s2">"dependabot/"</span><span class="k">*</span> <span class="o">]]</span><span class="p">;</span> <span class="k">then </span><span class="nv">ASSIGNEE</span><span class="o">=</span><span class="nv">$OWNER</span> <span class="k">fi</span> </code></pre></div></div> <p>However this <code class="language-plaintext highlighter-rouge">GitHub Actions</code> did not work properly in <code class="language-plaintext highlighter-rouge">Pull request</code> from <code class="language-plaintext highlighter-rouge">Dependabot</code>. For security reasons, <code class="language-plaintext highlighter-rouge">Dependabot</code>’s <code class="language-plaintext highlighter-rouge">Pull request</code> cannot use any <code class="language-plaintext highlighter-rouge">secrets</code> and can only perform <code class="language-plaintext highlighter-rouge">Readonly</code> operations, so an error occurred.</p> <p>To fix this, you need to use <code class="language-plaintext highlighter-rouge">pull_request_target</code> instead of <code class="language-plaintext highlighter-rouge">pull_request</code> in <code class="language-plaintext highlighter-rouge">on</code>.</p> <ul> <li><a href="https://github.blog/changelog/2021-02-19-github-actions-workflows-triggered-by-dependabot-prs-will-run-with-read-only-permissions/" rel="nofollow noreferrer" target="\_blank">GitHub Actions: Workflows triggered by Dependabot PRs will run with read-only permissions</a></li> <li><a href="https://securitylab.github.com/research/github-actions-preventing-pwn-requests/" rel="nofollow noreferrer" target="\_blank">Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests</a></li> </ul> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Set assigness</span> <span class="na">on</span><span class="pi">:</span> <span class="na">pull_request_target</span><span class="pi">:</span> <span class="na">types</span><span class="pi">:</span> <span class="pi">-</span> <span class="s">opened</span> </code></pre></div></div> <h2 id="actionsgithub-script">actions/github-script</h2> <p><code class="language-plaintext highlighter-rouge">GitHub</code> provides <code class="language-plaintext highlighter-rouge">actions/github-script</code> that allows you to write more readable code.</p> <ul> <li><a href="https://github.com/actions/github-script" rel="nofollow noreferrer" target="\_blank">actions/github-script</a></li> </ul> <p>By using <code class="language-plaintext highlighter-rouge">actions/github-script</code>, you can modify it as follows.</p> <div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Set assignees</span> <span class="na">on</span><span class="pi">:</span> <span class="na">pull_request_target</span><span class="pi">:</span> <span class="na">types</span><span class="pi">:</span> <span class="pi">-</span> <span class="s">opened</span> <span class="na">jobs</span><span class="pi">:</span> <span class="na">set-assignees</span><span class="pi">:</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Set assignees</span> <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span> <span class="na">timeout-minutes</span><span class="pi">:</span> <span class="m">1</span> <span class="na">steps</span><span class="pi">:</span> <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span> <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Set assignees</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/github-script@v6</span> <span class="na">with</span><span class="pi">:</span> <span class="na">github-token</span><span class="pi">:</span> <span class="s">$</span> <span class="na">script</span><span class="pi">:</span> <span class="pi">|</span> <span class="s">const { owner, repo } = context.repo;</span> <span class="s">const prNumber = context.payload.pull_request.number;</span> <span class="s">const response = await github.rest.issues.get({</span> <span class="s">owner,</span> <span class="s">repo,</span> <span class="s">issue_number: prNumber,</span> <span class="s">})</span> <span class="s">const { assignees } = response.data;</span> <span class="s">if (assignees.length === 0) {</span> <span class="s">let assignee = context.actor;</span> <span class="s">const branchName = context.payload.pull_request.head.ref;</span> <span class="s">if (branchName.startsWith('dependabot/')) {</span> <span class="s">assignee = owner;</span> <span class="s">}</span> <span class="s">await github.rest.issues.addAssignees({</span> <span class="s">owner: owner,</span> <span class="s">repo: repo,</span> <span class="s">issue_number: prNumber,</span> <span class="s">assignees: [assignee]</span> <span class="s">});</span> <span class="s">}</span> </code></pre></div></div> <h2 id="completed">Completed</h2> <p>Done! We’ve seen how to use <code class="language-plaintext highlighter-rouge">GitHub Actions</code> to automatically set the person who created the <code class="language-plaintext highlighter-rouge">Pull request</code> to <code class="language-plaintext highlighter-rouge">Assignees</code> of the <code class="language-plaintext highlighter-rouge">Pull request</code>. If you are manually setting the person who created the <code class="language-plaintext highlighter-rouge">Pull request</code> to <code class="language-plaintext highlighter-rouge">Assignees</code> every time you create a <code class="language-plaintext highlighter-rouge">Pull request</code>, try using this <code class="language-plaintext highlighter-rouge">GitHub Actions</code> to automate it.</p>dev.yakuza@gmail.com[macOS] Command to Prevent Sleep Mode2024-01-31T00:00:00+09:002024-02-02T21:52:29+09:00https://deku.posstree.com/share/macos/caffeinate-en<div id="contents_list"> <h2 id="contents">Contents</h2> <ul> <li><a href="#outline">Outline</a></li> <li><a href="#caffeinate">Caffeinate</a></li> <li><a href="#terminate-caffeinate">Terminate Caffeinate</a></li> <li><a href="#why-use-caffeinate">Why Use Caffeinate</a></li> <li><a href="#completed">Completed</a></li> </ul> </div> <h2 id="outline">Outline</h2> <p><code class="language-plaintext highlighter-rouge">macOS</code> is set to sleep mode by default. Sleep mode automatically turns off the screen or switches the machine to sleep mode after a certain period of time, helping to reduce power consumption, extend the battery life of a notebook, or increase the energy efficiency of a desktop.</p> <p>In this blog post, I will introduce how to prevent sleep mode from automatically switching using the <code class="language-plaintext highlighter-rouge">Caffeinate</code> command.</p> <h2 id="caffeinate">Caffeinate</h2> <p>The <code class="language-plaintext highlighter-rouge">Caffeinate</code> command is a command that prevents sleep mode from automatically switching in macOS. This command keeps the system active, preventing the screen from turning off or the computer from automatically entering sleep mode.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>caffeinate <span class="o">[</span>options] </code></pre></div></div> <p>If you want to prevent the computer from switching to sleep mode when no operation is performed, you can run the command as follows.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>caffeinate <span class="nt">-d</span> <span class="nt">-t</span> 3600 </code></pre></div></div> <ul> <li><code class="language-plaintext highlighter-rouge">-d</code>: Prevents the screen from turning off.</li> <li><code class="language-plaintext highlighter-rouge">-t</code>: Sets how long to keep the system active. The above command keeps the system active for 1 hour.</li> </ul> <p>You can see that the computer maintains an active state without switching to sleep mode by running the <code class="language-plaintext highlighter-rouge">Caffeinate</code> command in the terminal and running a time-consuming task in another terminal.</p> <h2 id="terminate-caffeinate">Terminate Caffeinate</h2> <p>When a time-consuming task is completed, you must terminate the <code class="language-plaintext highlighter-rouge">Caffeinate</code> command. You can terminate the <code class="language-plaintext highlighter-rouge">Caffeinate</code> command by pressing <code class="language-plaintext highlighter-rouge">Ctrl + C</code> in the terminal where the <code class="language-plaintext highlighter-rouge">Caffeinate</code> command is executed, or by closing the terminal where the <code class="language-plaintext highlighter-rouge">Caffeinate</code> command is executed.</p> <h2 id="why-use-caffeinate">Why Use Caffeinate</h2> <p>There is also a way to disable sleep mode in the settings, but it is generally better to keep it enabled because disabling sleep mode can increase power consumption and shorten battery life. If you forget to enable sleep mode after disabling it in the settings, the computer will continue to be used with sleep mode disabled, which can cause problems such as shortened battery life.</p> <p>Also, in the case of a company computer, settings are forced to turn off the screen automatically when you leave your seat for security reasons.</p> <p>At this time, if you need to run a time-consuming task (command) temporarily, you can use the <code class="language-plaintext highlighter-rouge">Caffeinate</code> command to prevent sleep mode from automatically switching, and terminate the command after the task is completed to keep the default settings.</p> <h2 id="completed">Completed</h2> <p>Done! We’ve seen how to prevent sleep mode from automatically switching in <code class="language-plaintext highlighter-rouge">macOS</code> using the <code class="language-plaintext highlighter-rouge">Caffeinate</code> command. If you want to prevent sleep mode from automatically switching while keeping the default settings, try using the <code class="language-plaintext highlighter-rouge">Caffeinate</code> command.</p>dev.yakuza@gmail.com[Flutter] Fix Lexical or Preprocessor Issue (Xcode): *.h file not found error2024-01-26T00:00:00+09:002024-02-01T11:50:51+09:00https://deku.posstree.com/flutter/error/test-concurrency-en<div id="contents_list"> <h2 id="contents">Contents</h2> <ul> <li><a href="#outline">Outline</a></li> <li><a href="#cause-of-error">Cause of error</a></li> <li><a href="#solve-error">Solve error</a></li> <li><a href="#completed">Completed</a></li> </ul> </div> <h2 id="outline">Outline</h2> <p>When I was working on a Flutter project, I suddenly encountered the following error.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>flutter Lexical or Preprocessor Issue <span class="o">(</span>Xcode<span class="o">)</span>: <span class="s1">'FirebaseABTesting/FirebaseABTesting.h'</span> file not found </code></pre></div></div> <p>Even if there was no modification, such an error occurred, and the project could not be built on iOS. In this blog post, I will introduce how to fix the <code class="language-plaintext highlighter-rouge">flutter Lexical or Preprocessor Issue (Xcode): *.h file not found</code> error that suddenly occurs in Flutter.</p> <h2 id="cause-of-error">Cause of error</h2> <p>If you encounter the <code class="language-plaintext highlighter-rouge">flutter Lexical or Preprocessor Issue (Xcode): *.h file not found</code> error while developing a project in Flutter, it is likely due to an Xcode update.</p> <p>In most cases, this is a problem caused by the version of <code class="language-plaintext highlighter-rouge">CocoaPods</code> being lowered due to an <code class="language-plaintext highlighter-rouge">Xcode</code> update. Therefore, even projects that were built normally may experience the <code class="language-plaintext highlighter-rouge">flutter Lexical or Preprocessor Issue (Xcode): *.h file not found</code> error due to the version of <code class="language-plaintext highlighter-rouge">CocoaPods</code> being lowered due to an <code class="language-plaintext highlighter-rouge">Xcode</code> update.</p> <h2 id="solve-error">Solve error</h2> <p>In this case, you can solve the problem by updating the version of <code class="language-plaintext highlighter-rouge">CocoaPods</code>. Since I installed <code class="language-plaintext highlighter-rouge">CocoaPods</code> with <code class="language-plaintext highlighter-rouge">Homebrew</code>, I updated the version of <code class="language-plaintext highlighter-rouge">CocoaPods</code> using <code class="language-plaintext highlighter-rouge">Homebrew</code> as follows.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew upgrade cocoapods </code></pre></div></div> <p>After updating <code class="language-plaintext highlighter-rouge">CocoaPods</code>, move to the problematic project and reinstall <code class="language-plaintext highlighter-rouge">Pod</code> by running the following command.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pod <span class="nb">install</span> </code></pre></div></div> <p>If necessary, delete the <code class="language-plaintext highlighter-rouge">Pods</code> folder and <code class="language-plaintext highlighter-rouge">Podfile.lock</code> file, and then reinstall <code class="language-plaintext highlighter-rouge">Pod</code> by running the <code class="language-plaintext highlighter-rouge">pod install</code> command again.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">rm</span> <span class="nt">-rf</span> Pods Podfile.lock pod <span class="nb">install</span> </code></pre></div></div> <p>Now, if you rebuild the project, you can see that it is built normally.</p> <h2 id="completed">Completed</h2> <p>Done! we’ve seen how to fix the <code class="language-plaintext highlighter-rouge">flutter Lexical or Preprocessor Issue (Xcode): *.h file not found</code> error that suddenly occurs in Flutter. If you encounter the <code class="language-plaintext highlighter-rouge">flutter Lexical or Preprocessor Issue (Xcode): *.h file not found</code> error, try updating the version of <code class="language-plaintext highlighter-rouge">CocoaPods</code> to solve it.</p>dev.yakuza@gmail.com[Flutter] Improve test execution speed with concurrency option2024-01-26T00:00:00+09:002024-01-28T20:31:50+09:00https://deku.posstree.com/flutter/test/test-concurrency-en<div id="contents_list"> <h2 id="contents">Contents</h2> <ul> <li><a href="#outline">Outline</a></li> <li><a href="#concurency-option">concurency option</a></li> <li><a href="#check-the-number-of-cores">Check the number of cores</a></li> <li><a href="#compare-test-execution-speed">Compare test execution speed</a></li> <li><a href="#compare-test-execution-speed-in-github-actions">Compare test execution speed in GitHub Actions</a></li> <li><a href="#completed">Completed</a></li> </ul> </div> <h2 id="outline">Outline</h2> <p>When developing an app in Flutter, you write test code and run it to improve the stability of the service. However, as the size of the project grows, the amount of test code also increases. So, the time it takes to run test code also increases.</p> <p>In this blog post, I will introduce how to run test code in parallel using the <code class="language-plaintext highlighter-rouge">concurrency</code> option to improve the test execution speed.</p> <h2 id="concurency-option">concurency option</h2> <p>When running a test in Flutter, you can run the test code in parallel using the <code class="language-plaintext highlighter-rouge">concurrency</code> option. This reduces the time it takes to run the test code.</p> <p>When you run the following command, you can check the options available in the Flutter test command.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>flutter <span class="nb">test</span> <span class="nt">-h</span> </code></pre></div></div> <p>Among the various options, you can check the <code class="language-plaintext highlighter-rouge">concurrency</code> option as follows.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">-j</span>, <span class="nt">--concurrency</span><span class="o">=</span><<span class="nb">jobs</span><span class="o">></span> The number of concurrent <span class="nb">test </span>processes to run. This will be ignored when running integration tests. </code></pre></div></div> <p>You can also check the <code class="language-plaintext highlighter-rouge">concurrency</code> option in the official documentation.</p> <ul> <li>Officail document: <a href="https://pub.dev/packages/test#test-concurrency" rel="nofollow noreferrer" target="\_blank">Test concurrency</a></li> </ul> <h2 id="check-the-number-of-cores">Check the number of cores</h2> <p>By default, Flutter test command is set to use half of the host CPU cores.</p> <blockquote> <p>Test suites run concurrently by default, using half of the host’s CPU cores.</p> </blockquote> <p>Therefore, if you set the cores less than half of the host machine’s cores to the <code class="language-plaintext highlighter-rouge">concurrency</code> option, the performance may be worse than when you run it without setting the <code class="language-plaintext highlighter-rouge">concurrency</code> option.</p> <p>So, you need to check the number of cores of the current machine and set the appropriate number of cores. You can check the number of cores of the machine by running the following command.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">nproc</span> <span class="nt">--all</span> </code></pre></div></div> <h2 id="compare-test-execution-speed">Compare test execution speed</h2> <p>Let’s see how much the test code execution speed improves by using the <code class="language-plaintext highlighter-rouge">concurrency</code> option. Run the following command in your Flutter project to run the test code.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>flutter <span class="nb">test</span> </code></pre></div></div> <p>After running the test code, you can check the time it took to run the test code as follows.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>02:10 +657: All tests passed! </code></pre></div></div> <p>Without the <code class="language-plaintext highlighter-rouge">concurrency</code> option, it takes <code class="language-plaintext highlighter-rouge">2 minutes and 10 seconds</code> to run the test code. Now, let’s run the test code using the <code class="language-plaintext highlighter-rouge">concurrency</code> option as follows..</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>flutter <span class="nb">test</span> <span class="nt">--concurrency</span><span class="o">=</span><span class="si">$(</span><span class="nb">nproc</span> <span class="nt">--all</span><span class="si">)</span> </code></pre></div></div> <p>After running the test code, you can check the time it took to run the test code as follows.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>02:02 +657: All tests passed! </code></pre></div></div> <p>You can see that the time it takes to run the test code is reduced by about <code class="language-plaintext highlighter-rouge">8 seconds</code> when using the <code class="language-plaintext highlighter-rouge">concurrency</code> option.</p> <h2 id="compare-test-execution-speed-in-github-actions">Compare test execution speed in GitHub Actions</h2> <p>In my case, the performance of the local machine (12 cores) is so good that I can’t see much effect, but you can see a bigger effect in the <code class="language-plaintext highlighter-rouge">CI/CD</code> environment. When running the test code in <code class="language-plaintext highlighter-rouge">GitHub Actions</code> without using the <code class="language-plaintext highlighter-rouge">concurrency</code> option, you can see that it takes about <code class="language-plaintext highlighter-rouge">7 minutes</code> as follows.</p> <picture> <source srcset="/assets/images/category/flutter/2024/test-concurrency/time_without_concurrency_option.avif" type="image/avif" /> <source srcset="/assets/images/category/flutter/2024/test-concurrency/time_without_concurrency_option.webp" type="image/webp" /> <img src="/assets/images/category/flutter/2024/test-concurrency/time_without_concurrency_option.png" alt="Flutter - test without concurrency option" /> </picture> <p>If you run the test code using the <code class="language-plaintext highlighter-rouge">concurrency</code> option in <code class="language-plaintext highlighter-rouge">GitHub Actions</code>, you can see that it takes about <code class="language-plaintext highlighter-rouge">4 minutes</code> as follows.</p> <picture> <source srcset="/assets/images/category/flutter/2024/test-concurrency/time_with_concurrency_option.avif" type="image/avif" /> <source srcset="/assets/images/category/flutter/2024/test-concurrency/time_with_concurrency_option.webp" type="image/webp" /> <img src="/assets/images/category/flutter/2024/test-concurrency/time_with_concurrency_option.png" alt="Flutter - test with concurrency option" /> </picture> <h2 id="completed">Completed</h2> <p>Done! we’ve seen how to improve the test execution speed in <code class="language-plaintext highlighter-rouge">Flutter</code> by using the <code class="language-plaintext highlighter-rouge">concurrency</code> option in the test. If you’re not using the <code class="language-plaintext highlighter-rouge">concurrency</code> option yet, try adding the <code class="language-plaintext highlighter-rouge">concurrency</code> option to improve the test execution speed.</p>dev.yakuza@gmail.com[Monoepo] Yarn Workspaces2024-01-18T00:00:00+09:002024-03-10T11:36:58+09:00https://deku.posstree.com/environment/monorepo/monorepo-yarn_workspaces-en<div id="contents_list"> <h2 id="contents">Contents</h2> <ul> <li><a href="#contents">Contents</a></li> <li><a href="#outline">Outline</a></li> <li><a href="#blog-series">Blog Series</a></li> <li><a href="#yarn-workspaces">Yarn Workspaces</a></li> <li><a href="#example">Example</a></li> <li><a href="#yarn-workspaces-setup">Yarn Workspaces Setup</a></li> <li><a href="#check-yarn-workspaces">Check Yarn Workspaces</a></li> <li><a href="#gitignore">gitignore</a></li> <li><a href="#completed">Completed</a></li> </ul> </div> <h2 id="outline">Outline</h2> <p>In this blog post, I will introduce how to set up a monorepo using <code class="language-plaintext highlighter-rouge">Yarn</code>’s <code class="language-plaintext highlighter-rouge">Workspaces</code>.</p> <h2 id="blog-series">Blog Series</h2> <p>This blog is a series. Please check other blog posts through the following links.</p> <ul> <li><a href="https://deku.posstree.com/environment/repository_strategy/" target="\_blank">[Project Management] Repository Strategy</a></li> <li><a href="https://deku.posstree.com/environment/monorepo/tools/" target="\_blank">[JavaScript] Tools for Monorepo</a></li> <li><a href="https://deku.posstree.com/environment/monorepo/module-resolutions/" target="\_blank">[Monorepo] Node JS module resolution</a></li> <li><a href="https://deku.posstree.com/environment/monorepo/symlink/" target="\_blank">[Monorepo] Symlink</a></li> <li>[Monorepo] Yarn Workspaces</li> </ul> <h2 id="yarn-workspaces">Yarn Workspaces</h2> <p>In the previous blog post(<code class="language-plaintext highlighter-rouge">Tools for Monorepo</code>), I introduced <code class="language-plaintext highlighter-rouge">Yarn</code>’s <code class="language-plaintext highlighter-rouge">Workspaces</code>.</p> <ul> <li><a href="https://deku.posstree.com/environment/monorepo/tools/" target="\_blank">[JavaScript] Tools for Monorepo</a></li> </ul> <p>In this blog post, I will introduce how to set up a monorepo using <code class="language-plaintext highlighter-rouge">Yarn</code>’s <code class="language-plaintext highlighter-rouge">Workspaces</code>.</p> <ul> <li>Office document: <a href="https://yarnpkg.com/features/workspaces" rel="nofollow noreferrer" target="\_blank">Workspaces</a></li> </ul> <p><code class="language-plaintext highlighter-rouge">Yarn</code>’s <code class="language-plaintext highlighter-rouge">Workspaces</code> is a feature that allows you to manage multiple projects in a single codebase using the <code class="language-plaintext highlighter-rouge">Symlink</code> introduced in the previous blog post. This feature makes it easy to set up a monorepo.</p> <ul> <li><a href="https://deku.posstree.com/environment/monorepo/symlink/" target="\_blank">[Monorepo] Symlink</a></li> </ul> <h2 id="example">Example</h2> <p>Let’s create an example to check the <code class="language-plaintext highlighter-rouge">Workspace</code> feature provided by <code class="language-plaintext highlighter-rouge">yarn</code>. First, create a folder and file structure as follows.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">.</span> └── src/ ├── module-a/ │ ├── index.js │ └── package.json └── module-b/ ├── index.js └── package.json </code></pre></div></div> <p>The <code class="language-plaintext highlighter-rouge">package.json</code> of <code class="language-plaintext highlighter-rouge">module-a</code> is as follows.</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">//</span><span class="w"> </span><span class="err">src/module-a/package.json</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"module-a"</span><span class="p">,</span><span class="w"> </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span><span class="w"> </span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"index.js"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>The <code class="language-plaintext highlighter-rouge">package.json</code> of <code class="language-plaintext highlighter-rouge">module-b</code> is as follows.</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">//</span><span class="w"> </span><span class="err">src/module-b/package.json</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"module-b"</span><span class="p">,</span><span class="w"> </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span><span class="w"> </span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"index.js"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>And the <code class="language-plaintext highlighter-rouge">index.js</code> of <code class="language-plaintext highlighter-rouge">module-b</code> is as follows.</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/module-b/index.js</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">module-b</span><span class="dl">'</span><span class="p">);</span> </code></pre></div></div> <p>Finally, the <code class="language-plaintext highlighter-rouge">index.js</code> of <code class="language-plaintext highlighter-rouge">module-a</code> is as follows.</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/module-a/index.js</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">module-a</span><span class="dl">'</span><span class="p">);</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">module-b</span><span class="dl">'</span><span class="p">);</span> </code></pre></div></div> <p>After configuring the files like this, run the following command to check if the module is loaded correctly.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>node src/module-a/index.js </code></pre></div></div> <p>After running the command, you can see the following error.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>module-a node:internal/modules/cjs/loader:1073 throw err<span class="p">;</span> ^ Error: Cannot find module <span class="s1">'module-b'</span> Require stack: - /Users/deku/temp/temp/src/module-a/index.js at Module._resolveFilename <span class="o">(</span>node:internal/modules/cjs/loader:1070:15<span class="o">)</span> at Module._load <span class="o">(</span>node:internal/modules/cjs/loader:923:27<span class="o">)</span> at Module.require <span class="o">(</span>node:internal/modules/cjs/loader:1137:19<span class="o">)</span> at require <span class="o">(</span>node:internal/modules/helpers:121:18<span class="o">)</span> at Object.<anonymous> <span class="o">(</span>/Users/deku/temp/temp/src/module-a/index.js:3:1<span class="o">)</span> at Module._compile <span class="o">(</span>node:internal/modules/cjs/loader:1255:14<span class="o">)</span> at Module._extensions..js <span class="o">(</span>node:internal/modules/cjs/loader:1309:10<span class="o">)</span> at Module.load <span class="o">(</span>node:internal/modules/cjs/loader:1113:32<span class="o">)</span> at Module._load <span class="o">(</span>node:internal/modules/cjs/loader:960:12<span class="o">)</span> at Function.executeUserEntryPoint <span class="o">[</span>as runMain] <span class="o">(</span>node:internal/modules/run_main:83:12<span class="o">)</span> <span class="o">{</span> code: <span class="s1">'MODULE_NOT_FOUND'</span>, requireStack: <span class="o">[</span> <span class="s1">'/Users/deku/temp/temp/src/module-a/index.js'</span> <span class="o">]</span> <span class="o">}</span> </code></pre></div></div> <div class="in-feed-ads ads-container"> <div class="ads-block ads-left"> <ins class="adsbygoogle" style="display: block; text-align: center" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-7987914246691031" data-ad-slot="2718813593"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div> <div class="ads-block ads-center"> <ins class="adsbygoogle" style="display: block; text-align: center" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-7987914246691031" data-ad-slot="6492035359"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div> </div> <h2 id="yarn-workspaces-setup">Yarn Workspaces Setup</h2> <p>Now let’s use <code class="language-plaintext highlighter-rouge">Yarn</code>’s <code class="language-plaintext highlighter-rouge">Workspaces</code> to make <code class="language-plaintext highlighter-rouge">module-a</code> use <code class="language-plaintext highlighter-rouge">module-b</code>. First, run the following command in the root folder(<code class="language-plaintext highlighter-rouge">/</code>) to prepare to use <code class="language-plaintext highlighter-rouge">Yarn</code>’s <code class="language-plaintext highlighter-rouge">Workspaces</code>.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yarn init <span class="nt">-y</span> </code></pre></div></div> <p>After that, the following <code class="language-plaintext highlighter-rouge">package.json</code> will be created in the root folder.</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"monorepo"</span><span class="p">,</span><span class="w"> </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span><span class="w"> </span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"index.js"</span><span class="p">,</span><span class="w"> </span><span class="nl">"license"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MIT"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>In order to use <code class="language-plaintext highlighter-rouge">Yarn</code>’s <code class="language-plaintext highlighter-rouge">Workspaces</code>, you need to modify this <code class="language-plaintext highlighter-rouge">package.json</code> file as follows.</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"monorepo"</span><span class="p">,</span><span class="w"> </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span><span class="w"> </span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"index.js"</span><span class="p">,</span><span class="w"> </span><span class="nl">"license"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MIT"</span><span class="p">,</span><span class="w"> </span><span class="nl">"private"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nl">"workspaces"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"packages"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"src/module-a"</span><span class="p">,</span><span class="w"> </span><span class="s2">"src/module-b"</span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>Monorepo has multiple projects, so it is not necessary to deploy to places like the <code class="language-plaintext highlighter-rouge">npm</code> registry. Therefore, set <code class="language-plaintext highlighter-rouge">private</code> to <code class="language-plaintext highlighter-rouge">true</code> to prevent the monorepo from being deployed. <code class="language-plaintext highlighter-rouge">Yarn V1</code> requires this setting, but <code class="language-plaintext highlighter-rouge">Yarn V2</code> does not require it. It is recommended to set <code class="language-plaintext highlighter-rouge">private</code> to <code class="language-plaintext highlighter-rouge">true</code> to manage it safely. Of course, individual projects in the monorepo do not need to set <code class="language-plaintext highlighter-rouge">private</code> because they can be deployed.</p> <p><code class="language-plaintext highlighter-rouge">Workspaces</code> can be set as an array(<code class="language-plaintext highlighter-rouge">[]</code>) or an object(<code class="language-plaintext highlighter-rouge">{}</code>). <code class="language-plaintext highlighter-rouge">Yarn</code> recommends writing it in object format. In <code class="language-plaintext highlighter-rouge">Workspaces</code>, an array is created with the key <code class="language-plaintext highlighter-rouge">packages</code> and each module is added. You can specify each one like this, but you can also specify all modules simply by using <code class="language-plaintext highlighter-rouge">*</code> as follows.</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"monorepo"</span><span class="p">,</span><span class="w"> </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span><span class="w"> </span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"index.js"</span><span class="p">,</span><span class="w"> </span><span class="nl">"license"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MIT"</span><span class="p">,</span><span class="w"> </span><span class="nl">"private"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w"> </span><span class="nl">"workspaces"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"packages"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"src/*"</span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <h2 id="check-yarn-workspaces">Check Yarn Workspaces</h2> <p>We are now ready to use <code class="language-plaintext highlighter-rouge">Yarn</code>’s <code class="language-plaintext highlighter-rouge">Workspaces</code>. Now run the following command to install the packages.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yarn <span class="nb">install</span> </code></pre></div></div> <p>Then you can see that the <code class="language-plaintext highlighter-rouge">node_modules</code> folder is created in the root folder and each module is connected via <code class="language-plaintext highlighter-rouge">Symlink</code>.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">.</span> ├── node_modules │ ├── module-a -> ../src/module-a │ └── module-b -> ../src/module-b ├── package.json ├── src │ ├── module-a │ │ ├── index.js │ │ └── package.json │ └── module-b │ ├── index.js │ └── package.json └── yarn.lock </code></pre></div></div> <p>Now let’s check if <code class="language-plaintext highlighter-rouge">module-a</code> can use <code class="language-plaintext highlighter-rouge">module-b</code> by running the following command.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>node src/module-a/index.js </code></pre></div></div> <p>Then you can see that <code class="language-plaintext highlighter-rouge">module-a</code> is executed without any problems.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>module-a module-b </code></pre></div></div> <p>Of course, since it is connected via <code class="language-plaintext highlighter-rouge">Symlink</code>, if you modify the code of <code class="language-plaintext highlighter-rouge">module-b</code>, you can use the modified code in <code class="language-plaintext highlighter-rouge">module-a</code>. Open the <code class="language-plaintext highlighter-rouge">src/module-b/index.js</code> file and modify it as follows.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>console.log<span class="o">(</span><span class="s1">'module-b!!!'</span><span class="o">)</span><span class="p">;</span> </code></pre></div></div> <p>Then run the following command to check if <code class="language-plaintext highlighter-rouge">module-a</code> works correctly.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>node src/module-a/index.js </code></pre></div></div> <p>After running the command, you can see that the modified content is displayed correctly.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>module-a module-b!!! </code></pre></div></div> <h2 id="gitignore">.gitignore</h2> <p>If you are managing the source code with <code class="language-plaintext highlighter-rouge">Git</code>, create a <code class="language-plaintext highlighter-rouge">.gitignore</code> file and modify it as follows to exclude the <code class="language-plaintext highlighter-rouge">node_modules</code> folder from <code class="language-plaintext highlighter-rouge">Git</code>.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># .gitignore</span> node_modules </code></pre></div></div> <h2 id="completed">Completed</h2> <p>Done! We’ve seen how to use <code class="language-plaintext highlighter-rouge">Yarn</code>’s <code class="language-plaintext highlighter-rouge">Workspaces</code> to set up a monorepo. <code class="language-plaintext highlighter-rouge">Yarn</code>’s <code class="language-plaintext highlighter-rouge">Workspaces</code> basically works with <code class="language-plaintext highlighter-rouge">Symlink</code>, so it’s good to understand <code class="language-plaintext highlighter-rouge">Symlink</code>. If you want to know more about <code class="language-plaintext highlighter-rouge">Symlink</code>, please see to the previous blog post.</p> <ul> <li><a href="https://deku.posstree.com/environment/monorepo/symlink/" target="\_blank">[Monorepo] Symlink</a></li> </ul> <p>Now you can try using <code class="language-plaintext highlighter-rouge">Yarn</code>’s <code class="language-plaintext highlighter-rouge">Workspaces</code> to set up a monorepo.</p>dev.yakuza@gmail.com[Monoepo] Symlink2024-01-17T00:00:00+09:002024-03-10T11:36:58+09:00https://deku.posstree.com/environment/monorepo/monorepo-symlink-en<div id="contents_list"> <h2 id="contents">Contents</h2> <ul> <li><a href="#contents">Contents</a></li> <li><a href="#outline">Outline</a></li> <li><a href="#blog-series">Blog Series</a></li> <li><a href="#symlink">Symlink</a></li> <li><a href="#example">Example</a></li> <li><a href="#create-symlink">Create Symlink</a></li> <li><a href="#check-symlink">Check Symlink</a></li> <li><a href="#completed">Completed</a></li> </ul> </div> <h2 id="outline">Outline</h2> <p>When connecting the dependencies of various modules in Monorepo, <code class="language-plaintext highlighter-rouge">Symlink</code> is used. In this blog post, I will explain <code class="language-plaintext highlighter-rouge">Symlink (Symbolic Link)</code>, which is the basic knowledge for understanding Monorepo.</p> <h2 id="blog-series">Blog Series</h2> <p>This blog is a series. Please check other blog posts through the following links.</p> <ul> <li><a href="https://deku.posstree.com/environment/repository_strategy/" target="\_blank">[Project Management] Repository Strategy</a></li> <li><a href="https://deku.posstree.com/environment/monorepo/tools/" target="\_blank">[JavaScript] Tools for Monorepo</a></li> <li><a href="https://deku.posstree.com/environment/monorepo/module-resolutions/" target="\_blank">[Monorepo] Node JS module resolution</a></li> <li>[Monorepo] Symlink</li> <li><a href="https://deku.posstree.com/environment/monorepo/yarn_workspaces/" target="\_blank">[Monorepo] Yarn Workspaces</a></li> </ul> <h2 id="symlink">Symlink</h2> <p><code class="language-plaintext highlighter-rouge">Symlink</code> is an abbreviation for <code class="language-plaintext highlighter-rouge">Symbolic Link</code>, and you can think of it as a <code class="language-plaintext highlighter-rouge">shortcut (System shortcut)</code> for a <code class="language-plaintext highlighter-rouge">file</code> or <code class="language-plaintext highlighter-rouge">directory</code>.</p> <p><code class="language-plaintext highlighter-rouge">Symlink</code> is a basic feature supported by major OS (macOS, Windows, Linux). In addition, it is also supported by <code class="language-plaintext highlighter-rouge">npm</code> and <code class="language-plaintext highlighter-rouge">yarn</code>, which are package managers for JavaScript.</p> <h2 id="example">Example</h2> <p>Let’s create an example to check the <code class="language-plaintext highlighter-rouge">Symlink</code> feature provided by <code class="language-plaintext highlighter-rouge">npm</code> and <code class="language-plaintext highlighter-rouge">yarn</code>. First, create the following folder and file structure.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">.</span> └── src/ ├── module-a/ │ ├── index.js │ └── package.json └── module-b/ ├── index.js └── package.json </code></pre></div></div> <p>The <code class="language-plaintext highlighter-rouge">package.json</code> of <code class="language-plaintext highlighter-rouge">module-a</code> is as follows.</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">//</span><span class="w"> </span><span class="err">src/module-a/package.json</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"module-a"</span><span class="p">,</span><span class="w"> </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span><span class="w"> </span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"index.js"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>The <code class="language-plaintext highlighter-rouge">package.json</code> of <code class="language-plaintext highlighter-rouge">module-b</code> is as follows.</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">//</span><span class="w"> </span><span class="err">src/module-b/package.json</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"module-b"</span><span class="p">,</span><span class="w"> </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span><span class="w"> </span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"index.js"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>And the <code class="language-plaintext highlighter-rouge">index.js</code> of <code class="language-plaintext highlighter-rouge">module-b</code> is as follows.</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/module-b/index.js</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">module-b</span><span class="dl">'</span><span class="p">);</span> </code></pre></div></div> <p>Finally, the <code class="language-plaintext highlighter-rouge">index.js</code> of <code class="language-plaintext highlighter-rouge">module-a</code> is as follows.</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/module-a/index.js</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">module-a</span><span class="dl">'</span><span class="p">);</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">module-b</span><span class="dl">'</span><span class="p">);</span> </code></pre></div></div> <p>After creating the file structure like this, run the following command to check if the module is imported well.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>node src/module-a/index.js </code></pre></div></div> <p>Then, you can see the following error.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>module-a node:internal/modules/cjs/loader:1073 throw err<span class="p">;</span> ^ Error: Cannot find module <span class="s1">'module-b'</span> Require stack: - /Users/deku/temp/temp/src/module-a/index.js at Module._resolveFilename <span class="o">(</span>node:internal/modules/cjs/loader:1070:15<span class="o">)</span> at Module._load <span class="o">(</span>node:internal/modules/cjs/loader:923:27<span class="o">)</span> at Module.require <span class="o">(</span>node:internal/modules/cjs/loader:1137:19<span class="o">)</span> at require <span class="o">(</span>node:internal/modules/helpers:121:18<span class="o">)</span> at Object.<anonymous> <span class="o">(</span>/Users/deku/temp/temp/src/module-a/index.js:3:1<span class="o">)</span> at Module._compile <span class="o">(</span>node:internal/modules/cjs/loader:1255:14<span class="o">)</span> at Module._extensions..js <span class="o">(</span>node:internal/modules/cjs/loader:1309:10<span class="o">)</span> at Module.load <span class="o">(</span>node:internal/modules/cjs/loader:1113:32<span class="o">)</span> at Module._load <span class="o">(</span>node:internal/modules/cjs/loader:960:12<span class="o">)</span> at Function.executeUserEntryPoint <span class="o">[</span>as runMain] <span class="o">(</span>node:internal/modules/run_main:83:12<span class="o">)</span> <span class="o">{</span> code: <span class="s1">'MODULE_NOT_FOUND'</span>, requireStack: <span class="o">[</span> <span class="s1">'/Users/deku/temp/temp/src/module-a/index.js'</span> <span class="o">]</span> <span class="o">}</span> </code></pre></div></div> <div class="in-feed-ads ads-container"> <div class="ads-block ads-left"> <ins class="adsbygoogle" style="display: block; text-align: center" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-7987914246691031" data-ad-slot="2718813593"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div> <div class="ads-block ads-center"> <ins class="adsbygoogle" style="display: block; text-align: center" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-7987914246691031" data-ad-slot="6492035359"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div> </div> <h2 id="create-symlink">Create Symlink</h2> <p>Now, let’s create a <code class="language-plaintext highlighter-rouge">Symlink</code> to check the <code class="language-plaintext highlighter-rouge">Symlink</code> feature. First, go to the <code class="language-plaintext highlighter-rouge">module-b</code> folder.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd </span>src/module-b </code></pre></div></div> <p>After then, run the following command to prepare to use <code class="language-plaintext highlighter-rouge">Symlink</code>.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm <span class="nb">link</span> <span class="c"># yarn link</span> </code></pre></div></div> <p>Then, go to the <code class="language-plaintext highlighter-rouge">module-a</code> folder that uses <code class="language-plaintext highlighter-rouge">module-b</code>.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> .. <span class="nb">cd </span>module-a </code></pre></div></div> <p>After then, run the following command to create a <code class="language-plaintext highlighter-rouge">Symlink</code> for <code class="language-plaintext highlighter-rouge">module-b</code>.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm <span class="nb">link </span>module-b </code></pre></div></div> <p>Then, you can see that the <code class="language-plaintext highlighter-rouge">node_modules</code> folder of the <code class="language-plaintext highlighter-rouge">module-a</code> folder is created as follows, and the <code class="language-plaintext highlighter-rouge">Symlink</code> of <code class="language-plaintext highlighter-rouge">module-b</code> is created in it.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">.</span> ├── index.js ├── node_modules │ └── module-b -> ../../module-b └── package.json </code></pre></div></div> <p>This <code class="language-plaintext highlighter-rouge">Symlink</code> folder is not a copy of <code class="language-plaintext highlighter-rouge">module-b</code>, but is linked to <code class="language-plaintext highlighter-rouge">module-b</code> itself. Therefore, if you modify the <code class="language-plaintext highlighter-rouge">src/module-b/index.js</code> file, the <code class="language-plaintext highlighter-rouge">src/module-a/node_modules/module-b/index.js</code> file will also be modified. Of course, if you modify the <code class="language-plaintext highlighter-rouge">src/module-a/node_modules/module-b/index.js</code> file, the <code class="language-plaintext highlighter-rouge">src/module-b/index.js</code> file will also be modified.</p> <h2 id="check-symlink">Check Symlink</h2> <p>Now, go to the root folder (<code class="language-plaintext highlighter-rouge">/</code>) of the project and run the following command to check if the module is imported well.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># cd ../..</span> node src/module-a/index.js </code></pre></div></div> <p>Then, you can see the following output without any problems.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>module-a module-b </code></pre></div></div> <p>Next, let’s modify the <code class="language-plaintext highlighter-rouge">src/module-b/index.js</code> file as follows.</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">module-b!!!!</span><span class="dl">'</span><span class="p">);</span> </code></pre></div></div> <p>Then, when you open the <code class="language-plaintext highlighter-rouge">src/module-a/node_modules/module-b/index.js</code> file, you can see that the modified content is reflected. Of course, if you run the following command, you can see that the modified content is shown well.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>node src/module-a/index.js module-a module-b!!!! </code></pre></div></div> <h2 id="completed">Completed</h2> <p>Done! We’ve seem what <code class="language-plaintext highlighter-rouge">Symlink</code> is, which is the basic knowledge for using Monorepo. When connecting the dependencies of various modules in Monorepo, <code class="language-plaintext highlighter-rouge">Symlink</code> is used. Therefore, if you understand <code class="language-plaintext highlighter-rouge">Symlink</code> in here, it will be helpful to use Monorepo.</p>dev.yakuza@gmail.com[Monorepo] Node JS module resolution2024-01-16T00:00:00+09:002024-03-10T11:36:58+09:00https://deku.posstree.com/environment/monorepo/monorepo-resolution-en<div id="contents_list"> <h2 id="contents">Contents</h2> <ul> <li><a href="#outline">Outline</a></li> <li><a href="#blog-series">Blog series</a></li> <li><a href="#module">Module</a></li> <li><a href="#loading-modules">Loading modules</a> <ul> <li><a href="#file-path">File path</a></li> <li><a href="#package-name">Package name</a></li> </ul> </li> <li><a href="#module-resolution-priority">Module resolution priority</a></li> <li><a href="#example">Example</a></li> <li><a href="#completed">Completed</a></li> </ul> </div> <h2 id="outline">Outline</h2> <p>In this blog post, I will explain how <code class="language-plaintext highlighter-rouge">NodeJS</code> loads modules as basic knowledge to understand Monorepo.</p> <h2 id="blog-series">Blog series</h2> <p>This blog is a series. Please check other blog posts through the following links.</p> <ul> <li><a href="https://deku.posstree.com/environment/repository_strategy/" target="\_blank">[Project Management] Repository Strategy</a></li> <li><a href="https://deku.posstree.com/environment/monorepo/tools/" target="\_blank">[JavaScript] Tools for Monorepo</a></li> <li>[Monorepo] Node JS module resolution</li> <li><a href="https://deku.posstree.com/environment/monorepo/symlink/" target="\_blank">[Monorepo] Symlink</a></li> <li><a href="https://deku.posstree.com/environment/monorepo/yarn_workspaces/" target="\_blank">[Monorepo] Yarn Workspaces</a></li> </ul> <h2 id="module">Module</h2> <p>In JavaScript, modules are features used to organize and separate code into reusable units. Modules make it easier to maintain and extend code. A module system that was originally not in JavaScript was introduced from ECMAScript 2015(ES6).</p> <p>Modules encapsulate code at the file level and allow you to import the required functionality from other files. Modules prevent conflicts in the global scope and allow you to clearly manage code dependencies.</p> <p>To use modules in JavaScript, you can use the following keywords.</p> <ul> <li><code class="language-plaintext highlighter-rouge">import</code>: Imports features exported from other modules into the current module.</li> <li><code class="language-plaintext highlighter-rouge">export</code>: Specifies functions, variables, classes, etc. to be exported from the current module to the outside.</li> </ul> <p>You can create modules in JavaScript as follows.</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// math.js</span> <span class="k">export</span> <span class="kd">function</span> <span class="nx">add</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">a</span> <span class="o">+</span> <span class="nx">b</span><span class="p">;</span> <span class="p">}</span> <span class="k">export</span> <span class="kd">function</span> <span class="nx">subtract</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">a</span> <span class="o">-</span> <span class="nx">b</span><span class="p">;</span> <span class="p">}</span> </code></pre></div></div> <p>Modules created in this way can be used as follows.</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// main.js</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">add</span><span class="p">,</span> <span class="nx">subtract</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./math</span><span class="dl">'</span><span class="p">;</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">add</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">3</span><span class="p">));</span> <span class="c1">// 8</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">subtract</span><span class="p">(</span><span class="mi">7</span><span class="p">,</span> <span class="mi">2</span><span class="p">));</span> <span class="c1">// 5</span> </code></pre></div></div> <h2 id="loading-modules">Loading modules</h2> <p>There are two ways to load modules in JavaScript.</p> <ul> <li>Using <code class="language-plaintext highlighter-rouge">File path</code></li> <li>Using <code class="language-plaintext highlighter-rouge">Package name</code></li> </ul> <h3 id="file-path">File path</h3> <p>When adding a module using <code class="language-plaintext highlighter-rouge">File path</code>, you can use relative path or absolute path as follows.</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Relative path</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">add</span><span class="p">,</span> <span class="nx">subtract</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../some/file/path/math</span><span class="dl">'</span><span class="p">;</span> <span class="c1">// Absolute path</span> <span class="k">import</span> <span class="p">{</span> <span class="nx">add</span><span class="p">,</span> <span class="nx">subtract</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">/src/math</span><span class="dl">'</span><span class="p">;</span> </code></pre></div></div> <h3 id="package-name">Package name</h3> <p>Modules in the <code class="language-plaintext highlighter-rouge">node_modules</code> folder can be loaded using <code class="language-plaintext highlighter-rouge">Package name</code>.</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// react module exists in node_modules</span> <span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span> </code></pre></div></div> <div class="in-feed-ads ads-container"> <div class="ads-block ads-left"> <ins class="adsbygoogle" style="display: block; text-align: center" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-7987914246691031" data-ad-slot="2718813593"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div> <div class="ads-block ads-center"> <ins class="adsbygoogle" style="display: block; text-align: center" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-7987914246691031" data-ad-slot="6492035359"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div> </div> <h2 id="module-resolution-priority">Module resolution priority</h2> <p>When <code class="language-plaintext highlighter-rouge">NodeJS</code> loads a module, first it checks if the module exists in the same folder, and then checks if the module exists in the <code class="language-plaintext highlighter-rouge">node_modules</code> in the same folder. If it does not exist in the same folder, it checks the <code class="language-plaintext highlighter-rouge">node_modules</code> folder int the parent folder, and if it does not exist in the parent folder, it checks the <code class="language-plaintext highlighter-rouge">node_modules</code> in the parnet folder of the parent folder.</p> <h2 id="example">Example</h2> <p>To check how <code class="language-plaintext highlighter-rouge">NodeJS</code> loads modules, let’s create the following folder structure.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">.</span> └── src/ ├── module-a/ │ ├── index.js │ └── package.json └── module-b/ ├── index.js └── package.json </code></pre></div></div> <p>The <code class="language-plaintext highlighter-rouge">package.json</code> of <code class="language-plaintext highlighter-rouge">module-a</code> is as follows.</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">//</span><span class="w"> </span><span class="err">src/module-a/package.json</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"module-a"</span><span class="p">,</span><span class="w"> </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span><span class="w"> </span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"index.js"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>The <code class="language-plaintext highlighter-rouge">index.js</code> of <code class="language-plaintext highlighter-rouge">module-b</code> is as follows.</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">//</span><span class="w"> </span><span class="err">src/module-b/package.json</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"module-b"</span><span class="p">,</span><span class="w"> </span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span><span class="w"> </span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"index.js"</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <p>The <code class="language-plaintext highlighter-rouge">index.js</code> of <code class="language-plaintext highlighter-rouge">module-a</code> is as follows.</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/module-b/index.js</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">module-b</span><span class="dl">'</span><span class="p">);</span> </code></pre></div></div> <p>Lastly, the <code class="language-plaintext highlighter-rouge">index.js</code> of <code class="language-plaintext highlighter-rouge">module-a</code> is as follows.</p> <div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/module-a/index.js</span> <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">module-a</span><span class="dl">'</span><span class="p">);</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">module-b</span><span class="dl">'</span><span class="p">);</span> </code></pre></div></div> <p>After configuring the files like this, run the following command to check if the module is loaded well.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>node src/module-a/index.js </code></pre></div></div> <p>Then, you can see the following error.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>module-a node:internal/modules/cjs/loader:1073 throw err<span class="p">;</span> ^ Error: Cannot find module <span class="s1">'module-b'</span> Require stack: - /Users/deku/temp/temp/src/module-a/index.js at Module._resolveFilename <span class="o">(</span>node:internal/modules/cjs/loader:1070:15<span class="o">)</span> at Module._load <span class="o">(</span>node:internal/modules/cjs/loader:923:27<span class="o">)</span> at Module.require <span class="o">(</span>node:internal/modules/cjs/loader:1137:19<span class="o">)</span> at require <span class="o">(</span>node:internal/modules/helpers:121:18<span class="o">)</span> at Object.<anonymous> <span class="o">(</span>/Users/deku/temp/temp/src/module-a/index.js:3:1<span class="o">)</span> at Module._compile <span class="o">(</span>node:internal/modules/cjs/loader:1255:14<span class="o">)</span> at Module._extensions..js <span class="o">(</span>node:internal/modules/cjs/loader:1309:10<span class="o">)</span> at Module.load <span class="o">(</span>node:internal/modules/cjs/loader:1113:32<span class="o">)</span> at Module._load <span class="o">(</span>node:internal/modules/cjs/loader:960:12<span class="o">)</span> at Function.executeUserEntryPoint <span class="o">[</span>as runMain] <span class="o">(</span>node:internal/modules/run_main:83:12<span class="o">)</span> <span class="o">{</span> code: <span class="s1">'MODULE_NOT_FOUND'</span>, requireStack: <span class="o">[</span> <span class="s1">'/Users/deku/temp/temp/src/module-a/index.js'</span> <span class="o">]</span> <span class="o">}</span> </code></pre></div></div> <p>Next, change the folder name <code class="language-plaintext highlighter-rouge">src</code> to <code class="language-plaintext highlighter-rouge">node_modules</code> and run it again.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>node node_modules/module-a/index.js </code></pre></div></div> <p>Then, you can see that it runs without any problems as follows.</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>module-a module-b </code></pre></div></div> <p>In the case that the folder name is <code class="language-plaintext highlighter-rouge">src</code>, when <code class="language-plaintext highlighter-rouge">module-a</code> loads <code class="language-plaintext highlighter-rouge">module-b</code> by <code class="language-plaintext highlighter-rouge">require('module-b');</code>, it can not load the file path, so it looks for <code class="language-plaintext highlighter-rouge">module-b</code> in the <code class="language-plaintext highlighter-rouge">node_modules</code> of the same folder. Since <code class="language-plaintext highlighter-rouge">node_modules</code> does not exist in the current folder, it checks the <code class="language-plaintext highlighter-rouge">node_modules</code> in the parent folder. Since there is no <code class="language-plaintext highlighter-rouge">node_modules</code> in the parent folder, the <code class="language-plaintext highlighter-rouge">MODULE_NOT_FOUND</code> error occurs.</p> <p>In the case that the folder name is <code class="language-plaintext highlighter-rouge">node_modules</code>, when <code class="language-plaintext highlighter-rouge">module-a</code> loads <code class="language-plaintext highlighter-rouge">module-b</code> by <code class="language-plaintext highlighter-rouge">require('module-b');</code>, it can not load the file path, so it looks for <code class="language-plaintext highlighter-rouge">module-b</code> in the <code class="language-plaintext highlighter-rouge">node_modules</code> of the same folder. Since <code class="language-plaintext highlighter-rouge">node_modules</code> does not exist in the current folder, it checks the <code class="language-plaintext highlighter-rouge">node_modules</code> in the parent folder. In the parent folder, there is a <code class="language-plaintext highlighter-rouge">node_modules</code> folder that we’ve changed the name, and the <code class="language-plaintext highlighter-rouge">module-b</code> exists in the <code class="language-plaintext highlighter-rouge">node_modules</code> folder, so it can be loaded without any problems.</p> <h2 id="completed">Completed</h2> <p>Done! we’ve senn how <code class="language-plaintext highlighter-rouge">NodeJS</code> loads modules. How <code class="language-plaintext highlighter-rouge">NodeJS</code> loads modules is knowledge that helps to configure a project as a Monorepo, so I introduced it in this blog post. I hope you take this opportunity to understand how <code class="language-plaintext highlighter-rouge">NodeJS</code> loads modules again.</p>dev.yakuza@gmail.com[JavaScript] Tools for Monorepo2024-01-11T00:00:00+09:002024-03-10T11:36:58+09:00https://deku.posstree.com/environment/monorepo/monorepo_tools-en<div id="contents_list"> <h2 id="contents">Contents</h2> <ul> <li><a href="#outline">Outline</a></li> <li><a href="#blog-series">Blog Series</a></li> <li><a href="#how-to-start-monorepo">How to start Monorepo</a></li> <li><a href="#package-manager">Package Manager</a></li> <li><a href="#monorepo-tool">Monorepo Tool</a> <ul> <li><a href="#lerna">Lerna</a></li> <li><a href="#nx">Nx</a></li> <li><a href="#turborepo">Turborepo</a></li> </ul> </li> <li><a href="#completed">Completed</a></li> </ul> </div> <h2 id="outline">Outline</h2> <p>In this blog post, I will introduce the tools needed to use Monorepo in a project developed in JavaScript.</p> <h2 id="blog-series">Blog Series</h2> <p>This blog is a series. You can check other blog posts through the following links.</p> <ul> <li><a href="https://deku.posstree.com/environment/repository_strategy/" target="\_blank">[Project Management] Repository Strategy</a></li> <li>[JavaScript] Tools for Monorepo</li> <li><a href="https://deku.posstree.com/environment/monorepo/module-resolutions/" target="\_blank">[Monorepo] Node JS module resolution</a></li> <li><a href="https://deku.posstree.com/environment/monorepo/symlink/" target="\_blank">[Monorepo] Symlink</a></li> <li><a href="https://deku.posstree.com/environment/monorepo/yarn_workspaces/" target="\_blank">[Monorepo] Yarn Workspaces</a></li> </ul> <h2 id="how-to-start-monorepo">How to start Monorepo</h2> <p>There are two ways to start Monorepo.</p> <ul> <li>Using a package manager</li> <li>Using a tool for Monorepo</li> </ul> <p>In this blog post, I will introduce the package manager and the Monorepo tool to support Monorepo in a JavaScript project.</p> <h2 id="package-manager">Package Manager</h2> <p>To configure Monorepo in a JavaScript project, you can use a package manager that supports Monorepo. The package managers that support Monorepo are <code class="language-plaintext highlighter-rouge">pnpm</code>, <code class="language-plaintext highlighter-rouge">yarn</code>, and <code class="language-plaintext highlighter-rouge">npm</code>.</p> <p>Package managers that support Monorepo have the following characteristics.</p> <table> <thead> <tr> <th>기능</th> <th>pnpm</th> <th>yarn</th> <th>npm</th> </tr> </thead> <tbody> <tr> <td>Support workspace</td> <td>✅</td> <td>✅</td> <td>✅</td> </tr> <tr> <td>Isolated node_modules</td> <td>✅(default)</td> <td>✅</td> <td>✅</td> </tr> <tr> <td>Hoisted node_modules</td> <td>✅</td> <td>✅</td> <td>✅(default)</td> </tr> <tr> <td>Peer auto install</td> <td>✅</td> <td>❌</td> <td>✅</td> </tr> <tr> <td>Plug’n’Play</td> <td>✅</td> <td>✅(default)</td> <td>✅</td> </tr> <tr> <td>Zero-Installs</td> <td>❌</td> <td>✅</td> <td>❌</td> </tr> <tr> <td>dependent patches</td> <td>✅</td> <td>✅</td> <td>❌</td> </tr> <tr> <td>NodeJS version management</td> <td>✅</td> <td>❌</td> <td>❌</td> </tr> <tr> <td>Lockfile</td> <td>✅(pnpm-lock.yaml)</td> <td>✅(yarn.lock)</td> <td>✅(package-lock.json)</td> </tr> <tr> <td>Overwrite support</td> <td>✅</td> <td>✅(resolution)</td> <td>✅</td> </tr> <tr> <td>Content-addressable repository</td> <td>✅</td> <td>❌</td> <td>❌</td> </tr> <tr> <td>Dynamic package execution</td> <td>✅(pnpm dlx)</td> <td>✅(yarn dlx)</td> <td>✅(npx)</td> </tr> <tr> <td>Listing licenses</td> <td>✅(pnpm licenses list)</td> <td>✅(Via a plugin)</td> <td>❌</td> </tr> </tbody> </table> <div class="in-feed-ads ads-container"> <div class="ads-block ads-left"> <ins class="adsbygoogle" style="display: block; text-align: center" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-7987914246691031" data-ad-slot="2718813593"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div> <div class="ads-block ads-center"> <ins class="adsbygoogle" style="display: block; text-align: center" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-7987914246691031" data-ad-slot="6492035359"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div> </div> <h2 id="monorepo-tool">Monorepo Tool</h2> <p>You can configure Monorepo with the <code class="language-plaintext highlighter-rouge">Workspace</code> feature provided by the package manager, but using a tool for Monorepo makes it easier to configure Monorepo. Also, using the <code class="language-plaintext highlighter-rouge">Cache</code> feature provided by the Monorepo tool, you can perform <code class="language-plaintext highlighter-rouge">Build</code> or <code class="language-plaintext highlighter-rouge">CI</code> faster.</p> <p>There are <code class="language-plaintext highlighter-rouge">Lerna</code>, <code class="language-plaintext highlighter-rouge">Nx</code>, and <code class="language-plaintext highlighter-rouge">Turborepo</code> tools for Monorepo in JavaScript projects.</p> <h3 id="lerna">Lerna</h3> <p><code class="language-plaintext highlighter-rouge">Lerna</code> is developed and managed as open source. It was acquired by <code class="language-plaintext highlighter-rouge">Nx</code> because it was difficult to manage with only the open source community.</p> <ul> <li>Lerna: <a href="https://lerna.js.org/" rel="nofollow noreferrer" target="\_blank">https://lerna.js.org/</a></li> </ul> <p>Currently, you can use <code class="language-plaintext highlighter-rouge">Lerna</code> as it is, but since it has been incorporated into <code class="language-plaintext highlighter-rouge">Nx</code>, it is not certain whether <code class="language-plaintext highlighter-rouge">Nx</code> will support <code class="language-plaintext highlighter-rouge">Lerna</code> as much as their tool, so if you plan to use <code class="language-plaintext highlighter-rouge">Lerna</code>, consider using <code class="language-plaintext highlighter-rouge">Nx</code> or watching the trend a little more.</p> <h3 id="nx">Nx</h3> <p><code class="language-plaintext highlighter-rouge">Nx</code> is developed and provided by a company called <code class="language-plaintext highlighter-rouge">Narwhal Technologies Inc.</code>. This company provides tools and services related to the <code class="language-plaintext highlighter-rouge">Angular</code> framework. <code class="language-plaintext highlighter-rouge">Nx</code> was developed by a company that develops tools related to the <code class="language-plaintext highlighter-rouge">Angular</code> framework, so <code class="language-plaintext highlighter-rouge">Nx</code> was originally developed as a monorepo management tool for <code class="language-plaintext highlighter-rouge">Angular</code> applications. Currently, <code class="language-plaintext highlighter-rouge">Nx</code> also supports other frameworks such as <code class="language-plaintext highlighter-rouge">React</code>.</p> <ul> <li>Nx: <a href="https://nx.dev/" rel="nofollow noreferrer" target="\_blank">https://nx.dev/</a></li> </ul> <p>If you are going to develop a project with <code class="language-plaintext highlighter-rouge">Angular</code> framework, <code class="language-plaintext highlighter-rouge">Nx</code> is a great choice. Of course, <code class="language-plaintext highlighter-rouge">Nx</code> can be a good choice in other frameworks, but <code class="language-plaintext highlighter-rouge">Nx</code> developed around the <code class="language-plaintext highlighter-rouge">Angular</code> framework and the company that develops tools focused on <code class="language-plaintext highlighter-rouge">Angular</code> developed <code class="language-plaintext highlighter-rouge">Nx</code>, so it will perform better in the <code class="language-plaintext highlighter-rouge">Angular</code> framework.</p> <p>The frameworks supported by <code class="language-plaintext highlighter-rouge">Nx</code> can be found through the following link.</p> <ul> <li>Recipes: <a href="https://nx.dev/recipes" rel="nofollow noreferrer" target="\_blank">https://nx.dev/recipes</a></li> </ul> <h3 id="turborepo">Turborepo</h3> <p><code class="language-plaintext highlighter-rouge">Turborepo</code> was developed by <a href="https://twitter.com/jaredpalmer" rel="nofollow noreferrer" target="\_blank">Jared Palmer</a>, but in 2021, <a href="https://vercel.com/blog/vercel-acquires-turborepo" rel="nofollow noreferrer" target="\_blank">Vercel</a> that develops and operates <code class="language-plaintext highlighter-rouge">NextJS</code> acquired it.</p> <ul> <li>Turbo: <a href="https://turbo.build/" rel="nofollow noreferrer" target="\_blank">https://turbo.build/</a></li> </ul> <p>If you are going to develop a project with <code class="language-plaintext highlighter-rouge">NextJS</code> or <code class="language-plaintext highlighter-rouge">React</code>, <code class="language-plaintext highlighter-rouge">Turborepo</code> is a great choice. Especially, if you use <code class="language-plaintext highlighter-rouge">NextJS</code>, using <code class="language-plaintext highlighter-rouge">Turborepo</code> developed and managed by the same company will be better than using other tools.</p> <p>The frameworks supported by <code class="language-plaintext highlighter-rouge">Turborepo</code> can be found through the following link.</p> <ul> <li>Turborepo Examples: <a href="https://turbo.build/repo/docs/getting-started/from-example" rel="nofollow noreferrer" target="\_blank">https://turbo.build/repo/docs/getting-started/from-example</a></li> </ul> <h2 id="completed">Completed</h2> <p>In this blog post, I introduced the tools needed to use Monorepo in a project developed in JavaScript. You can configure Monorepo with the features provided by the package manager, but using a tool for Monorepo makes it easier to configure Monorepo and use various features.</p> <p>If you are planning to configure a project as Monorepo, please consider introducing a tool for Monorepo in addition to the features of the package manager.</p>dev.yakuza@gmail.com[Project Management] Repository Strategy2024-01-04T00:00:00+09:002024-03-10T11:36:58+09:00https://deku.posstree.com/environment/repository_strategy-en<div id="contents_list"> <h2 id="contents">Contents</h2> <ul> <li><a href="#outline">Outline</a></li> <li><a href="#blog-series">Blog Series</a></li> <li><a href="#repository-strategy">Repository Strategy</a></li> <li><a href="#monolith">Monolith</a> <ul> <li><a href="#advantages-of-monolith">Advantages of Monolith</a></li> <li><a href="#disadvantages-of-monolith">Disadvantages of Monolith</a></li> <li><a href="#considerations-when-introducing-monolith">Considerations when introducing Monolith</a></li> </ul> </li> <li><a href="#multi-repo">Multi Repo</a> <ul> <li><a href="#advantages-of-multi-repo">Advantages of Multi Repo</a></li> <li><a href="#disadvantages-of-multi-repo">Disadvantages of Multi Repo</a></li> <li><a href="#considerations-when-introducing-multi-repo">Considerations when introducing Multi Repo</a></li> </ul> </li> <li><a href="#monorepo">Monorepo</a> <ul> <li><a href="#advantages-of-monorepo">Advantages of Monorepo</a></li> <li><a href="#disadvantages-of-monorepo">Disadvantages of Monorepo</a></li> <li><a href="#considerations-when-introducing-monorepo">Considerations when introducing Monorepo</a></li> </ul> </li> <li><a href="#completed">Completed</a></li> </ul> </div> <h2 id="outline">Outline</h2> <p>When developing a project, you will decide whether to use a single repository or multiple repositories. In this blog post, I will introduce repository strategies for managing projects, such as <code class="language-plaintext highlighter-rouge">Monolith</code>, <code class="language-plaintext highlighter-rouge">Multi Repo</code>, and <code class="language-plaintext highlighter-rouge">Monorepo</code>.</p> <h2 id="blog-series">Blog Series</h2> <p>This blog is a series. You can check other blog posts through the following links.</p> <ul> <li>[Project Management] Repository Strategy</li> <li><a href="https://deku.posstree.com/environment/monorepo/tools/" target="\_blank">[JavaScript] Tools for Monorepo</a></li> <li><a href="https://deku.posstree.com/environment/monorepo/module-resolutions/" target="\_blank">[Monorepo] Node JS module resolution</a></li> <li><a href="https://deku.posstree.com/environment/monorepo/symlink/" target="\_blank">[Monorepo] Symlink</a></li> <li><a href="https://deku.posstree.com/environment/monorepo/yarn_workspaces/" target="\_blank">[Monorepo] Yarn Workspaces</a></li> </ul> <h2 id="repository-strategy">Repository Strategy</h2> <p>There are <code class="language-plaintext highlighter-rouge">Monolith</code>, <code class="language-plaintext highlighter-rouge">Multi Repo</code>, and <code class="language-plaintext highlighter-rouge">Monorepo</code> as repository strategies like the following.</p> <picture> <source srcset="/assets/images/category/environment/2024/repository_strategy/repository_strategy.avif" type="image/avif" /> <source srcset="/assets/images/category/environment/2024/repository_strategy/repository_strategy.webp" type="image/webp" /> <img src="/assets/images/category/environment/2024/repository_strategy/repository_strategy.jpg" alt="Repository strategy" /> </picture> <ul> <li>Monolith: Monolith is creating a single service as a single project and managing it as a single repository.</li> <li>Multi Repo: Multi Repo is dividing a single service into multiple projects and managing them in multiple repositories.</li> <li>Monorepo: Monorepo is dividing a single service into multiple projects and managing them as a single repository.</li> </ul> <h2 id="monolith">Monolith</h2> <p>Monolith is a project management technique that designs software projects as a single codebase and a single application. In the monolith architecture, all components of the application are integrated into a single codebase and developed, deployed, and operated.</p> <h3 id="advantages-of-monolith">Advantages of Monolith</h3> <ul> <li>Easy management and maintenance: Since it is composed of a single codebase, overall management and maintenance are relatively easy.</li> <li>Easy integration testing: Since the entire system is composed of a single application, integration testing is easy.</li> <li>Improved initial development speed: At first, everything is in one place, so development can proceed quickly.</li> </ul> <h3 id="disadvantages-of-monolith">Disadvantages of Monolith</h3> <ul> <li>Scalability constraints: As the size of the application increases, scalability may become difficult.</li> <li>Technology stack dependency: Since it depends on a single technology stack, it may be difficult to introduce various technologies.</li> <li>Difficulty of deployment: Since the entire application needs to be modified and deployed, it may be difficult to deploy small changes.</li> </ul> <h3 id="considerations-when-introducing-monolith">Considerations when introducing Monolith</h3> <p>In the following cases, you can consider introducing Monolith.</p> <ul> <li>Small-scale project: If it is a small project, you can increase the initial development speed.</li> <li>If easy maintenance is required: For simple applications, maintenance may be easier.</li> <li>If the technology stack is clear: If a clear decision has been made on a specific technology stack, Monolith may be useful.</li> <li>If the team is small: If a small team is collaborating and developing, Monolith may be effective.</li> </ul> <p>However, as the size and requirements of the project increase, you need to consider other architectures such as distributed architecture or microservice architecture. This can increase the scalability, flexibility, and independence of the application.</p> <div class="in-feed-ads ads-container"> <div class="ads-block ads-left"> <ins class="adsbygoogle" style="display: block; text-align: center" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-7987914246691031" data-ad-slot="2718813593"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div> <div class="ads-block ads-center"> <ins class="adsbygoogle" style="display: block; text-align: center" data-ad-layout="in-article" data-ad-format="fluid" data-ad-client="ca-pub-7987914246691031" data-ad-slot="6492035359"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </div> </div> <h2 id="multi-repo">Multi Repo</h2> <p>Multi Repo is a project management technique that develops software projects in multiple independent repositories. Each repository corresponds to a specific feature, module, or service, and development. Each repository is managed separately for deployment and operation.</p> <h3 id="advantages-of-multi-repo">Advantages of Multi Repo</h3> <ul> <li>Independent development and deployment: Each repository can be developed, tested, and deployed independently, allowing for faster development cycles.</li> <li>Scalability: If the load of a specific module or service increases, only that repository can be scaled to improve the overall performance of the system.</li> <li>Technology stack diversity: Each repository can choose an independent technology stack, so you can use multiple technologies without relying on a specific technology.</li> <li>Separated codebase: Each repository can focus on a specific feature or business area, improving code readability and maintainability.</li> </ul> <h3 id="disadvantages-of-multi-repo">Disadvantages of Multi Repo</h3> <ul> <li>Difficulty in dependency management: It can be complicated to manage dependencies between various repositories.</li> <li>Difficulty in overall system integration: Each repository is developed independently, and conflicts may occur when integrating them, which may take time to resolve.</li> <li>Lack of comprehensive vision: It may be difficult to understand and manage the entire system, especially when an integrated vision is required.</li> </ul> <h3 id="considerations-when-introducing-multi-repo">Considerations when introducing Multi Repo</h3> <p>In the following cases, you can consider introducing Multi Repo.</p> <ul> <li>Large-scale project: If the project is large and contains various features or modules, you can manage each of them independently.</li> <li>Collaboration between teams: If multiple teams are working at the same time and want to develop independently, Multi Repo may be effective.</li> <li>Service-oriented architecture (SOA) or microservice architecture: Multi Repo is useful for managing each service independently, which fits well with SOA or microservice architecture.</li> </ul> <p>Multi Repo is suitable for supporting team collaboration and fast development cycles in a distributed environment. Especially in service-oriented architecture, it is important to manage each service separately.</p> <p>However, if the team size is small or the project size is small, too many technology stacks and difficulty in repository management may occur.</p> <h2 id="monorepo">Monorepo</h2> <p>Monorepo is a project management technique that designs software projects as a single large codebase and develops them as a single repository. In this method, all source code, libraries, and modules are stored and managed in one central location.</p> <h3 id="advantages-of-monorepo">Advantages of Monorepo</h3> <ul> <li>Easy dependency management: Since all dependencies are managed in one codebase, dependency conflicts and version management are easy.</li> <li>Easy to understand the entire system: It is easy to understand the overall structure and operation of the project. The entire system can be seen at a glance.</li> <li>Code reusability: Since all modules and libraries are in one repository, code reusability is easy.</li> <li>Easy integration testing: Integration testing for the entire system can be easily performed.</li> </ul> <h3 id="disadvantages-of-monorepo">Disadvantages of Monorepo</h3> <ul> <li>Difficulty in managing large-scale projects: As the size of the project increases, the build time increases, and collaboration between teams on all code may become difficult.</li> <li>Technology stack dependency: Since all code is in one repository, you may be constrained by a specific technology stack.</li> <li>Performance degradation of CI/CD: In a large-scale Monorepo, continuous integration and deployment performance may be degraded.</li> </ul> <h3 id="considerations-when-introducing-monorepo">Considerations when introducing Monorepo</h3> <p>In the following cases, you can consider introducing Monorepo.</p> <ul> <li>Small-scale project: If the project is small, it may be convenient to manage it in one repository.</li> <li>Collaboration between teams: If all teams work in one repository, code sharing and collaboration are easy.</li> <li>If dependency management is important: When dependencies are complicatedly intertwined within the project, Monorepo is easy to manage.</li> <li>If a single technology stack is required: When a specific technology stack needs to be used consistently, Monorepo may be suitable.</li> </ul> <p>Monorepo is especially useful for small-scale projects or team collaboration is required, and it can be effective when a specific technology stack needs to be unified. However, as the size and complexity of the project increase, the limitations of Monorepo may appear.</p> <h2 id="completed">Completed</h2> <p>Done! we’ve seen the repository strategies like Monolith, Multi Repo, and Monorepo for managing projects. There is no good or bad strategy. You can choose the appropriate strategy according to the size and requirements of your project.</p>dev.yakuza@gmail.com