CARVIEW |
Select Language
HTTP/2 200
date: Thu, 17 Jul 2025 13:59:41 GMT
content-type: application/atom+xml; charset=utf-8
content-length: 638043
vary: X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, X-Requested-With,Accept-Encoding, Accept, X-Requested-With
etag: W/"c2be4151903d759a269542476370aa4f"
cache-control: max-age=0, private, must-revalidate
strict-transport-security: max-age=31536000; includeSubdomains; preload
x-frame-options: deny
x-content-type-options: nosniff
x-xss-protection: 0
referrer-policy: origin-when-cross-origin, strict-origin-when-cross-origin
content-security-policy: default-src 'none'; base-uri 'self'; child-src github.githubassets.com github.com/assets-cdn/worker/ github.com/assets/ gist.github.com/assets-cdn/worker/; connect-src 'self' uploads.github.com www.githubstatus.com collector.github.com raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com *.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com objects-origin.githubusercontent.com copilot-proxy.githubusercontent.com proxy.individual.githubcopilot.com proxy.business.githubcopilot.com proxy.enterprise.githubcopilot.com *.actions.githubusercontent.com wss://*.actions.githubusercontent.com productionresultssa0.blob.core.windows.net/ productionresultssa1.blob.core.windows.net/ productionresultssa2.blob.core.windows.net/ productionresultssa3.blob.core.windows.net/ productionresultssa4.blob.core.windows.net/ productionresultssa5.blob.core.windows.net/ productionresultssa6.blob.core.windows.net/ productionresultssa7.blob.core.windows.net/ productionresultssa8.blob.core.windows.net/ productionresultssa9.blob.core.windows.net/ productionresultssa10.blob.core.windows.net/ productionresultssa11.blob.core.windows.net/ productionresultssa12.blob.core.windows.net/ productionresultssa13.blob.core.windows.net/ productionresultssa14.blob.core.windows.net/ productionresultssa15.blob.core.windows.net/ productionresultssa16.blob.core.windows.net/ productionresultssa17.blob.core.windows.net/ productionresultssa18.blob.core.windows.net/ productionresultssa19.blob.core.windows.net/ github-production-repository-image-32fea6.s3.amazonaws.com github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com wss://alive.github.com api.githubcopilot.com api.individual.githubcopilot.com api.business.githubcopilot.com api.enterprise.githubcopilot.com; font-src github.githubassets.com; form-action 'self' github.com gist.github.com copilot-workspace.githubnext.com objects-origin.githubusercontent.com; frame-ancestors 'none'; frame-src viewscreen.githubusercontent.com notebooks.githubusercontent.com; img-src 'self' data: blob: github.githubassets.com media.githubusercontent.com camo.githubusercontent.com identicons.github.com avatars.githubusercontent.com private-avatars.githubusercontent.com github-cloud.s3.amazonaws.com objects.githubusercontent.com release-assets.githubusercontent.com secured-user-images.githubusercontent.com/ user-images.githubusercontent.com/ private-user-images.githubusercontent.com opengraph.githubassets.com copilotprodattachments.blob.core.windows.net/github-production-copilot-attachments/ github-production-user-asset-6210df.s3.amazonaws.com customer-stories-feed.github.com spotlights-feed.github.com objects-origin.githubusercontent.com *.githubusercontent.com; manifest-src 'self'; media-src github.com user-images.githubusercontent.com/ secured-user-images.githubusercontent.com/ private-user-images.githubusercontent.com github-production-user-asset-6210df.s3.amazonaws.com gist.github.com; script-src github.githubassets.com; style-src 'unsafe-inline' github.githubassets.com; upgrade-insecure-requests; worker-src github.githubassets.com github.com/assets-cdn/worker/ github.com/assets/ gist.github.com/assets-cdn/worker/
server: github.com
accept-ranges: bytes
set-cookie: _gh_sess=iusAT8jA4Jkjc8EDvYkhDNB4ndzB4eFiomfxQU%2FhRsdrHDspKP%2FWJ9eaQtJddWQDXN1lbH0FwAqITRDipB%2F1GwNc5bgj8BSPonwF5PvN5MkTsCzgeLnThCvchAk8egR6YRZPCIjPu2BxOtskPWJGEFla4CbNoYZTGVeoSiUSggWQ13NZBpoheOFQKWprRCERotMFKXD%2B5h9bfOOJpiRPDR%2B2yNyvTNI3D2mrpFnY4XGHohV9yJM8ByOtiex1Qlk5nx6RMKgIZdzmLJPWcIOSvg%3D%3D--xgQ9FELAHmPowgvT--jp%2FqYyRzemg16sqY6H0bMA%3D%3D; Path=/; HttpOnly; Secure; SameSite=Lax
set-cookie: _octo=GH1.1.1890809947.1752760780; Path=/; Domain=github.com; Expires=Fri, 17 Jul 2026 13:59:40 GMT; Secure; SameSite=Lax
set-cookie: logged_in=no; Path=/; Domain=github.com; Expires=Fri, 17 Jul 2026 13:59:40 GMT; HttpOnly; Secure; SameSite=Lax
x-github-request-id: D49A:242D12:131BF1:15AD91:687901CC
tag:gist.github.com,2008:/banyudu
Recent Gists from banyudu
2024-01-22T02:11:27Z
tag:gist.github.com,2008:Gist/banyudu/48a38feb53d03909ab32ee96e388609b
2024-01-22T02:11:27Z
2024-01-22T02:11:27Z
Introducing S.T.A.R as an alternative to S.M.A.R.T objective definition
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/48a38feb53d03909ab32ee96e388609b#file-star-blog-md">star.blog.md</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-star-blog-md" class="file my-2">
<div id="file-star-blog-md-readme" class="Box-body readme blob p-5 p-xl-6 "
style="overflow: auto" tabindex="0" role="region"
aria-label="star.blog.md content, created by banyudu on 02:11AM on January 22, 2024."
>
<article class="markdown-body entry-content container-lg" itemprop="text"><p dir="auto">Introducing S.T.A.R as an alternative to S.M.A.R.T objective definition.</p>
<p dir="auto">Let's start by reviewing what S.M.A.R.T stands for:</p>
<ol dir="auto">
<li>S: Specific</li>
<li>M: Measurable</li>
<li>A: Achievable</li>
<li>R: Relevant</li>
<li>T: Time-bound</li>
</ol>
<p dir="auto">While SMART is helpful and informative, I encounter difficulties when pursuing personal objectives, especially when they conflict with work objectives.</p>
<p dir="auto">To better manage individual objectives, I propose a new Trust-based target management methodology: S.T.A.R (<strong>S</strong>imply <strong>T</strong>rust the <strong>A</strong>mount of <strong>R</strong>esource).</p>
<p dir="auto">Here's how it works:</p>
<p dir="auto">For individuals who are self-driven and responsible, you just need to provide them with enough resources (mainly time), and they will do their best to achieve the objective.</p>
<p dir="auto">S.T.A.R offers some advantages over S.M.A.R.T:</p>
<ol dir="auto">
<li>Insensitivity to specific measures: For example, if your objective is "Learning English," S.T.A.R doesn't specify whether you should be memorizing vocabulary or practicing listening. It simply ensures you have a set amount of time to learn English.</li>
<li>Conflict sensitivity: While S.M.A.R.T may lead to overpromising objectives due to a lack of clear perception of your own abilities, S.T.A.R won't allow you to promise more time than is realistically available in a day.</li>
</ol>
<p dir="auto">The essence of S.T.A.R is <strong>Trust</strong>, making it potentially unsuitable for defining company objectives. However, since one can always trust oneself, it could be a good approach for setting your new year's objectives.</p>
</article>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/2787db221933bbdd935f67b40da14096
2023-12-18T09:02:55Z
2023-12-18T09:02:55Z
Typescript truthy falsy guard
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/2787db221933bbdd935f67b40da14096#file-ts-truthy-falsy-ts">ts-truthy-falsy.ts</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-ts-truthy-falsy-ts" class="file my-2">
<div itemprop="text"
class="Box-body p-0 blob-wrapper data type-typescript "
style="overflow: auto" tabindex="0" role="region"
aria-label="ts-truthy-falsy.ts content, created by banyudu on 09:02AM on December 18, 2023."
>
<div class="js-check-hidden-unicode js-blob-code-container blob-code-content">
<template class="js-file-alert-template">
<div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
<span>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
<a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
</span>
<div data-view-component="true" class="flash-action"> <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn"> Show hidden characters
</a>
</div>
</div></template>
<template class="js-line-alert-template">
<span aria-label="This line has hidden Unicode characters" data-view-component="true" class="line-alert tooltipped tooltipped-e">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
</span></template>
<table data-hpc class="highlight tab-size js-file-line-container" data-tab-size="8" data-paste-markdown-skip data-tagsearch-path="ts-truthy-falsy.ts">
<tr>
<td id="file-ts-truthy-falsy-ts-L1" class="blob-num js-line-number js-blob-rnum" data-line-number="1"></td>
<td id="file-ts-truthy-falsy-ts-LC1" class="blob-code blob-code-inner js-file-line"><span class=pl-k>export</span> <span class=pl-k>const</span> <span class=pl-en>truthy</span> <span class=pl-c1>=</span> <span class=pl-kos>(</span><span class=pl-s1>v</span>: <span class=pl-smi>any</span><span class=pl-kos>)</span>: <span class=pl-s1>v</span> is <span class=pl-c1>true</span> <span class=pl-c1>=></span> <span class=pl-kos>{</span></td>
</tr>
<tr>
<td id="file-ts-truthy-falsy-ts-L2" class="blob-num js-line-number js-blob-rnum" data-line-number="2"></td>
<td id="file-ts-truthy-falsy-ts-LC2" class="blob-code blob-code-inner js-file-line"> <span class=pl-k>return</span> <span class=pl-c1>!</span><span class=pl-c1>!</span><span class=pl-kos>(</span><span class=pl-s1>v</span> <span class=pl-k>as</span> <span class=pl-smi>boolean</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-ts-truthy-falsy-ts-L3" class="blob-num js-line-number js-blob-rnum" data-line-number="3"></td>
<td id="file-ts-truthy-falsy-ts-LC3" class="blob-code blob-code-inner js-file-line"><span class=pl-kos>}</span></td>
</tr>
<tr>
<td id="file-ts-truthy-falsy-ts-L4" class="blob-num js-line-number js-blob-rnum" data-line-number="4"></td>
<td id="file-ts-truthy-falsy-ts-LC4" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-ts-truthy-falsy-ts-L5" class="blob-num js-line-number js-blob-rnum" data-line-number="5"></td>
<td id="file-ts-truthy-falsy-ts-LC5" class="blob-code blob-code-inner js-file-line"><span class=pl-k>export</span> <span class=pl-k>const</span> <span class=pl-en>falsy</span> <span class=pl-c1>=</span> <span class=pl-kos>(</span><span class=pl-s1>v</span>: <span class=pl-smi>any</span><span class=pl-kos>)</span>: <span class=pl-s1>v</span> is <span class=pl-c1>false</span> <span class=pl-c1>=></span> <span class=pl-kos>{</span></td>
</tr>
<tr>
<td id="file-ts-truthy-falsy-ts-L6" class="blob-num js-line-number js-blob-rnum" data-line-number="6"></td>
<td id="file-ts-truthy-falsy-ts-LC6" class="blob-code blob-code-inner js-file-line"> <span class=pl-k>return</span> <span class=pl-c1>!</span><span class=pl-kos>(</span><span class=pl-s1>v</span> <span class=pl-k>as</span> <span class=pl-smi>boolean</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-ts-truthy-falsy-ts-L7" class="blob-num js-line-number js-blob-rnum" data-line-number="7"></td>
<td id="file-ts-truthy-falsy-ts-LC7" class="blob-code blob-code-inner js-file-line"><span class=pl-kos>}</span></td>
</tr>
</table>
</div>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/00a85e34447db03bcd647af5b7b88bf1
2023-11-07T11:57:18Z
2025-05-29T10:59:56Z
Chrome插件开发浅浅谈
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/00a85e34447db03bcd647af5b7b88bf1#file-chrome-extension-development-blog-md">chrome-extension-development.blog.md</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-chrome-extension-development-blog-md" class="file my-2">
<div id="file-chrome-extension-development-blog-md-readme" class="Box-body readme blob p-5 p-xl-6 "
style="overflow: auto" tabindex="0" role="region"
aria-label="chrome-extension-development.blog.md content, created by banyudu on 11:57AM on November 07, 2023."
>
<article class="markdown-body entry-content container-lg" itemprop="text"><ul dir="auto">
<li>
<div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">浅谈Chrome插件开发</h1><a id="user-content-浅谈chrome插件开发" class="anchor" aria-label="Permalink: 浅谈Chrome插件开发" href="#浅谈chrome插件开发"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
</li>
<li>
<p dir="auto">可能有很多人都用过Chrome插件,有没有好奇过Chrome插件是怎么开发的?</p>
</li>
<li>
<p dir="auto">Chrome插件开发使用到的技术栈并不复杂,也是 HTML/CSS/JS 这些前端相关的技术,只是会有一套自己的 API 和限制等,下面带大家认识下 Chrome 插件的开发。</p>
</li>
<li></li>
<li>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">插件是什么?</h2><a id="user-content-插件是什么" class="anchor" aria-label="Permalink: 插件是什么?" href="#插件是什么"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><a target="_blank" rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/2416493/281028034-5c45782f-292f-48cd-867a-6af73c1978b4.png"><img src="https://user-images.githubusercontent.com/2416493/281028034-5c45782f-292f-48cd-867a-6af73c1978b4.png" alt="image" style="max-width: 100%;"></a></li>
</ul>
</li>
<li>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">插件怎么开发?</h2><a id="user-content-插件怎么开发" class="anchor" aria-label="Permalink: 插件怎么开发?" href="#插件怎么开发"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>原始阶段(类似于手动操作dom)
<ul dir="auto">
<li>引入各个浏览器(主要是Firefox)的sdk,调用sdk开发浏览器专用的插件</li>
<li>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-k">var</span> <span class="pl-s1">widgets</span> <span class="pl-c1">=</span> <span class="pl-en">require</span><span class="pl-kos">(</span><span class="pl-s">"sdk/widget"</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-k">var</span> <span class="pl-s1">tabs</span> <span class="pl-c1">=</span> <span class="pl-en">require</span><span class="pl-kos">(</span><span class="pl-s">"sdk/tabs"</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-k">var</span> <span class="pl-s1">widget</span> <span class="pl-c1">=</span> <span class="pl-s1">widgets</span><span class="pl-kos">.</span><span class="pl-en">Widget</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
<span class="pl-c1">id</span>: <span class="pl-s">"mozilla-link"</span><span class="pl-kos">,</span>
<span class="pl-c1">label</span>: <span class="pl-s">"Mozilla website"</span><span class="pl-kos">,</span>
<span class="pl-c1">contentURL</span>: <span class="pl-en">require</span><span class="pl-kos">(</span><span class="pl-s">"sdk/self"</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-c1">data</span><span class="pl-kos">.</span><span class="pl-en">url</span><span class="pl-kos">(</span><span class="pl-s">"icon-16.png"</span><span class="pl-kos">)</span><span class="pl-kos">,</span>
<span class="pl-en">onClick</span>: <span class="pl-k">function</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-s1">tabs</span><span class="pl-kos">.</span><span class="pl-en">open</span><span class="pl-kos">(</span><span class="pl-s">"https://developer.mozilla.org/"</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
</li>
<li>例子:
<ul dir="auto">
<li>油猴 插件</li>
</ul>
</li>
</ul>
</li>
<li>Web阶段(类似于 jQuery)
<ul dir="auto">
<li>开发方式:手写 manifest.json、js、css、html等</li>
<li>例子
<ul dir="auto">
<li>GoogleChrome/chrome-extensions-samples Google官方Demo</li>
</ul>
</li>
</ul>
</li>
<li>脚手架时代(类似于 create-react-app 或 angular cli等)
<ul dir="auto">
<li>开发方式:使用脚手架,或cli生成初始化模板,一般自带主流前端框架</li>
<li>例子
<ul dir="auto">
<li>yeoman/generator-chrome-extension 生成器</li>
<li>larscom/ng-chrome-extension Angular模板</li>
<li>guocaoyi/create-chrome-ext</li>
<li>lxieyang/chrome-extension-boilerplate-react</li>
</ul>
</li>
</ul>
</li>
<li>框架时代(类似于 Next.js、Umi等)
<ul dir="auto">
<li>开发方式:封装底层API,抽象出开发框架,拥有良好的文档和成熟的解决方案</li>
<li>例子
<ul dir="auto">
<li>PlasmoHQ/plasmo 类Next.js开发框架</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">现代化的 Chrome 插件</h2><a id="user-content-现代化的-chrome-插件" class="anchor" aria-label="Permalink: 现代化的 Chrome 插件" href="#现代化的-chrome-插件"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>Manifest V3
<ul dir="auto">
<li>Manifest V3是新一代插件开发标准,与之前的 Manifest V2相比,有很多的 API 的变化,是一次大的标准更新,各个浏览器厂商也都在努力实现(chrome >= 88)</li>
<li>Manifest中更注重安全性,封锁了几乎所有的使用远程(或动态)代码攻击用户的入口。当然同时也给插件开发者带来了诸多的限制。</li>
</ul>
</li>
<li>部分设定边界模糊
<ul dir="auto">
<li>类似于HTML5中的语义化标签,Chrome插件中的许多传统设定也逐渐模糊或者意义较小了</li>
<li>如Popup弹窗、 Options 选项页面,NewTab 新标签页等等,都不必再拘泥于传统的实现方式</li>
</ul>
</li>
</ul>
</li>
<li>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">理想中的框架&平台</h2><a id="user-content-理想中的框架平台" class="anchor" aria-label="Permalink: 理想中的框架&平台" href="#理想中的框架平台"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>开箱即用的开发者体验</li>
<li>约定式的规则</li>
<li>抽象、开放的API</li>
<li>CI、CD支持</li>
<li>国内环境版本更新支持</li>
<li>模块化隔离
<ul dir="auto">
<li>复杂插件不会仅用于一个网站,或一个业务之上。</li>
<li>需要有开箱即用的模块化支持</li>
<li>模块化细节:
<ul dir="auto">
<li>整个模块的启用、禁用</li>
<li>模块内各项功能的启用、禁用</li>
<li>模块文档</li>
<li>各模块、各功能页面提示</li>
<li>自动生成的选项页面</li>
<li>模块内约定式路由
<ul dir="auto">
<li>快速搜索</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">附图</h2><a id="user-content-附图" class="anchor" aria-label="Permalink: 附图" href="#附图"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/2416493/281028503-80fb713d-6edf-4698-88d9-76834b04478c.png"><img src="https://user-images.githubusercontent.com/2416493/281028503-80fb713d-6edf-4698-88d9-76834b04478c.png" alt="image" style="max-width: 100%;"></a></p>
</article>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/0735ede0b9236751b4dea4c733fc7b91
2023-04-12T01:43:33Z
2023-04-12T02:24:42Z
Generate git commit sha for every line of code in given file
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/0735ede0b9236751b4dea4c733fc7b91#file-git-blame-all">git-blame-all</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-git-blame-all" class="file my-2">
<div itemprop="text"
class="Box-body p-0 blob-wrapper data type-shell "
style="overflow: auto" tabindex="0" role="region"
aria-label="git-blame-all content, created by banyudu on 01:43AM on April 12, 2023."
>
<div class="js-check-hidden-unicode js-blob-code-container blob-code-content">
<template class="js-file-alert-template">
<div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
<span>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
<a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
</span>
<div data-view-component="true" class="flash-action"> <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn"> Show hidden characters
</a>
</div>
</div></template>
<template class="js-line-alert-template">
<span aria-label="This line has hidden Unicode characters" data-view-component="true" class="line-alert tooltipped tooltipped-e">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
</span></template>
<table data-hpc class="highlight tab-size js-file-line-container" data-tab-size="8" data-paste-markdown-skip data-tagsearch-path="git-blame-all">
<tr>
<td id="file-git-blame-all-L1" class="blob-num js-line-number js-blob-rnum" data-line-number="1"></td>
<td id="file-git-blame-all-LC1" class="blob-code blob-code-inner js-file-line"><span class="pl-c"><span class="pl-c">#!</span>/bin/bash</span></td>
</tr>
<tr>
<td id="file-git-blame-all-L2" class="blob-num js-line-number js-blob-rnum" data-line-number="2"></td>
<td id="file-git-blame-all-LC2" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-git-blame-all-L3" class="blob-num js-line-number js-blob-rnum" data-line-number="3"></td>
<td id="file-git-blame-all-LC3" class="blob-code blob-code-inner js-file-line"><span class="pl-c"><span class="pl-c">#</span> Check if a filename was provided as an argument</span></td>
</tr>
<tr>
<td id="file-git-blame-all-L4" class="blob-num js-line-number js-blob-rnum" data-line-number="4"></td>
<td id="file-git-blame-all-LC4" class="blob-code blob-code-inner js-file-line"><span class="pl-k">if</span> [ <span class="pl-k">-z</span> <span class="pl-s"><span class="pl-pds">"</span><span class="pl-smi">$1</span><span class="pl-pds">"</span></span> ]<span class="pl-k">;</span> <span class="pl-k">then</span></td>
</tr>
<tr>
<td id="file-git-blame-all-L5" class="blob-num js-line-number js-blob-rnum" data-line-number="5"></td>
<td id="file-git-blame-all-LC5" class="blob-code blob-code-inner js-file-line"> <span class="pl-c1">echo</span> <span class="pl-s"><span class="pl-pds">"</span>Usage: git blame-all filename<span class="pl-pds">"</span></span></td>
</tr>
<tr>
<td id="file-git-blame-all-L6" class="blob-num js-line-number js-blob-rnum" data-line-number="6"></td>
<td id="file-git-blame-all-LC6" class="blob-code blob-code-inner js-file-line"> <span class="pl-c1">exit</span> 1</td>
</tr>
<tr>
<td id="file-git-blame-all-L7" class="blob-num js-line-number js-blob-rnum" data-line-number="7"></td>
<td id="file-git-blame-all-LC7" class="blob-code blob-code-inner js-file-line"><span class="pl-k">fi</span></td>
</tr>
<tr>
<td id="file-git-blame-all-L8" class="blob-num js-line-number js-blob-rnum" data-line-number="8"></td>
<td id="file-git-blame-all-LC8" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-git-blame-all-L9" class="blob-num js-line-number js-blob-rnum" data-line-number="9"></td>
<td id="file-git-blame-all-LC9" class="blob-code blob-code-inner js-file-line"><span class="pl-c"><span class="pl-c">#</span> Loop through each line of the given file and run git blame</span></td>
</tr>
<tr>
<td id="file-git-blame-all-L10" class="blob-num js-line-number js-blob-rnum" data-line-number="10"></td>
<td id="file-git-blame-all-LC10" class="blob-code blob-code-inner js-file-line">line_number=1</td>
</tr>
<tr>
<td id="file-git-blame-all-L11" class="blob-num js-line-number js-blob-rnum" data-line-number="11"></td>
<td id="file-git-blame-all-LC11" class="blob-code blob-code-inner js-file-line"><span class="pl-k">while</span> <span class="pl-c1">read</span> line<span class="pl-k">;</span> <span class="pl-k">do</span></td>
</tr>
<tr>
<td id="file-git-blame-all-L12" class="blob-num js-line-number js-blob-rnum" data-line-number="12"></td>
<td id="file-git-blame-all-LC12" class="blob-code blob-code-inner js-file-line"> commit_sha=<span class="pl-s"><span class="pl-pds">$(</span>git blame -L <span class="pl-smi">$line_number</span>,<span class="pl-smi">$line_number</span> <span class="pl-s"><span class="pl-pds">"</span><span class="pl-smi">$1</span><span class="pl-pds">"</span></span> <span class="pl-k">|</span> cut -c -8<span class="pl-pds">)</span></span></td>
</tr>
<tr>
<td id="file-git-blame-all-L13" class="blob-num js-line-number js-blob-rnum" data-line-number="13"></td>
<td id="file-git-blame-all-LC13" class="blob-code blob-code-inner js-file-line"> <span class="pl-c"><span class="pl-c">#</span>echo "$line_number: $commit_sha"</span></td>
</tr>
<tr>
<td id="file-git-blame-all-L14" class="blob-num js-line-number js-blob-rnum" data-line-number="14"></td>
<td id="file-git-blame-all-LC14" class="blob-code blob-code-inner js-file-line"> <span class="pl-c1">echo</span> <span class="pl-s"><span class="pl-pds">"</span><span class="pl-smi">$commit_sha</span><span class="pl-pds">"</span></span> <span class="pl-c1">:</span> <span class="pl-s"><span class="pl-pds">"</span><span class="pl-smi">$line</span><span class="pl-pds">"</span></span></td>
</tr>
<tr>
<td id="file-git-blame-all-L15" class="blob-num js-line-number js-blob-rnum" data-line-number="15"></td>
<td id="file-git-blame-all-LC15" class="blob-code blob-code-inner js-file-line"> line_number=<span class="pl-s"><span class="pl-pds">$((</span>line_number<span class="pl-k">+</span><span class="pl-c1">1</span><span class="pl-pds">))</span></span></td>
</tr>
<tr>
<td id="file-git-blame-all-L16" class="blob-num js-line-number js-blob-rnum" data-line-number="16"></td>
<td id="file-git-blame-all-LC16" class="blob-code blob-code-inner js-file-line"><span class="pl-k">done</span> <span class="pl-k"><</span> <span class="pl-s"><span class="pl-pds">"</span><span class="pl-smi">$1</span><span class="pl-pds">"</span></span></td>
</tr>
</table>
</div>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/e1ae76bd5eb4bda990b08b9261bc9f1e
2022-09-27T03:05:32Z
2025-05-30T03:58:50Z
.npmrc for China users
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/e1ae76bd5eb4bda990b08b9261bc9f1e#file-npmrc">.npmrc</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-npmrc" class="file my-2">
<div itemprop="text"
class="Box-body p-0 blob-wrapper data type-npm-config "
style="overflow: auto" tabindex="0" role="region"
aria-label=".npmrc content, created by banyudu on 03:05AM on September 27, 2022."
>
<div class="js-check-hidden-unicode js-blob-code-container blob-code-content">
<template class="js-file-alert-template">
<div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
<span>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
<a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
</span>
<div data-view-component="true" class="flash-action"> <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn"> Show hidden characters
</a>
</div>
</div></template>
<template class="js-line-alert-template">
<span aria-label="This line has hidden Unicode characters" data-view-component="true" class="line-alert tooltipped tooltipped-e">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
</span></template>
<table data-hpc class="highlight tab-size js-file-line-container" data-tab-size="8" data-paste-markdown-skip data-tagsearch-path=".npmrc">
<tr>
<td id="file-npmrc-L1" class="blob-num js-line-number js-blob-rnum" data-line-number="1"></td>
<td id="file-npmrc-LC1" class="blob-code blob-code-inner js-file-line"><span class="pl-v">sass_binary_site</span><span class="pl-k">=</span>https://cdn.npmmirror.com/binaries/node-sass</td>
</tr>
<tr>
<td id="file-npmrc-L2" class="blob-num js-line-number js-blob-rnum" data-line-number="2"></td>
<td id="file-npmrc-LC2" class="blob-code blob-code-inner js-file-line"><span class="pl-v">disturl</span><span class="pl-k">=</span>https://registry.npmmirror.com/dist</td>
</tr>
<tr>
<td id="file-npmrc-L3" class="blob-num js-line-number js-blob-rnum" data-line-number="3"></td>
<td id="file-npmrc-LC3" class="blob-code blob-code-inner js-file-line"><span class="pl-v">profiler_binary_host_mirror</span><span class="pl-k">=</span>https://cdn.npmmirror.com/binaries/node-inspector/</td>
</tr>
<tr>
<td id="file-npmrc-L4" class="blob-num js-line-number js-blob-rnum" data-line-number="4"></td>
<td id="file-npmrc-LC4" class="blob-code blob-code-inner js-file-line"><span class="pl-v">fse_binary_host_mirror</span><span class="pl-k">=</span>https://cdn.npmmirror.com/binaries/fsevents/</td>
</tr>
<tr>
<td id="file-npmrc-L5" class="blob-num js-line-number js-blob-rnum" data-line-number="5"></td>
<td id="file-npmrc-LC5" class="blob-code blob-code-inner js-file-line"><span class="pl-v">phantomjs_cdnurl</span><span class="pl-k">=</span>https://cdn.npmmirror.com/binaries/phantomjs/</td>
</tr>
<tr>
<td id="file-npmrc-L6" class="blob-num js-line-number js-blob-rnum" data-line-number="6"></td>
<td id="file-npmrc-LC6" class="blob-code blob-code-inner js-file-line"><span class="pl-v">electron_mirror</span><span class="pl-k">=</span>https://cdn.npmmirror.com/binaries/electron/</td>
</tr>
<tr>
<td id="file-npmrc-L7" class="blob-num js-line-number js-blob-rnum" data-line-number="7"></td>
<td id="file-npmrc-LC7" class="blob-code blob-code-inner js-file-line"><span class="pl-v">chromedriver_cdnurl</span><span class="pl-k">=</span>https://cdn.npmmirror.com/binaries/chromedriver</td>
</tr>
<tr>
<td id="file-npmrc-L8" class="blob-num js-line-number js-blob-rnum" data-line-number="8"></td>
<td id="file-npmrc-LC8" class="blob-code blob-code-inner js-file-line"><span class="pl-v">operadriver_cdnurl</span><span class="pl-k">=</span>https://cdn.npmmirror.com/binaries/operadriver</td>
</tr>
<tr>
<td id="file-npmrc-L9" class="blob-num js-line-number js-blob-rnum" data-line-number="9"></td>
<td id="file-npmrc-LC9" class="blob-code blob-code-inner js-file-line"><span class="pl-v">selenium_cdnurl</span><span class="pl-k">=</span>https://cdn.npmmirror.com/binaries/selenium</td>
</tr>
<tr>
<td id="file-npmrc-L10" class="blob-num js-line-number js-blob-rnum" data-line-number="10"></td>
<td id="file-npmrc-LC10" class="blob-code blob-code-inner js-file-line"><span class="pl-v">node_inspector_cdnurl</span><span class="pl-k">=</span>https://cdn.npmmirror.com/binaries/node-inspector</td>
</tr>
<tr>
<td id="file-npmrc-L11" class="blob-num js-line-number js-blob-rnum" data-line-number="11"></td>
<td id="file-npmrc-LC11" class="blob-code blob-code-inner js-file-line"><span class="pl-v">fsevents_binary_host_mirror</span><span class="pl-k">=</span>https://registry.npmmirror.com/mirrors/fsevents/</td>
</tr>
<tr>
<td id="file-npmrc-L12" class="blob-num js-line-number js-blob-rnum" data-line-number="12"></td>
<td id="file-npmrc-LC12" class="blob-code blob-code-inner js-file-line"><span class="pl-v">puppeteer_download_host</span><span class="pl-k">=</span>https://registry.npmmirror.com/mirrors</td>
</tr>
<tr>
<td id="file-npmrc-L13" class="blob-num js-line-number js-blob-rnum" data-line-number="13"></td>
<td id="file-npmrc-LC13" class="blob-code blob-code-inner js-file-line"><span class="pl-v">sass_binary_site</span><span class="pl-k">=</span>https://cdn.npmmirror.com/binaries/node-sass/</td>
</tr>
<tr>
<td id="file-npmrc-L14" class="blob-num js-line-number js-blob-rnum" data-line-number="14"></td>
<td id="file-npmrc-LC14" class="blob-code blob-code-inner js-file-line"><span class="pl-v">canvas_binary_host_mirror</span><span class="pl-k">=</span>https://cdn.npmmirror.com/binaries/canvas/</td>
</tr>
<tr>
<td id="file-npmrc-L15" class="blob-num js-line-number js-blob-rnum" data-line-number="15"></td>
<td id="file-npmrc-LC15" class="blob-code blob-code-inner js-file-line"><span class="pl-v">sentrycli_cdnurl</span><span class="pl-k">=</span>https://cdn.npmmirror.com/binaries/sentry-cli</td>
</tr>
<tr>
<td id="file-npmrc-L16" class="blob-num js-line-number js-blob-rnum" data-line-number="16"></td>
<td id="file-npmrc-LC16" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-npmrc-L17" class="blob-num js-line-number js-blob-rnum" data-line-number="17"></td>
<td id="file-npmrc-LC17" class="blob-code blob-code-inner js-file-line"><span class="pl-v">registry</span><span class="pl-k">=</span><span class="pl-corl">https://registry.npmmirror.com/</span></td>
</tr>
</table>
</div>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/eab0f5bd40d968a9c7bb4446e1290e9a
2022-06-11T10:06:16Z
2022-06-11T10:06:16Z
useTranslation based on ProjectBundle
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/eab0f5bd40d968a9c7bb4446e1290e9a#file-usetranslation-ts">useTranslation.ts</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-usetranslation-ts" class="file my-2">
<div itemprop="text"
class="Box-body p-0 blob-wrapper data type-typescript "
style="overflow: auto" tabindex="0" role="region"
aria-label="useTranslation.ts content, created by banyudu on 10:06AM on June 11, 2022."
>
<div class="js-check-hidden-unicode js-blob-code-container blob-code-content">
<template class="js-file-alert-template">
<div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
<span>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
<a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
</span>
<div data-view-component="true" class="flash-action"> <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn"> Show hidden characters
</a>
</div>
</div></template>
<template class="js-line-alert-template">
<span aria-label="This line has hidden Unicode characters" data-view-component="true" class="line-alert tooltipped tooltipped-e">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
</span></template>
<table data-hpc class="highlight tab-size js-file-line-container" data-tab-size="8" data-paste-markdown-skip data-tagsearch-path="useTranslation.ts">
<tr>
<td id="file-usetranslation-ts-L1" class="blob-num js-line-number js-blob-rnum" data-line-number="1"></td>
<td id="file-usetranslation-ts-LC1" class="blob-code blob-code-inner js-file-line"><span class=pl-k>import</span> <span class=pl-kos>{</span> <span class=pl-s1>useCallback</span><span class=pl-kos>,</span> <span class=pl-s1>useEffect</span><span class=pl-kos>,</span> <span class=pl-s1>useState</span> <span class=pl-kos>}</span> <span class=pl-k>from</span> <span class=pl-s>'react'</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L2" class="blob-num js-line-number js-blob-rnum" data-line-number="2"></td>
<td id="file-usetranslation-ts-LC2" class="blob-code blob-code-inner js-file-line"><span class=pl-k>import</span> <span class=pl-kos>{</span> <span class=pl-v>FluentBundle</span><span class=pl-kos>,</span> <span class=pl-v>FluentResource</span> <span class=pl-kos>}</span> <span class=pl-k>from</span> <span class=pl-s>'@fluent/bundle'</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L3" class="blob-num js-line-number js-blob-rnum" data-line-number="3"></td>
<td id="file-usetranslation-ts-LC3" class="blob-code blob-code-inner js-file-line"><span class=pl-k>import</span> <span class=pl-kos>{</span> <span class=pl-s1>negotiateLanguages</span> <span class=pl-kos>}</span> <span class=pl-k>from</span> <span class=pl-s>"@fluent/langneg"</span><span class=pl-kos>;</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L4" class="blob-num js-line-number js-blob-rnum" data-line-number="4"></td>
<td id="file-usetranslation-ts-LC4" class="blob-code blob-code-inner js-file-line"><span class=pl-k>import</span> <span class=pl-s1>axios</span> <span class=pl-k>from</span> <span class=pl-s>'axios'</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L5" class="blob-num js-line-number js-blob-rnum" data-line-number="5"></td>
<td id="file-usetranslation-ts-LC5" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-usetranslation-ts-L6" class="blob-num js-line-number js-blob-rnum" data-line-number="6"></td>
<td id="file-usetranslation-ts-LC6" class="blob-code blob-code-inner js-file-line"><span class=pl-k>const</span> <span class=pl-c1>LOCALES_ALL</span> <span class=pl-c1>=</span> <span class=pl-kos>[</span><span class=pl-s>'zh-CN'</span><span class=pl-kos>,</span> <span class=pl-s>'en-US'</span><span class=pl-kos>]</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L7" class="blob-num js-line-number js-blob-rnum" data-line-number="7"></td>
<td id="file-usetranslation-ts-LC7" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-usetranslation-ts-L8" class="blob-num js-line-number js-blob-rnum" data-line-number="8"></td>
<td id="file-usetranslation-ts-LC8" class="blob-code blob-code-inner js-file-line"><span class=pl-k>async</span> <span class=pl-k>function</span> <span class=pl-en>getBundle</span> <span class=pl-kos>(</span><span class=pl-s1>locale</span>: <span class=pl-smi>string</span><span class=pl-kos>)</span>: <span class=pl-smi>Promise</span><span class=pl-c1><</span><span class=pl-smi>FluentBundle</span><span class=pl-c1>></span> <span class=pl-kos>{</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L9" class="blob-num js-line-number js-blob-rnum" data-line-number="9"></td>
<td id="file-usetranslation-ts-LC9" class="blob-code blob-code-inner js-file-line"> <span class=pl-k>const</span> <span class=pl-s1>url</span> <span class=pl-c1>=</span> <span class=pl-s>`/locale/<span class=pl-s1><span class=pl-kos>${</span><span class=pl-s1>locale</span><span class=pl-kos>}</span></span>.ftl`</span> <span class=pl-c>// fluent bundle file location</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L10" class="blob-num js-line-number js-blob-rnum" data-line-number="10"></td>
<td id="file-usetranslation-ts-LC10" class="blob-code blob-code-inner js-file-line"> <span class=pl-k>const</span> <span class=pl-s1>response</span> <span class=pl-c1>=</span> <span class=pl-k>await</span> <span class=pl-s1>axios</span><span class=pl-kos>.</span><span class=pl-en>get</span><span class=pl-kos>(</span><span class=pl-s1>url</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L11" class="blob-num js-line-number js-blob-rnum" data-line-number="11"></td>
<td id="file-usetranslation-ts-LC11" class="blob-code blob-code-inner js-file-line"> <span class=pl-k>const</span> <span class=pl-s1>bundle</span> <span class=pl-c1>=</span> <span class=pl-k>new</span> <span class=pl-v>FluentBundle</span><span class=pl-kos>(</span><span class=pl-s1>locale</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L12" class="blob-num js-line-number js-blob-rnum" data-line-number="12"></td>
<td id="file-usetranslation-ts-LC12" class="blob-code blob-code-inner js-file-line"> <span class=pl-s1>bundle</span><span class=pl-kos>.</span><span class=pl-en>addResource</span><span class=pl-kos>(</span><span class=pl-k>new</span> <span class=pl-v>FluentResource</span><span class=pl-kos>(</span><span class=pl-s1>response</span><span class=pl-kos>.</span><span class=pl-c1>data</span><span class=pl-kos>)</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L13" class="blob-num js-line-number js-blob-rnum" data-line-number="13"></td>
<td id="file-usetranslation-ts-LC13" class="blob-code blob-code-inner js-file-line"> <span class=pl-k>return</span> <span class=pl-s1>bundle</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L14" class="blob-num js-line-number js-blob-rnum" data-line-number="14"></td>
<td id="file-usetranslation-ts-LC14" class="blob-code blob-code-inner js-file-line"><span class=pl-kos>}</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L15" class="blob-num js-line-number js-blob-rnum" data-line-number="15"></td>
<td id="file-usetranslation-ts-LC15" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-usetranslation-ts-L16" class="blob-num js-line-number js-blob-rnum" data-line-number="16"></td>
<td id="file-usetranslation-ts-LC16" class="blob-code blob-code-inner js-file-line"><span class=pl-c>/**</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L17" class="blob-num js-line-number js-blob-rnum" data-line-number="17"></td>
<td id="file-usetranslation-ts-LC17" class="blob-code blob-code-inner js-file-line"><span class=pl-c> * useTranslation Hooks</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L18" class="blob-num js-line-number js-blob-rnum" data-line-number="18"></td>
<td id="file-usetranslation-ts-LC18" class="blob-code blob-code-inner js-file-line"><span class=pl-c> * <span class=pl-k>@example</span></span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L19" class="blob-num js-line-number js-blob-rnum" data-line-number="19"></td>
<td id="file-usetranslation-ts-LC19" class="blob-code blob-code-inner js-file-line"><span class=pl-c> * const { t } = useTranslation()</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L20" class="blob-num js-line-number js-blob-rnum" data-line-number="20"></td>
<td id="file-usetranslation-ts-LC20" class="blob-code blob-code-inner js-file-line"><span class=pl-c> * t('hello', 'Hello World!')</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L21" class="blob-num js-line-number js-blob-rnum" data-line-number="21"></td>
<td id="file-usetranslation-ts-LC21" class="blob-code blob-code-inner js-file-line"><span class=pl-c> * <span class=pl-k>@returns</span></span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L22" class="blob-num js-line-number js-blob-rnum" data-line-number="22"></td>
<td id="file-usetranslation-ts-LC22" class="blob-code blob-code-inner js-file-line"><span class=pl-c> */</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L23" class="blob-num js-line-number js-blob-rnum" data-line-number="23"></td>
<td id="file-usetranslation-ts-LC23" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-usetranslation-ts-L24" class="blob-num js-line-number js-blob-rnum" data-line-number="24"></td>
<td id="file-usetranslation-ts-LC24" class="blob-code blob-code-inner js-file-line"><span class=pl-k>export</span> <span class=pl-k>default</span> <span class=pl-k>function</span> <span class=pl-en>useTranslation</span><span class=pl-kos>(</span><span class=pl-kos>)</span> <span class=pl-kos>{</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L25" class="blob-num js-line-number js-blob-rnum" data-line-number="25"></td>
<td id="file-usetranslation-ts-LC25" class="blob-code blob-code-inner js-file-line"> <span class=pl-k>const</span> <span class=pl-kos>[</span><span class=pl-s1>bundle</span><span class=pl-kos>,</span> <span class=pl-s1>setBundle</span><span class=pl-kos>]</span> <span class=pl-c1>=</span> <span class=pl-en>useState</span><span class=pl-c1><</span><span class=pl-smi>FluentBundle</span> <span class=pl-c1>|</span> <span class=pl-c1>null</span><span class=pl-c1>></span><span class=pl-kos>(</span><span class=pl-c1>null</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L26" class="blob-num js-line-number js-blob-rnum" data-line-number="26"></td>
<td id="file-usetranslation-ts-LC26" class="blob-code blob-code-inner js-file-line"> <span class=pl-k>const</span> <span class=pl-s1>userLocales</span> <span class=pl-c1>=</span> <span class=pl-k>typeof</span> <span class=pl-s1>navigator</span> <span class=pl-c1>===</span> <span class=pl-s>'undefined'</span> ? <span class=pl-kos>[</span><span class=pl-kos>]</span> : <span class=pl-s1>navigator</span><span class=pl-kos>.</span><span class=pl-c1>languages</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L27" class="blob-num js-line-number js-blob-rnum" data-line-number="27"></td>
<td id="file-usetranslation-ts-LC27" class="blob-code blob-code-inner js-file-line"> <span class=pl-k>const</span> <span class=pl-s1>supportedLocales</span> <span class=pl-c1>=</span> <span class=pl-en>negotiateLanguages</span><span class=pl-kos>(</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L28" class="blob-num js-line-number js-blob-rnum" data-line-number="28"></td>
<td id="file-usetranslation-ts-LC28" class="blob-code blob-code-inner js-file-line"> <span class=pl-s1>userLocales</span><span class=pl-kos>,</span> <span class=pl-c>// requested locales</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L29" class="blob-num js-line-number js-blob-rnum" data-line-number="29"></td>
<td id="file-usetranslation-ts-LC29" class="blob-code blob-code-inner js-file-line"> <span class=pl-c1>LOCALES_ALL</span><span class=pl-kos>,</span> <span class=pl-c>// available locales</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L30" class="blob-num js-line-number js-blob-rnum" data-line-number="30"></td>
<td id="file-usetranslation-ts-LC30" class="blob-code blob-code-inner js-file-line"> <span class=pl-kos>{</span> <span class=pl-c1>defaultLocale</span>: <span class=pl-s>"en-US"</span> <span class=pl-kos>}</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L31" class="blob-num js-line-number js-blob-rnum" data-line-number="31"></td>
<td id="file-usetranslation-ts-LC31" class="blob-code blob-code-inner js-file-line"> <span class=pl-kos>)</span><span class=pl-kos>;</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L32" class="blob-num js-line-number js-blob-rnum" data-line-number="32"></td>
<td id="file-usetranslation-ts-LC32" class="blob-code blob-code-inner js-file-line"> <span class=pl-k>const</span> <span class=pl-s1>locale</span> <span class=pl-c1>=</span> <span class=pl-s1>supportedLocales</span><span class=pl-kos>[</span><span class=pl-c1>0</span><span class=pl-kos>]</span> <span class=pl-c1>??</span> <span class=pl-s>"en-US"</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L33" class="blob-num js-line-number js-blob-rnum" data-line-number="33"></td>
<td id="file-usetranslation-ts-LC33" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-usetranslation-ts-L34" class="blob-num js-line-number js-blob-rnum" data-line-number="34"></td>
<td id="file-usetranslation-ts-LC34" class="blob-code blob-code-inner js-file-line"> <span class=pl-en>useEffect</span><span class=pl-kos>(</span><span class=pl-kos>(</span><span class=pl-kos>)</span> <span class=pl-c1>=></span> <span class=pl-kos>{</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L35" class="blob-num js-line-number js-blob-rnum" data-line-number="35"></td>
<td id="file-usetranslation-ts-LC35" class="blob-code blob-code-inner js-file-line"> <span class=pl-en>getBundle</span><span class=pl-kos>(</span><span class=pl-s1>locale</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L36" class="blob-num js-line-number js-blob-rnum" data-line-number="36"></td>
<td id="file-usetranslation-ts-LC36" class="blob-code blob-code-inner js-file-line"> <span class=pl-kos>.</span><span class=pl-en>then</span><span class=pl-kos>(</span><span class=pl-s1>bundle</span> <span class=pl-c1>=></span> <span class=pl-en>setBundle</span><span class=pl-kos>(</span><span class=pl-s1>bundle</span><span class=pl-kos>)</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L37" class="blob-num js-line-number js-blob-rnum" data-line-number="37"></td>
<td id="file-usetranslation-ts-LC37" class="blob-code blob-code-inner js-file-line"> <span class=pl-kos>.</span><span class=pl-en>catch</span><span class=pl-kos>(</span><span class=pl-smi>console</span><span class=pl-kos>.</span><span class=pl-c1>error</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L38" class="blob-num js-line-number js-blob-rnum" data-line-number="38"></td>
<td id="file-usetranslation-ts-LC38" class="blob-code blob-code-inner js-file-line"> <span class=pl-kos>}</span><span class=pl-kos>,</span><span class=pl-kos>[</span> <span class=pl-s1>locale</span> <span class=pl-kos>]</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L39" class="blob-num js-line-number js-blob-rnum" data-line-number="39"></td>
<td id="file-usetranslation-ts-LC39" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-usetranslation-ts-L40" class="blob-num js-line-number js-blob-rnum" data-line-number="40"></td>
<td id="file-usetranslation-ts-LC40" class="blob-code blob-code-inner js-file-line"> <span class=pl-k>const</span> <span class=pl-s1>translate</span> <span class=pl-c1>=</span> <span class=pl-en>useCallback</span><span class=pl-kos>(</span><span class=pl-kos>(</span><span class=pl-s1>id</span>: <span class=pl-smi>string</span><span class=pl-kos>,</span> <span class=pl-s1>fallback</span>?: <span class=pl-smi>string</span><span class=pl-kos>)</span> <span class=pl-c1>=></span> <span class=pl-kos>{</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L41" class="blob-num js-line-number js-blob-rnum" data-line-number="41"></td>
<td id="file-usetranslation-ts-LC41" class="blob-code blob-code-inner js-file-line"> <span class=pl-k>return</span> <span class=pl-s1>bundle</span><span class=pl-kos>?.</span><span class=pl-en>getMessage</span><span class=pl-kos>(</span><span class=pl-s1>id</span><span class=pl-kos>)</span><span class=pl-kos>?.</span><span class=pl-c1>value</span> <span class=pl-k>as</span> <span class=pl-smi>string</span> <span class=pl-c1>??</span> <span class=pl-s1>fallback</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L42" class="blob-num js-line-number js-blob-rnum" data-line-number="42"></td>
<td id="file-usetranslation-ts-LC42" class="blob-code blob-code-inner js-file-line"> <span class=pl-kos>}</span><span class=pl-kos>,</span> <span class=pl-kos>[</span><span class=pl-s1>bundle</span><span class=pl-kos>]</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L43" class="blob-num js-line-number js-blob-rnum" data-line-number="43"></td>
<td id="file-usetranslation-ts-LC43" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-usetranslation-ts-L44" class="blob-num js-line-number js-blob-rnum" data-line-number="44"></td>
<td id="file-usetranslation-ts-LC44" class="blob-code blob-code-inner js-file-line"> <span class=pl-k>return</span> <span class=pl-kos>{</span> <span class=pl-c1>t</span>: <span class=pl-s1>translate</span><span class=pl-kos>,</span> locale <span class=pl-kos>}</span></td>
</tr>
<tr>
<td id="file-usetranslation-ts-L45" class="blob-num js-line-number js-blob-rnum" data-line-number="45"></td>
<td id="file-usetranslation-ts-LC45" class="blob-code blob-code-inner js-file-line"><span class=pl-kos>}</span></td>
</tr>
</table>
</div>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/b6e08bb0d9890db848f6e83cb83e0c79
2022-03-06T06:18:47Z
2024-11-07T02:36:33Z
从React骂战看技术的政治性
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/b6e08bb0d9890db848f6e83cb83e0c79#file-react-spam-issue-to-the-politics-of-technology-blog-md">react-spam-issue-to-the-politics-of-technology.blog.md</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-react-spam-issue-to-the-politics-of-technology-blog-md" class="file my-2">
<div id="file-react-spam-issue-to-the-politics-of-technology-blog-md-readme" class="Box-body readme blob p-5 p-xl-6 "
style="overflow: auto" tabindex="0" role="region"
aria-label="react-spam-issue-to-the-politics-of-technology.blog.md content, created by banyudu on 06:18AM on March 06, 2022."
>
<article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">从React骂战看技术的政治性</h1><a id="user-content-从react骂战看技术的政治性" class="anchor" aria-label="Permalink: 从React骂战看技术的政治性" href="#从react骂战看技术的政治性"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/273617ba0800cac3001b37ae01354679a563d8deb05e783f7e8c4ecb7efd2773/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303232303330363134313931303634322e706e67"><img src="https://camo.githubusercontent.com/273617ba0800cac3001b37ae01354679a563d8deb05e783f7e8c4ecb7efd2773/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303232303330363134313931303634322e706e67" alt="wheat field" data-canonical-src="https://banyudu.github.io/images/image-20220306141910642.png" style="max-width: 100%;"></a></p>
<p dir="auto">3月3日晚,我在 Twitter 上注意到有一些关于 React 的 Github 仓库出现 issue 骂战的消息,同时一些微信群中也开始转发这个消息。</p>
<p dir="auto">一些看起来是中国开发者的 Github 账号,在 React 的官方仓库狂刷垃圾 issue。</p>
<p dir="auto">事情已经过去两三天,这些 issue 早已被删除,但事发当晚的一些截图还在:</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/37c3983a765c2f1aa9352f101a08038f5a43a618afbac38ae852f83c5effba90/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303232303330353134343431333731342e706e67"><img src="https://camo.githubusercontent.com/37c3983a765c2f1aa9352f101a08038f5a43a618afbac38ae852f83c5effba90/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303232303330353134343431333731342e706e67" alt="image-20220305144413714" data-canonical-src="https://banyudu.github.io/images/image-20220305144413714.png" style="max-width: 100%;"></a></p>
<blockquote>
<p dir="auto">图源 <a href="https://twitter.com/lidangzzz/status/1499327169306275844?s=21" rel="nofollow">https://twitter.com/lidangzzz/status/1499327169306275844?s=21</a></p>
</blockquote>
<p dir="auto">我很反感这种行为,在我看来这相当于在人家门口拉屎,除了能阿Q精神胜利一下之外,几乎没什么可见的好处,反倒是会让臭名远扬。</p>
<p dir="auto">但是这也并不代表我赞同 React 官方团队的做法,在我看来技术社区就专门做技术,挂这种政治言论容易引起一部分社区成员的不适。</p>
<p dir="auto">但是后来我深入了解了一下,发现我的这个想法过于单纯了,<strong>技术与政治挂勾的事情其实有很多</strong>,事实上,之前也有过一些关于技术是否应当涉及政治的讨论。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">技术与政治</h2><a id="user-content-技术与政治" class="anchor" aria-label="Permalink: 技术与政治" href="#技术与政治"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">技术无国界</h3><a id="user-content-技术无国界" class="anchor" aria-label="Permalink: 技术无国界" href="#技术无国界"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">我很难回想起什么时候有的“技术无国界”的想法,最早可能是源于小学时读到的华罗庚的“科学无国界,但科学家有国界”的说法(最早是法国科学家巴斯德拒绝接受发动对法战争的德国所颁发的一个奖时说的)。</p>
<p dir="auto">在我的整个求学和工作过程中,“技术无国界”一直有很好的印证,即开源运动的蓬勃发展。</p>
<p dir="auto">在我刚上大学时,GNU/Linux 操作系统及周边社区对我产生了很大的影响,我深深地为开源运动着迷,在同学之中鼓吹自由软件,还学王垠的一篇博文<a href="https://www.douban.com/group/topic/12121637/?_i=6465162WZSOBqo" rel="nofollow">王垠:完全用Linux工作</a>中提到的那样完全使用 Linux。现在回想起来,也是很中二的了。</p>
<blockquote>
<p dir="auto">当然王垠现在的名声不太好了,且他自己也发文解释过这个事<a href="https://www.yinwang.org/blog-cn/2013/03/07/linux-windows-mac" rel="nofollow">谈 Linux,Windows 和 Mac</a>。</p>
</blockquote>
<p dir="auto">工作之后,一直从事业务系统的开发工作。所见所用的高质量软件,许多都是开源社区的结晶。</p>
<p dir="auto">人们相信开源软件的质量,也以能给开源项目贡献代码为荣。</p>
<p dir="auto">与之相对的是,一些“技术有国界”的做法,如“国产操作系统”、“国产芯片(汉芯)”、‘国产编程语言(如易语言)“等,往往给人一些落后和愚昧的印象。尤其是在汉芯骗局及各种国产操作系统套壳骗补之后,大家对国产的技术或多或少有一些提防。</p>
<p dir="auto">此消彼长,技术无国界的想法伴随着开源的成功和国产的失败逐渐成长起来。</p>
<p dir="auto">”技术无国界“的基础,加上国内严苛的审查制度,催生了另一种思想:”不谈政治“。这种论调常见于各种技术群、论坛之中,我也是它的拥趸。</p>
<p dir="auto">但是无国界并不意味着无政治,政治其实是无处不在的。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">政治无处不在</h3><a id="user-content-政治无处不在" class="anchor" aria-label="Permalink: 政治无处不在" href="#政治无处不在"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">政治并非只在国与国之间,日常民生中就有很多的政治因素。</p>
<p dir="auto">举例来说,国外的 ”Black Live Matter“ 运动、”Me Too“ 运动、国内的 ”丰县铁链女“ 事件等,都在社会上引起了极大的反响,其中也包括各大技术社区。</p>
<p dir="auto">以 "Black Live Matter" 为例,它当初在技术圈造成的政治影响不比这次乌克兰的小。很多大的技术社区,如 Rust、Node、React 等都在版本更新说明和官网等位置挂出了显眼的标语支持 "Black Live Matter"。</p>
<p dir="auto">于国内而言, ”丰县铁链女“ 事件也引起了广泛的关注和讨论,虽然没有见到哪个技术社区直接在官网挂标语,但是各种技术群中确实不少讨论的。</p>
<p dir="auto">关于技术要不要涉及政治,其实之前早已经有过一些讨论,大家可以看一下:</p>
<ul dir="auto">
<li><a href="https://www.cyut.edu.tw/~ckhung/b/sts/artifacts-politics.php" rel="nofollow">「技術物有政治性嗎?」 摘要</a></li>
<li><a href="https://users.rust-lang.org/t/rust-says-tech-will-always-be-political/43627" rel="nofollow"> Rust says tech will* always be political </a></li>
</ul>
<p dir="auto">这里面将技术为什么与政治有关,whataboutism 等都进行了一番讨论和说明。</p>
<p dir="auto">首先技术并不是无辜的,它会影响到政治。在我们讨论事件时,经常会听到”但是这样做太没效率了“之类的言论,它就是由技术影响到了政治的情况。</p>
<p dir="auto">很多人都看过《三体》,也了解”黑暗森林威慑“,为什么罗辑能成功执剑而程心不可以,甚至为什么会需要有”执剑人“这样一个独裁者,这就是 ”黑暗森林“ 理论及相应的威慑技术决定了政治体系的例子。</p>
<p dir="auto">既然政治无处不在,那是什么导致国内和国外主流社区对待政治事件的不同反应呢?</p>
<p dir="auto">我认为本质上都是一种趋利避害的行为,但是在不同的环境中结出了不同的果。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">政治正确</h3><a id="user-content-政治正确" class="anchor" aria-label="Permalink: 政治正确" href="#政治正确"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">按照维基百科的定义:</p>
<blockquote>
<p dir="auto"><strong>政治正确</strong>(英语:Political correctness),多指在言辞、行为、政策中出于顾忌<a href="https://zh.wikipedia.org/wiki/%E6%84%8F%E8%AF%86%E5%BD%A2%E6%80%81" rel="nofollow">意识形态</a>、<a href="https://zh.wikipedia.org/wiki/%E4%BB%B7%E5%80%BC%E8%A7%82" rel="nofollow">价值观</a>和<a href="https://zh.wikipedia.org/wiki/%E8%88%86%E8%AE%BA" rel="nofollow">舆论</a>压力而优先照顾某些观念的<a href="https://zh.wikipedia.org/wiki/%E8%87%AA%E6%88%91%E5%AE%A1%E6%9F%A5" rel="nofollow">自我审查</a>意识,通常表示过分避免对社会某些<a href="https://zh.wikipedia.org/wiki/%E7%BE%A4%E4%BD%93" rel="nofollow">群体</a>造成冒犯[<a href="https://zh.wikipedia.org/wiki/%E6%94%BF%E6%B2%BB%E6%AD%A3%E7%A2%BA#cite_note-tsyzm-1" rel="nofollow">1]</a>。也可以按其字面意义,指符合官方立场、社会主流价值观(这种多半发生在非<a href="https://zh.wikipedia.org/wiki/%E8%87%AA%E7%94%B1%E6%B0%91%E4%B8%BB%E5%88%B6" rel="nofollow">自由民主制</a>国家[<a href="https://zh.wikipedia.org/wiki/%E6%94%BF%E6%B2%BB%E6%AD%A3%E7%A2%BA#cite_note-udn-2" rel="nofollow">2]</a>、由当局强行要求的政治正确亦称为思想正确)。</p>
</blockquote>
<p dir="auto">在东西方,政治正确的具体表现不同。</p>
<p dir="auto">我们的”避而不谈“是一种政治正确,西方的声援 "Black Live Matter"、声援乌克兰等也是一种政治正确。</p>
<p dir="auto">因为如果他们不这么做,会给他们带来一些麻烦。</p>
<p dir="auto">如果不理解这个事的话,可以参考下这种言论:</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/46b08acb2861ea3630370545b3d1434a244e7e98d72c295b335cc51d94514002/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303232303330363038303534383834322e706e67"><img src="https://camo.githubusercontent.com/46b08acb2861ea3630370545b3d1434a244e7e98d72c295b335cc51d94514002/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303232303330363038303534383834322e706e67" alt="image-20220306080548842" data-canonical-src="https://banyudu.github.io/images/image-20220306080548842.png" style="max-width: 100%;"></a></p>
<p dir="auto">在”最安全的时候最勇敢“尚且被拉出来批斗,那”不谈政治“的人,相当于最安全的时候也不勇敢,又会被如何处置呢?</p>
<p dir="auto">当这些技术社区的核心开发者主要是欧美人的时候,会做出哪种选择自然不用说。</p>
<p dir="auto">有些时候,影响到社区或公司的甚至不是政治正确,而是直接的来自政府的压力,如美国制裁伊朗期间,Github封锁了很多伊朗人的私有仓库,就是受政策而非价值观的影响了,毕竟在美国的长臂管辖范围下,这些公司无法独善其身。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">淮南淮北</h3><a id="user-content-淮南淮北" class="anchor" aria-label="Permalink: 淮南淮北" href="#淮南淮北"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">就像人的童年经历会极大地影响成年后的性格,国家、民族的过往经历也会极大地影响到其意识形态和相应的”政治正确“。在语言不通、墙、信息茧房等作用之下,这些差异会越来越大,到一种互相不能理解的程度。</p>
<p dir="auto">举例来说,欧美很难理解中国的”百年耻辱“,而国内的人也往往也理解不了国外严厉的”反种族歧视“等政治正确。因为欧美(尤其是美国)没有经历过军阀割据、帝国主义瓜分领土、昔日大国被隔壁小国侵略将近灭国,大使馆被炸等等事件,所以很难理解”小粉红“们的逻辑和”战狼“文化,也就理解不了为什么国内一部分人支持俄国。虽然作为一种侵略战争,俄国对乌克兰的战争不具备正义性,乌克兰的难民更应该被同情。但是对北约东扩的担忧压过了这些想法。</p>
<p dir="auto">同时因为中国不是移民国家,没有经历过大规模的不同民族、人种混居,黑人广受歧视等历史事件,所以也很难切身体会到种族歧视给社会带来的巨大伤害,所以现在中国依然充斥着大量的歧视性言论。但是对于拐卖妇女儿童,国内是有很大的切身体会的,所以”丰县铁链女“事件才会发酵到今天这种程度。</p>
<hr>
<p dir="auto">综上,我认为技术和政治的挂勾是难以避免的,需要正视这个问题。”不谈政治“本身也是政治的一种。但是因为东西方文化差异,双方在是否表态以及表什么态上有重大的分歧。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">路在何方?</h2><a id="user-content-路在何方" class="anchor" aria-label="Permalink: 路在何方?" href="#路在何方"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">在经历了针对伊朗、阿富汗、俄罗斯等的制裁,和针对中国的贸易战之后,相信很多人也已经看到了美国长臂管辖的威力。这些事件一次又一次地冲击着我对国际化、全球化的信心。</p>
<p dir="auto">这些问题早就有人考虑过,且无论是宏观(如双循环)还是微观上(如鸿蒙)都有一些具体的举措出来。</p>
<p dir="auto">下面我结合自身了解到的知识,猜测下未来可能的发展方向。</p>
<p dir="auto"><strong>未来技术上也会有双循环,会出现以 Web 3.0 为基础的外循环和当前互联网逐渐崩坏后形成的各区域内循环</strong></p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">Web 3.0</h3><a id="user-content-web-30" class="anchor" aria-label="Permalink: Web 3.0" href="#web-30"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">构建在区块链之上,Web 3.0 现在带有很多的许诺和争议。</p>
<p dir="auto">构建在区块链上的 DAO (Decentralized autonomous organization,即分布式自治组织),在未来有可能成为一种匿名的”网络公国“,不同国家不同种族的人因为共同的价值观加入到各种不同的网络公国之中。</p>
<p dir="auto">这些网络公国不同于当前技术社区的点在于,它不依赖于某个具体的公司实体的运营,因而也就能绕过国家政权的管制。</p>
<p dir="auto">在这种前提之下,它不再会有 “实名制” 等迫于政府要求而带来的限制,也不会因为核心成员被捕裹挟其它成员,所有用户都可以随意创建和切换马甲,因而无论是发出政治呼吁,或是散播谣言,或是行骗,或是研发技术,任何行为都不受限制。</p>
<p dir="auto">Web 3.0上呼风唤雨的人,现实中可能是个不起眼的清洁工,因为他需要掩饰自己的真实身份。</p>
<p dir="auto">所以很容易地可以预料到,Web 3.0中会分裂出很多的团体,各个不同的团体之间也可能会彼此对战或联盟,形成“网络公国”。另外也很容易可以预料到,现实中的政府会对这种团体进行打压。</p>
<p dir="auto">这个方向可以看做是技术驱动下的政治变更,因为有了更难以追索的去中心化的技术,所以才可能会有上述的网络公国的可能。</p>
<p dir="auto">Web 3.0 作为一个更加自由的基石,会促成一个新的国际外循环。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">各区域内循环</h3><a id="user-content-各区域内循环" class="anchor" aria-label="Permalink: 各区域内循环" href="#各区域内循环"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">现有的基于各大Web服务商的互联网,则会因为愈来愈多的冲突,分裂成多个区域互联网。</p>
<p dir="auto">单纯从开源社区一个点来说,以后会出现越来越多的中文内容,即放弃目前通用的以英语作为通用语,努力融入全世界的做法,而转向到更好地团结国内的(英文水平不能流畅地表达)一部分人。</p>
<p dir="auto">这样做前期是会有较大的好处的,一方面前面已经有华为的鸿蒙等承担压力,虽然因为贩卖爱国主义情怀等各种嘲讽,但是无疑开创了一个先河。</p>
<p dir="auto">使用中文的好处在于,一方面能团结国内大多数的开发者,另一方面因为大多国内开发者可以阅读英文(虽然不一定流畅),而欧美地区开发者越鲜少能阅读英文。这样会形成一种吸血模式,国内的社区源源不断地吸取国际社区的知识,却因为语言的问题,使得国外的社区能得到的反馈较少。</p>
<p dir="auto">这种方式有短期的好处,但是从长期来看是会引起很大的反感的。可以看到类似 <code>antd</code> 这样的国内大开源项目,也在尽量保持英文模式。</p>
<p dir="auto">然而劣币驱逐良币,被吸血越来越多的国际开源社区,会因为不堪重负逐渐崩坏,而作为吸血方的区域互联网、区域社区则可以趁机崛起。</p>
<p dir="auto">综上,对于国内的小粉红程序员来说,与其去 React 的 Github 仓库搞一些垃圾 issue,不如更多地在 Github 等平台上输出中文内容(Github issue 讨论、Slack 群、Commit 消息、注释内容等),建一些基于中文协作的社区和开源项目。</p>
<p dir="auto">当然,有人很鄙视这种做法,认为类似 CSDN 这样的国内社区是恶臭养盅,出不来什么好项目。这个谁又说得好呢?CSDN固然恶臭,中文社区却未必不能诞生一些明星项目出来,类似掘金和简书这样的平台做得就还可以。</p>
<p dir="auto">当然以上仅是我一家之言,很粗糙的想法,欢迎讨论。</p>
<hr>
<blockquote>
<p dir="auto">引用资料:</p>
<ol dir="auto">
<li><a href="https://www.cyut.edu.tw/~ckhung/b/sts/artifacts-politics.php" rel="nofollow">「技術物有政治性嗎?」 摘要</a></li>
<li><a href="https://twitter.com/lidangzzz/status/1499327169306275844?s=21" rel="nofollow">https://twitter.com/lidangzzz/status/1499327169306275844?s=21</a></li>
<li>[<a href="https://users.rust-lang.org/t/rust-says-tech-will-always-be-political/43627" rel="nofollow">Rust says tech will* always be political</a>](<a href="https://users.rust-lang.org/t/rust-says-tech-will-always-be-political/43627" rel="nofollow">https://users.rust-lang.org/t/rust-says-tech-will-always-be-political/43627</a>)</li>
</ol>
</blockquote>
</article>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/32792f05c07cc7f32575f51198e510d4
2022-02-18T06:44:42Z
2023-09-21T03:04:20Z
现代化的CSS
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/32792f05c07cc7f32575f51198e510d4#file-modern-css-blog-md">modern-css.blog.md</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-modern-css-blog-md" class="file my-2">
<div id="file-modern-css-blog-md-readme" class="Box-body readme blob p-5 p-xl-6 "
style="overflow: auto" tabindex="0" role="region"
aria-label="modern-css.blog.md content, created by banyudu on 06:44AM on February 18, 2022."
>
<article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">现代化的 CSS</h1><a id="user-content-现代化的-css" class="anchor" aria-label="Permalink: 现代化的 CSS" href="#现代化的-css"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/a803e212c42faddffdc24e4f0982c08bdac3267c3d7791e8322bcf80db798f3b/68747470733a2f2f696d616765732e756e73706c6173682e636f6d2f70686f746f2d313531373133343139313131382d3964353935653463386332623f69786c69623d72622d312e322e3126697869643d4d6e77784d6a4133664442384d48787761473930627931775957646c66487838664756756644423866487838266175746f3d666f726d6174266669743d63726f7026773d3131373026713d3830"><img src="https://camo.githubusercontent.com/a803e212c42faddffdc24e4f0982c08bdac3267c3d7791e8322bcf80db798f3b/68747470733a2f2f696d616765732e756e73706c6173682e636f6d2f70686f746f2d313531373133343139313131382d3964353935653463386332623f69786c69623d72622d312e322e3126697869643d4d6e77784d6a4133664442384d48787761473930627931775957646c66487838664756756644423866487838266175746f3d666f726d6174266669743d63726f7026773d3131373026713d3830" alt="CSS" data-canonical-src="https://images.unsplash.com/photo-1517134191118-9d595e4c8c2b?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80" style="max-width: 100%;"></a></p>
<p dir="auto">什么是现代化的CSS?</p>
<p dir="auto">在 10 年前可能是 Sass / Less / Stylus 等 CSS 预处理器,以及逐渐落地中的 CSS3 标准。</p>
<p dir="auto">在 5 年前则是 PostCSS 后处理器,相当于 CSS 界中的 Babel,帮助前段开发摆脱了 Vendor Prefix,如 -webkit-xxx 这样的样式写法。</p>
<p dir="auto">Sass 等预处理器提升了 CSS 的便利程度;PostCSS 及 Webpack 等工程化套件提升了其可靠性;</p>
<p dir="auto">在它们的基础之上, 2022 年的今天,现代化的 CSS,关注的重点转向了 CSS 的架构、设计模式方面。怎么才能降低大型项目 CSS 的复杂度,提高可维护性?CSS 能不能被组件化、工具化?</p>
<p dir="auto">2021 年大放异彩的 Tailwind CSS 及衍生框架给出了一些具有特色的解决方案,TailwindCSS 在 4 年的时间内,拿下了 53.7K 的 Star。下面我结合 Tailwind CSS 中的一些设计,谈谈我对 CSS 架构的理解。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Demo</h2><a id="user-content-demo" class="anchor" aria-label="Permalink: Demo" href="#demo"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">为了方便理解,首先让我们看看 Tailwind 的一个简单示例,对它有一个粗略的了解:</p>
<div class="highlight highlight-text-html-basic" dir="auto"><pre><span class="pl-kos"><</span><span class="pl-ent">div</span> <span class="pl-c1">class</span>="<span class="pl-s">p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4</span>"<span class="pl-kos">></span>
<span class="pl-kos"><</span><span class="pl-ent">div</span> <span class="pl-c1">class</span>="<span class="pl-s">shrink-0</span>"<span class="pl-kos">></span>
<span class="pl-kos"><</span><span class="pl-ent">img</span> <span class="pl-c1">class</span>="<span class="pl-s">h-12 w-12</span>" <span class="pl-c1">src</span>="<span class="pl-s">/img/logo.svg</span>" <span class="pl-c1">alt</span>="<span class="pl-s">ChitChat Logo</span>"<span class="pl-kos">></span>
<span class="pl-kos"></</span><span class="pl-ent">div</span><span class="pl-kos">></span>
<span class="pl-kos"><</span><span class="pl-ent">div</span><span class="pl-kos">></span>
<span class="pl-kos"><</span><span class="pl-ent">div</span> <span class="pl-c1">class</span>="<span class="pl-s">text-xl font-medium text-black</span>"<span class="pl-kos">></span>ChitChat<span class="pl-kos"></</span><span class="pl-ent">div</span><span class="pl-kos">></span>
<span class="pl-kos"><</span><span class="pl-ent">p</span> <span class="pl-c1">class</span>="<span class="pl-s">text-slate-500</span>"<span class="pl-kos">></span>You have a new message!<span class="pl-kos"></</span><span class="pl-ent">p</span><span class="pl-kos">></span>
<span class="pl-kos"></</span><span class="pl-ent">div</span><span class="pl-kos">></span>
<span class="pl-kos"></</span><span class="pl-ent">div</span><span class="pl-kos">></span></pre></div>
<p dir="auto">说是 CSS 的示例,但是 Demo 却是不折不扣的 HTML,这是因为 Tailwind CSS 是将样式直接写到了 HTML 之内。</p>
<p dir="auto">不难看出 <code>class</code> 中的 <code>font-medium text-black bg-white</code> 等都是描述样式用的。</p>
<p dir="auto">可能有些同学看到这里已经开始皱眉头了,可能会有一些这样的疑问:</p>
<ol dir="auto">
<li>这不都是 bootstrap 玩剩下的吗?早就被淘汰了</li>
<li>代码和样式应该分离,这样不符合最佳实践</li>
<li>看起来太乱了</li>
</ol>
<p dir="auto">在解释这些问题之前,让我们先试着回溯一下历史,讲讲 CSS 架构的发展历程,这对于理解 Tailwind CSS 背后的“哲学”有非常大的帮助。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">历史</h3><a id="user-content-历史" class="anchor" aria-label="Permalink: 历史" href="#历史"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">CSS 是一门很古老、应用很广泛的语言,当然也有很多人说它算不上一门语言,不过无论怎样,样式领域中,它是当之无愧的霸主。</p>
<p dir="auto">在它漫长的发展历程中,诞生了许许多多的“最佳实践”,譬如 className 要语义化,以及内容和样式分离等等。但是还是有很多人反映 CSS 难懂难学,虽然入门简单,一看即会,但是要想深入掌握,则需要耗费巨大的精力,且往往并不被认可。</p>
<p dir="auto">具有其他领域编程经验的人触碰到前端时,往往可以通过其他领域的经验,快速地掌握 HTML / JS 等语法,却很难做到不畏惧 CSS,更不要提做好 CSS 的架构。也许在不少人的心中,CSS 是没有什么架构的概念的。</p>
<p dir="auto">所以,有没有这么一种可能,CSS 的“最佳实践”们,并不好使?</p>
<p dir="auto">过去的十年是前端日新月异的十年,相比于最初的 HTML / CSS / Javascript 三板斧,伴随着 React 的流行而深入人心的是 JSX 的语法,HTML 和 Javascript 的主次地位发生了变化。然而这么多年以来,CSS 的写法却基本未产生明显的变化,依旧是独立于 HTML / Javascript 存在,通过 <code>class / id</code> 等锚点对页面进行装饰。</p>
<p dir="auto">让我们暂时抛去既有的成见,重新从软件工程中可靠性、可理解性、可维护性、可重用性等角度,剖析历史中出现的一些 CSS 架构,寻找各种架构的优缺点,最后达成一个对 CSS 架构的更深入的理解。</p>
<p dir="auto">先看有哪些主流的 CSS 架构。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">模块级 CSS</h3><a id="user-content-模块级-css" class="anchor" aria-label="Permalink: 模块级 CSS" href="#模块级-css"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">主流的模块级架构是 <a href="https://getbem.com/introduction/" rel="nofollow">Block Element Modifer</a>,简称 BEM,它的前身是 OOCSS(面向对象的 CSS)及 SMACSS(可扩展模块化架构的 CSS)等。</p>
<p dir="auto">从原理上来说,是将页面元素首先划成不同的区块(可重用组件),如电商商品页面会有 预览区(<code>.preview</code>)、SKU列表区(<code>.sku-list</code>)、评论区(<code>.comments</code>)、详情区(<code>.detail</code>)等等。然后再于区块的内部,定义一些元素,如 SKU 列表区中的 Button (<code>.sku-list__button</code>),最后还可以加状态修饰符,如禁用状态的按钮(<code>.sku-list__button--disabled</code>)。</p>
<p dir="auto">它有两个比较核心的原则:</p>
<ol dir="auto">
<li>结构和样式分离</li>
<li>容器和内容分离</li>
</ol>
<p dir="auto">BEM 的规范易于理解,即使是新手,也很容易看懂并模仿。它也有缺点,就是类名特别长,尤其是在有 Sass / Less 嵌套的时候。</p>
<p dir="auto">大部分的“最佳实践”,如语义化的 className,就是为使 BEM 架构能够正常运转而提出来的。</p>
<p dir="auto">BEM 架构具有如下的优点:</p>
<ul dir="auto">
<li>可读性强</li>
<li>模块隔离(手动保证唯一性,或者启用 CSS Module)</li>
<li>可维护性强(质量角度)(修改 A 模块不担心影响到 B 模块)</li>
</ul>
<p dir="auto">但是它也有如下的缺点:</p>
<ul dir="auto">
<li>冗余度高(区块内的相似元素难以复用样式,容易催生 CV 工程师,先复制再修改)</li>
<li>过于自由,缺乏规范(举例来说,有人统计过Gitlab网站中有几百种颜色值)</li>
<li>class 名称比较长</li>
<li>难以实现响应式设计</li>
<li>难以组件化</li>
</ul>
<blockquote>
<p dir="auto">需要注意的是,由于 antd 等组件库的兴起,BEM 模式的元素样式难以复用的问题,得到了很大的缓解。</p>
</blockquote>
<p dir="auto">另外对于 BEM 模式尝试解决的一个问题:HTML结构与样式分离,它解决的也不够好,HTML确实不关心CSS怎么写,但是CSS常常需要关心 HTML 的内部细节,因此在对 HTML 结构做调整的时候,一般都需要对 CSS 对相应的调整。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">原子化 CSS</h3><a id="user-content-原子化-css" class="anchor" aria-label="Permalink: 原子化 CSS" href="#原子化-css"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">也称为功能性 CSS,通过定义一系列原子化的全局样式,实现精简样式开发工作的目的。</p>
<p dir="auto">如 Bootstrap 中的 Label 示例:</p>
<div class="highlight highlight-text-html-basic" dir="auto"><pre><span class="pl-kos"><</span><span class="pl-ent">span</span> <span class="pl-c1">class</span>="<span class="pl-s">label label-default</span>"<span class="pl-kos">></span>Default<span class="pl-kos"></</span><span class="pl-ent">span</span><span class="pl-kos">></span>
<span class="pl-kos"><</span><span class="pl-ent">span</span> <span class="pl-c1">class</span>="<span class="pl-s">label label-primary</span>"<span class="pl-kos">></span>Primary<span class="pl-kos"></</span><span class="pl-ent">span</span><span class="pl-kos">></span>
<span class="pl-kos"><</span><span class="pl-ent">span</span> <span class="pl-c1">class</span>="<span class="pl-s">label label-success</span>"<span class="pl-kos">></span>Success<span class="pl-kos"></</span><span class="pl-ent">span</span><span class="pl-kos">></span>
<span class="pl-kos"><</span><span class="pl-ent">span</span> <span class="pl-c1">class</span>="<span class="pl-s">label label-info</span>"<span class="pl-kos">></span>Info<span class="pl-kos"></</span><span class="pl-ent">span</span><span class="pl-kos">></span>
<span class="pl-kos"><</span><span class="pl-ent">span</span> <span class="pl-c1">class</span>="<span class="pl-s">label label-warning</span>"<span class="pl-kos">></span>Warning<span class="pl-kos"></</span><span class="pl-ent">span</span><span class="pl-kos">></span>
<span class="pl-kos"><</span><span class="pl-ent">span</span> <span class="pl-c1">class</span>="<span class="pl-s">label label-danger</span>"<span class="pl-kos">></span>Danger<span class="pl-kos"></</span><span class="pl-ent">span</span><span class="pl-kos">></span></pre></div>
<p dir="auto">它的原理很容易理解,带有相同 <code>class</code> 的 <code>HTML</code> 结构会共享同一段样式,不同样式的差异通过 <code>class</code> 的不同组合体现。</p>
<p dir="auto">原子化 CSS 具有以下的优点:</p>
<ol dir="auto">
<li>样式规范化</li>
<li>易于实现响应式设计</li>
<li>组件化</li>
<li>样式隔离性更高</li>
<li>不易产生无用样式代码堆积</li>
</ol>
<p dir="auto">但是它也有如下的缺点:</p>
<ol dir="auto">
<li>学习成本高</li>
<li>无法覆盖所有样式需求,这会导致如下两种可能性:
<ol dir="auto">
<li>混用原子化与 BEM,做成四不像。</li>
<li>自行扩展原子化语法,进一步增大学习成本。</li>
</ol>
</li>
<li>生成的 HTML 可读性差(尤其是对不熟练的人)</li>
<li>会有一些用不到的样式被引入</li>
</ol>
<p dir="auto">另外回到上面提的页面结构和样式分离方面,原子化 CSS 其实也一样做不到分离。</p>
<p dir="auto">BEM 模式是 CSS 依赖于 HTML 的结构,而原子化 CSS 是 HTML 依赖于 CSS。</p>
<p dir="auto">但是它有一个稍微优于 BEM 的地方,因为 HTML 和 样式(即 class)写在了一起,当 HTML 结构发生变化的时候,CSS 会被一并修改。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">CSS in JS</h3><a id="user-content-css-in-js" class="anchor" aria-label="Permalink: CSS in JS" href="#css-in-js"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">这是伴随着 React 兴起的一种 CSS 架构,代表产品是 styled component。</p>
<p dir="auto">它将 css 与 jsx 写在同一个文件之中,可以实现如下的好处:</p>
<ol dir="auto">
<li>隔离性更高(强制性的局部样式,相当于默认开启的CSS Module。)</li>
<li>不易产生无用样式代码堆积</li>
<li>基于状态动态计算样式变得简单易读</li>
</ol>
<p dir="auto">当然它也有一些坏处:</p>
<ol dir="auto">
<li>生成的 HTML 难以阅读</li>
<li>缺乏统一标准</li>
<li>学习成本高</li>
<li>运行时消耗</li>
<li>难以实现响应式设计等</li>
</ol>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">小结</h3><a id="user-content-小结" class="anchor" aria-label="Permalink: 小结" href="#小结"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">基于上面的对比,我们大致可以得出如下的印象:</p>
<ol dir="auto">
<li>BEM 模式自由度高,但比较依赖于“最佳实践”进行约束</li>
<li>原子化模式比较规范且开箱即用,但有学习成本且较难支撑全部需求。</li>
<li>CSS in JS 优势不太明显</li>
<li>从过往经验来看,Bootstrap这种原子化模式随着 React 和 组件库的兴起,已经比较没落了。</li>
</ol>
<p dir="auto">下面看看同样是原子化模式的 Tailwind 是怎么做到优于 Bootstrap,并再次像 BEM 发起挑战的。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Tailwind CSS 亮点介绍</h2><a id="user-content-tailwind-css-亮点介绍" class="anchor" aria-label="Permalink: Tailwind CSS 亮点介绍" href="#tailwind-css-亮点介绍"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Tailwind CSS 有许多的亮点,相比于 Bootstrap,无论是 API 的丰富度和清晰度,还是可扩展性,乃至性能,都有不俗的表现。</p>
<p dir="auto">它的 API 让人愿意投入时间学习,不会轻易过时。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">规范、清晰、丰富的工具 API</h3><a id="user-content-规范清晰丰富的工具-api" class="anchor" aria-label="Permalink: 规范、清晰、丰富的工具 API" href="#规范清晰丰富的工具-api"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">不同于 bootstrap 从组件的角度出发,设计了诸如 <code>.btn</code>、<code>.btn-primary</code>等风格的 API,Tailwind 做得更底层一些,它设计了一套用于替换 CSS 基本能力,含有规律的 API 集合。</p>
<p dir="auto">再次回顾上面提到的Demo:</p>
<div class="highlight highlight-text-html-basic" dir="auto"><pre><span class="pl-kos"><</span><span class="pl-ent">div</span> <span class="pl-c1">class</span>="<span class="pl-s">p-6 max-w-sm mx-auto bg-white rounded-xl shadow-lg flex items-center space-x-4</span>"<span class="pl-kos">></span>
<span class="pl-kos"><</span><span class="pl-ent">div</span> <span class="pl-c1">class</span>="<span class="pl-s">shrink-0</span>"<span class="pl-kos">></span>
<span class="pl-kos"><</span><span class="pl-ent">img</span> <span class="pl-c1">class</span>="<span class="pl-s">h-12 w-12</span>" <span class="pl-c1">src</span>="<span class="pl-s">/img/logo.svg</span>" <span class="pl-c1">alt</span>="<span class="pl-s">ChitChat Logo</span>"<span class="pl-kos">></span>
<span class="pl-kos"></</span><span class="pl-ent">div</span><span class="pl-kos">></span>
<span class="pl-kos"><</span><span class="pl-ent">div</span><span class="pl-kos">></span>
<span class="pl-kos"><</span><span class="pl-ent">div</span> <span class="pl-c1">class</span>="<span class="pl-s">text-xl font-medium text-black</span>"<span class="pl-kos">></span>ChitChat<span class="pl-kos"></</span><span class="pl-ent">div</span><span class="pl-kos">></span>
<span class="pl-kos"><</span><span class="pl-ent">p</span> <span class="pl-c1">class</span>="<span class="pl-s">text-slate-500</span>"<span class="pl-kos">></span>You have a new message!<span class="pl-kos"></</span><span class="pl-ent">p</span><span class="pl-kos">></span>
<span class="pl-kos"></</span><span class="pl-ent">div</span><span class="pl-kos">></span>
<span class="pl-kos"></</span><span class="pl-ent">div</span><span class="pl-kos">></span></pre></div>
<p dir="auto">这个里面:</p>
<ul dir="auto">
<li>p-6 代表 padding: 1.5em,即 6 * 0.25em; m-6 代码 margin,其余与 p-6 一致。</li>
<li>h-12 代表 height: 3em,即 12 * 0.25em; w-12 代表宽度,其余与 h-12 一致。</li>
<li>bg-white 代表 background-color: white;</li>
</ul>
<p dir="auto">看到这里肯定会有很多问题,如:</p>
<ol dir="auto">
<li>如何确定 padding 后面可以跟哪些数字呢?比如 p-10000 行不行</li>
<li>可以使用哪些颜色呢?bg-white 代表白色,bg-blue 代表蓝色,那淡蓝色怎么表示呢?</li>
<li>需要记住所有的 class 名称吗?</li>
<li>支持扩展样式吗?怎么扩展?</li>
</ol>
<p dir="auto">先讲一下 Tailwind 的 API 设计原则,这些问题就比较容易弄明白了。</p>
<p dir="auto">Tailwind 的 API 是有很强的规律的,一般来说,可以简单地认为它的规律是 {修饰符}:{类型}-{值}</p>
<p dir="auto">如 hover:dark:w-12 代表当进入 hover 态时,将宽度 (w) 设置为 12 (单位是 0.25em),即 3em。</p>
<p dir="auto">类型是固定的,但是值和部分修饰符可以自定义或基于默认的进行扩展。</p>
<p dir="auto">API具有如下规律:</p>
<ul dir="auto">
<li>修饰符无限叠加:如 dark:hover:w-12,指在黑暗模式下才为 hover 态设置宽度 3em,它也等价于 hover:dark:w-12,即修饰符顺序无关。</li>
<li>同类型随机组合:相同性质的类型共享所有值,可随机组合。如只要有 w-12(宽 3em),就会有 h-12(高3em)。有 text-blue (字体蓝色)就会有 bg-blue(背景蓝色)。</li>
<li>值可扩展,如默认情况下没有 p-10000,但是可以通过自定义扩展实现,且只要有 p-10000,就同时会有 px-10000 py-10000 m-10000 max-m-10000 h-10000 w-10000 等等。</li>
</ul>
<p dir="auto">棕上,学习 API 可分为如下三个部分:</p>
<ol dir="auto">
<li>学习有哪些修饰符,如 暗黑模式为 <code>dark:</code>,hover 态为 <code>hover:</code>等。</li>
<li>学习 CSS 的属性对应的 Tailwind 类型值,如 padding 为 p,padding-left 为 pl,margin 为 m,宽高分别为 w / h 等。</li>
<li>学习几个主要的值域,如 color 有哪些,spacing 有哪些,font-family有哪些等。另外因为这一部分可以扩展,其实完全可以在团队内自定义一套。</li>
</ol>
<p dir="auto">举例来说,假设在 <code>tailwind.config.js</code>中加入如下的自定义配置:</p>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-smi">module</span><span class="pl-kos">.</span><span class="pl-c1">exports</span> <span class="pl-c1">=</span> <span class="pl-kos">{</span>
<span class="pl-c1">theme</span>: <span class="pl-kos">{</span>
<span class="pl-c1">colors</span>: <span class="pl-kos">{</span>
<span class="pl-c1">primary</span>: <span class="pl-en">var</span><span class="pl-kos">(</span><span class="pl-c1">--</span><span class="pl-s1">primary</span><span class="pl-c1">-</span><span class="pl-s1">color</span><span class="pl-kos">,</span> <span class="pl-s">'#f7fafc'</span><span class="pl-kos">)</span><span class="pl-kos">,</span>
<span class="pl-c1">secondary</span>: <span class="pl-s">'#1a202c'</span>
<span class="pl-c">// ...</span>
<span class="pl-kos">}</span>
<span class="pl-kos">}</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">就可以使用 <code>bg-primary</code> 实现 <code>background-color: #f7fafc</code> 的效果。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">按需裁剪的体积优化</h3><a id="user-content-按需裁剪的体积优化" class="anchor" aria-label="Permalink: 按需裁剪的体积优化" href="#按需裁剪的体积优化"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">修饰符顺序无关,就代表 CSS 中既需要有 <code>dark:hover:w-12</code>,也需要有 <code>hover:dark:w-12</code>,还有如下各个 spacing 的所有可能组合:</p>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-smi">module</span><span class="pl-kos">.</span><span class="pl-c1">exports</span> <span class="pl-c1">=</span> <span class="pl-kos">{</span>
<span class="pl-c1">theme</span>: <span class="pl-kos">{</span>
<span class="pl-c1">spacing</span>: <span class="pl-kos">{</span>
<span class="pl-s">'1'</span>: <span class="pl-s">'0.25em'</span><span class="pl-kos">,</span>
<span class="pl-s">'2'</span>: <span class="pl-s">'0.5em'</span><span class="pl-kos">,</span>
<span class="pl-s">'3'</span>: <span class="pl-s">'0.75em'</span><span class="pl-kos">,</span>
<span class="pl-s">'4'</span>: <span class="pl-s">'1em'</span><span class="pl-kos">,</span>
<span class="pl-s">'5'</span>: <span class="pl-s">'1.25em'</span><span class="pl-kos">,</span>
<span class="pl-s">'6'</span>: <span class="pl-s">'1.5em'</span><span class="pl-kos">,</span>
<span class="pl-c">// ...</span>
<span class="pl-s">'96'</span>: <span class="pl-s">'24em'</span>
<span class="pl-kos">}</span>
<span class="pl-kos">}</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">那么里面必然含有很多不必要的样式,数量级非常地大,体积会很快膨胀到一个不可接受的地步。</p>
<p dir="auto">那么 tailwind css 是怎么解决的这个问题呢?它有两种模式:</p>
<ol dir="auto">
<li>预先生成全集CSS定义,然后检查代码中未使用的 class,对不需要的 CSS 进行裁剪。</li>
<li>从空白CSS开始,检查代码中使用的class,按需生成相应的CSS代码。也称为 JIT 模式。</li>
</ol>
<p dir="auto">随着 Tailwind CSS 支持的 API 和修饰符越来越多,方式1出现了较大的性能问题,往往需要用户主动关闭一些不需要的功能(如flex)或修饰符(如dark),因此从 v3 开始,JIT 模式称为默认配置。</p>
<p dir="auto">这一点也决定了代码中不能把单个 class 名称动态拼接,即如果用到了 <code>bg-black</code> 和 <code>bg-white</code>,代码中必须有这两个字符串的明文,而不能是:</p>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-k">const</span> <span class="pl-s1">bg</span> <span class="pl-c1">=</span> <span class="pl-kos">{</span> <span class="pl-c1">pending</span>: <span class="pl-s">'white'</span><span class="pl-kos">,</span> <span class="pl-c1">ready</span>: <span class="pl-s">'black'</span><span class="pl-kos">}</span>
<span class="pl-k">const</span> <span class="pl-s1">className</span> <span class="pl-c1">=</span> <span class="pl-s">`bg-<span class="pl-s1"><span class="pl-kos">${</span><span class="pl-s1">bg</span><span class="pl-kos">[</span><span class="pl-s1">state</span><span class="pl-kos">]</span><span class="pl-kos">}</span></span>`</span>
<span class="pl-k">return</span> <span class="pl-c1"><</span><span class="pl-s1">div</span> <span class="pl-c1">className</span><span class="pl-c1">=</span><span class="pl-kos">{</span><span class="pl-s1">className</span><span class="pl-kos">}</span><span class="pl-c1">></span><span class="pl-c1"><</span><span class="pl-s1">div</span><span class="pl-c1">></span></pre></div>
<p dir="auto">这样的动态 className。</p>
<p dir="auto">但是动态组合 className 是可以的:</p>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-k">const</span> <span class="pl-s1">bg</span> <span class="pl-c1">=</span> <span class="pl-kos">{</span> <span class="pl-c1">pending</span>: <span class="pl-s">'bg-white'</span><span class="pl-kos">,</span> <span class="pl-c1">ready</span>: <span class="pl-s">'bg-white'</span><span class="pl-kos">}</span>
<span class="pl-k">const</span> <span class="pl-s1">className</span> <span class="pl-c1">=</span> <span class="pl-s">`<span class="pl-s1"><span class="pl-kos">${</span><span class="pl-s1">bg</span><span class="pl-kos">[</span><span class="pl-s1">state</span><span class="pl-kos">]</span><span class="pl-kos">}</span></span> p-2 text-lg`</span>
<span class="pl-k">return</span> <span class="pl-c1"><</span><span class="pl-s1">div</span> <span class="pl-c1">className</span><span class="pl-c1">=</span><span class="pl-kos">{</span><span class="pl-s1">className</span><span class="pl-kos">}</span><span class="pl-c1">></span><span class="pl-c1"><</span><span class="pl-s1">div</span><span class="pl-c1">></span></pre></div>
<p dir="auto">JIT 模式给 Tailwind CSS 带来的能力提升是飞跃式的,因为一般来说,单个项目中使用的样式其实不会有很多种(尤其是在有原子化CSS进行规范的情况下),所以 Tailwind CSS 可以不断地完善 API,扩大其能力,而不用担心生产环境 CSS 体积变得臃肿。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">移动优先的响应式设计原则</h3><a id="user-content-移动优先的响应式设计原则" class="anchor" aria-label="Permalink: 移动优先的响应式设计原则" href="#移动优先的响应式设计原则"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">在 Tailwind 中设置响应式设计是很简单的,例:</p>
<div class="highlight highlight-text-html-basic" dir="auto"><pre><span class="pl-kos"><</span><span class="pl-ent">div</span> <span class="pl-c1">class</span>="<span class="pl-s">bg-green-500 md:bg-red-500 lg:bg-yellow-500</span>"<span class="pl-kos">></span>
<span class="pl-c"><!-- ... --></span>
<span class="pl-kos"></</span><span class="pl-ent">div</span><span class="pl-kos">></span></pre></div>
<p dir="auto">示例中设置了默认的背景色为绿色,中等屏背景色为红色,大屏背景色为黄色。</p>
<p dir="auto">默认的显示器大小配置如下:</p>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-smi">module</span><span class="pl-kos">.</span><span class="pl-c1">exports</span> <span class="pl-c1">=</span> <span class="pl-kos">{</span>
<span class="pl-c1">theme</span>: <span class="pl-kos">{</span>
<span class="pl-c1">screens</span>: <span class="pl-kos">{</span>
<span class="pl-s">'sm'</span>: <span class="pl-s">'640px'</span><span class="pl-kos">,</span>
<span class="pl-c">// => @media (min-width: 640px) { ... }</span>
<span class="pl-s">'md'</span>: <span class="pl-s">'768px'</span><span class="pl-kos">,</span>
<span class="pl-c">// => @media (min-width: 768px) { ... }</span>
<span class="pl-s">'lg'</span>: <span class="pl-s">'1024px'</span><span class="pl-kos">,</span>
<span class="pl-c">// => @media (min-width: 1024px) { ... }</span>
<span class="pl-s">'xl'</span>: <span class="pl-s">'1280px'</span><span class="pl-kos">,</span>
<span class="pl-c">// => @media (min-width: 1280px) { ... }</span>
<span class="pl-s">'2xl'</span>: <span class="pl-s">'1536px'</span><span class="pl-kos">,</span>
<span class="pl-c">// => @media (min-width: 1536px) { ... }</span>
<span class="pl-kos">}</span>
<span class="pl-kos">}</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">所谓移动优先,就是带有 screen 修饰符(即 sm, md等)的属性,只应用于大于等于它的显示屏上。做响应式设计的时候,应该优先以不加修饰符的形式完成小屏的设计,再逐渐放大屏幕,针对出现问题的地方,添加额外的带 screen 修饰符的 class.</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">支持扩展,支持插件</h3><a id="user-content-支持扩展支持插件" class="anchor" aria-label="Permalink: 支持扩展,支持插件" href="#支持扩展支持插件"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">如上所述,Tailwind 不仅可以扩展各种值的枚举项,也可以扩展部分修饰符。甚至还可以写插件和 preset。</p>
<p dir="auto">因此可以比较方便地将团队的样式配置抽取成公共的 NPM 包进行共享。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Tailwind CSS 劣势</h2><a id="user-content-tailwind-css-劣势" class="anchor" aria-label="Permalink: Tailwind CSS 劣势" href="#tailwind-css-劣势"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">和Bootstrap类似,学习曲线陡峭依然是 Tailwind CSS的一个劣势,在使用它的早期阶段,会经常需要查阅官方资料,寻找某个 CSS 属性对应的 class 是什么。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">总结</h2><a id="user-content-总结" class="anchor" aria-label="Permalink: 总结" href="#总结"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">以上就是关于现代化的 CSS 框架 Tailwind CSS 的介绍,希望通过对不同的 CSS 架构的对比分析,形成对 Tailwind CSS 的更深入的理解。</p>
<p dir="auto">以上。</p>
</article>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/3e700abb0c911caa9d40bc3c808de4d0
2021-12-30T08:19:22Z
2023-09-24T10:45:46Z
zx - 面向前端的Shell编程利器
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/3e700abb0c911caa9d40bc3c808de4d0#file-zx-introduction-blog-md">zx-introduction.blog.md</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-zx-introduction-blog-md" class="file my-2">
<div id="file-zx-introduction-blog-md-readme" class="Box-body readme blob p-5 p-xl-6 "
style="overflow: auto" tabindex="0" role="region"
aria-label="zx-introduction.blog.md content, created by banyudu on 08:19AM on December 30, 2021."
>
<article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">zx - 面向前端的Shell编程利器</h1><a id="user-content-zx---面向前端的shell编程利器" class="anchor" aria-label="Permalink: zx - 面向前端的Shell编程利器" href="#zx---面向前端的shell编程利器"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">Shell 简介</h3><a id="user-content-shell-简介" class="anchor" aria-label="Permalink: Shell 简介" href="#shell-简介"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Shell,或者说命令行,对于处理工作中遇到的重复性工作有极大的帮助。</p>
<p dir="auto">说到 Shell,很多人都能想到 <code>ls / cat / grep / sort / sed / awk</code> 等各种常用命令,它们组合起来可以完成各种各样的任务。在这一点上,Shell是极棒的。</p>
<p dir="auto">举例来说,要统计一个目录及其所有子目录中的文件数量,可以用</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>find <span class="pl-c1">.</span> <span class="pl-k">|</span> wc -l</pre></div>
<p dir="auto">这样的命令组合来实现。</p>
<p dir="auto">当问题更复杂的时候,单行的命令往往难以实现,需要加入一些循环控制,条件判断等,这时候就需要使用到 Shell 脚本编程了。</p>
<p dir="auto">然而 Shell 脚本并不像单条命令那样酷,它里面有很多的难以记忆的语法,对新手来说极不友好。</p>
<p dir="auto">基于这个问题,复杂的脚本很多都迁移到了一些更友好的编程语言之上,如前些年的 Perl,近些年的 Python / Ruby,大多数情况下,它们的定位是“胶水”,在核心的单行 Shell 命令之间,做一些逻辑判断和循环控制等处理。当然近些年随着 Python 的发展,也有越来越多的直接使用 Python 库而非 Shell 命令进行脚本编程的场景,这就是另一回事了。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">前端的脚本编程</h2><a id="user-content-前端的脚本编程" class="anchor" aria-label="Permalink: 前端的脚本编程" href="#前端的脚本编程"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">不同于运维、后端常用的 Shell、Python 脚本,前端更熟悉 Javascript / Typescript 这些,也有很多前端的工程化工具都是用 JS 写的。</p>
<p dir="auto">但是 JS 的脚本使用起来有较长的成本,如果完全不使用 Shell 的话,就需要安装很多的依赖项和库,当任务的复杂度没到一定级别的时候,这样做反而不如 Shell 简单。</p>
<p dir="auto">因此在处理一些相对简单,又比单条命令略复杂的任务场景时,Shell 编程依旧是一个不错的选择。有没有办法用 JS 作为胶水语言呢?Google 给出了一个比较酷的方案:<a href="https://github.com/google/zx">zx</a>。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">ZX 示例</h3><a id="user-content-zx-示例" class="anchor" aria-label="Permalink: ZX 示例" href="#zx-示例"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">先看一个使用 <a href="https://github.com/google/zx">zx</a> 的例子:</p>
<div class="highlight highlight-source-js" dir="auto"><pre>#!/usr/bin/env zx
<span class="pl-k">await</span> <span class="pl-en">$</span><span class="pl-s">`cat package.json | grep name`</span>
<span class="pl-k">let</span> <span class="pl-s1">branch</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-en">$</span><span class="pl-s">`git branch --show-current`</span>
<span class="pl-k">await</span> <span class="pl-en">$</span><span class="pl-s">`dep deploy --branch=<span class="pl-s1"><span class="pl-kos">${</span><span class="pl-s1">branch</span><span class="pl-kos">}</span></span>`</span>
<span class="pl-k">await</span> <span class="pl-v">Promise</span><span class="pl-kos">.</span><span class="pl-en">all</span><span class="pl-kos">(</span><span class="pl-kos">[</span>
<span class="pl-en">$</span><span class="pl-s">`sleep 1; echo 1`</span><span class="pl-kos">,</span>
<span class="pl-en">$</span><span class="pl-s">`sleep 2; echo 2`</span><span class="pl-kos">,</span>
<span class="pl-en">$</span><span class="pl-s">`sleep 3; echo 3`</span><span class="pl-kos">,</span>
<span class="pl-kos">]</span><span class="pl-kos">)</span>
<span class="pl-k">let</span> <span class="pl-s1">name</span> <span class="pl-c1">=</span> <span class="pl-s">'foo bar'</span>
<span class="pl-k">await</span> <span class="pl-en">$</span><span class="pl-s">`mkdir /tmp/<span class="pl-s1"><span class="pl-kos">${</span><span class="pl-s1">name</span><span class="pl-kos">}</span></span>`</span></pre></div>
<p dir="auto">很容易可以看到上面的 <code>let / await / Promise</code> 等都是 JS 编程中的关键字、全局变量等,而 <code>cat package.json | grep name</code> 等则是 Shell 命令。</p>
<p dir="auto">这样 zx 的用途就比较好理解了,它是一个使用 JS 语法编程,支持使用 <code>$</code> 作为函数,调用 Shell 命令的脚本工具。</p>
<p dir="auto">同时在这个示例中我们还可以看到,它直接在脚本最顶层使用了 <code>await</code> 关键字,这是<code>Node.js</code> <code>14.x</code> 版本中才支持的, <code>zx</code> 的文档中有要求需要安装 <code>Node.js</code> 的 <code>14.13.1</code> 以上的版本。</p>
<p dir="auto">如果不熟悉 Shell 编程的同学,可能对如何运行上面的脚本有些疑问,它有两种用法,和其它的脚本语言没有区别:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre><span class="pl-c"><span class="pl-c">#</span> 先保存上面的脚本内容到 test.mjs 或任意其它的文件中(后缀名随意)</span>
<span class="pl-c"><span class="pl-c">#</span> 使用之前,需要安装 zx 全局包:</span>
npm i -g zx
<span class="pl-c"><span class="pl-c">#</span> 第一种方式,使用解释器 + 文件名的方式</span>
zx test.mjs
<span class="pl-c"><span class="pl-c">#</span> 第二种方式,给文件加可执行权限,然后直接执行</span>
chmod a+x test.mjs <span class="pl-c"><span class="pl-c">#</span> 加权限 (仅类 Unix 系统,如 Linux / MacOS 等)</span>
./test.mjs <span class="pl-c"><span class="pl-c">#</span> 执行</span>
<span class="pl-c"><span class="pl-c">#</span> 第二种方式需要在文件头部有 #!/usr/bin/env zx 这一行(shbang),第一种方式不需要</span></pre></div>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">内置函数</h3><a id="user-content-内置函数" class="anchor" aria-label="Permalink: 内置函数" href="#内置函数"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">zx 提供的不仅仅是一个 <code>$</code> 函数,它还提供了一些其它在脚本编程中常用的工具函数。</p>
<ul dir="auto">
<li><code>cd()</code> 切换当前目录</li>
<li><code>fetch()</code> 内置的 <a href="https://www.npmjs.com/package/node-fetch" rel="nofollow">node-fetch</a> 包,用于网络请求</li>
<li><code>question()</code> 内置的 <a href="https://nodejs.org/api/readline.html" rel="nofollow">readline</a> 包,用于读取用户输入,询问用户选项等</li>
<li><code>sleep()</code> 使用 setTimeout 实现的一个等待函数</li>
<li><code>nothrow()</code> 捕捉 <code>$</code> 执行命令时遇到的非0返回值,使其不抛异常。一般来说,Shell 编程中 Exit Code不为0代表有异常</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">内置的全局变量</h3><a id="user-content-内置的全局变量" class="anchor" aria-label="Permalink: 内置的全局变量" href="#内置的全局变量"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">zx 还内置了一些 package 的引用,并做了成全局变量。</p>
<ul dir="auto">
<li><code>chalk</code> 即 <a href="https://www.npmjs.com/package/chalk" rel="nofollow">chalk</a> 包,用于输出彩色的内容。</li>
<li><code>fs</code> 引用的 <a href="https://www.npmjs.com/package/fs-extra" rel="nofollow">fs-extra</a> 包,用于完成常见的文件操作。</li>
<li><code>globby</code> 引用的 <a href="https://github.com/sindresorhus/globby">globby</a> 包,用于模糊搜索文件名。</li>
<li><code>os</code> 引用的 <a href="https://nodejs.org/api/os.html" rel="nofollow">os</a> 包,用于获取系统信息。</li>
<li><code>path</code> 引用的 <a href="https://nodejs.org/api/path.html" rel="nofollow">path</a> 包,用于对路径做处理。</li>
<li><code>minimist</code> 引用的 <a href="https://www.npmjs.com/package/minimist" rel="nofollow">minimist</a> 包,用于处理命令行参数。</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">比较酷的功能</h3><a id="user-content-比较酷的功能" class="anchor" aria-label="Permalink: 比较酷的功能" href="#比较酷的功能"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">zx提供了一些看起来比较酷的功能,如它可以直接执行远程脚本。</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>zx https://myhost.com/myscript</pre></div>
<p dir="auto">还可以把脚本放在 Markdown中</p>
<div class="highlight highlight-text-md" dir="auto"><pre><span class="pl-mh">## <span class="pl-en">非常酷的脚本</span></span>
这是一个 JS 代码块,它里面的内容会被执行
<span class="pl-s">```</span><span class="pl-en">js</span>
<span class="pl-k">let</span> myvar <span class="pl-k">=</span> <span class="pl-c1">1</span>
<span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">'</span>hello js<span class="pl-pds">'</span></span>)
<span class="pl-s">```</span>
这是一个 Shell 代码块,它里面的内容也会被执行
<span class="pl-s">```</span><span class="pl-en">shell</span>
<span class="pl-c1">echo</span> hello shell
<span class="pl-s">```</span>
其它的编程语言的代码块,会被忽略掉。
同一语言不同的代码块之间,变量是不隔离的,可以继续使用之前定义的变量
<span class="pl-s">```</span><span class="pl-en">js</span>
<span class="pl-en">console</span>.<span class="pl-c1">log</span>(myvar)
<span class="pl-s">```</span></pre></div>
<p dir="auto">还有一些的配置选项及FAQ,此处不再赘述,需要的同学可以直接访问 <a href="https://github.com/google/zx">Github</a> 或 <a href="https://www.npmjs.com/package/zx" rel="nofollow">NPM</a> 查看。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">使用心得</h2><a id="user-content-使用心得" class="anchor" aria-label="Permalink: 使用心得" href="#使用心得"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>可以引用其它的 npm 包,像正常的 JS 项目一样安装到 node_modules 中即可,如 lodash 等。</li>
<li>Markdown 模式可以提供良好的文档,但是不利于脚本的调试,变量跳转、批量重命名等</li>
<li>zx 适合写 胶水式的 Shell 脚本,一般会有一些约定的前提条件(如安装了 xx 命令),如果想要实现通用的复杂脚本,还是直接上 npm 包更好一些。</li>
</ul>
</article>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/f794ee6b58834ce41dc1ca230abbe6e3
2021-11-24T03:26:32Z
2021-11-24T07:04:43Z
前端调试之网络代理
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/f794ee6b58834ce41dc1ca230abbe6e3#file-debug-frontend-app-with-proxy-blog-md">debug-frontend-app-with-proxy.blog.md</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-debug-frontend-app-with-proxy-blog-md" class="file my-2">
<div id="file-debug-frontend-app-with-proxy-blog-md-readme" class="Box-body readme blob p-5 p-xl-6 "
style="overflow: auto" tabindex="0" role="region"
aria-label="debug-frontend-app-with-proxy.blog.md content, created by banyudu on 03:26AM on November 24, 2021."
>
<article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">前端调试之网络代理</h1><a id="user-content-前端调试之网络代理" class="anchor" aria-label="Permalink: 前端调试之网络代理" href="#前端调试之网络代理"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">说到前端调试,脑海中是不是自然地就想到了源代码,想到开发环境等等概念?</p>
<p dir="auto">它们是必需的吗?</p>
<p dir="auto">移动应用调试时常有抓包的概念,它是什么?只能用来看日志吗?</p>
<p dir="auto">本文带你了解一个新的前端应用调试思路,从工具应用到原理解析。希望能对读者有所启发,起到抛砖引玉的作用。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">多环境的困局</h2><a id="user-content-多环境的困局" class="anchor" aria-label="Permalink: 多环境的困局" href="#多环境的困局"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">软件开发过程中,为了保证质量,往往会将应用部署多个备份。即我们所熟知的生产环境、灰度环境、测试环境、开发环境等。</p>
<p dir="auto">相应地,软件测试的过程也会分成多个阶段进行。</p>
<p dir="auto">一般来说,测试人员会在各个不同的环境进行测试,但是开发人员总是会在本地进行修改。这个差异大多数时候不是什么问题,因为相同的代码会有相同的表现。</p>
<p dir="auto">但是有的时候事情没那么简单,我们或多或少都遇到过一些这样的场景:</p>
<ol dir="auto">
<li>生产环境或测试环境能复现的问题,在开发人员本地环境中无法复现(如需要特定数据)。</li>
<li>开发环境、测试环境中上下游系统不稳定,导致无法正常使用。</li>
<li>开发环境、测试环境中上下游系统改版,无法保证与生产环境一致。</li>
<li>线上应用为微服务集群,难以在本地完美复制。</li>
</ol>
<p dir="auto">应用的架构越是复杂,想在本地搭建出一套100%一致的系统,就越困难。</p>
<p dir="auto">试想下一个前端微应用,嵌入在门户首页中,本地运行正常,即使在生产环境独立访问也是正常的,只有嵌入在门户首页的时候才会出问题。这种情况的调试难度是较高的,使用传统方案很难解决,往往要靠猜测来实现。</p>
<p dir="auto">这个时候,如果能随时将代码部署到生产环境进行调试,是不是就很容易啦?</p>
<p dir="auto">当然,我们都知道这样是不现实,不负责的做法。生产环境不能用于调试。</p>
<p dir="auto">然而,<strong>前端不一样!</strong></p>
<p dir="auto">这里,就需要提一下前端和后端应用的不同之处了。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">前端与后端的区别</h2><a id="user-content-前端与后端的区别" class="anchor" aria-label="Permalink: 前端与后端的区别" href="#前端与后端的区别"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<blockquote>
<p dir="auto">这里所指的前端,均为狭义前端,即运行在浏览器或原生应用WebView之中的,由HTML/CSS/JS/WASM等组成的Web应用。不包含Node.js及原生app。</p>
</blockquote>
<p dir="auto">前端与后端的区别,在于生产环境和运行环境的不统一。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">后端环境</h3><a id="user-content-后端环境" class="anchor" aria-label="Permalink: 后端环境" href="#后端环境"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">对于后端应用来说,生产环境即运行环境,现在一般是容器化的集群部署,通过Docker或类似工具提供稳定的运行环境。</p>
<p dir="auto">虽然很多后端应用会有集群,分布式部署。但是一般来说,各个副本之间没有差别,代码和运行环境完全一致。</p>
<p dir="auto">甚至很多无状态的后端应用,可以随时无缝替换彼此。</p>
<p dir="auto">总结一下:后端应用运行在服务端,程序变更之后,会影响到很多用户。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">前端环境</h3><a id="user-content-前端环境" class="anchor" aria-label="Permalink: 前端环境" href="#前端环境"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">前端在部署上也类似于后端,无论是单节点还是多节点,返回的 HTML/CSS/JS等静态资源是始终一致的。</p>
<p dir="auto">但是它的运行环境却千差万别。</p>
<p dir="auto">有的运行在Chrome中,有的在Firefox中,有的在桌面端,有的在移动端。即使是同一个浏览器,还有不同版本的差别。</p>
<p dir="auto">它们可以归结为一个环境:用户端。</p>
<p dir="auto">因为是用户自己的环境,所以在它的上面再怎么折腾,也影响不到其它的用户。</p>
<p dir="auto">这就使得我们可以直接调试 “生产环境”!</p>
<p dir="auto">具体怎么做呢?这就回到了本文的题目中来了,网络代理。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">网络代理调试</h2><a id="user-content-网络代理调试" class="anchor" aria-label="Permalink: 网络代理调试" href="#网络代理调试"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">在说这个之前,先看一个“中间人攻击(MITM)”的示意图。</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/24527359b0f11f4d431760763154d996064524bce28eec7187c4dabe5df35773/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f6d616e2d696e2d7468652d6d6964646c652d61747461636b2d686f772d61766f69642d32303231313132343134303733323837382e706e67"><img src="https://camo.githubusercontent.com/24527359b0f11f4d431760763154d996064524bce28eec7187c4dabe5df35773/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f6d616e2d696e2d7468652d6d6964646c652d61747461636b2d686f772d61766f69642d32303231313132343134303733323837382e706e67" alt="Man-in-the-middle attacks (MITM) and how to avoid them" data-canonical-src="https://banyudu.github.io/images/man-in-the-middle-attack-how-avoid-20211124140732878.png" style="max-width: 100%;"></a></p>
<blockquote>
<p dir="auto">图片来自于 <a href="https://dpsvdv74uwwos.cloudfront.net/statics/img/ogimage/man-in-the-middle-attack-how-avoid.png%EF%BC%8C%E4%BE%B5%E5%88%A0" rel="nofollow">https://dpsvdv74uwwos.cloudfront.net/statics/img/ogimage/man-in-the-middle-attack-how-avoid.png,侵删</a></p>
</blockquote>
<p dir="auto">网络代理调试,运用的就是这个攻击的原理。</p>
<p dir="auto">简单来说,步骤如下:</p>
<ol dir="auto">
<li>通过上述的攻击原理,搭建一个“中间人”角色</li>
<li>篡改客户端(浏览器、APP等)的网络请求,使其指向 “中间人”</li>
<li>在“中间人”角色中,提供本地构建的代码用于调试。</li>
</ol>
<p dir="auto">那么具体来说有什么手段呢?</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">网络代理的手段</h3><a id="user-content-网络代理的手段" class="anchor" aria-label="Permalink: 网络代理的手段" href="#网络代理的手段"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">根据运行环境的不同,可采用的手段也不完全相同。</p>
<p dir="auto">主要分为以下两类:</p>
<ol dir="auto">
<li>基于抓包工具实现的中间人攻击,适用于所有场景。特点是功能强,但配置复杂,易用性稍差。</li>
<li>基于浏览器插件实现的中间人攻击,适用于Chrome、Firefox浏览器及其它兼容Chrome插件的浏览器。特点是配置简单,但需要了解一些坑点,某些情况下需要代码配合。</li>
</ol>
<p dir="auto">两者的原理也相差较大,下面我分别介绍下对应的工具及原理。</p>
<div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">基于抓包工具的中间人攻击</h4><a id="user-content-基于抓包工具的中间人攻击" class="anchor" aria-label="Permalink: 基于抓包工具的中间人攻击" href="#基于抓包工具的中间人攻击"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">抓包工具有不少,有的需要付费使用,如老牌的Charles,有些虽然提供收费模式,但免费功能也凑合能用,如Mac上的ProxyMan。</p>
<p dir="auto">它们的特点是:</p>
<ol dir="auto">
<li>需要安装证书才能攻击 HTTPS 资源。</li>
<li>浏览器端无感知,不需要修改代码配合。</li>
</ol>
<p dir="auto">这里我主要介绍ProxyMan。</p>
<p dir="auto"><a href="https://proxyman.io/" rel="nofollow">ProxyMan</a> 是一个macOS上的抓包工具,可以监控请求列表。</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/9c6300a2052ef82d316d54381a479257a81f64a77b73ec9c164694e694cd8143/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f44617368626f6172645f50726f78796d616e5f5570646174652d32303231313132343134303734313838352e6a7067"><img src="https://camo.githubusercontent.com/9c6300a2052ef82d316d54381a479257a81f64a77b73ec9c164694e694cd8143/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f44617368626f6172645f50726f78796d616e5f5570646174652d32303231313132343134303734313838352e6a7067" alt="Proxyman Dashboard on macOS" data-canonical-src="https://banyudu.github.io/images/Dashboard_Proxyman_Update-20211124140741885.jpg" style="max-width: 100%;"></a></p>
<p dir="auto">也可以捕捉请求并设置自定义响应,映射到本地文件。</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/355c10c98c2e5c94ad411be5738a446541f1272e878b8895559fb589c765bb7c/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f61737365747325323532462d4c6c50745f364265506e4a336f4b337361503125323532462d4d6764413642356c6f4959353558507330574325323532462d4d67644176524d63706e39386b437974755a6b253235324653637265656e5f53686f745f323032312d30382d30395f61745f31315f32385f34302d32303231313132343134303931343439392e706e67"><img src="https://camo.githubusercontent.com/355c10c98c2e5c94ad411be5738a446541f1272e878b8895559fb589c765bb7c/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f61737365747325323532462d4c6c50745f364265506e4a336f4b337361503125323532462d4d6764413642356c6f4959353558507330574325323532462d4d67644176524d63706e39386b437974755a6b253235324653637265656e5f53686f745f323032312d30382d30395f61745f31315f32385f34302d32303231313132343134303931343439392e706e67" alt="img" data-canonical-src="https://banyudu.github.io/images/assets%252F-LlPt_6BePnJ3oK3saP1%252F-MgdA6B5loIY55XPs0WC%252F-MgdAvRMcpn98kCytuZk%252FScreen_Shot_2021-08-09_at_11_28_40-20211124140914499.png" style="max-width: 100%;"></a></p>
<p dir="auto">还可以映射到另一个URL:</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/1cbc28be91f5d42dd2fa2c859b7dc32727bcd50552924e9bedceee4911818420/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303231313132343135303334313133332e706e67"><img src="https://camo.githubusercontent.com/1cbc28be91f5d42dd2fa2c859b7dc32727bcd50552924e9bedceee4911818420/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303231313132343135303334313133332e706e67" alt="Map Remote" data-canonical-src="https://banyudu.github.io/images/image-20211124150341133.png" style="max-width: 100%;"></a></p>
<div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">基于浏览器插件的中间人攻击</h4><a id="user-content-基于浏览器插件的中间人攻击" class="anchor" aria-label="Permalink: 基于浏览器插件的中间人攻击" href="#基于浏览器插件的中间人攻击"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">因为一些安全方面的考量,浏览器插件并不能实现系统级别网络代理带来的无感知体验。</p>
<p dir="auto">但是它可以为网络请求做重定向。</p>
<p dir="auto">即虽然浏览器插件能篡改网络接口,但是在DevTools中会有记录,且会受到各种浏览器自身的限制,如混合内容限制等。</p>
<p dir="auto">所以它的特点是:</p>
<ol dir="auto">
<li>存在混合内容限制(127.0.0.1除外)。</li>
<li>存在跨域限制。</li>
<li>对于前端应用的 webpack 分片,需要特殊处理(publicPath)。</li>
<li>配置和切换较为简单</li>
</ol>
<p dir="auto">浏览器插件可以使用 Resource Override。</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/3c33abd0449c5e000622dca764dfdebb5d575a00c825c75e9f3d9fb538f448f0/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f7363456969455468365f61697449505478523878326d567255653767377a7064317a6f5235647866774c4e4743696d3038367045434c7575437a4b325348676751787174496b4676357639427935746130312d37484d6e5a4d413d773634302d683430302d653336352d726a2d7363307830306666666666662d32303231313132343134303831383733392e6a706567"><img src="https://camo.githubusercontent.com/3c33abd0449c5e000622dca764dfdebb5d575a00c825c75e9f3d9fb538f448f0/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f7363456969455468365f61697449505478523878326d567255653767377a7064317a6f5235647866774c4e4743696d3038367045434c7575437a4b325348676751787174496b4676357639427935746130312d37484d6e5a4d413d773634302d683430302d653336352d726a2d7363307830306666666666662d32303231313132343134303831383733392e6a706567" alt="img" data-canonical-src="https://banyudu.github.io/images/scEiiETh6_aitIPTxR8x2mVrUe7g7zpd1zoR5dxfwLNGCim086pECLuuCzK2SHggQxqtIkFv5v9By5ta01-7HMnZMA=w640-h400-e365-rj-sc0x00ffffff-20211124140818739.jpeg" style="max-width: 100%;"></a></p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">网络安全</h2><a id="user-content-网络安全" class="anchor" aria-label="Permalink: 网络安全" href="#网络安全"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">通过上面的例子也可以看出,在网络无保障的情况下,前端看到的内容很可能是不安全的。</p>
<p dir="auto">这提醒我们,在安装证书,尤其是CA证书时,一定要谨慎。</p>
<p dir="auto">不然很可能会受到中间人攻击而不自知。</p>
<hr>
<p dir="auto">以上就是通过网络代理(转发)调试前端应用的所有内容。</p>
</article>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/379914d0c0c5c982787b61dbf1ff7d5f
2021-10-15T04:03:39Z
2024-04-08T07:54:53Z
多套Git全局配置并存方案
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/379914d0c0c5c982787b61dbf1ff7d5f#file-setup-multiple-global-config-for-git-blog-md">setup-multiple-global-config-for-git.blog.md</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-setup-multiple-global-config-for-git-blog-md" class="file my-2">
<div id="file-setup-multiple-global-config-for-git-blog-md-readme" class="Box-body readme blob p-5 p-xl-6 "
style="overflow: auto" tabindex="0" role="region"
aria-label="setup-multiple-global-config-for-git.blog.md content, created by banyudu on 04:03AM on October 15, 2021."
>
<article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">多套Git全局配置并存方案</h1><a id="user-content-多套git全局配置并存方案" class="anchor" aria-label="Permalink: 多套Git全局配置并存方案" href="#多套git全局配置并存方案"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">背景</h2><a id="user-content-背景" class="anchor" aria-label="Permalink: 背景" href="#背景"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">相信很多人经历过与我类似的困扰,工作和业余项目的作者信息相互干扰。</p>
<p dir="auto">具体来说,就是希望工作项目都使用工作邮箱作为 <code>user.email</code>的配置,而业余项目都使用个人邮箱作为<code>user.email</code>的配置,<code>user.name</code>字段同理。</p>
<p dir="auto">因为有两套不同的作者信息,所以就需要有两套不同的Git配置。</p>
<p dir="auto">但是因为 Git 的全局配置只能有一套,所以就只能在各个项目中单独配置至少一方的配置文件,比较麻烦且容易出错。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">解决方案</h2><a id="user-content-解决方案" class="anchor" aria-label="Permalink: 解决方案" href="#解决方案"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">我设想的方案是这样的:</p>
<ol dir="auto">
<li>通过目录结构将Git仓库分组。如工作项目检出到 <code>~/work/<子目录></code> 下,而业余项目检出到 <code>~/personal/<子目录></code>下。</li>
<li>为每个目录单独保存一份全局配置。</li>
</ol>
<p dir="auto">如果这个方案可行的话,只需要将Git仓库检出到相应的子目录之中,就可以自动应用正确的全局配置,无需再为每个仓库单独配置。</p>
<p dir="auto">经过一番搜索,我在 <a href="https://stackoverflow.com/questions/21307793/set-git-config-values-for-all-child-folders" rel="nofollow">StackOverflow</a> 中找到了为每个目录单独保存一份全局配置的方案,那就是 <a href="https://git-scm.com/docs/git-config#_conditional_includes" rel="nofollow">conditional_includes</a>。</p>
<p dir="auto">原理很简单,就是在Git的全局配置(如 Mac / Linux 系统中的 <code>~/.gitconfig</code> 文件)中,使用 <code>includeIf</code> 关键字和一些条件做判断,如果符合条件,就会载入相应的配置。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">实例</h2><a id="user-content-实例" class="anchor" aria-label="Permalink: 实例" href="#实例"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">举个例子说明具体的用法</p>
<p dir="auto">假如需要设置 <code>~/work/</code> 下所有的Git仓库的默认作者为“小猪佩奇”,而 <code>~/personal/</code> 下所有的Git仓库的默认作者为“小马宝莉”,则可以编辑 <code>~/.gitconfig</code>文件,添加如下的内容:</p>
<div class="highlight highlight-source-ini" dir="auto"><pre><span class="pl-en">[includeIf "gitdir:~/work/"]</span>
<span class="pl-k">path</span> = ~/work/.gitconfig
<span class="pl-en">[includeIf "gitdir:~/personal/"]</span>
<span class="pl-k">path</span> = ~/personal/.gitconfig</pre></div>
<p dir="auto">同时在 <code>~/work/</code> 和 <code>~/personal/</code> 目录下各创建一个 <code>.gitconfig</code> 文件(文件名随意,但要和上面的 path 对应上)。</p>
<div class="highlight highlight-source-ini" dir="auto"><pre><span class="pl-c"><span class="pl-c">#</span> ~/work/.gitconfig 文件</span>
<span class="pl-en">[user]</span>
<span class="pl-k">name</span> = 小猪佩奇</pre></div>
<div class="highlight highlight-source-ini" dir="auto"><pre><span class="pl-c"><span class="pl-c">#</span> ~/personal/.gitconfig 文件</span>
<span class="pl-en">[user]</span>
<span class="pl-k">name</span> = 小马宝莉</pre></div>
<p dir="auto">这样一来就完成了。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">工具固化</h2><a id="user-content-工具固化" class="anchor" aria-label="Permalink: 工具固化" href="#工具固化"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">为了简化上述的流程,我创建了一个工具 <a href="https://github.com/banyudu/git-dir-config">git-dir-config</a>,自动管理上面的流程,这样就可以简化操作为:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre><span class="pl-c1">cd</span> <span class="pl-k">~</span>/work <span class="pl-k">&&</span> git dir-config user.name 小猪佩奇
<span class="pl-c1">cd</span> <span class="pl-k">~</span>/personal <span class="pl-k">&&</span> git dir-config user.name 小马宝莉</pre></div>
<p dir="auto">使用之前需要先全局安装这个npm包:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>npm i -g git-dir-config</pre></div>
</article>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/f7472f935897adfbb54cc8eb38dc5373
2021-10-11T05:43:27Z
2024-11-11T06:47:00Z
Figma插件开发浅浅谈
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/f7472f935897adfbb54cc8eb38dc5373#file-figma-plugin-development-intro-blog-md">figma-plugin-development-intro.blog.md</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-figma-plugin-development-intro-blog-md" class="file my-2">
<div id="file-figma-plugin-development-intro-blog-md-readme" class="Box-body readme blob p-5 p-xl-6 "
style="overflow: auto" tabindex="0" role="region"
aria-label="figma-plugin-development-intro.blog.md content, created by banyudu on 05:43AM on October 11, 2021."
>
<article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Figma插件开发浅浅谈</h2><a id="user-content-figma插件开发浅浅谈" class="anchor" aria-label="Permalink: Figma插件开发浅浅谈" href="#figma插件开发浅浅谈"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Figma是一款优秀的设计工具,不仅可以便捷地实现协作开发,同时还提供了丰富的扩展能力,使得我们可以通过编写插件实现自定义的功能。</p>
<p dir="auto">Figma官方是有提供<a href="https://www.figma.com/plugin-docs/intro/" rel="nofollow">开发者文档</a>的,资料也比较齐全。推荐同时读一下 <a href="https://www.figma.com/plugin-docs/intro/" rel="nofollow">https://www.figma.com/plugin-docs/intro/</a>。</p>
<p dir="auto">这篇文章里面,我会先从介绍Figma插件的概念说起,让读者对Figma插件大致有个印象,然后会再介绍一些插件开发过程中的实践经验和心得体会。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Figma插件开发简介</h2><a id="user-content-figma插件开发简介" class="anchor" aria-label="Permalink: Figma插件开发简介" href="#figma插件开发简介"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">技术栈</h3><a id="user-content-技术栈" class="anchor" aria-label="Permalink: 技术栈" href="#技术栈"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Figma插件类似于浏览器插件,使用 Web 技术(HTML/CSS/JS)进行开发。开发过浏览器插件的同学应该能很快上手,即使没有浏览器的开发经验,只要有过前端开发的经验,入门写一个插件也并不困难。此外常用的JS框架也可以正常使用,如React、Vue框架、Antd组件库及Webpack打包工具链等。</p>
<p dir="auto">因为Figma插件一般用于操作Figma文件,所以在开发Figma插件的过程中,也会需要学习一些Figma内部的节点类型和各自的属性和方法,这些在官方文档中都可以找到。</p>
<p dir="auto">值得一提的是Figma默认采用了Typescript,同时也为Figma的数据格式提供了丰富的类型定义,会大大降低开发的难度。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">运行时</h3><a id="user-content-运行时" class="anchor" aria-label="Permalink: 运行时" href="#运行时"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">不同于技术栈的类似,Figma插件在运行时与浏览器插件会有较大的不同。</p>
<p dir="auto">浏览器插件一般会分为 popup 页、background 代码和content script三个部分,各自有不同的能力和使用场景。Figma插件主要是分成 main 和 ui 两个部分。</p>
<p dir="auto">Figma插件的 main 和 ui 两个部分虽然都可以认为是JS,但是运行环境和能力有较大的不同。</p>
<p dir="auto">在讲具体的区别之前,先要理解Figma自身的运行环境。Figma既有自身的客户端软件(其实也是内嵌了web),又可以直接运行在浏览器之中。它使用了Web相关的技术(HTML/CSS/JS/WASM)。简单来说,Figma自身就是一个功能比较强大的Web应用。</p>
<p dir="auto">出于安全和性能等方面的考虑,Figma将插件代码分成两个部分:main 和 ui。其中 main 代码运行在沙箱之中,ui 部分代码运行在 iframe 之中。</p>
<p dir="auto">Figma为插件提供的沙箱环境,可以访问到 figma 全局变量用于获取文档内容,但是屏蔽了其它的各种全局变量,如window、document以及fetch等,可以避免恶意插件越权访问未授权的内容或破坏程序运行环境,提升安全性。</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/bce519e9c732fc4d844d7da7ce2c85247fcc04d8fb18cfe3a6b97d6aa6f78b64/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f6669676d612d706c7567696e2e64726177696f2e706e67"><img src="https://camo.githubusercontent.com/bce519e9c732fc4d844d7da7ce2c85247fcc04d8fb18cfe3a6b97d6aa6f78b64/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f6669676d612d706c7567696e2e64726177696f2e706e67" alt="figma-plugin.drawio" data-canonical-src="https://banyudu.github.io/images/figma-plugin.drawio.png" style="max-width: 100%;"></a></p>
<p dir="auto">因为运行时代码是分为两个部分的,编程过程中不可避免地会涉及到两个窗口之间的通信。Figma中是用postMessage完成的。</p>
<p dir="auto">原理部分讲得差不多了,下面来讲下具体的开发实践。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">Figma插件开发实践</h2><a id="user-content-figma插件开发实践" class="anchor" aria-label="Permalink: Figma插件开发实践" href="#figma插件开发实践"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">初始化工程</h3><a id="user-content-初始化工程" class="anchor" aria-label="Permalink: 初始化工程" href="#初始化工程"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">开发插件最好是从一个现成可用的插件改起。在这方面figma提供了一些友好的帮助。</p>
<p dir="auto">可以选择从Figma客户端软件的 "Plugins -> Development -> New Plugin"中通过交互页面生成一个插件模板,也可以直接从Github中找到示例代码。</p>
<p dir="auto">Figma客户端软件中提供的demo比较简单,建议从Github仓库的Samples中找个例子开始,如使用React的话,可以使用模板工程:<a href="https://github.com/figma/plugin-samples/tree/master/webpack-react">https://github.com/figma/plugin-samples/tree/master/webpack-react</a></p>
<p dir="auto">查看更多模板工程:<a href="https://github.com/figma/plugin-samples/">https://github.com/figma/plugin-samples/</a></p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">调试插件</h3><a id="user-content-调试插件" class="anchor" aria-label="Permalink: 调试插件" href="#调试插件"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">调试插件需要有一个Figma本地客户端,从菜单"Plugins -> Development -> Import plugin from manifest"进入,选择上一步中创建的figma插件中的manifest.json文件即可。</p>
<p dir="auto">在选择之后,figma插件就会出现在"Plugins -> Development" 的子菜单之中。</p>
<p dir="auto">先看一个manifest.json文件的经典结构:</p>
<div class="highlight highlight-source-json" dir="auto"><pre>{
<span class="pl-ent">"name"</span>: <span class="pl-s"><span class="pl-pds">"</span>test<span class="pl-pds">"</span></span>,
<span class="pl-ent">"id"</span>: <span class="pl-s"><span class="pl-pds">"</span>1029157888727225342<span class="pl-pds">"</span></span>,
<span class="pl-ent">"api"</span>: <span class="pl-s"><span class="pl-pds">"</span>1.0.0<span class="pl-pds">"</span></span>,
<span class="pl-ent">"main"</span>: <span class="pl-s"><span class="pl-pds">"</span>code.js<span class="pl-pds">"</span></span>,
<span class="pl-ent">"editorType"</span>: [
<span class="pl-s"><span class="pl-pds">"</span>figma<span class="pl-pds">"</span></span>
],
<span class="pl-ent">"ui"</span>: <span class="pl-s"><span class="pl-pds">"</span>ui.html<span class="pl-pds">"</span></span>
}</pre></div>
<p dir="auto">其中的 main 和 ui 两个 key 分别指定的就是 plugin 代码(运行在沙箱之中)和 ui 代码(运行在iframe之中)的入口了。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">热更新</h3><a id="user-content-热更新" class="anchor" aria-label="Permalink: 热更新" href="#热更新"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">对于前端的研发同学来说,可能比较习惯于热更新的开发模式,但是Figma插件的官方samples仓库中尚未提供关于热更新的demo,所以这里我着重讲一下热更新的实现。</p>
<p dir="auto">在开始之前先要强调一下,因为main代码运行在沙箱环境中,而沙箱环境未提供网络访问的能力,所以目前尚未找到沙箱中的代码的热更新方式,只能实现自动编译,不能自动加载。这里指的热更新,是指的UI中的热更新。</p>
<p dir="auto">在开始配置热更新之前,webpack配置文件的关键代码如下(<a href="https://github.com/figma/plugin-samples/blob/master/webpack-react/webpack.config.js">查看完整版</a>):</p>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-k">const</span> <span class="pl-v">InlineChunkHtmlPlugin</span> <span class="pl-c1">=</span> <span class="pl-en">require</span><span class="pl-kos">(</span><span class="pl-s">'react-dev-utils/InlineChunkHtmlPlugin'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-k">const</span> <span class="pl-v">HtmlWebpackPlugin</span> <span class="pl-c1">=</span> <span class="pl-en">require</span><span class="pl-kos">(</span><span class="pl-s">'html-webpack-plugin'</span><span class="pl-kos">)</span>
<span class="pl-smi">module</span><span class="pl-kos">.</span><span class="pl-en">exports</span> <span class="pl-c1">=</span> <span class="pl-kos">(</span><span class="pl-s1">env</span><span class="pl-kos">,</span> <span class="pl-s1">argv</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">(</span><span class="pl-kos">{</span>
<span class="pl-c1">entry</span>: <span class="pl-kos">{</span>
<span class="pl-c1">ui</span>: <span class="pl-s">'./src/ui.tsx'</span><span class="pl-kos">,</span> <span class="pl-c">// The entry point for your UI code</span>
<span class="pl-c1">code</span>: <span class="pl-s">'./src/code.ts'</span><span class="pl-kos">,</span> <span class="pl-c">// The entry point for your plugin code</span>
<span class="pl-kos">}</span><span class="pl-kos">,</span>
<span class="pl-c1">output</span>: <span class="pl-kos">{</span>
<span class="pl-c1">filename</span>: <span class="pl-s">'[name].js'</span><span class="pl-kos">,</span>
<span class="pl-c1">path</span>: <span class="pl-s1">path</span><span class="pl-kos">.</span><span class="pl-en">resolve</span><span class="pl-kos">(</span><span class="pl-s1">__dirname</span><span class="pl-kos">,</span> <span class="pl-s">'dist'</span><span class="pl-kos">)</span><span class="pl-kos">,</span> <span class="pl-c">// Compile into a folder called "dist"</span>
<span class="pl-kos">}</span><span class="pl-kos">,</span>
<span class="pl-c1">plugins</span>: <span class="pl-kos">[</span>
<span class="pl-k">new</span> <span class="pl-v">HtmlWebpackPlugin</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
<span class="pl-c1">inject</span>: <span class="pl-s">"body"</span><span class="pl-kos">,</span>
<span class="pl-c1">template</span>: <span class="pl-s">'./src/ui.html'</span><span class="pl-kos">,</span>
<span class="pl-c1">filename</span>: <span class="pl-s">'ui.html'</span><span class="pl-kos">,</span>
<span class="pl-c1">chunks</span>: <span class="pl-kos">[</span><span class="pl-s">'ui'</span><span class="pl-kos">]</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">,</span>
<span class="pl-k">new</span> <span class="pl-v">InlineChunkHtmlPlugin</span><span class="pl-kos">(</span><span class="pl-v">HtmlWebpackPlugin</span><span class="pl-kos">,</span> <span class="pl-kos">[</span><span class="pl-pds"><span class="pl-c1">/</span><span class="pl-s">u</span><span class="pl-s">i</span><span class="pl-c1">/</span></span><span class="pl-kos">]</span><span class="pl-kos">)</span><span class="pl-kos">,</span>
<span class="pl-kos">]</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span></pre></div>
<p dir="auto">按照这个配置文件,webpack会将代码打包出 code.js 和 ui.html 两个文件。ui.js 相关的内容会内联注入到 ui.html 文件之中。</p>
<p dir="auto">要支持热更新,就需要做出如下的改变(development模式中):</p>
<ol dir="auto">
<li>启用 webpackDevServer,以提供热更新需要的资源地址和 websocket 接口。</li>
<li>不使用InlineChunkHtmlPlugin进行注入,而是维持独立的入口JS文件地址</li>
<li>代码中使用 react-hot-loader 进行热更新</li>
<li>package.json中同时执行 watch 模式,实时编译 code.js 的代码</li>
</ol>
<p dir="auto">下面看一下关键代码:</p>
<blockquote>
<p dir="auto">package.json</p>
</blockquote>
<div class="highlight highlight-source-json" dir="auto"><pre>{
<span class="pl-ent">"scripts"</span>: {
<span class="pl-ent">"dev:plugin"</span>: <span class="pl-s"><span class="pl-pds">"</span>cross-env NODE_ENV=development webpack --mode=development --watch<span class="pl-pds">"</span></span>,
<span class="pl-ent">"dev:ui"</span>: <span class="pl-s"><span class="pl-pds">"</span>cross-env NODE_ENV=development webpack serve --mode=development<span class="pl-pds">"</span></span>,
<span class="pl-ent">"dev"</span>: <span class="pl-s"><span class="pl-pds">"</span>run-p dev:plugin dev:ui<span class="pl-pds">"</span></span>,
}
}</pre></div>
<p dir="auto">其中 dev:plugin 会来打包 plugin 部分的代码,即运行在沙箱环境中的部分。这一部分代码不会被热更新,只会被实时编译。当它发生变化的时候,需要重新载入插件才能生效。</p>
<p dir="auto">而 dev:ui 的代码用来启动 webpackDevServer,主要用于提供 ui.js。</p>
<blockquote>
<p dir="auto">webpack.config.js</p>
</blockquote>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-smi">module</span><span class="pl-kos">.</span><span class="pl-en">exports</span> <span class="pl-c1">=</span> <span class="pl-kos">(</span><span class="pl-s1">env</span><span class="pl-kos">,</span> <span class="pl-s1">argv</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-k">const</span> <span class="pl-s1">isProduction</span> <span class="pl-c1">=</span> <span class="pl-s1">argv</span><span class="pl-kos">.</span><span class="pl-c1">mode</span> <span class="pl-c1">===</span> <span class="pl-s">'production'</span>
<span class="pl-k">return</span> <span class="pl-kos">{</span>
<span class="pl-c1">devtool</span>: <span class="pl-s1">isProduction</span> ? <span class="pl-c1">false</span> : <span class="pl-s">'inline-source-map'</span><span class="pl-kos">,</span>
<span class="pl-c1">entry</span>: <span class="pl-kos">{</span>
<span class="pl-c1">ui</span>: <span class="pl-s">'./src/ui.tsx'</span><span class="pl-kos">,</span>
<span class="pl-c1">code</span>: <span class="pl-s">'./src/code.ts'</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">,</span>
<span class="pl-c1">resolve</span>: <span class="pl-kos">{</span>
<span class="pl-c1">extensions</span>: <span class="pl-kos">[</span><span class="pl-s">'.tsx'</span><span class="pl-kos">,</span> <span class="pl-s">'.ts'</span><span class="pl-kos">,</span> <span class="pl-s">'.jsx'</span><span class="pl-kos">,</span> <span class="pl-s">'.js'</span><span class="pl-kos">]</span><span class="pl-kos">,</span>
<span class="pl-c1">alias</span>: <span class="pl-kos">{</span> <span class="pl-s">'react-dom'</span>: <span class="pl-s">'@hot-loader/react-dom'</span> <span class="pl-kos">}</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">,</span>
<span class="pl-c1">output</span>: <span class="pl-kos">{</span>
<span class="pl-c1">filename</span>: <span class="pl-s">'[name].js'</span><span class="pl-kos">,</span>
<span class="pl-c1">path</span>: <span class="pl-s1">path</span><span class="pl-kos">.</span><span class="pl-en">resolve</span><span class="pl-kos">(</span><span class="pl-s1">__dirname</span><span class="pl-kos">,</span> <span class="pl-s">'dist'</span><span class="pl-kos">)</span><span class="pl-kos">,</span>
<span class="pl-c1">publicPath</span>: <span class="pl-s">'https://localhost:8000/'</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">,</span>
<span class="pl-c1">plugins</span>: <span class="pl-kos">[</span>
<span class="pl-k">new</span> <span class="pl-v">HtmlWebpackPlugin</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
<span class="pl-c1">template</span>: <span class="pl-s">'./src/ui.html'</span><span class="pl-kos">,</span>
<span class="pl-c1">filename</span>: <span class="pl-s">'ui.html'</span><span class="pl-kos">,</span>
<span class="pl-c1">chunks</span>: <span class="pl-kos">[</span><span class="pl-s">'ui'</span><span class="pl-kos">]</span><span class="pl-kos">,</span>
<span class="pl-c1">inject</span>: <span class="pl-s">'body'</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">,</span>
<span class="pl-s1">isProduction</span> <span class="pl-c1">&&</span> <span class="pl-k">new</span> <span class="pl-v">InlineChunkHtmlPlugin</span><span class="pl-kos">(</span><span class="pl-v">HtmlWebpackPlugin</span><span class="pl-kos">,</span> <span class="pl-kos">[</span><span class="pl-pds"><span class="pl-c1">/</span><span class="pl-s">u</span><span class="pl-s">i</span><span class="pl-c1">/</span></span><span class="pl-kos">]</span><span class="pl-kos">)</span><span class="pl-kos">,</span>
<span class="pl-kos">]</span><span class="pl-kos">.</span><span class="pl-en">filter</span><span class="pl-kos">(</span><span class="pl-v">Boolean</span><span class="pl-kos">)</span><span class="pl-kos">,</span>
<span class="pl-c1">devServer</span>: <span class="pl-s1">isProduction</span>
? <span class="pl-c1">undefined</span>
: <span class="pl-kos">{</span>
<span class="pl-c1">port</span>: <span class="pl-c1">8000</span><span class="pl-kos">,</span>
<span class="pl-c1">host</span>: <span class="pl-s">'0.0.0.0'</span><span class="pl-kos">,</span>
<span class="pl-c1">allowedHosts</span>: <span class="pl-s">'all'</span><span class="pl-kos">,</span>
<span class="pl-c1">hot</span>: <span class="pl-c1">true</span><span class="pl-kos">,</span>
<span class="pl-c1">headers</span>: <span class="pl-kos">{</span>
<span class="pl-s">'Access-Control-Allow-Origin'</span>: <span class="pl-s">'*'</span><span class="pl-kos">,</span>
<span class="pl-s">'Access-Control-Allow-Methods'</span>:
<span class="pl-s">'GET, POST, PUT, DELETE, PATCH, OPTIONS'</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">,</span>
<span class="pl-c1">client</span>: <span class="pl-kos">{</span>
<span class="pl-c1">webSocketURL</span>: <span class="pl-s">'ws://127.0.0.1:8000/ws'</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span><span class="pl-kos">,</span>
<span class="pl-kos">}</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">最后是启用下 react-hot-loader</p>
<blockquote>
<p dir="auto">ui.tsx</p>
</blockquote>
<div class="highlight highlight-source-ts" dir="auto"><pre><span class="pl-c">// <span class="pl-k">@ts</span>-ignore</span>
<span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-smi">module</span><span class="pl-kos">.</span><span class="pl-c1">hot</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-c">// <span class="pl-k">@ts</span>-ignore</span>
<span class="pl-smi">module</span><span class="pl-kos">.</span><span class="pl-c1">hot</span><span class="pl-kos">.</span><span class="pl-c1">accept</span><span class="pl-kos">(</span><span class="pl-s">'./ui.tsx'</span><span class="pl-kos">,</span> <span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-c1">render</span><span class="pl-kos">(</span><span class="pl-c1"><</span><span class="pl-smi">App</span><span class="pl-c1"></span> <span class="pl-c1">/</span><span class="pl-s">></span><span class="pl-s">,</span><span class="pl-s"> </span><span class="pl-s">d</span><span class="pl-s">o</span><span class="pl-s">c</span><span class="pl-s">u</span><span class="pl-s">m</span><span class="pl-s">e</span><span class="pl-s">n</span><span class="pl-s">t</span>.<span class="pl-s">g</span><span class="pl-s">e</span><span class="pl-s">t</span><span class="pl-s">E</span><span class="pl-s">l</span><span class="pl-s">e</span><span class="pl-s">m</span><span class="pl-s">e</span><span class="pl-s">n</span><span class="pl-s">t</span><span class="pl-s">B</span><span class="pl-s">y</span><span class="pl-s">I</span><span class="pl-s">d</span><span class="pl-kos">(</span><span class="pl-s">'</span><span class="pl-s">m</span><span class="pl-s">y</span><span class="pl-s">-</span><span class="pl-s">p</span><span class="pl-s">l</span><span class="pl-s">u</span><span class="pl-s">g</span><span class="pl-s">i</span><span class="pl-s">n</span><span class="pl-s">'</span><span class="pl-kos">)</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span></pre></div>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">窗口间通信</h3><a id="user-content-窗口间通信" class="anchor" aria-label="Permalink: 窗口间通信" href="#窗口间通信"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">最后再说一下窗口间的通信。</p>
<p dir="auto">上面提到,Figma插件的代码分为 ui 和 main 两个部分,其中 ui 部分能访问网络和存储等,但不能访问到Figma文档的数据,而 main 部分可以读写 figma 文件,却不能访问外部网络。</p>
<p dir="auto">当插件既需要访问网络,又需要对figma文件做读写的时候,一般就会涉及到两个窗口之间的通信的问题。</p>
<p dir="auto">虽然通信机制被 figma 包装了一层,但本质上还是 Window.postMessage 那一套。</p>
<p dir="auto">在 ui 中想要获取 figma 的文档内容的话,就需要先 postMessage 发给 main,然后 main 中处理数据完毕后,再 postMessage 给 ui,需要双方约定一些机制,才能让这个机制相对来说比较好用。</p>
<p dir="auto">这里我采用的是一种 callback 的机制,ui 中 postMessage 的时候,带上 callback 函数,内部自动 id 参数,同时 figma 的 onmessage 处理后,再发送一个带相同 id 参数的 postMessage,根据 id 定位到 callback 函数并执行回调。</p>
<p dir="auto">具体的实现可以参考下面给出的Github仓库。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">React-Router</h3><a id="user-content-react-router" class="anchor" aria-label="Permalink: React-Router" href="#react-router"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">因为 figma 插件 iframe 中用的 src 地址是一个 dataurl,所以在用 react-router 的时候有一些注意事项。</p>
<p dir="auto">如不能使用 BrowserRouter,必须用 MemoryRouter。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">插件模板代码</h2><a id="user-content-插件模板代码" class="anchor" aria-label="Permalink: 插件模板代码" href="#插件模板代码"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">一码胜千言,最后我梳理了一个模板代码,包含了上面提到的内容,有需要的同学可以使用它作为模板进行开发。</p>
<p dir="auto">模板地址为:<a href="https://github.com/banyudu/figma-plugin-sample-react-hot-reload">https://github.com/banyudu/figma-plugin-sample-react-hot-reload</a></p>
</article>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/964ff9646879dbfabc8c7de3ca789002
2021-08-28T12:57:11Z
2024-10-17T09:29:59Z
使用Rust构建wasm包并发布到npm
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/964ff9646879dbfabc8c7de3ca789002#file-hello-wasm-with-rust-and-npm-blog-md">hello-wasm-with-rust-and-npm.blog.md</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-hello-wasm-with-rust-and-npm-blog-md" class="file my-2">
<div id="file-hello-wasm-with-rust-and-npm-blog-md-readme" class="Box-body readme blob p-5 p-xl-6 "
style="overflow: auto" tabindex="0" role="region"
aria-label="hello-wasm-with-rust-and-npm.blog.md content, created by banyudu on 12:57PM on August 28, 2021."
>
<article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">使用Rust构建wasm包并发布到npm</h1><a id="user-content-使用rust构建wasm包并发布到npm" class="anchor" aria-label="Permalink: 使用Rust构建wasm包并发布到npm" href="#使用rust构建wasm包并发布到npm"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/dba022659774d9fd01b1e26cd1a84a31b6e1701c995ab4c6c9511f298cb0213a/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f7761736d2d7061636b2d30312e706e67"><img src="https://camo.githubusercontent.com/dba022659774d9fd01b1e26cd1a84a31b6e1701c995ab4c6c9511f298cb0213a/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f7761736d2d7061636b2d30312e706e67" alt="wasm-pack-image" data-canonical-src="https://banyudu.github.io/images/wasm-pack-01.png" style="max-width: 100%;"></a></p>
<p dir="auto">WebAssembly既拥有大量的前端输入(Rust、C++、Go、AssemblyScript),又拥有大量的运行时支持,可以内嵌在大量的语言中运行,也可以独立运行,可以说是编程界的(未来)最佳配角了,结合npm使用自然不在话下。</p>
<p dir="auto">考虑到WebAssembly目前的发展度还不够成熟,为了避免踩坑,还是先尝试下最传统的使用场景:使用Rust编译wasm包,并通过npm发布,最后用于浏览器和Node.js之中。</p>
<p dir="auto">本文中我会使用Rust构建一个npm包,并分别在浏览器和Node.js中打印出 "Hello Wasm!" 的语句。</p>
<p dir="auto">在开始之前,需要配置下开发环境:</p>
<ul dir="auto">
<li>安装Rust:<a href="https://www.rust-lang.org/tools/install" rel="nofollow">https://www.rust-lang.org/tools/install</a></li>
<li>安装 wasm-pack: <a href="https://rustwasm.github.io/wasm-pack/installer/" rel="nofollow">https://rustwasm.github.io/wasm-pack/installer/</a></li>
<li>安装Node.js及相关工具包</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">新建工程</h2><a id="user-content-新建工程" class="anchor" aria-label="Permalink: 新建工程" href="#新建工程"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">可以使用 wasm-pack 工具快速地初始化一个 wasm 包的工程</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>$ wasm-pack new hello-wasm
[INFO]: ⬇️ Installing cargo-generate...
🐑 Generating a new rustwasm project with name <span class="pl-s"><span class="pl-pds">'</span>hello-wasm<span class="pl-pds">'</span></span>...
🔧 Creating project called <span class="pl-s"><span class="pl-pds">`</span>hello-wasm<span class="pl-pds">`</span></span>...
✨ Done<span class="pl-k">!</span> New project created /private/tmp/wasm/hello-wasm
[INFO]: 🐑 Generated new project at /hello-wasm</pre></div>
<p dir="auto">这个命令会创建一个Rust的工程,包含如下的代码结构:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>$ <span class="pl-c1">cd</span> hello-wasm
$ tree <span class="pl-c1">.</span>
<span class="pl-c1">.</span>
├── Cargo.toml
├── LICENSE_APACHE
├── LICENSE_MIT
├── README.md
├── src
│ ├── lib.rs
│ └── utils.rs
└── tests
└── web.rs
2 directories, 7 files</pre></div>
<p dir="auto">里面比较重要的有两个文件,一个是 Cargo.toml ,是整个项目的配置文件,类似于 package.json。</p>
<div class="highlight highlight-source-toml" dir="auto"><pre>[<span class="pl-en">package</span>]
<span class="pl-smi">name</span> = <span class="pl-s"><span class="pl-pds">"</span>hello-wasm<span class="pl-pds">"</span></span>
<span class="pl-smi">version</span> = <span class="pl-s"><span class="pl-pds">"</span>0.1.0<span class="pl-pds">"</span></span>
[<span class="pl-en">lib</span>]
<span class="pl-smi">crate-type</span> = [<span class="pl-s"><span class="pl-pds">"</span>cdylib<span class="pl-pds">"</span></span>, <span class="pl-s"><span class="pl-pds">"</span>rlib<span class="pl-pds">"</span></span>]
[<span class="pl-en">dependencies</span>]
<span class="pl-smi">wasm-bindgen</span> = <span class="pl-s"><span class="pl-pds">"</span>0.2.63<span class="pl-pds">"</span></span>
</pre></div>
<p dir="auto">这里的 name 即对应于 npm 包的 name,可以按需修改。注意这里不支持 @scope,如果需要 scope,可以在下面构建步骤中添加。</p>
<p dir="auto">另一个是 src/lib.rs ,包含着核心代码:</p>
<div class="highlight highlight-source-rust" dir="auto"><pre><span class="pl-k">use</span> wasm_bindgen<span class="pl-kos">::</span>prelude<span class="pl-kos">::</span><span class="pl-c1">*</span><span class="pl-kos">;</span>
<span class="pl-c1">#<span class="pl-kos">[</span>wasm_bindgen<span class="pl-kos">]</span></span>
<span class="pl-k">extern</span> <span class="pl-kos">{</span>
<span class="pl-k">fn</span> <span class="pl-en">alert</span><span class="pl-kos">(</span><span class="pl-s1">s</span><span class="pl-kos">:</span> <span class="pl-c1">&</span><span class="pl-smi">str</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span>
<span class="pl-c1">#<span class="pl-kos">[</span>wasm_bindgen<span class="pl-kos">]</span></span>
<span class="pl-k">pub</span> <span class="pl-k">fn</span> <span class="pl-en">greet</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-en">alert</span><span class="pl-kos">(</span><span class="pl-s">"Hello, hello-wasm!"</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span></pre></div>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">修改代码</h2><a id="user-content-修改代码" class="anchor" aria-label="Permalink: 修改代码" href="#修改代码"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">默认生成的代码里面,通过<code>wasm_bindgen</code>声明了使用外部的 <code>alert</code> 函数,并导出了一个 <code>greet</code> 函数,在调用的时候会使用 <code>alert</code> 提示一段文本。</p>
<p dir="auto">这就限制了它只能用在浏览器里面,因为Node.js中默认不带有全局的 alert 函数。</p>
<p dir="auto">为了使这个包既能用于浏览器之中,也能用于Node.js之中,我需要把它修改成调用 console.log,而非 alert。</p>
<p dir="auto">因为 console.log 不是 Rust 的内置方法,所以也需要使用 wasm_bindgen 声明,类似于 alert。</p>
<p dir="auto"><a href="https://rustwasm.github.io/docs/wasm-bindgen/examples/console-log.html" rel="nofollow">wasm_bindgen的帮助文档</a>中给了两个使用 <code>console.log</code>的方法,分别如下:</p>
<p dir="auto"><strong>第一种,使用 extern 声明</strong></p>
<div class="highlight highlight-source-rust" dir="auto"><pre><span class="pl-c1">#<span class="pl-kos">[</span>wasm_bindgen<span class="pl-kos">]</span></span>
<span class="pl-k">extern</span> <span class="pl-s">"C"</span> <span class="pl-kos">{</span>
<span class="pl-c1">#<span class="pl-kos">[</span>wasm_bindgen<span class="pl-kos">(</span>js_namespace = console<span class="pl-kos">)</span><span class="pl-kos">]</span></span>
<span class="pl-k">fn</span> <span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-s1">s</span><span class="pl-kos">:</span> <span class="pl-c1">&</span><span class="pl-smi">str</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-c1">#<span class="pl-kos">[</span>wasm_bindgen<span class="pl-kos">(</span>js_namespace = console<span class="pl-kos">,</span> js_name = log<span class="pl-kos">)</span><span class="pl-kos">]</span></span>
<span class="pl-k">fn</span> <span class="pl-en">log_u32</span><span class="pl-kos">(</span><span class="pl-s1">a</span><span class="pl-kos">:</span> <span class="pl-smi">u32</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-c1">#<span class="pl-kos">[</span>wasm_bindgen<span class="pl-kos">(</span>js_namespace = console<span class="pl-kos">,</span> js_name = log<span class="pl-kos">)</span><span class="pl-kos">]</span></span>
<span class="pl-k">fn</span> <span class="pl-en">log_many</span><span class="pl-kos">(</span><span class="pl-s1">a</span><span class="pl-kos">:</span> <span class="pl-c1">&</span><span class="pl-smi">str</span><span class="pl-kos">,</span> <span class="pl-s1">b</span><span class="pl-kos">:</span> <span class="pl-c1">&</span><span class="pl-smi">str</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span>
<span class="pl-k">fn</span> <span class="pl-en">bare_bones</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-s">"Hello from Rust!"</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-en">log_u32</span><span class="pl-kos">(</span><span class="pl-c1">42</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-en">log_many</span><span class="pl-kos">(</span><span class="pl-s">"Logging"</span><span class="pl-kos">,</span> <span class="pl-s">"many values!"</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">这个示例中给了三个函数,分别覆盖 console.log 可接受参数的一部分子集。因为Rust是强类型的语言,并且不像TS那样可以声明 union 类型(也许可以,但是我还没学到),所以用起来不会像 JS 或 TS 那样灵活。虽然 TS 号称是强类型语言,但是和这种真正的强类型比起来还是方便太多了。</p>
<p dir="auto">Rust中也支持泛型和可变参数列表,只是可能定义起来会比较复杂,例子中暂时没涉及到。暂时不必灰心,且慢慢了解吧。</p>
<p dir="auto"><strong>第二种,使用web_sys库</strong></p>
<p dir="auto">Rust中有 <code>crates</code> 模块仓库,类似于 npm 仓库。其中有一个 <a href="https://crates.io/crates/web-sys" rel="nofollow">web_sys</a> 库就可以提供 <code>console.log</code> 工具。</p>
<p dir="auto">用法如下:</p>
<div class="highlight highlight-source-rust" dir="auto"><pre><span class="pl-k">fn</span> <span class="pl-en">using_web_sys</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-k">use</span> web_sys<span class="pl-kos">::</span>console<span class="pl-kos">;</span>
console<span class="pl-kos">::</span><span class="pl-en">log_1</span><span class="pl-kos">(</span><span class="pl-c1">&</span><span class="pl-s">"Hello using web-sys"</span><span class="pl-kos">.</span><span class="pl-en">into</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-k">let</span> js<span class="pl-kos">:</span> <span class="pl-smi">JsValue</span> = <span class="pl-c1">4</span><span class="pl-kos">.</span><span class="pl-en">into</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
console<span class="pl-kos">::</span><span class="pl-en">log_2</span><span class="pl-kos">(</span><span class="pl-c1">&</span><span class="pl-s">"Logging arbitrary values looks like"</span><span class="pl-kos">.</span><span class="pl-en">into</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">,</span> <span class="pl-c1">&</span>js<span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">同时需要修改<code>Cargo.toml</code>,添加<code>web_sys</code>依赖。</p>
<div class="highlight highlight-source-toml" dir="auto"><pre>[<span class="pl-en">dependencies</span>]
<span class="pl-smi">wasm-bindgen</span> = <span class="pl-s"><span class="pl-pds">"</span>0.2.63<span class="pl-pds">"</span></span>
<span class="pl-smi">web-sys</span> = { <span class="pl-smi">version</span> = <span class="pl-s"><span class="pl-pds">"</span>0.3.53<span class="pl-pds">"</span></span>, <span class="pl-smi">features</span> = [<span class="pl-s"><span class="pl-pds">'</span>console<span class="pl-pds">'</span></span>] }</pre></div>
<p dir="auto">这里,我采用了第二种用法,最新的代码如下:</p>
<div class="highlight highlight-source-rust" dir="auto"><pre><span class="pl-c1">#<span class="pl-kos">[</span>wasm_bindgen<span class="pl-kos">]</span></span>
<span class="pl-k">pub</span> <span class="pl-k">fn</span> <span class="pl-en">greet</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-k">use</span> web_sys<span class="pl-kos">::</span>console<span class="pl-kos">;</span>
console<span class="pl-kos">::</span><span class="pl-en">log_1</span><span class="pl-kos">(</span><span class="pl-c1">&</span><span class="pl-s">"Hello Wasm!"</span><span class="pl-kos">.</span><span class="pl-en">into</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span></pre></div>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">构建&发布</h2><a id="user-content-构建发布" class="anchor" aria-label="Permalink: 构建&发布" href="#构建发布"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">构建使用 <code>wasm-pack</code> 工具提供的 build 子命令即可。</p>
<div class="highlight highlight-source-shell" dir="auto"><pre><span class="pl-c"><span class="pl-c">#</span> scope 可以在构建时修改 npm 包的 name,如这里就改成了 @banyudu/hello-wasm</span>
<span class="pl-c"><span class="pl-c">#</span> 按需调整成自己的scope,或不用scope</span>
$ wasm-pack build --scope banyudu --target nodejs
[INFO]: 🎯 Checking <span class="pl-k">for</span> the Wasm target...
[INFO]: 🌀 Compiling to Wasm...
Compiling proc-macro2 v1.0.28
Compiling unicode-xid v0.2.2
Compiling wasm-bindgen-shared v0.2.76
Compiling syn v1.0.75
Compiling log v0.4.14
Compiling cfg-if v1.0.0
Compiling bumpalo v3.7.0
Compiling lazy_static v1.4.0
Compiling wasm-bindgen v0.2.76
Compiling cfg-if v0.1.10
Compiling quote v1.0.9
Compiling wasm-bindgen-backend v0.2.76
Compiling wasm-bindgen-macro-support v0.2.76
Compiling wasm-bindgen-macro v0.2.76
Compiling js-sys v0.3.53
Compiling console_error_panic_hook v0.1.6
Compiling web-sys v0.3.53
Compiling hello-wasm v0.1.0 (/private/tmp/wasm/hello-wasm)
warning: <span class="pl-k">function</span> <span class="pl-en">is</span> never used: <span class="pl-s"><span class="pl-pds">`</span>set_panic_hook<span class="pl-pds">`</span></span>
--<span class="pl-k">></span> src/utils.rs:1:8
<span class="pl-k">|</span>
1 <span class="pl-k">|</span> pub fn <span class="pl-en">set_panic_hook</span>() {
<span class="pl-k">|</span> ^^^^^^^^^^^^^^
<span class="pl-k">|</span>
= note: <span class="pl-s"><span class="pl-pds">`</span><span class="pl-c"><span class="pl-c">#</span>[warn(dead_code)]</span><span class="pl-pds">`</span></span> on by default
warning: 1 warning emitted
Finished release [optimized] target(s) <span class="pl-k">in</span> 17.69s
[INFO]: ⬇️ Installing wasm-bindgen...
[INFO]: Optimizing wasm binaries with <span class="pl-s"><span class="pl-pds">`</span>wasm-opt<span class="pl-pds">`</span></span>...
[INFO]: Optional fields missing from Cargo.toml: <span class="pl-s"><span class="pl-pds">'</span>description<span class="pl-pds">'</span></span>, <span class="pl-s"><span class="pl-pds">'</span>repository<span class="pl-pds">'</span></span>, and <span class="pl-s"><span class="pl-pds">'</span>license<span class="pl-pds">'</span></span>. These are not necessary, but recommended
[INFO]: ✨ Done <span class="pl-k">in</span> 18.26s
[INFO]: 📦 Your wasm pkg is ready to publish at /private/tmp/wasm/hello-wasm/pkg.</pre></div>
<p dir="auto">构建完成后,再执行发布操作,因为wasm是跨平台的,不区分宿主环境,所以不用像Node.js的C++包那样把二进制文件单独提出来下载,直接放在 npm 包中即可:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>$ wasm-pack publish --access public -t nodejs --tag nodejs
npm notice
npm notice 📦 @banyudu/hello-wasm@0.2.0
npm notice === Tarball Contents ===
npm notice 2.2kB README.md
npm notice 13.0kB hello_wasm_bg.wasm
npm notice 80B hello_wasm.d.ts
npm notice 2.0kB hello_wasm.js
npm notice 262B package.json
npm notice === Tarball Details ===
npm notice name: @banyudu/hello-wasm
npm notice version: 0.2.0
npm notice filename: @banyudu/hello-wasm-0.2.0.tgz
npm notice package size: 7.9 kB
npm notice unpacked size: 17.5 kB
npm notice shasum: 114f7e2c5eeaeac79714a14b5165b21cbd005c0b
npm notice integrity: sha512-44pOZDcQE0aSu[...]FcyfvfBwgIfkQ==
npm notice total files: 5
npm notice
+ @banyudu/hello-wasm@0.2.0
[INFO]: 💥 published your package<span class="pl-k">!</span></pre></div>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">导入和运行</h2><a id="user-content-导入和运行" class="anchor" aria-label="Permalink: 导入和运行" href="#导入和运行"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">node.js</h3><a id="user-content-nodejs" class="anchor" aria-label="Permalink: node.js" href="#nodejs"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">新建一个 node.js 工程,并安装<code>@banyudu/hello-wasm@0.2.0</code></p>
<div class="highlight highlight-source-shell" dir="auto"><pre>npm i @banyudu/hello-wasm@0.2.0</pre></div>
<p dir="auto">然后新建一个 index.js,内容如下:</p>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-en">require</span><span class="pl-kos">(</span><span class="pl-s">'@banyudu/hello-wasm'</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">greet</span><span class="pl-kos">(</span><span class="pl-kos">)</span></pre></div>
<p dir="auto">运行 <code>node index.js</code>,可以看到 <code>Hello Wasm!</code>的输出,说明能正常加载运行。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">浏览器</h3><a id="user-content-浏览器" class="anchor" aria-label="Permalink: 浏览器" href="#浏览器"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">在前端项目中使用 wasm 似乎略有一些复杂。</p>
<p dir="auto">使用create-react-app新建一个React工程,并在代码中引入 @banyudu/hello-wasm。</p>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-k">import</span> <span class="pl-s">"./styles.css"</span><span class="pl-kos">;</span>
<span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-s1">greet</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'@banyudu/hello-wasm'</span>
<span class="pl-k">export</span> <span class="pl-k">default</span> <span class="pl-k">function</span> <span class="pl-v">App</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-k">return</span> <span class="pl-kos">(</span>
<span class="pl-c1"><</span><span class="pl-s1">div</span> <span class="pl-c1">className</span><span class="pl-c1">=</span><span class="pl-s">"App"</span><span class="pl-c1">></span>
<span class="pl-c1"><</span><span class="pl-s1">button</span> <span class="pl-c1">onClick</span><span class="pl-c1">=</span><span class="pl-kos">{</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-en">greet</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">}</span><span class="pl-c1">></span>Click Me to say Hello Wasm in console<span class="pl-kos"></</span><span class="pl-s1">button</span><span class="pl-c1">></span>
<span class="pl-kos"></</span><span class="pl-s1">div</span><span class="pl-c1">></span>
<span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">这个时候会报如下的错:</p>
<p dir="auto"><code>Module parse failed: magic header not detected</code></p>
<p dir="auto">这是因为wasm没有被正确的加载,需要使用<code>wasm-loader</code>处理。</p>
<p dir="auto">使用<code>react-app-rewired</code>添加自定义配置 config-overrides.js:</p>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-k">const</span> <span class="pl-s1">path</span> <span class="pl-c1">=</span> <span class="pl-en">require</span><span class="pl-kos">(</span><span class="pl-s">'path'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-smi">module</span><span class="pl-kos">.</span><span class="pl-en">exports</span> <span class="pl-c1">=</span> <span class="pl-k">function</span> <span class="pl-en">override</span><span class="pl-kos">(</span><span class="pl-s1">config</span><span class="pl-kos">,</span> <span class="pl-s1">env</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-c">// Make file-loader ignore WASM files</span>
<span class="pl-k">const</span> <span class="pl-s1">wasmExtensionRegExp</span> <span class="pl-c1">=</span> <span class="pl-pds"><span class="pl-c1">/</span><span class="pl-cce">\.</span><span class="pl-s">w</span><span class="pl-s">a</span><span class="pl-s">s</span><span class="pl-s">m</span><span class="pl-cce">$</span><span class="pl-c1">/</span></span><span class="pl-kos">;</span>
<span class="pl-s1">config</span><span class="pl-kos">.</span><span class="pl-c1">resolve</span><span class="pl-kos">.</span><span class="pl-c1">extensions</span><span class="pl-kos">.</span><span class="pl-en">push</span><span class="pl-kos">(</span><span class="pl-s">'.wasm'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-s1">config</span><span class="pl-kos">.</span><span class="pl-c1">module</span><span class="pl-kos">.</span><span class="pl-c1">rules</span><span class="pl-kos">.</span><span class="pl-en">forEach</span><span class="pl-kos">(</span><span class="pl-s1">rule</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-kos">(</span><span class="pl-s1">rule</span><span class="pl-kos">.</span><span class="pl-c1">oneOf</span> <span class="pl-c1">||</span> <span class="pl-kos">[</span><span class="pl-kos">]</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">forEach</span><span class="pl-kos">(</span><span class="pl-s1">oneOf</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">oneOf</span><span class="pl-kos">.</span><span class="pl-c1">loader</span> <span class="pl-c1">&&</span> <span class="pl-s1">oneOf</span><span class="pl-kos">.</span><span class="pl-c1">loader</span><span class="pl-kos">.</span><span class="pl-en">indexOf</span><span class="pl-kos">(</span><span class="pl-s">'file-loader'</span><span class="pl-kos">)</span> <span class="pl-c1">>=</span> <span class="pl-c1">0</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-s1">oneOf</span><span class="pl-kos">.</span><span class="pl-c1">exclude</span><span class="pl-kos">.</span><span class="pl-en">push</span><span class="pl-kos">(</span><span class="pl-s1">wasmExtensionRegExp</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-c">// Add a dedicated loader for WASM</span>
<span class="pl-s1">config</span><span class="pl-kos">.</span><span class="pl-c1">module</span><span class="pl-kos">.</span><span class="pl-c1">rules</span><span class="pl-kos">.</span><span class="pl-en">push</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
<span class="pl-c1">test</span>: <span class="pl-s1">wasmExtensionRegExp</span><span class="pl-kos">,</span>
<span class="pl-c1">include</span>: <span class="pl-s1">path</span><span class="pl-kos">.</span><span class="pl-en">resolve</span><span class="pl-kos">(</span><span class="pl-s1">__dirname</span><span class="pl-kos">,</span> <span class="pl-s">'src'</span><span class="pl-kos">)</span><span class="pl-kos">,</span>
<span class="pl-c1">use</span>: <span class="pl-kos">[</span><span class="pl-kos">{</span> <span class="pl-c1">loader</span>: <span class="pl-en">require</span><span class="pl-kos">.</span><span class="pl-en">resolve</span><span class="pl-kos">(</span><span class="pl-s">'wasm-loader'</span><span class="pl-kos">)</span><span class="pl-kos">,</span> <span class="pl-c1">options</span>: <span class="pl-kos">{</span><span class="pl-kos">}</span> <span class="pl-kos">}</span><span class="pl-kos">]</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-k">return</span> <span class="pl-s1">config</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span><span class="pl-kos">;</span></pre></div>
<p dir="auto">再运行React项目,还是有错误,现在的错误是:<code>WebAssembly module is included in initial chunk.</code></p>
<p dir="auto">这个错误相对来说好理解一点,也就是说导入wasm模块必须异步进行,将代码改成如下的形式:</p>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-k">import</span> <span class="pl-s">"./styles.css"</span><span class="pl-kos">;</span>
<span class="pl-k">const</span> <span class="pl-en">handleClick</span> <span class="pl-c1">=</span> <span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-k">import</span><span class="pl-kos">(</span><span class="pl-s">'@banyudu/hello-wasm'</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">then</span><span class="pl-kos">(</span><span class="pl-s1">wasm</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-s1">wasm</span><span class="pl-kos">.</span><span class="pl-en">greet</span><span class="pl-kos">(</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span>
<span class="pl-k">export</span> <span class="pl-k">default</span> <span class="pl-k">function</span> <span class="pl-v">App</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-k">return</span> <span class="pl-kos">(</span>
<span class="pl-c1"><</span><span class="pl-s1">div</span> <span class="pl-c1">className</span><span class="pl-c1">=</span><span class="pl-s">"App"</span><span class="pl-c1">></span>
<span class="pl-c1"><</span><span class="pl-s1">button</span> <span class="pl-c1">onClick</span><span class="pl-c1">=</span><span class="pl-kos">{</span><span class="pl-en">handleClick</span><span class="pl-kos">}</span><span class="pl-c1">></span>Click Me to say Hello Wasm in console<span class="pl-kos"></</span><span class="pl-s1">button</span><span class="pl-c1">></span>
<span class="pl-kos"></</span><span class="pl-s1">div</span><span class="pl-c1">></span>
<span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">但是错误还没有结束,现在变成了<code>TypeError: TextDecoder is not a constructor</code>.</p>
<p dir="auto">wasm-pack在构建的时候支持多种模式,有nodejs,也有web等,这个错看起来像是引用了node.js的模块导致的,所以我怀疑是wasm-pack构建的时候不能设置成node.js。</p>
<p dir="auto">果然使用<code>wasm-pack build --scope banyudu </code>重新构建之后就正常了。</p>
<p dir="auto">前端示例代码在<a href="https://github.com/banyudu/react-wasm-sample">Github仓库</a>中。</p>
<p dir="auto">前端项目中使用wasm略显复杂,而且看起来<code>wasm-pack</code>也无法同时提供node.js和web兼容的npm包?也许以后会有更便捷的方式。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">总结</h2><a id="user-content-总结" class="anchor" aria-label="Permalink: 总结" href="#总结"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">以上就是一个完整的使用Rust构建wasm包,发布到npm,并在node.js和React项目中分别引用的实际过程。</p>
<p dir="auto">通过使用体验来看,WebAssembly在Node.js中表现较好,只要是新版本Node.js,使用的时候不需要特殊配置,而前端React项目中使用比较复杂,需要修改Webpack配置,且引入的方式也必须是dynamic的。</p>
<p dir="auto">暂时还算不上很理想,期待以后会有更好的发展。</p>
</article>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/194a317e1254067bd023dbdd6ce10ac1
2021-08-25T02:54:56Z
2021-08-25T03:02:22Z
rust-hello-wasm-nodejs
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/194a317e1254067bd023dbdd6ce10ac1#file-cargo-toml">Cargo.toml</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-cargo-toml" class="file my-2">
<div itemprop="text"
class="Box-body p-0 blob-wrapper data type-toml "
style="overflow: auto" tabindex="0" role="region"
aria-label="Cargo.toml content, created by banyudu on 02:54AM on August 25, 2021."
>
<div class="js-check-hidden-unicode js-blob-code-container blob-code-content">
<template class="js-file-alert-template">
<div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
<span>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
<a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
</span>
<div data-view-component="true" class="flash-action"> <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn"> Show hidden characters
</a>
</div>
</div></template>
<template class="js-line-alert-template">
<span aria-label="This line has hidden Unicode characters" data-view-component="true" class="line-alert tooltipped tooltipped-e">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
</span></template>
<table data-hpc class="highlight tab-size js-file-line-container" data-tab-size="8" data-paste-markdown-skip data-tagsearch-path="Cargo.toml">
<tr>
<td id="file-cargo-toml-L1" class="blob-num js-line-number js-blob-rnum" data-line-number="1"></td>
<td id="file-cargo-toml-LC1" class="blob-code blob-code-inner js-file-line">[<span class="pl-en">package</span>]</td>
</tr>
<tr>
<td id="file-cargo-toml-L2" class="blob-num js-line-number js-blob-rnum" data-line-number="2"></td>
<td id="file-cargo-toml-LC2" class="blob-code blob-code-inner js-file-line"><span class="pl-smi">name</span> = <span class="pl-s"><span class="pl-pds">"</span>hello-wasm<span class="pl-pds">"</span></span></td>
</tr>
<tr>
<td id="file-cargo-toml-L3" class="blob-num js-line-number js-blob-rnum" data-line-number="3"></td>
<td id="file-cargo-toml-LC3" class="blob-code blob-code-inner js-file-line"><span class="pl-smi">version</span> = <span class="pl-s"><span class="pl-pds">"</span>0.1.2<span class="pl-pds">"</span></span></td>
</tr>
<tr>
<td id="file-cargo-toml-L4" class="blob-num js-line-number js-blob-rnum" data-line-number="4"></td>
<td id="file-cargo-toml-LC4" class="blob-code blob-code-inner js-file-line"><span class="pl-smi">authors</span> = [<span class="pl-s"><span class="pl-pds">"</span>Yudu Ban <banyudu@gmail.com><span class="pl-pds">"</span></span>]</td>
</tr>
<tr>
<td id="file-cargo-toml-L5" class="blob-num js-line-number js-blob-rnum" data-line-number="5"></td>
<td id="file-cargo-toml-LC5" class="blob-code blob-code-inner js-file-line"><span class="pl-smi">edition</span> = <span class="pl-s"><span class="pl-pds">"</span>2018<span class="pl-pds">"</span></span></td>
</tr>
<tr>
<td id="file-cargo-toml-L6" class="blob-num js-line-number js-blob-rnum" data-line-number="6"></td>
<td id="file-cargo-toml-LC6" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-cargo-toml-L7" class="blob-num js-line-number js-blob-rnum" data-line-number="7"></td>
<td id="file-cargo-toml-LC7" class="blob-code blob-code-inner js-file-line">[<span class="pl-en">lib</span>]</td>
</tr>
<tr>
<td id="file-cargo-toml-L8" class="blob-num js-line-number js-blob-rnum" data-line-number="8"></td>
<td id="file-cargo-toml-LC8" class="blob-code blob-code-inner js-file-line"><span class="pl-smi">crate-type</span> = [<span class="pl-s"><span class="pl-pds">"</span>cdylib<span class="pl-pds">"</span></span>, <span class="pl-s"><span class="pl-pds">"</span>rlib<span class="pl-pds">"</span></span>]</td>
</tr>
<tr>
<td id="file-cargo-toml-L9" class="blob-num js-line-number js-blob-rnum" data-line-number="9"></td>
<td id="file-cargo-toml-LC9" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-cargo-toml-L10" class="blob-num js-line-number js-blob-rnum" data-line-number="10"></td>
<td id="file-cargo-toml-LC10" class="blob-code blob-code-inner js-file-line">[<span class="pl-en">features</span>]</td>
</tr>
<tr>
<td id="file-cargo-toml-L11" class="blob-num js-line-number js-blob-rnum" data-line-number="11"></td>
<td id="file-cargo-toml-LC11" class="blob-code blob-code-inner js-file-line"><span class="pl-smi">default</span> = [<span class="pl-s"><span class="pl-pds">"</span>console_error_panic_hook<span class="pl-pds">"</span></span>]</td>
</tr>
<tr>
<td id="file-cargo-toml-L12" class="blob-num js-line-number js-blob-rnum" data-line-number="12"></td>
<td id="file-cargo-toml-LC12" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-cargo-toml-L13" class="blob-num js-line-number js-blob-rnum" data-line-number="13"></td>
<td id="file-cargo-toml-LC13" class="blob-code blob-code-inner js-file-line">[<span class="pl-en">dependencies</span>]</td>
</tr>
<tr>
<td id="file-cargo-toml-L14" class="blob-num js-line-number js-blob-rnum" data-line-number="14"></td>
<td id="file-cargo-toml-LC14" class="blob-code blob-code-inner js-file-line"><span class="pl-smi">wasm-bindgen</span> = <span class="pl-s"><span class="pl-pds">"</span>0.2.63<span class="pl-pds">"</span></span></td>
</tr>
<tr>
<td id="file-cargo-toml-L15" class="blob-num js-line-number js-blob-rnum" data-line-number="15"></td>
<td id="file-cargo-toml-LC15" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-cargo-toml-L16" class="blob-num js-line-number js-blob-rnum" data-line-number="16"></td>
<td id="file-cargo-toml-LC16" class="blob-code blob-code-inner js-file-line"><span class="pl-c"><span class="pl-c">#</span> The `console_error_panic_hook` crate provides better debugging of panics by</span></td>
</tr>
<tr>
<td id="file-cargo-toml-L17" class="blob-num js-line-number js-blob-rnum" data-line-number="17"></td>
<td id="file-cargo-toml-LC17" class="blob-code blob-code-inner js-file-line"><span class="pl-c"><span class="pl-c">#</span> logging them with `console.error`. This is great for development, but requires</span></td>
</tr>
<tr>
<td id="file-cargo-toml-L18" class="blob-num js-line-number js-blob-rnum" data-line-number="18"></td>
<td id="file-cargo-toml-LC18" class="blob-code blob-code-inner js-file-line"><span class="pl-c"><span class="pl-c">#</span> all the `std::fmt` and `std::panicking` infrastructure, so isn't great for</span></td>
</tr>
<tr>
<td id="file-cargo-toml-L19" class="blob-num js-line-number js-blob-rnum" data-line-number="19"></td>
<td id="file-cargo-toml-LC19" class="blob-code blob-code-inner js-file-line"><span class="pl-c"><span class="pl-c">#</span> code size when deploying.</span></td>
</tr>
<tr>
<td id="file-cargo-toml-L20" class="blob-num js-line-number js-blob-rnum" data-line-number="20"></td>
<td id="file-cargo-toml-LC20" class="blob-code blob-code-inner js-file-line"><span class="pl-smi">console_error_panic_hook</span> = { <span class="pl-smi">version</span> = <span class="pl-s"><span class="pl-pds">"</span>0.1.6<span class="pl-pds">"</span></span>, <span class="pl-smi">optional</span> = <span class="pl-c1">true</span> }</td>
</tr>
<tr>
<td id="file-cargo-toml-L21" class="blob-num js-line-number js-blob-rnum" data-line-number="21"></td>
<td id="file-cargo-toml-LC21" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-cargo-toml-L22" class="blob-num js-line-number js-blob-rnum" data-line-number="22"></td>
<td id="file-cargo-toml-LC22" class="blob-code blob-code-inner js-file-line"><span class="pl-c"><span class="pl-c">#</span> `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size</span></td>
</tr>
<tr>
<td id="file-cargo-toml-L23" class="blob-num js-line-number js-blob-rnum" data-line-number="23"></td>
<td id="file-cargo-toml-LC23" class="blob-code blob-code-inner js-file-line"><span class="pl-c"><span class="pl-c">#</span> compared to the default allocator's ~10K. It is slower than the default</span></td>
</tr>
<tr>
<td id="file-cargo-toml-L24" class="blob-num js-line-number js-blob-rnum" data-line-number="24"></td>
<td id="file-cargo-toml-LC24" class="blob-code blob-code-inner js-file-line"><span class="pl-c"><span class="pl-c">#</span> allocator, however.</span></td>
</tr>
<tr>
<td id="file-cargo-toml-L25" class="blob-num js-line-number js-blob-rnum" data-line-number="25"></td>
<td id="file-cargo-toml-LC25" class="blob-code blob-code-inner js-file-line"><span class="pl-c"><span class="pl-c">#</span></span></td>
</tr>
<tr>
<td id="file-cargo-toml-L26" class="blob-num js-line-number js-blob-rnum" data-line-number="26"></td>
<td id="file-cargo-toml-LC26" class="blob-code blob-code-inner js-file-line"><span class="pl-c"><span class="pl-c">#</span> Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.</span></td>
</tr>
<tr>
<td id="file-cargo-toml-L27" class="blob-num js-line-number js-blob-rnum" data-line-number="27"></td>
<td id="file-cargo-toml-LC27" class="blob-code blob-code-inner js-file-line"><span class="pl-smi">wee_alloc</span> = { <span class="pl-smi">version</span> = <span class="pl-s"><span class="pl-pds">"</span>0.4.5<span class="pl-pds">"</span></span>, <span class="pl-smi">optional</span> = <span class="pl-c1">true</span> }</td>
</tr>
<tr>
<td id="file-cargo-toml-L28" class="blob-num js-line-number js-blob-rnum" data-line-number="28"></td>
<td id="file-cargo-toml-LC28" class="blob-code blob-code-inner js-file-line"><span class="pl-smi">log</span> = <span class="pl-s"><span class="pl-pds">"</span>0.4.14<span class="pl-pds">"</span></span></td>
</tr>
<tr>
<td id="file-cargo-toml-L29" class="blob-num js-line-number js-blob-rnum" data-line-number="29"></td>
<td id="file-cargo-toml-LC29" class="blob-code blob-code-inner js-file-line"><span class="pl-smi">wasm-bindgen-console-logger</span> = <span class="pl-s"><span class="pl-pds">"</span>0.1.1<span class="pl-pds">"</span></span></td>
</tr>
<tr>
<td id="file-cargo-toml-L30" class="blob-num js-line-number js-blob-rnum" data-line-number="30"></td>
<td id="file-cargo-toml-LC30" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-cargo-toml-L31" class="blob-num js-line-number js-blob-rnum" data-line-number="31"></td>
<td id="file-cargo-toml-LC31" class="blob-code blob-code-inner js-file-line">[<span class="pl-en">dev-dependencies</span>]</td>
</tr>
<tr>
<td id="file-cargo-toml-L32" class="blob-num js-line-number js-blob-rnum" data-line-number="32"></td>
<td id="file-cargo-toml-LC32" class="blob-code blob-code-inner js-file-line"><span class="pl-smi">wasm-bindgen-test</span> = <span class="pl-s"><span class="pl-pds">"</span>0.3.13<span class="pl-pds">"</span></span></td>
</tr>
<tr>
<td id="file-cargo-toml-L33" class="blob-num js-line-number js-blob-rnum" data-line-number="33"></td>
<td id="file-cargo-toml-LC33" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-cargo-toml-L34" class="blob-num js-line-number js-blob-rnum" data-line-number="34"></td>
<td id="file-cargo-toml-LC34" class="blob-code blob-code-inner js-file-line">[<span class="pl-en">profile</span>.<span class="pl-en">release</span>]</td>
</tr>
<tr>
<td id="file-cargo-toml-L35" class="blob-num js-line-number js-blob-rnum" data-line-number="35"></td>
<td id="file-cargo-toml-LC35" class="blob-code blob-code-inner js-file-line"><span class="pl-c"><span class="pl-c">#</span> Tell `rustc` to optimize for small code size.</span></td>
</tr>
<tr>
<td id="file-cargo-toml-L36" class="blob-num js-line-number js-blob-rnum" data-line-number="36"></td>
<td id="file-cargo-toml-LC36" class="blob-code blob-code-inner js-file-line"><span class="pl-smi">opt-level</span> = <span class="pl-s"><span class="pl-pds">"</span>s<span class="pl-pds">"</span></span></td>
</tr>
</table>
</div>
</div>
</div>
</div>
<a href="https://gist.github.com/banyudu/194a317e1254067bd023dbdd6ce10ac1#file-lib-rs">lib.rs</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-lib-rs" class="file my-2">
<div itemprop="text"
class="Box-body p-0 blob-wrapper data type-rust "
style="overflow: auto" tabindex="0" role="region"
aria-label="lib.rs content, created by banyudu on 02:54AM on August 25, 2021."
>
<div class="js-check-hidden-unicode js-blob-code-container blob-code-content">
<template class="js-file-alert-template">
<div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
<span>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
<a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
</span>
<div data-view-component="true" class="flash-action"> <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn"> Show hidden characters
</a>
</div>
</div></template>
<template class="js-line-alert-template">
<span aria-label="This line has hidden Unicode characters" data-view-component="true" class="line-alert tooltipped tooltipped-e">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
</span></template>
<table data-hpc class="highlight tab-size js-file-line-container" data-tab-size="8" data-paste-markdown-skip data-tagsearch-path="lib.rs">
<tr>
<td id="file-lib-rs-L1" class="blob-num js-line-number js-blob-rnum" data-line-number="1"></td>
<td id="file-lib-rs-LC1" class="blob-code blob-code-inner js-file-line"><span class=pl-k>mod</span> utils<span class=pl-kos>;</span></td>
</tr>
<tr>
<td id="file-lib-rs-L2" class="blob-num js-line-number js-blob-rnum" data-line-number="2"></td>
<td id="file-lib-rs-LC2" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-lib-rs-L3" class="blob-num js-line-number js-blob-rnum" data-line-number="3"></td>
<td id="file-lib-rs-LC3" class="blob-code blob-code-inner js-file-line"><span class=pl-k>use</span> log<span class=pl-kos>::</span><span class=pl-kos>{</span>info<span class=pl-kos>}</span><span class=pl-kos>;</span></td>
</tr>
<tr>
<td id="file-lib-rs-L4" class="blob-num js-line-number js-blob-rnum" data-line-number="4"></td>
<td id="file-lib-rs-LC4" class="blob-code blob-code-inner js-file-line"><span class=pl-k>use</span> wasm_bindgen<span class=pl-kos>::</span>prelude<span class=pl-kos>::</span><span class=pl-c1>*</span><span class=pl-kos>;</span></td>
</tr>
<tr>
<td id="file-lib-rs-L5" class="blob-num js-line-number js-blob-rnum" data-line-number="5"></td>
<td id="file-lib-rs-LC5" class="blob-code blob-code-inner js-file-line"><span class=pl-k>use</span> wasm_bindgen_console_logger<span class=pl-kos>::</span><span class=pl-v>DEFAULT_LOGGER</span><span class=pl-kos>;</span></td>
</tr>
<tr>
<td id="file-lib-rs-L6" class="blob-num js-line-number js-blob-rnum" data-line-number="6"></td>
<td id="file-lib-rs-LC6" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-lib-rs-L7" class="blob-num js-line-number js-blob-rnum" data-line-number="7"></td>
<td id="file-lib-rs-LC7" class="blob-code blob-code-inner js-file-line"><span class=pl-c>// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global</span></td>
</tr>
<tr>
<td id="file-lib-rs-L8" class="blob-num js-line-number js-blob-rnum" data-line-number="8"></td>
<td id="file-lib-rs-LC8" class="blob-code blob-code-inner js-file-line"><span class=pl-c>// allocator.</span></td>
</tr>
<tr>
<td id="file-lib-rs-L9" class="blob-num js-line-number js-blob-rnum" data-line-number="9"></td>
<td id="file-lib-rs-LC9" class="blob-code blob-code-inner js-file-line"><span class=pl-c1>#<span class=pl-kos>[</span>cfg<span class=pl-kos>(</span>feature = <span class=pl-s>"wee_alloc"</span><span class=pl-kos>)</span><span class=pl-kos>]</span></span></td>
</tr>
<tr>
<td id="file-lib-rs-L10" class="blob-num js-line-number js-blob-rnum" data-line-number="10"></td>
<td id="file-lib-rs-LC10" class="blob-code blob-code-inner js-file-line"><span class=pl-c1>#<span class=pl-kos>[</span>global_allocator<span class=pl-kos>]</span></span></td>
</tr>
<tr>
<td id="file-lib-rs-L11" class="blob-num js-line-number js-blob-rnum" data-line-number="11"></td>
<td id="file-lib-rs-LC11" class="blob-code blob-code-inner js-file-line"><span class=pl-k>static</span> <span class=pl-v>ALLOC</span><span class=pl-kos>:</span> wee_alloc<span class=pl-kos>::</span><span class=pl-smi>WeeAlloc</span> = wee_alloc<span class=pl-kos>::</span><span class=pl-smi>WeeAlloc</span><span class=pl-kos>::</span><span class=pl-v>INIT</span><span class=pl-kos>;</span></td>
</tr>
<tr>
<td id="file-lib-rs-L12" class="blob-num js-line-number js-blob-rnum" data-line-number="12"></td>
<td id="file-lib-rs-LC12" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-lib-rs-L13" class="blob-num js-line-number js-blob-rnum" data-line-number="13"></td>
<td id="file-lib-rs-LC13" class="blob-code blob-code-inner js-file-line"><span class=pl-c1>#<span class=pl-kos>[</span>wasm_bindgen<span class=pl-kos>]</span></span></td>
</tr>
<tr>
<td id="file-lib-rs-L14" class="blob-num js-line-number js-blob-rnum" data-line-number="14"></td>
<td id="file-lib-rs-LC14" class="blob-code blob-code-inner js-file-line"><span class=pl-k>pub</span> <span class=pl-k>fn</span> <span class=pl-en>greet</span><span class=pl-kos>(</span><span class=pl-kos>)</span> <span class=pl-kos>{</span></td>
</tr>
<tr>
<td id="file-lib-rs-L15" class="blob-num js-line-number js-blob-rnum" data-line-number="15"></td>
<td id="file-lib-rs-LC15" class="blob-code blob-code-inner js-file-line"> log<span class=pl-kos>::</span><span class=pl-en>set_logger</span><span class=pl-kos>(</span><span class=pl-c1>&</span><span class=pl-v>DEFAULT_LOGGER</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>unwrap</span><span class=pl-kos>(</span><span class=pl-kos>)</span><span class=pl-kos>;</span></td>
</tr>
<tr>
<td id="file-lib-rs-L16" class="blob-num js-line-number js-blob-rnum" data-line-number="16"></td>
<td id="file-lib-rs-LC16" class="blob-code blob-code-inner js-file-line"> log<span class=pl-kos>::</span><span class=pl-en>set_max_level</span><span class=pl-kos>(</span>log<span class=pl-kos>::</span><span class=pl-smi>LevelFilter</span><span class=pl-kos>::</span><span class=pl-v>Info</span><span class=pl-kos>)</span><span class=pl-kos>;</span></td>
</tr>
<tr>
<td id="file-lib-rs-L17" class="blob-num js-line-number js-blob-rnum" data-line-number="17"></td>
<td id="file-lib-rs-LC17" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-lib-rs-L18" class="blob-num js-line-number js-blob-rnum" data-line-number="18"></td>
<td id="file-lib-rs-LC18" class="blob-code blob-code-inner js-file-line"> <span class=pl-en>info</span><span class=pl-en>!</span><span class=pl-kos>(</span><span class=pl-s>"Hello Wasm!"</span><span class=pl-kos>)</span><span class=pl-kos>;</span></td>
</tr>
<tr>
<td id="file-lib-rs-L19" class="blob-num js-line-number js-blob-rnum" data-line-number="19"></td>
<td id="file-lib-rs-LC19" class="blob-code blob-code-inner js-file-line"><span class=pl-kos>}</span></td>
</tr>
</table>
</div>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/085db1d4f0cb5a847a811bf582f0a755
2021-07-14T13:37:45Z
2023-03-07T07:23:56Z
浅谈ipv4和ipv6中的端口占用
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/085db1d4f0cb5a847a811bf582f0a755#file-ipv4-ipv6-port-listen-blog-md">ipv4-ipv6-port-listen.blog.md</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-ipv4-ipv6-port-listen-blog-md" class="file my-2">
<div id="file-ipv4-ipv6-port-listen-blog-md-readme" class="Box-body readme blob p-5 p-xl-6 "
style="overflow: auto" tabindex="0" role="region"
aria-label="ipv4-ipv6-port-listen.blog.md content, created by banyudu on 01:37PM on July 14, 2021."
>
<article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">浅谈ipv4和ipv6中的端口占用</h1><a id="user-content-浅谈ipv4和ipv6中的端口占用" class="anchor" aria-label="Permalink: 浅谈ipv4和ipv6中的端口占用" href="#浅谈ipv4和ipv6中的端口占用"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">前两天在调试一个本地应用的时候,偶然发现一个奇怪的问题:nginx和spring boot应用竟然同时监听了8080端口,且能正常工作!</p>
<p dir="auto">这实在是太震惊了,我一向认为只有父子进程才可以共用端口,而nginx和调试中的spring boot应用很明显不是父子关系。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">问题表现</h2><a id="user-content-问题表现" class="anchor" aria-label="Permalink: 问题表现" href="#问题表现"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">为了研究这个问题,我做了如下的几个测试:</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">测试一,场景复原</h3><a id="user-content-测试一场景复原" class="anchor" aria-label="Permalink: 测试一,场景复原" href="#测试一场景复原"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ol dir="auto">
<li>先启动nginx,此时访问 <a href="https://127.0.0.1:8080" rel="nofollow">https://127.0.0.1:8080</a> 和 <a href="https://localhost:8080" rel="nofollow">https://localhost:8080</a> 均可以正常访问到nginx。</li>
<li>再启动 spring boot应用,此时 <a href="https://127.0.0.1:8080" rel="nofollow">https://127.0.0.1:8080</a> 能访问到nginx,<a href="https://localhost:8080%E8%83%BD%E8%AE%BF%E9%97%AE%E5%88%B0spring" rel="nofollow">https://localhost:8080能访问到spring</a> boot.</li>
</ol>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">测试2:调整顺序</h3><a id="user-content-测试2调整顺序" class="anchor" aria-label="Permalink: 测试2:调整顺序" href="#测试2调整顺序"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ol dir="auto">
<li>先启动spring boot应用,此时访问 <a href="https://127.0.0.1:8080" rel="nofollow">https://127.0.0.1:8080</a> 和 <a href="https://localhost:8080" rel="nofollow">https://localhost:8080</a> 均可以正常访问到spring boot。</li>
<li>再启动nginx,报端口占用,启动失败</li>
</ol>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">测试3:启动其它服务占用8080</h3><a id="user-content-测试3启动其它服务占用8080" class="anchor" aria-label="Permalink: 测试3:启动其它服务占用8080" href="#测试3启动其它服务占用8080"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">这里我使用的是<a href="https://www.npmjs.com/package/serve" rel="nofollow">serve</a>,一个基于node.js的本地静态文件http服务,启动命令:<code>npx serve -p 8080</code></p>
<ol dir="auto">
<li>先启动nginx,再启动serve,能正常启动。127.0.0.1指向nginx, localhost指向serve.</li>
<li>先启动serve,再启动nginx,无法正常启动。</li>
<li>先启动serve,再启动spring boot,无法正常启动。</li>
<li>先启动spring boot,再启动nginx,无法正常启动。</li>
</ol>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">问题分析</h2><a id="user-content-问题分析" class="anchor" aria-label="Permalink: 问题分析" href="#问题分析"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">通过spring boot启动后nginx无法启动等现象,可以证明不同进程之间端口冲突确实是存在的。</p>
<p dir="auto">那么如何解释nginx启动后 spring boot 可以正常启动的现象呢?</p>
<p dir="auto">最初我猜想spring boot可能魔改了nginx的配置文件并重启,通过proxy_pass做了反向代理,但是这个过于天方夜潭了,且后面serve也能伴随nginx启动说明不是spring boot的问题。</p>
<p dir="auto">最后还是从<code>sudo lsof -i:8080</code>的命令返回结果中看出了端倪:</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/6fc4f33ca932f083d86c03a9145659f0977e7f8df5f27b3cdaf8742366da3d3a/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303231303731343230353231333432332e706e67"><img src="https://camo.githubusercontent.com/6fc4f33ca932f083d86c03a9145659f0977e7f8df5f27b3cdaf8742366da3d3a/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303231303731343230353231333432332e706e67" alt="image-20210714205213423" data-canonical-src="https://banyudu.github.io/images/image-20210714205213423.png" style="max-width: 100%;"></a></p>
<div class="highlight highlight-source-shell" dir="auto"><pre>nginx 71366 root 7u IPv4 0x613d5a88e3eee6cd 0t0 TCP <span class="pl-k">*</span>:http-alt (LISTEN)
nginx 71367 nobody 7u IPv4 0x613d5a88e3eee6cd 0t0 TCP <span class="pl-k">*</span>:http-alt (LISTEN)
java 78340 yudu 196u IPv6 0x613d5a88dddfdc05 0t0 TCP <span class="pl-k">*</span>:http-alt (LISTEN)</pre></div>
<p dir="auto">从图中可以看出,有两个Nginx进程,带有IPv4标识,还有一个Java进程,还有IPv6标识。</p>
<p dir="auto">从这里开始,问题总算有了眉目。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">结果验证</h2><a id="user-content-结果验证" class="anchor" aria-label="Permalink: 结果验证" href="#结果验证"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">为了验证是否真的由ipv4、ipv6导致了端口的重复监听,我写了如下的两个Node.js脚本:</p>
<p dir="auto"><code>ipv4.js</code></p>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-c">// 监听 ipv4 端口,并在收到请求时返回 Hello ipv4!</span>
<span class="pl-en">require</span><span class="pl-kos">(</span><span class="pl-s">'http'</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">createServer</span><span class="pl-kos">(</span><span class="pl-k">function</span> <span class="pl-kos">(</span><span class="pl-s1">req</span><span class="pl-kos">,</span> <span class="pl-s1">res</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-s1">res</span><span class="pl-kos">.</span><span class="pl-en">writeHead</span><span class="pl-kos">(</span><span class="pl-c1">200</span><span class="pl-kos">,</span> <span class="pl-kos">{</span><span class="pl-s">'Content-Type'</span>: <span class="pl-s">'text/plain'</span><span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-s1">res</span><span class="pl-kos">.</span><span class="pl-en">write</span><span class="pl-kos">(</span><span class="pl-s">'Hello ipv4!\n'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-s1">res</span><span class="pl-kos">.</span><span class="pl-en">end</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">listen</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
<span class="pl-c1">port</span>: <span class="pl-c1">9999</span><span class="pl-kos">,</span>
<span class="pl-c1">host</span>: <span class="pl-s">'127.0.0.1'</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p dir="auto"><code>ipv6.js</code></p>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-c">// 监听 ipv6 端口,并在收到请求时返回 Hello ipv6!</span>
<span class="pl-en">require</span><span class="pl-kos">(</span><span class="pl-s">'http'</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">createServer</span><span class="pl-kos">(</span><span class="pl-k">function</span> <span class="pl-kos">(</span><span class="pl-s1">req</span><span class="pl-kos">,</span> <span class="pl-s1">res</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-s1">res</span><span class="pl-kos">.</span><span class="pl-en">writeHead</span><span class="pl-kos">(</span><span class="pl-c1">200</span><span class="pl-kos">,</span> <span class="pl-kos">{</span><span class="pl-s">'Content-Type'</span>: <span class="pl-s">'text/plain'</span><span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-s1">res</span><span class="pl-kos">.</span><span class="pl-en">write</span><span class="pl-kos">(</span><span class="pl-s">'Hello ipv6!\n'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-s1">res</span><span class="pl-kos">.</span><span class="pl-en">end</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">listen</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
<span class="pl-c1">port</span>: <span class="pl-c1">9999</span><span class="pl-kos">,</span>
<span class="pl-c1">host</span>: <span class="pl-s">'::1'</span><span class="pl-kos">,</span>
<span class="pl-c1">ipv6Only</span>: <span class="pl-c1">true</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p dir="auto">先启动ipv4:<code>node ipv4.js</code>,再启动 ipv6:<code>node ipv6.js</code>,没有出现报错。此时访问各种本地url,得到结果如下:</p>
<ul dir="auto">
<li><a href="https://127.0.0.1:9999" rel="nofollow">https://127.0.0.1:9999</a> => Hello ipv4!</li>
<li><a href="https://localhost:9999" rel="nofollow">https://localhost:9999</a> => Hello ipv6!</li>
<li>https://[::1]:9999 => Hello ipv6!</li>
</ul>
<p dir="auto">这里面 [::1]就是ipv6形式的本地地址,类似于ipv4中的127.0.0.1。</p>
<p dir="auto">再看<code>/etc/hosts</code>中localhost的配置:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre><span class="pl-c"><span class="pl-c">#</span> localhost is used to configure the loopback interface</span>
127.0.0.1 localhost
::1 localhost</pre></div>
<p dir="auto">可以看出系统默认将localhost绑定到了 <code>127.0.0.1</code> 和 <code>::1</code>上,所以如果我们加下hosts配置</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>127.0.0.1 test1.localhost <span class="pl-c"><span class="pl-c">#</span> ipv4 only</span>
::1 test2.localhost <span class="pl-c"><span class="pl-c">#</span> ipv6 only</span>
127.0.0.1 test3.localhost <span class="pl-c"><span class="pl-c">#</span> ipv4 and ipv6</span>
::1 test3.localhost
::1 test4.localhost <span class="pl-c"><span class="pl-c">#</span> ipv6 and ipv4</span>
127.0.0.1 test4.localhost</pre></div>
<p dir="auto">再做一次上面的测试,会得到如下的结果:</p>
<ul dir="auto">
<li><a href="https://test1.localhost:9999" rel="nofollow">https://test1.localhost:9999</a> => Hello ipv6!</li>
<li><a href="https://test2.localhost:9999" rel="nofollow">https://test2.localhost:9999</a> => Hello ipv4!</li>
<li><a href="https://test3.localhost:9999" rel="nofollow">https://test3.localhost:9999</a> => Hello ipv6!</li>
<li><a href="https://test4.localhost:9999" rel="nofollow">https://test4.localhost:9999</a> => Hello ipv6!</li>
</ul>
<p dir="auto">这就说明确实可以给ipv4或ipv6单独加hosts配置,且在我测试的机器中(macOS) ipv6是优先使用的,与定义的顺序无关(这一点未找到明确的理论支持,在不同的系统中可能会表现不一致)。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">杂项</h2><a id="user-content-杂项" class="anchor" aria-label="Permalink: 杂项" href="#杂项"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">在研究问题的过程中,陆续地学习到了一些不同的知识点,也在这里记录一下:</p>
<ul dir="auto">
<li>
<p dir="auto">IPv4、IPv6双协议栈:有些系统会在进程监听ipv6端口的同时,也自动为其监听ipv4端口,而有些系统则可能不会</p>
</li>
<li>
<p dir="auto">nginx中启用ipv6的方法:</p>
<p dir="auto">将 <code>listen 8080;</code> 换成 <code>listen [::]:8080;</code>它会自动监听 ipv6和ipv4端口。</p>
</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">总结</h2><a id="user-content-总结" class="anchor" aria-label="Permalink: 总结" href="#总结"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">ipv4和ipv6的端口号是两套不同的集合,尽管系统层面上往往会同时监听ipv4和ipv6的端口,但是实际上可能人为地控制只监听ipv4或只监听ipv6(参考上方的Node.js脚本)。</p>
<p dir="auto">通过这种方式,两个不相关的进程监听“同一个端口”变成了可能。</p>
</article>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/fe940a9b1597c8c56aa5aa58bb22ae84
2021-07-14T12:07:12Z
2021-07-14T12:07:12Z
nodejs ipv6 ipv4 test
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/fe940a9b1597c8c56aa5aa58bb22ae84#file-ipv4-js">ipv4.js</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-ipv4-js" class="file my-2">
<div itemprop="text"
class="Box-body p-0 blob-wrapper data type-javascript "
style="overflow: auto" tabindex="0" role="region"
aria-label="ipv4.js content, created by banyudu on 12:07PM on July 14, 2021."
>
<div class="js-check-hidden-unicode js-blob-code-container blob-code-content">
<template class="js-file-alert-template">
<div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
<span>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
<a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
</span>
<div data-view-component="true" class="flash-action"> <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn"> Show hidden characters
</a>
</div>
</div></template>
<template class="js-line-alert-template">
<span aria-label="This line has hidden Unicode characters" data-view-component="true" class="line-alert tooltipped tooltipped-e">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
</span></template>
<table data-hpc class="highlight tab-size js-file-line-container" data-tab-size="8" data-paste-markdown-skip data-tagsearch-path="ipv4.js">
<tr>
<td id="file-ipv4-js-L1" class="blob-num js-line-number js-blob-rnum" data-line-number="1"></td>
<td id="file-ipv4-js-LC1" class="blob-code blob-code-inner js-file-line"><span class=pl-en>require</span><span class=pl-kos>(</span><span class=pl-s>'http'</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>createServer</span><span class=pl-kos>(</span><span class=pl-k>function</span> <span class=pl-kos>(</span><span class=pl-s1>req</span><span class=pl-kos>,</span> <span class=pl-s1>res</span><span class=pl-kos>)</span> <span class=pl-kos>{</span></td>
</tr>
<tr>
<td id="file-ipv4-js-L2" class="blob-num js-line-number js-blob-rnum" data-line-number="2"></td>
<td id="file-ipv4-js-LC2" class="blob-code blob-code-inner js-file-line"> <span class=pl-s1>res</span><span class=pl-kos>.</span><span class=pl-en>writeHead</span><span class=pl-kos>(</span><span class=pl-c1>200</span><span class=pl-kos>,</span> <span class=pl-kos>{</span><span class=pl-s>'Content-Type'</span>: <span class=pl-s>'text/plain'</span><span class=pl-kos>}</span><span class=pl-kos>)</span><span class=pl-kos>;</span></td>
</tr>
<tr>
<td id="file-ipv4-js-L3" class="blob-num js-line-number js-blob-rnum" data-line-number="3"></td>
<td id="file-ipv4-js-LC3" class="blob-code blob-code-inner js-file-line"> <span class=pl-s1>res</span><span class=pl-kos>.</span><span class=pl-en>write</span><span class=pl-kos>(</span><span class=pl-s>'Hello ipv4!\n'</span><span class=pl-kos>)</span><span class=pl-kos>;</span></td>
</tr>
<tr>
<td id="file-ipv4-js-L4" class="blob-num js-line-number js-blob-rnum" data-line-number="4"></td>
<td id="file-ipv4-js-LC4" class="blob-code blob-code-inner js-file-line"> <span class=pl-s1>res</span><span class=pl-kos>.</span><span class=pl-en>end</span><span class=pl-kos>(</span><span class=pl-kos>)</span><span class=pl-kos>;</span></td>
</tr>
<tr>
<td id="file-ipv4-js-L5" class="blob-num js-line-number js-blob-rnum" data-line-number="5"></td>
<td id="file-ipv4-js-LC5" class="blob-code blob-code-inner js-file-line"><span class=pl-kos>}</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>listen</span><span class=pl-kos>(</span><span class=pl-kos>{</span></td>
</tr>
<tr>
<td id="file-ipv4-js-L6" class="blob-num js-line-number js-blob-rnum" data-line-number="6"></td>
<td id="file-ipv4-js-LC6" class="blob-code blob-code-inner js-file-line"> <span class=pl-c1>port</span>: <span class=pl-c1>9999</span><span class=pl-kos>,</span></td>
</tr>
<tr>
<td id="file-ipv4-js-L7" class="blob-num js-line-number js-blob-rnum" data-line-number="7"></td>
<td id="file-ipv4-js-LC7" class="blob-code blob-code-inner js-file-line"> <span class=pl-c1>host</span>: <span class=pl-s>'127.0.0.1'</span></td>
</tr>
<tr>
<td id="file-ipv4-js-L8" class="blob-num js-line-number js-blob-rnum" data-line-number="8"></td>
<td id="file-ipv4-js-LC8" class="blob-code blob-code-inner js-file-line"><span class=pl-kos>}</span><span class=pl-kos>)</span><span class=pl-kos>;</span></td>
</tr>
</table>
</div>
</div>
</div>
</div>
<a href="https://gist.github.com/banyudu/fe940a9b1597c8c56aa5aa58bb22ae84#file-ipv6-js">ipv6.js</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-ipv6-js" class="file my-2">
<div itemprop="text"
class="Box-body p-0 blob-wrapper data type-javascript "
style="overflow: auto" tabindex="0" role="region"
aria-label="ipv6.js content, created by banyudu on 12:07PM on July 14, 2021."
>
<div class="js-check-hidden-unicode js-blob-code-container blob-code-content">
<template class="js-file-alert-template">
<div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
<span>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
<a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
</span>
<div data-view-component="true" class="flash-action"> <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn"> Show hidden characters
</a>
</div>
</div></template>
<template class="js-line-alert-template">
<span aria-label="This line has hidden Unicode characters" data-view-component="true" class="line-alert tooltipped tooltipped-e">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
</span></template>
<table data-hpc class="highlight tab-size js-file-line-container" data-tab-size="8" data-paste-markdown-skip data-tagsearch-path="ipv6.js">
<tr>
<td id="file-ipv6-js-L1" class="blob-num js-line-number js-blob-rnum" data-line-number="1"></td>
<td id="file-ipv6-js-LC1" class="blob-code blob-code-inner js-file-line"><span class=pl-en>require</span><span class=pl-kos>(</span><span class=pl-s>'http'</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>createServer</span><span class=pl-kos>(</span><span class=pl-k>function</span> <span class=pl-kos>(</span><span class=pl-s1>req</span><span class=pl-kos>,</span> <span class=pl-s1>res</span><span class=pl-kos>)</span> <span class=pl-kos>{</span></td>
</tr>
<tr>
<td id="file-ipv6-js-L2" class="blob-num js-line-number js-blob-rnum" data-line-number="2"></td>
<td id="file-ipv6-js-LC2" class="blob-code blob-code-inner js-file-line"> <span class=pl-s1>res</span><span class=pl-kos>.</span><span class=pl-en>writeHead</span><span class=pl-kos>(</span><span class=pl-c1>200</span><span class=pl-kos>,</span> <span class=pl-kos>{</span><span class=pl-s>'Content-Type'</span>: <span class=pl-s>'text/plain'</span><span class=pl-kos>}</span><span class=pl-kos>)</span><span class=pl-kos>;</span></td>
</tr>
<tr>
<td id="file-ipv6-js-L3" class="blob-num js-line-number js-blob-rnum" data-line-number="3"></td>
<td id="file-ipv6-js-LC3" class="blob-code blob-code-inner js-file-line"> <span class=pl-s1>res</span><span class=pl-kos>.</span><span class=pl-en>write</span><span class=pl-kos>(</span><span class=pl-s>'Hello ipv6!\n'</span><span class=pl-kos>)</span><span class=pl-kos>;</span></td>
</tr>
<tr>
<td id="file-ipv6-js-L4" class="blob-num js-line-number js-blob-rnum" data-line-number="4"></td>
<td id="file-ipv6-js-LC4" class="blob-code blob-code-inner js-file-line"> <span class=pl-s1>res</span><span class=pl-kos>.</span><span class=pl-en>end</span><span class=pl-kos>(</span><span class=pl-kos>)</span><span class=pl-kos>;</span></td>
</tr>
<tr>
<td id="file-ipv6-js-L5" class="blob-num js-line-number js-blob-rnum" data-line-number="5"></td>
<td id="file-ipv6-js-LC5" class="blob-code blob-code-inner js-file-line"><span class=pl-kos>}</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>listen</span><span class=pl-kos>(</span><span class=pl-kos>{</span></td>
</tr>
<tr>
<td id="file-ipv6-js-L6" class="blob-num js-line-number js-blob-rnum" data-line-number="6"></td>
<td id="file-ipv6-js-LC6" class="blob-code blob-code-inner js-file-line"> <span class=pl-c1>port</span>: <span class=pl-c1>9999</span><span class=pl-kos>,</span> </td>
</tr>
<tr>
<td id="file-ipv6-js-L7" class="blob-num js-line-number js-blob-rnum" data-line-number="7"></td>
<td id="file-ipv6-js-LC7" class="blob-code blob-code-inner js-file-line"> <span class=pl-c1>host</span>: <span class=pl-s>'::1'</span><span class=pl-kos>,</span></td>
</tr>
<tr>
<td id="file-ipv6-js-L8" class="blob-num js-line-number js-blob-rnum" data-line-number="8"></td>
<td id="file-ipv6-js-LC8" class="blob-code blob-code-inner js-file-line"> <span class=pl-c1>ipv6Only</span>: <span class=pl-c1>true</span></td>
</tr>
<tr>
<td id="file-ipv6-js-L9" class="blob-num js-line-number js-blob-rnum" data-line-number="9"></td>
<td id="file-ipv6-js-LC9" class="blob-code blob-code-inner js-file-line"><span class=pl-kos>}</span><span class=pl-kos>)</span><span class=pl-kos>;</span></td>
</tr>
</table>
</div>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/ee58333f01c46e181d4de0b90f4ad9ee
2021-07-13T16:18:46Z
2021-07-14T02:13:14Z
React自定义hooks useOutdatedEffect 介绍
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/ee58333f01c46e181d4de0b90f4ad9ee#file-react-use-outdated-effect-blog-md">react-use-outdated-effect.blog.md</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-react-use-outdated-effect-blog-md" class="file my-2">
<div id="file-react-use-outdated-effect-blog-md-readme" class="Box-body readme blob p-5 p-xl-6 "
style="overflow: auto" tabindex="0" role="region"
aria-label="react-use-outdated-effect.blog.md content, created by banyudu on 04:18PM on July 13, 2021."
>
<article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">React自定义hooks useOutdatedEffect 介绍</h1><a id="user-content-react自定义hooks-useoutdatedeffect-介绍" class="anchor" aria-label="Permalink: React自定义hooks useOutdatedEffect 介绍" href="#react自定义hooks-useoutdatedeffect-介绍"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">前言</h2><a id="user-content-前言" class="anchor" aria-label="Permalink: 前言" href="#前言"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">React中有一个常见的问题,数据获取之后,组件已经销毁,此时会有这样一段警告:</p>
<p dir="auto"><code>Can't perform a React state update on an unmounted component</code></p>
<p dir="auto">相应地,也有一些常见的解决方案,如:</p>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-en">useEffect</span><span class="pl-kos">(</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-k">let</span> <span class="pl-s1">isMounted</span> <span class="pl-c1">=</span> <span class="pl-c1">true</span><span class="pl-kos">;</span> <span class="pl-c">// note mutable flag</span>
<span class="pl-en">someAsyncOperation</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">then</span><span class="pl-kos">(</span><span class="pl-s1">data</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">isMounted</span><span class="pl-kos">)</span> <span class="pl-en">setState</span><span class="pl-kos">(</span><span class="pl-s1">data</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-c">// add conditional check</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span>
<span class="pl-k">return</span> <span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span> <span class="pl-s1">isMounted</span> <span class="pl-c1">=</span> <span class="pl-c1">false</span> <span class="pl-kos">}</span><span class="pl-kos">;</span> <span class="pl-c">// use cleanup to toggle value, if unmounted</span>
<span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-kos">[</span><span class="pl-kos">]</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-c">// adjust dependencies to your needs</span></pre></div>
<p dir="auto">但是却鲜少有人提及另外一种场景,useEffect异步操作之后,虽然组件仍然处于挂载状态,但是当初的条件已经发生了改变,这种情况下容易出现bug。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">问题</h2><a id="user-content-问题" class="anchor" aria-label="Permalink: 问题" href="#问题"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">举例来说,对于如下的代码:</p>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-k">import</span> <span class="pl-v">React</span><span class="pl-kos">,</span> <span class="pl-kos">{</span> <span class="pl-c1">FC</span><span class="pl-kos">,</span> <span class="pl-s1">useState</span><span class="pl-kos">,</span> <span class="pl-s1">useEffect</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'react'</span>
<span class="pl-k">import</span> <span class="pl-s1">axios</span> <span class="pl-k">from</span> <span class="pl-s">'axios'</span>
<span class="pl-k">const</span> <span class="pl-v">App</span> <span class="pl-c1">=</span> <span class="pl-kos">(</span><span class="pl-s1">props</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-k">const</span> <span class="pl-kos">{</span> id <span class="pl-kos">}</span> <span class="pl-c1">=</span> <span class="pl-s1">props</span>
<span class="pl-k">const</span> <span class="pl-kos">[</span><span class="pl-s1">dataSource</span><span class="pl-kos">,</span> <span class="pl-s1">setDataSource</span><span class="pl-kos">]</span> <span class="pl-c1">=</span> <span class="pl-en">useState</span><span class="pl-kos">(</span><span class="pl-c1">null</span><span class="pl-kos">)</span>
<span class="pl-en">useEffect</span><span class="pl-kos">(</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-k">const</span> <span class="pl-en">fetchData</span> <span class="pl-c1">=</span> <span class="pl-k">async</span> <span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">id</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-k">const</span> <span class="pl-kos">{</span> data <span class="pl-kos">}</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-s1">axios</span><span class="pl-kos">.</span><span class="pl-en">get</span><span class="pl-kos">(</span><span class="pl-s">`/api/mydata/<span class="pl-s1"><span class="pl-kos">${</span><span class="pl-s1">id</span><span class="pl-kos">}</span></span>`</span><span class="pl-kos">)</span>
<span class="pl-s1">setDataSource</span><span class="pl-kos">(</span><span class="pl-s1">data</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span> <span class="pl-k">else</span> <span class="pl-kos">{</span>
<span class="pl-s1">setDataSource</span><span class="pl-kos">(</span><span class="pl-c1">null</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span>
<span class="pl-kos">}</span>
<span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-kos">[</span><span class="pl-s1">id</span><span class="pl-kos">]</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">当<code>id</code>有值的时候,这个组件的<code>useEffect</code>会先请求数据,再设置状态(setDataSource),在<code>id</code>为空的情况下,则会直接清空<code>dataSource</code>。</p>
<p dir="auto">但是因为<code>id</code>不为空时,effect代码是异步代码(<code>axios.get</code>),而在<code>id</code>为空时,则是一个简单的同步代码。</p>
<p dir="auto">那么就有可能出现这样的问题:假如id先有值,后清空,那么第二次<code>useEffect</code>(无数据获取)会更快执行完,而第一次<code>useEffect</code>则执行较慢一些,导致最后<code>dataSource</code>是有值的,反映的是第一次<code>useEffect</code>对应的老数据。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">解决方案</h2><a id="user-content-解决方案" class="anchor" aria-label="Permalink: 解决方案" href="#解决方案"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">为了解决这个问题,我提取了一个公共的npm包:<code>use-outdated-effect</code>,这里简单介绍下它的用法:</p>
<ol dir="auto">
<li>将<code>useEffect</code>换成<code>useOutdatedEffect</code></li>
<li>为<code>useEffect</code>中的callback函数增加两个参数:<code>outdated</code>和<code>unmounted</code>,均为函数,分别返回effect dependencies和组件装载状态的当前情况。</li>
</ol>
<p dir="auto">对于上面的问题,可以用如下的方式改进:</p>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-k">const</span> <span class="pl-v">App</span> <span class="pl-c1">=</span> <span class="pl-kos">(</span><span class="pl-s1">props</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-k">const</span> <span class="pl-kos">{</span> id <span class="pl-kos">}</span> <span class="pl-c1">=</span> <span class="pl-s1">props</span>
<span class="pl-k">const</span> <span class="pl-kos">[</span><span class="pl-s1">dataSource</span><span class="pl-kos">,</span> <span class="pl-s1">setDataSource</span><span class="pl-kos">]</span> <span class="pl-c1">=</span> <span class="pl-en">useState</span><span class="pl-kos">(</span><span class="pl-c1">null</span><span class="pl-kos">)</span>
<span class="pl-en">useOutdatedEffect</span><span class="pl-kos">(</span><span class="pl-kos">(</span><span class="pl-s1">outdated</span><span class="pl-kos">,</span> <span class="pl-s1">unmounted</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-k">const</span> <span class="pl-en">fetchData</span> <span class="pl-c1">=</span> <span class="pl-k">async</span> <span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-k">const</span> <span class="pl-kos">{</span> data <span class="pl-kos">}</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-s1">axios</span><span class="pl-kos">.</span><span class="pl-en">get</span><span class="pl-kos">(</span><span class="pl-s">`/api/mydata/<span class="pl-s1"><span class="pl-kos">${</span><span class="pl-s1">id</span><span class="pl-kos">}</span></span>`</span><span class="pl-kos">)</span>
<span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">outdated</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-c">// check whether dependencies changed. In this example, it's the id variable</span>
<span class="pl-c">// id changed, stop the current operations</span>
<span class="pl-k">return</span>
<span class="pl-kos">}</span>
<span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">unmounted</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-c">// check whether component is unmounted</span>
<span class="pl-c">// component destroied, stop the current operations</span>
<span class="pl-k">return</span>
<span class="pl-kos">}</span>
<span class="pl-s1">setDataSource</span><span class="pl-kos">(</span><span class="pl-s1">data</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span>
<span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-kos">[</span><span class="pl-s1">id</span><span class="pl-kos">]</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span></pre></div>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">实现原理</h2><a id="user-content-实现原理" class="anchor" aria-label="Permalink: 实现原理" href="#实现原理"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">在自定义hooks的内部使用<code>useRef</code>维护一个 myRef 的变量,每当dependencies发生变化,自动设置<code>myRef.current</code>为一个新的值。</p>
<p dir="auto">因为多个<code>useEffect</code>中的代码同时生效时是按顺序执行的,所以可以在自定义hooks中使用两个<code>useEffect</code>,第一个改<code>myRef.current</code>的值,第二个先记录<code>myRef.current</code>的旧值,在异步逻辑完成之后,再重新判断一次获取最新值,只有校验通过之后,才继续进行下一步。</p>
<p dir="auto">关键代码:</p>
<div class="highlight highlight-source-ts" dir="auto"><pre><span class="pl-k">const</span> <span class="pl-en">useAsyncEffect</span> <span class="pl-c1">=</span> <span class="pl-kos">(</span>
<span class="pl-s1">effect</span>: <span class="pl-kos">(</span><span class="pl-s1">outdated</span>: <span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-smi">boolean</span><span class="pl-kos">,</span> <span class="pl-s1">unmounted</span>: <span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-smi">boolean</span><span class="pl-kos">,</span> <span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-smi">ReturnType</span><span class="pl-c1"><</span><span class="pl-smi">EffectCallback</span><span class="pl-c1">></span><span class="pl-kos">,</span>
<span class="pl-s1">inputs</span>?: <span class="pl-smi">DependencyList</span><span class="pl-kos">,</span>
<span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-k">const</span> <span class="pl-s1">asyncFlag</span> <span class="pl-c1">=</span> <span class="pl-en">useRef</span><span class="pl-c1"><</span><span class="pl-smi">number</span><span class="pl-c1">></span><span class="pl-kos">(</span><span class="pl-c1">0</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-en">useEffect</span><span class="pl-kos">(</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span> <span class="pl-s1">asyncFlag</span><span class="pl-kos">.</span><span class="pl-c1">current</span> <span class="pl-c1">+=</span> <span class="pl-c1">1</span> <span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-s1">inputs</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-en">useEffect</span><span class="pl-kos">(</span><span class="pl-k">function</span> <span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-k">let</span> <span class="pl-s1">result</span>: <span class="pl-smi">ReturnType</span><span class="pl-c1"><</span><span class="pl-smi">EffectCallback</span><span class="pl-c1">></span> <span class="pl-c1">|</span> <span class="pl-c1">undefined</span><span class="pl-kos">;</span>
<span class="pl-k">let</span> <span class="pl-s1">unmounted</span> <span class="pl-c1">=</span> <span class="pl-c1">false</span><span class="pl-kos">;</span>
<span class="pl-k">const</span> <span class="pl-s1">innerAsyncFlag</span> <span class="pl-c1">=</span> <span class="pl-s1">asyncFlag</span><span class="pl-kos">.</span><span class="pl-c1">current</span><span class="pl-kos">;</span>
<span class="pl-k">const</span> <span class="pl-s1">maybePromise</span> <span class="pl-c1">=</span> <span class="pl-en">effect</span><span class="pl-kos">(</span>
<span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-s1">asyncFlag</span><span class="pl-kos">.</span><span class="pl-c1">current</span> <span class="pl-c1">!==</span> <span class="pl-s1">innerAsyncFlag</span><span class="pl-kos">,</span>
<span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-s1">unmounted</span><span class="pl-kos">,</span>
<span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-v">Promise</span><span class="pl-kos">.</span><span class="pl-en">resolve</span><span class="pl-kos">(</span><span class="pl-s1">maybePromise</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">then</span><span class="pl-kos">(</span><span class="pl-kos">(</span><span class="pl-s1">value</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span> <span class="pl-s1">result</span> <span class="pl-c1">=</span> <span class="pl-s1">value</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-k">return</span> <span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span> <span class="pl-s1">unmounted</span> <span class="pl-c1">=</span> <span class="pl-c1">true</span><span class="pl-kos">;</span> <span class="pl-s1">result</span> <span class="pl-c1">&&</span> <span class="pl-en">result</span>?.<span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-kos">}</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-s1">inputs</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span><span class="pl-kos">;</span></pre></div>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">安装使用</h2><a id="user-content-安装使用" class="anchor" aria-label="Permalink: 安装使用" href="#安装使用"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="highlight highlight-source-shell" dir="auto"><pre>npm i use-outdated-effect</pre></div>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-en">useOutdatedEffect</span><span class="pl-kos">(</span><span class="pl-kos">(</span><span class="pl-s1">outdated</span><span class="pl-kos">,</span> <span class="pl-s1">unmounted</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-k">const</span> <span class="pl-en">fetchData</span> <span class="pl-c1">=</span> <span class="pl-k">async</span> <span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-k">const</span> <span class="pl-kos">{</span> data <span class="pl-kos">}</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-s1">axios</span><span class="pl-kos">.</span><span class="pl-en">get</span><span class="pl-kos">(</span><span class="pl-s">`/api/mydata/<span class="pl-s1"><span class="pl-kos">${</span><span class="pl-s1">id</span><span class="pl-kos">}</span></span>`</span><span class="pl-kos">)</span>
<span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">outdated</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-c">// check whether dependencies changed. In this example, it's the id variable</span>
<span class="pl-c">// id changed, stop the current operations</span>
<span class="pl-k">return</span>
<span class="pl-kos">}</span>
<span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">unmounted</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">)</span> <span class="pl-kos">{</span> <span class="pl-c">// check whether component is unmounted</span>
<span class="pl-c">// component destroied, stop the current operations</span>
<span class="pl-k">return</span>
<span class="pl-kos">}</span>
<span class="pl-en">setDataSource</span><span class="pl-kos">(</span><span class="pl-s1">data</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span>
<span class="pl-kos">}</span><span class="pl-kos">,</span> <span class="pl-kos">[</span><span class="pl-s1">id</span><span class="pl-kos">]</span><span class="pl-kos">)</span></pre></div>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">更多</h2><a id="user-content-更多" class="anchor" aria-label="Permalink: 更多" href="#更多"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">更多细节,参考<a href="https://github.com/banyudu/use-outdated-effect#readme">Github仓库</a>。</p>
</article>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/cf5a6c8ff4b6c8acec97a5517c0fa583
2021-07-10T07:14:32Z
2024-07-17T12:57:48Z
前端AST处理实践指南(基于ts-morph)
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/cf5a6c8ff4b6c8acec97a5517c0fa583#file-frontend-ast-parse-practice-blog-md">frontend-ast-parse-practice.blog.md</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-frontend-ast-parse-practice-blog-md" class="file my-2">
<div id="file-frontend-ast-parse-practice-blog-md-readme" class="Box-body readme blob p-5 p-xl-6 "
style="overflow: auto" tabindex="0" role="region"
aria-label="frontend-ast-parse-practice.blog.md content, created by banyudu on 07:14AM on July 10, 2021."
>
<article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">前端AST处理实践指南(基于ts-morph)</h1><a id="user-content-前端ast处理实践指南基于ts-morph" class="anchor" aria-label="Permalink: 前端AST处理实践指南(基于ts-morph)" href="#前端ast处理实践指南基于ts-morph"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">背景</h2><a id="user-content-背景" class="anchor" aria-label="Permalink: 背景" href="#背景"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">最近在工作中遇到了一些重复性的任务,为了提升效率,使用了AST进行了批量处理。</p>
<p dir="auto">ts-morph是一个适用于Javascript、Typescript的AST处理工具库,基于<code>typescript</code>实现。</p>
<p dir="auto">虽然之前就有用过<code>ts-morph</code>,并进行过介绍:<a href="https://banyudu.com/posts/batch-update-code-with-ts-morph.a519e9?v=Lc4GrR" rel="nofollow">使用ts-morph批量修改代码</a>,但是再次使用的时候,还是遇到了不少的坑。</p>
<p dir="auto">为了备忘并对读者有一些借鉴作用,特总结了实践指南如下:</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">安装和配置 ts-morph</h2><a id="user-content-安装和配置-ts-morph" class="anchor" aria-label="Permalink: 安装和配置 ts-morph" href="#安装和配置-ts-morph"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">安装</h3><a id="user-content-安装" class="anchor" aria-label="Permalink: 安装" href="#安装"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">ts-morph 发布在 npm 镜像仓库上,直接使用 <code>npm</code> 命令就可以安装。</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>npm install --save-dev ts-morph</pre></div>
<p dir="auto">在安装之前,需要先考虑下安装的位置,即独立成工具项目,或集成在业务项目中。</p>
<p dir="auto">鉴于<code>ts-morph</code>的体积比较大(1.53 MB),且使用频次不频繁,不建议随项目安装。独立成一个工具项目既不会占用业务项目的<code>node_modules</code>体积,也可以较容易地应用于多个项目。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">配置</h3><a id="user-content-配置" class="anchor" aria-label="Permalink: 配置" href="#配置"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">关于实际使用案例,可以参考我之前的一个小工具项目:<a href="https://github.com/banyudu/transformer">transformer</a>,之前在改造 <a href="https://github.com/banyudu/tsterser">tsterser</a> 项目时曾大量应用过。</p>
<p dir="auto">这里给出一些关键的文件:</p>
<div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">package.json</h4><a id="user-content-packagejson" class="anchor" aria-label="Permalink: package.json" href="#packagejson"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">依赖配置</p>
<div class="highlight highlight-source-json" dir="auto"><pre><span class="pl-ent">"dependencies"</span>: {
<span class="pl-ent">"ts-morph"</span>: <span class="pl-s"><span class="pl-pds">"</span>^11.0.0<span class="pl-pds">"</span></span>,
<span class="pl-ent">"ts-node"</span>: <span class="pl-s"><span class="pl-pds">"</span>^10.0.0<span class="pl-pds">"</span></span>,
<span class="pl-ent">"typescript"</span>: <span class="pl-s"><span class="pl-pds">"</span>^4.3.5<span class="pl-pds">"</span></span>,
<span class="pl-ent">"yargs"</span>: <span class="pl-s"><span class="pl-pds">"</span>^15.4.1<span class="pl-pds">"</span></span>
}</pre></div>
<div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">utils</h4><a id="user-content-utils" class="anchor" aria-label="Permalink: utils" href="#utils"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">根据<code>tsconfig.json</code> 文件,生成包含整个项目的 <code>Project</code> ,或按给定文件生成只包含指定文件的 <code>Project</code>。</p>
<div class="highlight highlight-source-ts" dir="auto"><pre><span class="pl-k">export</span> <span class="pl-k">function</span> <span class="pl-en">getProject</span> <span class="pl-kos">(</span><span class="pl-kos">)</span>: <span class="pl-smi">Project</span> <span class="pl-kos">{</span>
<span class="pl-c">// if tsconfig.json specified, use it</span>
<span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-kos">(</span><span class="pl-s1">argv</span><span class="pl-kos">.</span><span class="pl-c1">config</span> <span class="pl-c1">??</span> <span class="pl-s">''</span><span class="pl-kos">)</span> <span class="pl-c1">!==</span> <span class="pl-s">''</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-k">return</span> <span class="pl-k">new</span> <span class="pl-v">Project</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
<span class="pl-c1">tsConfigFilePath</span>: <span class="pl-s1">argv</span><span class="pl-kos">.</span><span class="pl-c1">c</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span>
<span class="pl-c">// otherwise use arguments in argv as source files</span>
<span class="pl-k">const</span> <span class="pl-s1">project</span> <span class="pl-c1">=</span> <span class="pl-k">new</span> <span class="pl-v">Project</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-kos">}</span><span class="pl-kos">)</span>
<span class="pl-s1">argv</span><span class="pl-kos">.</span><span class="pl-c1">_</span><span class="pl-kos">.</span><span class="pl-en">forEach</span><span class="pl-kos">(</span><span class="pl-s1">srcPath</span> <span class="pl-c1">=></span> <span class="pl-s1">project</span><span class="pl-kos">.</span><span class="pl-en">addSourceFileAtPath</span><span class="pl-kos">(</span><span class="pl-s1">srcPath</span><span class="pl-kos">)</span><span class="pl-kos">)</span>
<span class="pl-k">return</span> <span class="pl-s1">project</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">遍历节点</p>
<div class="highlight highlight-source-ts" dir="auto"><pre><span class="pl-k">export</span> <span class="pl-k">function</span> <span class="pl-en">walk</span> <span class="pl-kos">(</span><span class="pl-s1">node</span>: <span class="pl-smi">Node</span><span class="pl-kos">,</span> <span class="pl-s1">callback</span>: <span class="pl-kos">(</span><span class="pl-s1">node</span>: <span class="pl-smi">Node</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-smi">boolean</span><span class="pl-kos">)</span>: <span class="pl-smi">boolean</span> <span class="pl-kos">{</span>
<span class="pl-k">return</span> <span class="pl-en">_walk</span><span class="pl-kos">(</span><span class="pl-s1">node</span><span class="pl-kos">,</span> <span class="pl-s1">callback</span><span class="pl-kos">,</span> <span class="pl-k">new</span> <span class="pl-v">Set</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span>
<span class="pl-k">function</span> <span class="pl-en">_walk</span> <span class="pl-kos">(</span><span class="pl-s1">node</span>: <span class="pl-smi">Node</span><span class="pl-kos">,</span> <span class="pl-s1">callback</span>: <span class="pl-kos">(</span><span class="pl-s1">node</span>: <span class="pl-smi">Node</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-smi">boolean</span><span class="pl-kos">,</span> <span class="pl-s1">parsed</span>: <span class="pl-smi">Set</span><span class="pl-c1"><</span><span class="pl-smi">Node</span><span class="pl-c1">></span><span class="pl-kos">)</span>: <span class="pl-smi">boolean</span> <span class="pl-kos">{</span>
<span class="pl-k">try</span> <span class="pl-kos">{</span>
<span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">parsed</span><span class="pl-kos">.</span><span class="pl-en">has</span><span class="pl-kos">(</span><span class="pl-s1">node</span><span class="pl-kos">)</span> <span class="pl-c1">||</span> <span class="pl-s1">node</span><span class="pl-kos">.</span><span class="pl-c1">compilerNode</span> <span class="pl-c1">===</span> <span class="pl-c1">null</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-k">return</span> <span class="pl-c1">false</span>
<span class="pl-kos">}</span>
<span class="pl-s1">parsed</span><span class="pl-kos">.</span><span class="pl-en">add</span><span class="pl-kos">(</span><span class="pl-s1">node</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span> <span class="pl-k">catch</span> <span class="pl-kos">(</span><span class="pl-s1">error</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-k">return</span> <span class="pl-c1">false</span>
<span class="pl-kos">}</span>
<span class="pl-k">const</span> <span class="pl-s1">children</span> <span class="pl-c1">=</span> <span class="pl-s1">node</span><span class="pl-kos">.</span><span class="pl-en">getChildren</span><span class="pl-kos">(</span><span class="pl-kos">)</span>
<span class="pl-c">// visit root node</span>
<span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-c1">!</span><span class="pl-en">callback</span><span class="pl-kos">(</span><span class="pl-s1">node</span><span class="pl-kos">)</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-k">return</span> <span class="pl-c1">false</span>
<span class="pl-kos">}</span>
<span class="pl-c">// visit children, depth-first-search</span>
<span class="pl-k">for</span> <span class="pl-kos">(</span><span class="pl-k">let</span> <span class="pl-s1">i</span> <span class="pl-c1">=</span> <span class="pl-c1">0</span><span class="pl-kos">;</span> <span class="pl-s1">i</span> <span class="pl-c1"><</span> <span class="pl-s1">children</span><span class="pl-kos">.</span><span class="pl-c1">length</span><span class="pl-kos">;</span> <span class="pl-s1">i</span><span class="pl-c1">++</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-c1">!</span><span class="pl-en">_walk</span><span class="pl-kos">(</span><span class="pl-s1">children</span><span class="pl-kos">[</span><span class="pl-s1">i</span><span class="pl-kos">]</span><span class="pl-kos">,</span> <span class="pl-s1">callback</span><span class="pl-kos">,</span> <span class="pl-s1">parsed</span><span class="pl-kos">)</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-k">return</span> <span class="pl-c1">false</span>
<span class="pl-kos">}</span>
<span class="pl-kos">}</span>
<span class="pl-k">return</span> <span class="pl-c1">true</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">更多代码,请移步 <a href="https://github.com/banyudu/transformer">transformer</a> 中查看。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">应用指南</h2><a id="user-content-应用指南" class="anchor" aria-label="Permalink: 应用指南" href="#应用指南"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">在安装和配置好<code>ts-morph</code>之后,就进入到实战的环节中了。</p>
<p dir="auto">我将实战环节分为如下的几个过程:</p>
<ul dir="auto">
<li>
<p dir="auto">方案选择</p>
<blockquote>
<p dir="auto">当前面临的工作,是否适合用AST解决。</p>
</blockquote>
</li>
<li>
<p dir="auto">AST结构分析</p>
<blockquote>
<p dir="auto">待处理的文件的语法树长什么样?</p>
</blockquote>
</li>
<li>
<p dir="auto">脚本编写&运行</p>
<blockquote>
<p dir="auto">编写适合的脚本</p>
</blockquote>
</li>
<li>
<p dir="auto">常见的坑</p>
<blockquote>
<p dir="auto">理解和绕过常见的问题</p>
</blockquote>
</li>
</ul>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">方案选择</h3><a id="user-content-方案选择" class="anchor" aria-label="Permalink: 方案选择" href="#方案选择"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">很多批量的任务,可以通过批量搜索替换,或者编写Shell脚本完成,这些任务需要满足以下两个条件之一:</p>
<ol dir="auto">
<li>规则简单,如批量替换。在大多数的IDE中可完成,如VSCode支持正则表达式替换。</li>
<li>新代码,也可说成是代码生成器。即使规则很复杂,也可以通过脚本处理,因为它不需要与原有代码做处理,本质上还是一个模板生成的过程。</li>
</ol>
<p dir="auto">而那些既规则复杂(难以用正则表达式描述),又是修改原有代码的任务,就适合用AST处理了。</p>
<p dir="auto">在没有脚本积累的时候(即第一次使用时),使用AST处理批量任务会是一个比较耗时的过程(留出一天左右的时间),之后就会快很多。所以也可以考虑先使用AST处理一些简单任务,积累经验。</p>
<p dir="auto">另外还有一些任务是比较推荐用AST来实现的:</p>
<ol dir="auto">
<li>准确度要求高:批量替换虽然简单,但是容易替换出bug,在数据比较多的时候,可能会注意不到bug的产生。因此在<code>webpack-loader</code>、<code>eslint-plugin</code>、<code>babel-plugin</code>等应用场景中,都应该优先考虑使用AST。</li>
<li>分析报表:类似于一些代码质量检查工具等,需要出分析报告的场景,用AST可以给出精确的报告,且当报表规则变化时,调整也方便。</li>
<li>自动化任务:批量替换等方式,因为可靠度不够高,往往需要人的二次甄别,因此不太适合处理自动化任务。</li>
</ol>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">AST结构分析</h3><a id="user-content-ast结构分析" class="anchor" aria-label="Permalink: AST结构分析" href="#ast结构分析"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">批量处理的脚本,从原理上来说,是先定位到相应的节点位置(类似于CSS选择器),然后再执行一些相应的操作(如增减属性、替换文本)。</p>
<p dir="auto">因此,有一个可视化的结构分析工具是比较重要的,实现类似于浏览器devtools的效果。</p>
<p dir="auto">对于TS、JS相关的代码,可以使用一个在线的AST分析网站:<a href="https://ts-ast-viewer.com/%E3%80%82" rel="nofollow">https://ts-ast-viewer.com/。</a></p>
<p dir="auto">举例来说,对于如下的一段TS代码:</p>
<div class="highlight highlight-source-ts" dir="auto"><pre><span class="pl-k">const</span> <span class="pl-s1">arr</span>: <span class="pl-smi">number</span><span class="pl-kos">[</span><span class="pl-kos">]</span> <span class="pl-c1">=</span> <span class="pl-kos">[</span><span class="pl-c1">1</span><span class="pl-kos">,</span> <span class="pl-c1">2</span><span class="pl-kos">,</span> <span class="pl-c1">3</span><span class="pl-kos">,</span> <span class="pl-c1">4</span><span class="pl-kos">]</span><span class="pl-kos">;</span>
<span class="pl-k">const</span> <span class="pl-s1">obj</span> <span class="pl-c1">=</span> <span class="pl-kos">{</span> <span class="pl-c1">hello</span>: <span class="pl-c1">1</span><span class="pl-kos">,</span> <span class="pl-c1">world</span>: <span class="pl-c1">2</span> <span class="pl-kos">}</span><span class="pl-kos">;</span>
<span class="pl-k">const</span> <span class="pl-en">func</span> <span class="pl-c1">=</span> <span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-s">'func'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-k">return</span> <span class="pl-s1">arr</span><span class="pl-kos">.</span><span class="pl-c1">length</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">可以看到类似这样的 AST 结构:</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/c8cd77f487a0fa21681482bd2cdc8c64ecb852236ca8da203ad665a9c77c1e74/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303231303731303134303332303233342e706e67"><img src="https://camo.githubusercontent.com/c8cd77f487a0fa21681482bd2cdc8c64ecb852236ca8da203ad665a9c77c1e74/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303231303731303134303332303233342e706e67" alt="ast-preview" data-canonical-src="https://banyudu.github.io/images/image-20210710140320234.png" style="max-width: 100%;"></a></p>
<p dir="auto">然后,在代码中可以使用类似于如下的选择器,选择到相应的节点:</p>
<div class="highlight highlight-source-ts" dir="auto"><pre><span class="pl-c">// 寻找文件中的第一个类型为 VariableDeclaration 的节点</span>
<span class="pl-k">const</span> <span class="pl-s1">arrNode</span> <span class="pl-c1">=</span> <span class="pl-s1">file</span><span class="pl-kos">.</span><span class="pl-en">getFirstDescendantByKind</span><span class="pl-kos">(</span><span class="pl-v">SyntaxKind</span><span class="pl-kos">.</span><span class="pl-c1">VariableDeclaration</span><span class="pl-kos">)</span></pre></div>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">脚本编写&运行</h3><a id="user-content-脚本编写运行" class="anchor" aria-label="Permalink: 脚本编写&运行" href="#脚本编写运行"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">编写</h3><a id="user-content-编写" class="anchor" aria-label="Permalink: 编写" href="#编写"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">首先看一个简单的脚本示意代码:</p>
<div class="highlight highlight-source-ts" dir="auto"><pre><span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-s1">getProject</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'../utils'</span><span class="pl-kos">;</span>
<span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-v">SyntaxKind</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'@ts-morph/common'</span>
<span class="pl-kos">(</span><span class="pl-k">async</span> <span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-k">const</span> <span class="pl-s1">project</span> <span class="pl-c1">=</span> <span class="pl-en">getProject</span><span class="pl-kos">(</span><span class="pl-kos">)</span>
<span class="pl-k">const</span> <span class="pl-s1">files</span> <span class="pl-c1">=</span> <span class="pl-s1">project</span><span class="pl-kos">.</span><span class="pl-en">getSourceFiles</span><span class="pl-kos">(</span><span class="pl-kos">)</span>
<span class="pl-k">for</span> <span class="pl-kos">(</span><span class="pl-k">const</span> <span class="pl-s1">file</span> <span class="pl-k">of</span> <span class="pl-s1">files</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-k">const</span> <span class="pl-s1">actions</span>: <span class="pl-smi">Array</span><span class="pl-c1"><</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-smi"><span class="pl-k">void</span></span><span class="pl-c1">></span> <span class="pl-c1">=</span> <span class="pl-kos">[</span><span class="pl-kos">]</span>
<span class="pl-c">// 各种节点获取逻辑和判断逻辑</span>
<span class="pl-k">const</span> <span class="pl-s1">arrNode</span> <span class="pl-c1">=</span> <span class="pl-s1">file</span><span class="pl-kos">.</span><span class="pl-en">getFirstDescendantByKind</span><span class="pl-kos">(</span><span class="pl-v">SyntaxKind</span><span class="pl-kos">.</span><span class="pl-c1">VariableDeclaration</span><span class="pl-kos">)</span>
<span class="pl-c">// 先把待处理事项存起来</span>
<span class="pl-s1">actions</span><span class="pl-kos">.</span><span class="pl-en">unshift</span><span class="pl-kos">(</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-s1">arrNode</span><span class="pl-kos">.</span><span class="pl-en">replaceWithText</span><span class="pl-kos">(</span><span class="pl-s">'{ hello: 1, world: 2 }'</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span>
<span class="pl-c">// 最后一并处理</span>
<span class="pl-s1">actions</span><span class="pl-kos">.</span><span class="pl-en">forEach</span><span class="pl-kos">(</span><span class="pl-kos">(</span><span class="pl-s1">act</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-en">act</span><span class="pl-kos">(</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span>
<span class="pl-k">await</span> <span class="pl-s1">project</span><span class="pl-kos">.</span><span class="pl-en">save</span><span class="pl-kos">(</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">catch</span><span class="pl-kos">(</span><span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-c1">error</span><span class="pl-kos">)</span></pre></div>
<p dir="auto">上面的代码的逻辑比较简单,主要分为以下几个部分:</p>
<ol dir="auto">
<li>
<p dir="auto">getProject() 获取工程,上面有给出<code>getProject</code>函数的代码供参考</p>
</li>
<li>
<p dir="auto"><code>project.getSourceFiles()</code>获取文件列表,这里可以过滤掉不需要处理的文件</p>
</li>
<li>
<p dir="auto"><code>file.getFirstDescendantByKind</code>定位节点,<code>getFirstDescendantByKind</code>是一种选择器,类似的选择器有很多,举例来说,对于获取子元素,有如下的常见方式:</p>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>名称</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td>getChildren</td>
<td>返回所有子元素</td>
</tr>
<tr>
<td>getChildAtIndex</td>
<td>返回第index个子元素</td>
</tr>
<tr>
<td>getChildCount</td>
<td>返回子元素数量</td>
</tr>
<tr>
<td>getChildAtPos</td>
<td>返回指定位置的子元素(pos代替在文件流中的配置)</td>
</tr>
<tr>
<td>getChildrenOfKind</td>
<td>获取指定类型的子元素列表</td>
</tr>
<tr>
<td>getDescendants</td>
<td>获取后代(子、孙、重孙等所有层级)列表</td>
</tr>
<tr>
<td>getDescendantsOfKind</td>
<td>获取指定类型的所有后代列表</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">类似的方法比较多,这里就不一一介绍了。在实际使用过程中,Typescript的提示会很有帮助。如果想要得到一个比较全的列表,也可以参考<a href="https://github.com/dsherret/ts-morph/blob/e192d41bd52b3add716a49f63fafd9bd4595425f/packages/ts-morph/lib/ts-morph.d.ts#L2922">ts-morph的类型文件</a>。</p>
</li>
<li>
<p dir="auto"><code>replaceWithText</code> 替换节点内容为新的文本。</p>
<p dir="auto">这是一种常见的修改方式,先通过<code>getText()</code>获取到其文本内容,在处理之后,再调用<code>replaceText</code>将原有内容替换成新的内容。</p>
<p dir="auto">这是一个比较万金油型的替换方式,除了这种方式以外,还有一些根据节点类型而定的方法,比如 <code>addStatements、addProperty、insertProperty</code>等,各种有其适用的场景。</p>
</li>
<li>
<p dir="auto"><code>await project.save</code> 执行保存动作,此时会保存所有变更到磁盘中。</p>
</li>
</ol>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">运行</h3><a id="user-content-运行" class="anchor" aria-label="Permalink: 运行" href="#运行"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">在代码编写完成后,使用<code>ts-node</code>工具执行脚本,并给出目标工程的<code>tsconfig.json</code>文件路径,或待处理的单个文件路径,即可运行。</p>
<p dir="auto">如对于 <a href="https://github.com/banyudu/transformer">transformer</a> 中的脚本,可以通过如下的命令来运行。</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>ts-node src/scripts/xxxxx.ts -c /path/to/project/tsconfig.json</pre></div>
<blockquote>
<p dir="auto">这里的 -c 参数是提供tsconfig.json文件路径用的,工程内使用<code>yargs</code>和<code>getProject</code>函数处理。</p>
</blockquote>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">常见的坑</h3><a id="user-content-常见的坑" class="anchor" aria-label="Permalink: 常见的坑" href="#常见的坑"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>修改操作(如<code>replaceText</code>)导致原有节点失效</li>
<li>修改操作(如<code>replaceText</code>)提示语法错误</li>
</ul>
<div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">原有节点失效</h4><a id="user-content-原有节点失效" class="anchor" aria-label="Permalink: 原有节点失效" href="#原有节点失效"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">在<code>getProject</code>操作之后,<code>ts-morph</code>根据涉及到的文件的内容,构建出了AST模型,可以用来遍历查询。</p>
<p dir="auto">但是当在文件中执行了替换操作(如<code>replaceText</code>)之后,<code>ts-morph</code>建立的模型会被改变,继而会导致之前查询的节点不再可用。</p>
<p dir="auto">此时会出现这样的错误:</p>
<p dir="auto"><code>InvalidOperationError: Attempted to get information from a node that was removed or forgotten.</code></p>
<p dir="auto">要避免这个问题,就要注意将查询阶段与修改阶段分开,如上面给出的例子,先将所有待操作的指令存储在一个<code>actions</code>数组中,在每个文件遍历完成后,再统一执行。</p>
<p dir="auto">同时为了避免修改文件上方的代码,影响到下方代码的锚点,应该先改下面的,再改上面的。</p>
<p dir="auto">即</p>
<div class="highlight highlight-source-ts" dir="auto"><pre><span class="pl-s1">actions</span><span class="pl-kos">.</span><span class="pl-en">unshift</span><span class="pl-kos">(</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span> <span class="pl-c">// 使用 unshift 来倒序</span>
<span class="pl-c">// 具体操作</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span>
<span class="pl-s1">actions</span><span class="pl-kos">.</span><span class="pl-en">forEach</span><span class="pl-kos">(</span><span class="pl-s1">act</span> <span class="pl-c1">=></span> <span class="pl-en">act</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">)</span> <span class="pl-c">// 遍历执行</span></pre></div>
<div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">语法错误</h4><a id="user-content-语法错误" class="anchor" aria-label="Permalink: 语法错误" href="#语法错误"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto"><code>replaceText</code> 操作有时会提示语法错误,因为<code>ts-morph</code>认为新插入节点的类型和原有类型不一致,可能是个bug,常见于注释代码的场景。</p>
<p dir="auto">对于确认没有语法错误的情况,可以先调用<code>removeText</code>删除原有节点,再使用<code>insertText</code>插入新节点。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">总结</h2><a id="user-content-总结" class="anchor" aria-label="Permalink: 总结" href="#总结"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">以上就是我总结的前端AST处理工具实践指南的内容,主要基于 <code>ts-morph</code> 进行操作,并使用了<a href="https://ts-ast-viewer.com/" rel="nofollow">https://ts-ast-viewer.com/</a> 这个网站查看语法树。</p>
<p dir="auto">想要参考具体代码的,可以移步到我之前写的一个Git仓库<a href="https://github.com/banyudu/transformer">transformer</a>。</p>
<p dir="auto">希望对大家有所帮助!</p>
</article>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/c1623bece56a14d7495aa3bb7b09647a
2021-06-14T01:47:26Z
2021-06-14T01:58:14Z
Typescript中的类型联动
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/c1623bece56a14d7495aa3bb7b09647a#file-typescript-related-type-blog-md">typescript-related-type.blog.md</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-typescript-related-type-blog-md" class="file my-2">
<div id="file-typescript-related-type-blog-md-readme" class="Box-body readme blob p-5 p-xl-6 "
style="overflow: auto" tabindex="0" role="region"
aria-label="typescript-related-type.blog.md content, created by banyudu on 01:47AM on June 14, 2021."
>
<article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">Typescript中的类型联动</h1><a id="user-content-typescript中的类型联动" class="anchor" aria-label="Permalink: Typescript中的类型联动" href="#typescript中的类型联动"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">问题说明</h2><a id="user-content-问题说明" class="anchor" aria-label="Permalink: 问题说明" href="#问题说明"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">类型联动,指的是一个对象中,A属性的值变化,会引起B属性的类型变化。</p>
<p dir="auto">假设有这样一个类型:</p>
<div class="highlight highlight-source-ts" dir="auto"><pre><span class="pl-k">type</span> <span class="pl-smi">Component</span> <span class="pl-c1">=</span> <span class="pl-kos">{</span>
<span class="pl-c1">type</span>: <span class="pl-s">'button'</span> <span class="pl-c1">|</span> <span class="pl-s">'input'</span> <span class="pl-c1">|</span> <span class="pl-s">'select'</span> <span class="pl-c1">|</span> <span class="pl-s">'textarea'</span><span class="pl-kos">;</span>
<span class="pl-c1">payload</span>: <span class="pl-kos">{</span>
<span class="pl-c1">onChange</span>: <span class="pl-smi">Function</span><span class="pl-kos">;</span>
<span class="pl-c1">onClick</span>: <span class="pl-smi">Function</span><span class="pl-kos">;</span>
<span class="pl-c1">type</span>: <span class="pl-smi">string</span><span class="pl-kos">;</span>
<span class="pl-c1">placeholder</span>: <span class="pl-smi">string</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">类型联动,指的是当 type 的值发生变化时,payload的类型也跟着变化,比如当type为'input'时,可能希望payload中包含onChange,而当type为'button'时,则不希望payload中包含onChange。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">粗糙的解决方案</h2><a id="user-content-粗糙的解决方案" class="anchor" aria-label="Permalink: 粗糙的解决方案" href="#粗糙的解决方案"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">最粗糙的解决方案,是用 Union 的形式,将多种类型组合起来。</p>
<p dir="auto">类似下面的形式:</p>
<div class="highlight highlight-source-ts" dir="auto"><pre><span class="pl-k">type</span> <span class="pl-smi">ButtonComponent</span> <span class="pl-c1">=</span> <span class="pl-kos">{</span>
<span class="pl-c1">type</span>: <span class="pl-s">'button'</span><span class="pl-kos">;</span>
<span class="pl-c1">payload</span>: <span class="pl-kos">{</span>
<span class="pl-c1">onClick</span>: <span class="pl-smi">Function</span><span class="pl-kos">;</span>
<span class="pl-c1">type</span>: <span class="pl-smi">string</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span>
<span class="pl-kos">}</span>
<span class="pl-k">type</span> <span class="pl-smi">InputComponent</span> <span class="pl-c1">=</span> <span class="pl-kos">{</span>
<span class="pl-c1">type</span>: <span class="pl-s">'input'</span><span class="pl-kos">;</span>
<span class="pl-c1">payload</span>: <span class="pl-kos">{</span>
<span class="pl-c1">onChange</span>: <span class="pl-smi">Function</span><span class="pl-kos">;</span>
<span class="pl-c1">placeholder</span>: <span class="pl-smi">string</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span>
<span class="pl-kos">}</span>
<span class="pl-c">// ... 将所有可能的类型写一遍</span>
<span class="pl-c">// 最后,给一个union类型</span>
<span class="pl-k">type</span> <span class="pl-smi">Component</span> <span class="pl-c1">=</span> <span class="pl-smi">ButtonComponent</span> <span class="pl-c1">|</span> <span class="pl-smi">InputComponent</span> <span class="pl-c1">|</span> ... <span class="pl-c1">|</span> <span class="pl-smi">OtherComponents</span>
<span class="pl-c">// => {type: 'button', payload: {onClick: Function, type: string}} | {type: 'input', payload: {onChange: Function, placeholder: string}}</span></pre></div>
<p dir="auto">这种方法比较笨拙,因为它需要把每种可能的组合都写一遍。</p>
<p dir="auto">手写类型的时候,还可以勉强用一下,当遇到需要类型推导的场景时,就不好使了。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">类型推导</h2><a id="user-content-类型推导" class="anchor" aria-label="Permalink: 类型推导" href="#类型推导"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">使用如下的代码,可以推导出我们想要的类型</p>
<div class="highlight highlight-source-ts" dir="auto"><pre><span class="pl-c">// 先定义一个类型映射关系</span>
<span class="pl-k">type</span> <span class="pl-smi">ComponentPayload</span> <span class="pl-c1">=</span> <span class="pl-kos">{</span>
<span class="pl-c1">button</span>: <span class="pl-kos">{</span>
<span class="pl-c1">onClick</span>: <span class="pl-smi">Function</span><span class="pl-kos">;</span>
<span class="pl-c1">type</span>: <span class="pl-smi">string</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span>
<span class="pl-c1">input</span>: <span class="pl-kos">{</span>
<span class="pl-c1">onChange</span>: <span class="pl-smi">Function</span><span class="pl-kos">;</span>
<span class="pl-c1">placeholder</span>: <span class="pl-smi">string</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span>
<span class="pl-c">// 其它略过不写</span>
<span class="pl-kos">}</span>
<span class="pl-c">// 使用 keyof 获取到所有的 key</span>
<span class="pl-k">type</span> <span class="pl-smi">ComponentPayloadKeys</span> <span class="pl-c1">=</span> <span class="pl-k">keyof</span> <span class="pl-smi">ComponentPayload</span><span class="pl-kos">;</span>
<span class="pl-c">// => 'button' | 'input'</span>
<span class="pl-c">// 再结合类型推导,构造出valueof</span>
<span class="pl-k">type</span> <span class="pl-smi">ComponentPayloadValues</span> <span class="pl-c1">=</span> <span class="pl-smi">ComponentPayload</span><span class="pl-kos">[</span><span class="pl-k">keyof</span> <span class="pl-smi">ComponentPayload</span><span class="pl-kos">]</span>
<span class="pl-c">// => { onClick: Function; type: string; } | { onChange: Function; placeholder: string; }</span>
<span class="pl-c">// 如果先构造出 {type => {type, payload}} 的格式,就可以用上面的valueof的方式,获取到目标类型</span>
<span class="pl-k">type</span> <span class="pl-smi">Component</span> <span class="pl-c1">=</span> <span class="pl-kos">{</span>
<span class="pl-kos">[</span><span class="pl-smi">T</span> <span class="pl-k">in</span> <span class="pl-k">keyof</span> <span class="pl-smi">ComponentPayload</span><span class="pl-kos">]</span>: <span class="pl-kos">{</span>
<span class="pl-c1">type</span>: <span class="pl-smi">T</span><span class="pl-kos">;</span>
<span class="pl-c1">payload</span>: <span class="pl-smi">ComponentPayload</span><span class="pl-kos">[</span><span class="pl-smi">T</span><span class="pl-kos">]</span>
<span class="pl-kos">}</span>
<span class="pl-kos">}</span><span class="pl-kos">[</span><span class="pl-k">keyof</span> <span class="pl-smi">ComponentPayload</span><span class="pl-kos">]</span>
<span class="pl-c">// => {type: 'button', payload: {onClick: Function, type: string}} | {type: 'input', payload: {onChange: Function, placeholder: string}}</span></pre></div>
<p dir="auto">至此,就大功告成了。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">总结</h2><a id="user-content-总结" class="anchor" aria-label="Permalink: 总结" href="#总结"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">TS中的类型跟随值变化,本质是还是类型跟随类型变化,只是TS中允许把值作为类型的一种。</p>
<p dir="auto">通过<code>keyof、Generic</code>等操作生成Union类型,可以实现类型之间的联动。</p>
</article>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/3a8cd15fd5bfd2fc04a757d65723da87
2021-06-07T04:14:16Z
2021-06-07T04:14:16Z
React测试驱动开发 - 环境搭建
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/3a8cd15fd5bfd2fc04a757d65723da87#file-react-test-driven-development-part3-setup-environment-blog-md">react-test-driven-development-part3-setup-environment.blog.md</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-react-test-driven-development-part3-setup-environment-blog-md" class="file my-2">
<div id="file-react-test-driven-development-part3-setup-environment-blog-md-readme" class="Box-body readme blob p-5 p-xl-6 "
style="overflow: auto" tabindex="0" role="region"
aria-label="react-test-driven-development-part3-setup-environment.blog.md content, created by banyudu on 04:14AM on June 07, 2021."
>
<article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">React测试驱动开发 - 环境搭建</h1><a id="user-content-react测试驱动开发---环境搭建" class="anchor" aria-label="Permalink: React测试驱动开发 - 环境搭建" href="#react测试驱动开发---环境搭建"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<blockquote>
<p dir="auto">工欲善其事,必先利其器</p>
</blockquote>
<p dir="auto">久等了,本篇讲一下React项目测试环境搭建。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">工具集</h2><a id="user-content-工具集" class="anchor" aria-label="Permalink: 工具集" href="#工具集"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>名称</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td>jest</td>
<td>测试框架</td>
</tr>
<tr>
<td>testing-library</td>
<td>测试工具包</td>
</tr>
<tr>
<td>msw</td>
<td>mock接口请求</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<p dir="auto">当然也会有一些其它可选的工具,如使用mocha代替jest,使用test-cafe、cypress等代替testing-library。这些随个人喜好可以调整。</p>
<p dir="auto">下面分别介绍下三类工程的测试环境搭建:create-react-app、next.js、umijs。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">在Create-React-App 工程中搭建测试环境</h2><a id="user-content-在create-react-app-工程中搭建测试环境" class="anchor" aria-label="Permalink: 在Create-React-App 工程中搭建测试环境" href="#在create-react-app-工程中搭建测试环境"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">create-react-app中内置了测试功能,开箱即用。</p>
<p dir="auto">首先使用<code>create-react-app</code>命令初始化一个工程:</p>
<p dir="auto"><code>npx create-react-app with-cra </code></p>
<p dir="auto">执行这个命令之后,就会得到一个名为<code>with-cra</code>的工程了。</p>
<p dir="auto">最新版本的<code>create-react-app</code>中已经内置了测试套件,所以现在已经可以执行<code>npm test</code>命令了。</p>
<p dir="auto">看看它默认生成的测试文件:</p>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-s1">render</span><span class="pl-kos">,</span> <span class="pl-s1">screen</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'@testing-library/react'</span><span class="pl-kos">;</span>
<span class="pl-k">import</span> <span class="pl-v">App</span> <span class="pl-k">from</span> <span class="pl-s">'./App'</span><span class="pl-kos">;</span>
<span class="pl-en">test</span><span class="pl-kos">(</span><span class="pl-s">'renders learn react link'</span><span class="pl-kos">,</span> <span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-en">render</span><span class="pl-kos">(</span><span class="pl-c1"><</span><span class="pl-v">App</span> <span class="pl-kos">/></span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-k">const</span> <span class="pl-s1">linkElement</span> <span class="pl-c1">=</span> <span class="pl-s1">screen</span><span class="pl-kos">.</span><span class="pl-en">getByText</span><span class="pl-kos">(</span><span class="pl-pds"><span class="pl-c1">/</span><span class="pl-s">l</span><span class="pl-s">e</span><span class="pl-s">a</span><span class="pl-s">r</span><span class="pl-s">n</span><span class="pl-s"> </span><span class="pl-s">r</span><span class="pl-s">e</span><span class="pl-s">a</span><span class="pl-s">c</span><span class="pl-s">t</span><span class="pl-c1">/</span>i</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-en">expect</span><span class="pl-kos">(</span><span class="pl-s1">linkElement</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">toBeInTheDocument</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p dir="auto">执行<code>npm test</code>可以看到如下的输出:</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/fc55330335679113b5660fabc96e00cdc98b0dfe25bc51428a5db40aa357e155/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303231303532393038333031323233382e706e67"><img src="https://camo.githubusercontent.com/fc55330335679113b5660fabc96e00cdc98b0dfe25bc51428a5db40aa357e155/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303231303532393038333031323233382e706e67" alt="image-20210529083012238" data-canonical-src="https://banyudu.github.io/images/image-20210529083012238.png" style="max-width: 100%;"></a></p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">在Next.js工程中搭建测试环境</h2><a id="user-content-在nextjs工程中搭建测试环境" class="anchor" aria-label="Permalink: 在Next.js工程中搭建测试环境" href="#在nextjs工程中搭建测试环境"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Next.js中并没有默认提供test套件,需要新建工程之后手动配置。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">新建工程</h3><a id="user-content-新建工程" class="anchor" aria-label="Permalink: 新建工程" href="#新建工程"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">执行<code> npx create-next-app with-nextjs</code>命令,新建一个名为<code>with-nextjs</code>的Next.js工程。</p>
<p dir="auto">此时执行<code>npm test</code>可以看到<code>Error: no test specified</code>的报错,因为它默认没有配置test套件。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">安装&配置测试套件</h3><a id="user-content-安装配置测试套件" class="anchor" aria-label="Permalink: 安装&配置测试套件" href="#安装配置测试套件"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">首先,使用npm安装相关依赖:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>npm install --save-dev @testing-library/react jest @testing-library/jest-dom identity-obj-proxy</pre></div>
<p dir="auto">然后再加入以下的配置:</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">Package.json配置</h3><a id="user-content-packagejson配置" class="anchor" aria-label="Permalink: Package.json配置" href="#packagejson配置"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">package.json文件中,在"scripts"段中新增如下行:</p>
<div class="highlight highlight-source-json" dir="auto"><pre>{
<span class="pl-ent">"scripts"</span>: {
<span class="pl-ent">"test"</span>: <span class="pl-s"><span class="pl-pds">"</span>jest<span class="pl-pds">"</span></span>
}
}</pre></div>
<div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">Babel配置</h4><a id="user-content-babel配置" class="anchor" aria-label="Permalink: Babel配置" href="#babel配置"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">添加<code>.babelrc</code>配置文件,内容如下:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>{
<span class="pl-s"><span class="pl-pds">"</span>presets<span class="pl-pds">"</span></span>: [<span class="pl-s"><span class="pl-pds">"</span>next/babel<span class="pl-pds">"</span></span>]
}</pre></div>
<div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">Jest配置</h4><a id="user-content-jest配置" class="anchor" aria-label="Permalink: Jest配置" href="#jest配置"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">添加<code>jest.config.json</code>配置文件,内容如下:</p>
<div class="highlight highlight-source-json" dir="auto"><pre>{
<span class="pl-ent">"moduleNameMapper"</span>: {
<span class="pl-ent">"<span class="pl-cce">\\</span>.(css|less|scss|sass)$"</span>: <span class="pl-s"><span class="pl-pds">"</span>identity-obj-proxy<span class="pl-pds">"</span></span>
},
<span class="pl-ent">"testEnvironment"</span>: <span class="pl-s"><span class="pl-pds">"</span>jsdom<span class="pl-pds">"</span></span>
}</pre></div>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">添加测试文件</h3><a id="user-content-添加测试文件" class="anchor" aria-label="Permalink: 添加测试文件" href="#添加测试文件"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">在<code>pages/index.js</code>同目录下,建立一个新文件<code>index.spec.js</code>,并添加如下的内容:</p>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-s1">render</span><span class="pl-kos">,</span> <span class="pl-s1">screen</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'@testing-library/react'</span><span class="pl-kos">;</span>
<span class="pl-k">import</span> <span class="pl-s">'@testing-library/jest-dom'</span>
<span class="pl-k">import</span> <span class="pl-v">Home</span> <span class="pl-k">from</span> <span class="pl-s">'./index'</span>
<span class="pl-en">test</span><span class="pl-kos">(</span><span class="pl-s">'renders get started'</span><span class="pl-kos">,</span> <span class="pl-k">async</span> <span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-en">render</span><span class="pl-kos">(</span><span class="pl-c1"><</span><span class="pl-v">Home</span> <span class="pl-kos">/></span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-k">const</span> <span class="pl-s1">greetingElement</span> <span class="pl-c1">=</span> <span class="pl-s1">screen</span><span class="pl-kos">.</span><span class="pl-en">getByText</span><span class="pl-kos">(</span><span class="pl-s">'Get started by editing'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-en">expect</span><span class="pl-kos">(</span><span class="pl-s1">greetingElement</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">toBeInTheDocument</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span></pre></div>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">执行测试</h3><a id="user-content-执行测试" class="anchor" aria-label="Permalink: 执行测试" href="#执行测试"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">运行<code>npm test</code>,可以看到类似如下的输出,代表测试套件配置完成。</p>
<div class="highlight highlight-source-shell" dir="auto"><pre><span class="pl-k">></span> with-nextjs@0.1.0 <span class="pl-c1">test</span> /Users/yudu/dev/banyudu/react-test-examples/examples/with-nextjs
<span class="pl-k">></span> jest
PASS pages/index.spec.js
✓ renders get started (43 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.112 s, estimated 2 s</pre></div>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">在Umi.js工程中搭建测试环境</h2><a id="user-content-在umijs工程中搭建测试环境" class="anchor" aria-label="Permalink: 在Umi.js工程中搭建测试环境" href="#在umijs工程中搭建测试环境"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">umi.js中也内置了测试功能,但不如CRA的好用,需要额外设置一下。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">搭建一个umijs的工程</h3><a id="user-content-搭建一个umijs的工程" class="anchor" aria-label="Permalink: 搭建一个umijs的工程" href="#搭建一个umijs的工程"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="highlight highlight-source-shell" dir="auto"><pre>mkdir with-umi
<span class="pl-c1">cd</span> with-umi
npx @umijs/create-umi-app
npm i</pre></div>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">安装相关依赖</h3><a id="user-content-安装相关依赖" class="anchor" aria-label="Permalink: 安装相关依赖" href="#安装相关依赖"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="highlight highlight-source-shell" dir="auto"><pre>npm install --save-dev @testing-library/react @testing-library/jest-dom babel-jest @babel/preset-react @babel/preset-env</pre></div>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">添加Babel配置</h3><a id="user-content-添加babel配置" class="anchor" aria-label="Permalink: 添加Babel配置" href="#添加babel配置"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="highlight highlight-source-json" dir="auto"><pre>{
<span class="pl-ent">"presets"</span>: [<span class="pl-s"><span class="pl-pds">"</span>@babel/preset-env<span class="pl-pds">"</span></span>, [<span class="pl-s"><span class="pl-pds">"</span>@babel/preset-react<span class="pl-pds">"</span></span>, {
<span class="pl-ent">"runtime"</span>: <span class="pl-s"><span class="pl-pds">"</span>automatic<span class="pl-pds">"</span></span>
}]]
}</pre></div>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">添加Jest配置</h3><a id="user-content-添加jest配置" class="anchor" aria-label="Permalink: 添加Jest配置" href="#添加jest配置"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">修改<code>package.json</code>文件,添加jest相关配置:</p>
<div class="highlight highlight-source-json" dir="auto"><pre>{
<span class="pl-ent">"jest"</span>: {
<span class="pl-ent">"moduleNameMapper"</span>: {
<span class="pl-ent">"<span class="pl-cce">\\</span>.(css|less|scss|sass)$"</span>: <span class="pl-s"><span class="pl-pds">"</span>identity-obj-proxy<span class="pl-pds">"</span></span>
},
<span class="pl-ent">"transform"</span>: {
<span class="pl-ent">"<span class="pl-cce">\\</span>.[jt]sx?$"</span>: <span class="pl-s"><span class="pl-pds">"</span>babel-jest<span class="pl-pds">"</span></span>
},
<span class="pl-ent">"testEnvironment"</span>: <span class="pl-s"><span class="pl-pds">"</span>jsdom<span class="pl-pds">"</span></span>
}
}</pre></div>
<p dir="auto">注意这里因为<code>umi-test</code>没有读取<code>jest.config.json</code>,而是读取的<code>package.json</code>中的<code>jest</code>key,所以不能把jest的配置独立提取成配置文件。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">添加测试文件</h3><a id="user-content-添加测试文件-1" class="anchor" aria-label="Permalink: 添加测试文件" href="#添加测试文件-1"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="highlight highlight-source-ts" dir="auto"><pre><span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-s1">render</span><span class="pl-kos">,</span> <span class="pl-s1">screen</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'@testing-library/react'</span><span class="pl-kos">;</span>
<span class="pl-k">import</span> <span class="pl-s">'@testing-library/jest-dom'</span>
<span class="pl-k">import</span> <span class="pl-v">Home</span> <span class="pl-k">from</span> <span class="pl-s">'./index'</span>
<span class="pl-en">test</span><span class="pl-kos">(</span><span class="pl-s">'renders Page index'</span><span class="pl-kos">,</span> <span class="pl-k">async</span> <span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-c1">render</span><span class="pl-kos">(</span><span class="pl-c1"><</span><span class="pl-smi">Home</span><span class="pl-c1"></span> <span class="pl-c1">/</span><span class="pl-s">></span><span class="pl-kos">)</span><span class="pl-s">;</span>
<span class="pl-s1">const</span> <span class="pl-s1">greetingElement</span> <span class="pl-c1">=</span> <span class="pl-s1">screen</span><span class="pl-kos">.</span><span class="pl-en">getByText</span><span class="pl-kos">(</span><span class="pl-s">'Page index'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-en">expect</span><span class="pl-kos">(</span><span class="pl-s1">greetingElement</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">toBeInTheDocument</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span></pre></div>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">执行测试</h3><a id="user-content-执行测试-1" class="anchor" aria-label="Permalink: 执行测试" href="#执行测试-1"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">执行测试,得到如下输出:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>➜ with-umi git:(main) npm <span class="pl-c1">test</span>
<span class="pl-k">></span> @ <span class="pl-c1">test</span> /Users/yudu/dev/banyudu/react-test-examples/examples/with-umi
<span class="pl-k">></span> umi-test
PASS src/pages/index.spec.tsx
✓ renders Page index (25 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 3.83 s
Ran all <span class="pl-c1">test</span> suites.</pre></div>
<p dir="auto">说明测试套件搭建成功。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">总结</h2><a id="user-content-总结" class="anchor" aria-label="Permalink: 总结" href="#总结"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">以上就是针对三种脚手架项目(create-react-app、next.js、umi.js)的测试套件搭建说明。相关的示例代码可以查阅<a href="https://github.com/banyudu/react-test-examples/tree/main/examples">Github项目</a>。</p>
<p dir="auto">下一期更新一些基础控件的测试套路,敬请期待。</p>
</article>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/0d5b295c9429931418f87006af6eeb5e
2021-05-15T03:33:50Z
2021-06-16T09:41:38Z
React测试驱动开发 - 实践篇(总纲)
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/0d5b295c9429931418f87006af6eeb5e#file-react-test-driven-development-part2-practice-pandect-blog-md">react-test-driven-development-part2-practice-pandect.blog.md</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-react-test-driven-development-part2-practice-pandect-blog-md" class="file my-2">
<div id="file-react-test-driven-development-part2-practice-pandect-blog-md-readme" class="Box-body readme blob p-5 p-xl-6 "
style="overflow: auto" tabindex="0" role="region"
aria-label="react-test-driven-development-part2-practice-pandect.blog.md content, created by banyudu on 03:33AM on May 15, 2021."
>
<article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">React测试驱动开发 - 实践篇(总纲)</h1><a id="user-content-react测试驱动开发---实践篇总纲" class="anchor" aria-label="Permalink: React测试驱动开发 - 实践篇(总纲)" href="#react测试驱动开发---实践篇总纲"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">上篇<a href="https://banyudu.com/posts/react-test-driven-development-part1-theory.e816bc?v=yN2nWA" rel="nofollow">React测试驱动开发 - 理论篇</a>中提到了React测试驱动开发的一些理论知识,这篇里面讲一下实践过程。</p>
<p dir="auto">因为内容会比较多,一篇文档中全部讲完不太现实。所以我计划分成下面的几个部分来讲:</p>
<ul dir="auto">
<li><a href="https://banyudu.com/posts/react-test-driven-development-part3-setup-environment.3a8cd1" rel="nofollow">测试环境搭建</a></li>
<li>页面模型</li>
<li>基本控件</li>
<li>常用交互流程</li>
<li>可维护性</li>
</ul>
<p dir="auto">具体的实践代码会在一个Git仓库中提供:<a href="https://github.com/banyudu/react-test-examples%E3%80%82">https://github.com/banyudu/react-test-examples。</a></p>
<p dir="auto">欢迎提issue和pr。</p>
</article>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/e816bcb30ba1bd2e6fdd0c263a2c9051
2021-04-29T06:06:24Z
2021-04-29T06:21:03Z
React测试驱动开发 - 理论篇
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/e816bcb30ba1bd2e6fdd0c263a2c9051#file-react-test-driven-development-part1-theory-blog-md">react-test-driven-development-part1-theory.blog.md</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-react-test-driven-development-part1-theory-blog-md" class="file my-2">
<div id="file-react-test-driven-development-part1-theory-blog-md-readme" class="Box-body readme blob p-5 p-xl-6 "
style="overflow: auto" tabindex="0" role="region"
aria-label="react-test-driven-development-part1-theory.blog.md content, created by banyudu on 06:06AM on April 29, 2021."
>
<article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">React测试驱动开发 - 理论篇</h1><a id="user-content-react测试驱动开发---理论篇" class="anchor" aria-label="Permalink: React测试驱动开发 - 理论篇" href="#react测试驱动开发---理论篇"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">前端业务开发中如何做到测试驱动?怎么样平衡测试的效果与维护的成本?</p>
<p dir="auto">这些问题我摸索了挺长时间,也略微有了一些心得,在这里总结一下。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">测试的意义</h2><a id="user-content-测试的意义" class="anchor" aria-label="Permalink: 测试的意义" href="#测试的意义"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">在开始之前,先问几个问题:</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">为什么要做测试?</h3><a id="user-content-为什么要做测试" class="anchor" aria-label="Permalink: 为什么要做测试?" href="#为什么要做测试"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto"><strong>质量!质量!质量!</strong></p>
<p dir="auto">测试当然是为了质量,这是毋庸置疑的。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">为什么开发要做测试?</h3><a id="user-content-为什么开发要做测试" class="anchor" aria-label="Permalink: 为什么开发要做测试?" href="#为什么开发要做测试"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ol dir="auto">
<li>可以提升交付物质量,避免被老板、QA等骂</li>
<li>可以沉淀测试用例,对以后重构、技术升级有帮助。</li>
</ol>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">为什么要测试驱动开发?</h3><a id="user-content-为什么要测试驱动开发" class="anchor" aria-label="Permalink: 为什么要测试驱动开发?" href="#为什么要测试驱动开发"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ol dir="auto">
<li>提前思考测试用例,可以早点发现不确定的需求</li>
<li>有助于避免返工</li>
</ol>
<p dir="auto">当然这一点见仁见智,有的人可能倾向于认为,先开发再测试是比较好的。</p>
<p dir="auto">然而我的经验告诉我,凡是 “<strong>等以后有时间再做XXX</strong>” 的,基本就没戏。所以要做测试的话,最好就是直接测试先行。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">任务太紧了,没有时间写自测用例</h3><a id="user-content-任务太紧了没有时间写自测用例" class="anchor" aria-label="Permalink: 任务太紧了,没有时间写自测用例" href="#任务太紧了没有时间写自测用例"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">这个问题要从两个方面来看:一是是否真的没有时间写用例;二是有没有高估写用例所需的时间?</p>
<p dir="auto">一方面,在业务开发的过程中,一般会经过需求评审、技术评审等阶段,涉及到需求的讨论、前后端接口的讨论等,在这些阶段中,可以抽出一部分时间写用例,它对于“需求评审”和“技术评审”这两个阶段也是相辅相成的。</p>
<p dir="auto">所以,如果能打好时间差,有效地利用业务开发前的方案评审制订时间,写用例的时间可以压缩不少。</p>
<p dir="auto">另一方面,虽然大家口头上承认单元测试的好处,但是实际开发中很少有人用到。这就导致大家对测试用例应该怎么写这件事很不熟悉,也自然地会高估其所需的时间。</p>
<p dir="auto">而实际上,当测试流程用得比较熟悉之后,写用例所需的时间是会比一开始的时候大大降低的。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">前端工程不适合写测试用例?</h3><a id="user-content-前端工程不适合写测试用例" class="anchor" aria-label="Permalink: 前端工程不适合写测试用例?" href="#前端工程不适合写测试用例"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">有些人认为前端的页面变化较快,用例的保质期很短。随便一个div的结构变化,就会导致测试失效。</p>
<p dir="auto">这个主要也是写测试的时候的一些方法论的问题,怎么样写出来保质期较长的测试用例,也是需要学习和总结的。关于这一点,后面再展开来写。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">与自动化测试的工作重合</h3><a id="user-content-与自动化测试的工作重合" class="anchor" aria-label="Permalink: 与自动化测试的工作重合" href="#与自动化测试的工作重合"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">自动化测试团队,一般认为属于测试团队的一部分。他们会使用诸如 selenium + webdriver、cypress、testcafe等做一些端到端的测试。</p>
<p dir="auto">如果说他们已经有了相关的自动化测试用例,那么前端开发再做一份单元测试会不会浪费工作量呢?</p>
<p dir="auto">这里我的答案是不会,因为前端的单元测试相比于自动化测试有如下的长处:</p>
<ol dir="auto">
<li>时效性好:单元测试在功能尚未交付时,就可以开始构建。而自动化测试团队一般需要在功能交付之后,再对一些重点功能做测试。俗话说远水解不了近渴,它对开发中的功能的质量保证是没有帮助的。</li>
<li>性能好:不同于自动化测试需要完整的浏览器运行环境(即使是无头浏览器),单元测试可以直接针对代码做白盒测试。因为过程中不涉及到真实dom渲染等与浏览器的交互,也可以mock一些慢接口,所以性能会好一些。</li>
<li>可用性好:通过mock等手段,可以使前端的单元测试不依赖后端接口,这样即使后端服务出了问题,也不影响前端测试的可用性。另外因为它使用前端开发套件就可以运行,不需要安装selenium、cypress、python等测试套件,所以对前端开发来说更方便易用。</li>
<li>用例丰富:因为性能等综合原因影响,自动化测试中不能写很多很细的用例,不然时间会大大增加。这方面单元测试要好很多。</li>
</ol>
<p dir="auto">另外,写单元测试对自动化测试也会产生一些正面的作用。如开发在单元测试过程中,为了方便书写用例,会使页面中带有一些易用的选择器属性(如自定义属性、类名等),这样自动化测试团队也可以从中受益,减轻写选择器时的工作量。</p>
<p dir="auto">当然,这里并不是说不需要关注测试重复的问题,每一个测试用例背后都有着一系列的开发维护和理解的成本,同时也会带来运行时的成本。如果有多个团队在从不同维度做自动化测试,在设计测试用例的时候,应当提前沟通好各自覆盖的领域。发挥各自擅长的部分,协同完成整个自动化质量体系的构建。</p>
<hr>
<p dir="auto">下面介绍下我总结的测试的一些理论。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">测试的原则</h2><a id="user-content-测试的原则" class="anchor" aria-label="Permalink: 测试的原则" href="#测试的原则"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">这里我要援引以下两篇文档,有兴趣的同学推荐看下:</p>
<ol dir="auto">
<li><a href="https://martinfowler.com/articles/practical-test-pyramid.html" rel="nofollow">practical-test-pyramid</a></li>
<li><a href="https://testing-library.com/docs/guiding-principles/" rel="nofollow">testing-library guiding-principles</a></li>
</ol>
<p dir="auto">测试领域有一个经典的金字塔模型:</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/7329183406fcdc9cf28cdf2b90611198509ef56dda7558a6d6280c21ab7acf97/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f74657374507972616d69642e706e67"><img src="https://camo.githubusercontent.com/7329183406fcdc9cf28cdf2b90611198509ef56dda7558a6d6280c21ab7acf97/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f74657374507972616d69642e706e67" alt="" data-canonical-src="https://banyudu.github.io/images/testPyramid.png" style="max-width: 100%;"></a></p>
<p dir="auto">它表达了如下的观点:</p>
<ol dir="auto">
<li>测试可以从多个不同的维度进行</li>
<li>越接近用户真实体验,测试的独立性越差,性能越差。</li>
</ol>
<p dir="auto">按照测试金字塔的理论,我们应该写大量的单元测试、中等数量的集成测试、以及少部分的端到端测试。</p>
<p dir="auto">但是同时<a href="https://testing-library.com/docs/guiding-principles/" rel="nofollow">testing-library</a>的理论中也指出,测试越是靠近最终使用场景,越能带来更高的信心。</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/b576ca271ed5d1a2f2fe0e327cd96bbd098f8aba639c2490ff11e6d6c9f458a6/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303231303432393134303332373335302e706e67"><img src="https://camo.githubusercontent.com/b576ca271ed5d1a2f2fe0e327cd96bbd098f8aba639c2490ff11e6d6c9f458a6/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303231303432393134303332373335302e706e67" alt="image-20210429140327350" data-canonical-src="https://banyudu.github.io/images/image-20210429140327350.png" style="max-width: 100%;"></a></p>
<p dir="auto">在我看来,传统的测试理论(如测试金字塔)到现在仍然有效,但是它们更多的是偏重逻辑端,对现在的前端开发领域难以形成良好的理论指导。如前端的测试是否属于测试金字塔的最顶端?有没有办法做单元测试?这些是缺乏理论指导的。</p>
<p dir="auto"><a href="https://testing-library.com/" rel="nofollow">Testing-library</a> 中提出的测试理念,以及一系列测试套件,以接近集成测试的成本,提供了类似端到端测试的体验,是一种比较友好的测试理论和框架。</p>
<p dir="auto">后面我要说的就是如何利用<a href="https://testing-library.com/" rel="nofollow">testing-library</a> 测试前端应用,给出一些理论参考。</p>
<p dir="auto">总结下我认为前端业务系统的开发人员编写测试代码的原则是:</p>
<ol dir="auto">
<li>
<p dir="auto">应当尽可能独立。</p>
<p dir="auto">前端的测试代码应当尽可能地不依赖后端接口。当需要使用接口时,可以使用mock工具进行模拟。</p>
</li>
<li>
<p dir="auto">应当有较好的保质期。</p>
<p dir="auto">前端的测试代码应当尽可能地从用户角度出发,不涉及到具体的技术细节。诸如snapshot测试等,因为一个微小的dom结构变化就失效的测试,不应该是主要的测试手段。</p>
</li>
<li>
<p dir="auto">应当有较低的运行成本。</p>
<p dir="auto">每次运行的成本应尽可能地低,避免等待API接口等耗时操作的产生。</p>
</li>
<li>
<p dir="auto">应当尽可能从用户角度进行测试(纯逻辑单元除外)。</p>
<p dir="auto">越靠近用户真实使用场景,测试越有效果。</p>
</li>
</ol>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">写出便于测试的代码</h2><a id="user-content-写出便于测试的代码" class="anchor" aria-label="Permalink: 写出便于测试的代码" href="#写出便于测试的代码"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">测试的复杂性,不仅取决于业务的复杂度,也取决于代码的实现方式。</p>
<p dir="auto">业务复杂度是很难改变的,而代码的实现方式比较容易更改,尤其是在提前考虑到测试问题的时候。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">代码的实现方式从哪些方面影响测试复杂度?</h3><a id="user-content-代码的实现方式从哪些方面影响测试复杂度" class="anchor" aria-label="Permalink: 代码的实现方式从哪些方面影响测试复杂度?" href="#代码的实现方式从哪些方面影响测试复杂度"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">锚点的选定</h4><a id="user-content-锚点的选定" class="anchor" aria-label="Permalink: 锚点的选定" href="#锚点的选定"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">对前端测试而言,锚点的选定是至关重要的,首先确定元素的位置,然后才能进行接下来的操作。</p>
<p dir="auto">如果在编写代码时,没有考虑到测试的需求,那么这个步骤可能会很困难。</p>
<p dir="auto">定位元素的过程,会用到<code>querySelector</code>等API,一般会使用<code>class</code>、<code>tagName</code>、<code>xpath</code>等属性进行定位。</p>
<p dir="auto">示例:</p>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-c">// 根据xPath定位元素</span>
<span class="pl-k">function</span> <span class="pl-en">getElementByXpath</span><span class="pl-kos">(</span><span class="pl-s1">path</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-k">return</span> <span class="pl-smi">document</span><span class="pl-kos">.</span><span class="pl-en">evaluate</span><span class="pl-kos">(</span><span class="pl-s1">path</span><span class="pl-kos">,</span> <span class="pl-smi">document</span><span class="pl-kos">,</span> <span class="pl-c1">null</span><span class="pl-kos">,</span> <span class="pl-v">XPathResult</span><span class="pl-kos">.</span><span class="pl-c1">FIRST_ORDERED_NODE_TYPE</span><span class="pl-kos">,</span> <span class="pl-c1">null</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-c1">singleNodeValue</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span>
<span class="pl-en">getElementByXpath</span><span class="pl-kos">(</span><span class="pl-s">"//*[@id="</span><span class="pl-s1">rso</span><span class="pl-s">"]/div[1]/div[1]/div/div[2]/div[1]/a/h3"</span><span class="pl-kos">)</span>
<span class="pl-c">// 根据 class 定位元素</span>
<span class="pl-smi">document</span><span class="pl-kos">.</span><span class="pl-en">querySelector</span><span class="pl-kos">(</span><span class="pl-s">".myclass"</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-c">// 根据自定义属性定位元素</span>
<span class="pl-smi">document</span><span class="pl-kos">.</span><span class="pl-en">querySelector</span><span class="pl-kos">(</span><span class="pl-s">"[data-testid='审核通过按钮']"</span><span class="pl-kos">)</span></pre></div>
<p dir="auto">不同的锚点选定方式,又是怎么影响了测试的复杂度呢?</p>
<p dir="auto">这又要从两个维度来说:</p>
<ol dir="auto">
<li>锚点可读性:<code>自定义属性定位元素 > class >> xPath</code></li>
<li>锚点保质期:<code>xPath ~= class >> xPath</code></li>
</ol>
<p dir="auto">稍微解释一下:</p>
<p dir="auto">首先,从规范的角度来说,class是不推荐写中文的,且有一定的命名规则,不能随意书写。所以能写中文的自定义属性(可以是data-testid,或data-xxx,随便写)可读性会大于 class。class虽然可读性没那么好,但至少还能用于代码搜索,而 xPath 既不可读,也没办法用于代码搜索,属于可读性最差的实现方式。</p>
<p dir="auto">然后,因为前端页面经常会有结构上的变化。这个时候xPath会跟随变化,继而导致测试用例失败。而class和自定义属性则可以保持不变。所以使用自定义属性和class作为锚点选择器,保质期会更长。</p>
<p dir="auto">因此,如果开发人员能在实现功能时,主动加上便于测试的自定义属性(如data-testid),或class,会大大降低测试时锚点的选择难度,以及大幅提高其保质期。</p>
<div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">代码的模块化</h4><a id="user-content-代码的模块化" class="anchor" aria-label="Permalink: 代码的模块化" href="#代码的模块化"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">单元测试除了测功能,也可以直接引入给定的函数、类等进行测试。</p>
<p dir="auto">如果将一些纯逻辑的单元独立成模块导出,就可以很容易地写一些针对此部分的单元测试。</p>
<p dir="auto">而如果将代码全部糅合在一起,测试时就很难针对一小部分功能独立测试了。</p>
<hr>
<p dir="auto">以上就是本文的全部内容啦,后面我会逐渐更新一些具体的问题处理和实践指南,敬请期待。</p>
</article>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/f8d69386e52340c302dcde69cb223d0f
2021-04-17T07:00:11Z
2021-04-17T07:00:12Z
Node.js中spawn子进程继承Shell中的颜色等信息
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/f8d69386e52340c302dcde69cb223d0f#file-spwan-inherit-stdio-js">spwan-inherit-stdio.js</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-spwan-inherit-stdio-js" class="file my-2">
<div itemprop="text"
class="Box-body p-0 blob-wrapper data type-javascript "
style="overflow: auto" tabindex="0" role="region"
aria-label="spwan-inherit-stdio.js content, created by banyudu on 07:00AM on April 17, 2021."
>
<div class="js-check-hidden-unicode js-blob-code-container blob-code-content">
<template class="js-file-alert-template">
<div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
<span>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
<a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
</span>
<div data-view-component="true" class="flash-action"> <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn"> Show hidden characters
</a>
</div>
</div></template>
<template class="js-line-alert-template">
<span aria-label="This line has hidden Unicode characters" data-view-component="true" class="line-alert tooltipped tooltipped-e">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
</span></template>
<table data-hpc class="highlight tab-size js-file-line-container" data-tab-size="8" data-paste-markdown-skip data-tagsearch-path="spwan-inherit-stdio.js">
<tr>
<td id="file-spwan-inherit-stdio-js-L1" class="blob-num js-line-number js-blob-rnum" data-line-number="1"></td>
<td id="file-spwan-inherit-stdio-js-LC1" class="blob-code blob-code-inner js-file-line"><span class=pl-k>const</span> <span class=pl-kos>{</span> spawn <span class=pl-kos>}</span> <span class=pl-c1>=</span> <span class=pl-en>require</span><span class=pl-kos>(</span><span class=pl-s>'child_process'</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-spwan-inherit-stdio-js-L2" class="blob-num js-line-number js-blob-rnum" data-line-number="2"></td>
<td id="file-spwan-inherit-stdio-js-LC2" class="blob-code blob-code-inner js-file-line"><span class=pl-k>const</span> <span class=pl-s1>params</span> <span class=pl-c1>=</span> <span class=pl-kos>[</span><span class=pl-s>'--someoption=somevalue'</span><span class=pl-kos>,</span> <span class=pl-s>'subcommand'</span><span class=pl-kos>,</span> <span class=pl-s>'any other params'</span><span class=pl-kos>]</span></td>
</tr>
<tr>
<td id="file-spwan-inherit-stdio-js-L3" class="blob-num js-line-number js-blob-rnum" data-line-number="3"></td>
<td id="file-spwan-inherit-stdio-js-LC3" class="blob-code blob-code-inner js-file-line"><span class=pl-k>const</span> <span class=pl-s1>myCmd</span> <span class=pl-c1>=</span> <span class=pl-en>spawn</span><span class=pl-kos>(</span><span class=pl-s>'command-name'</span><span class=pl-kos>,</span> <span class=pl-s1>params</span><span class=pl-kos>,</span> <span class=pl-kos>{</span> <span class=pl-c1>stdio</span>:<span class=pl-s>'inherit'</span> <span class=pl-kos>}</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-spwan-inherit-stdio-js-L4" class="blob-num js-line-number js-blob-rnum" data-line-number="4"></td>
<td id="file-spwan-inherit-stdio-js-LC4" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-spwan-inherit-stdio-js-L5" class="blob-num js-line-number js-blob-rnum" data-line-number="5"></td>
<td id="file-spwan-inherit-stdio-js-LC5" class="blob-code blob-code-inner js-file-line"><span class=pl-c>// 加上 { stdio:'inherit' } 选项之后,子进程会接管父进程的标准输入输出,这样console中的表现就和直接执行子进程相同,而不会丢失颜色、清屏等信息了</span></td>
</tr>
</table>
</div>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/f9668dbf6840b10ad7d25461b978c302
2021-04-08T08:35:31Z
2021-04-08T08:35:39Z
企业微信发送图片
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/f9668dbf6840b10ad7d25461b978c302#file-qywx-bot-post-image-js">qywx-bot-post-image.js</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-qywx-bot-post-image-js" class="file my-2">
<div itemprop="text"
class="Box-body p-0 blob-wrapper data type-javascript "
style="overflow: auto" tabindex="0" role="region"
aria-label="qywx-bot-post-image.js content, created by banyudu on 08:35AM on April 08, 2021."
>
<div class="js-check-hidden-unicode js-blob-code-container blob-code-content">
<template class="js-file-alert-template">
<div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
<span>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
<a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
</span>
<div data-view-component="true" class="flash-action"> <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn"> Show hidden characters
</a>
</div>
</div></template>
<template class="js-line-alert-template">
<span aria-label="This line has hidden Unicode characters" data-view-component="true" class="line-alert tooltipped tooltipped-e">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
</span></template>
<table data-hpc class="highlight tab-size js-file-line-container" data-tab-size="8" data-paste-markdown-skip data-tagsearch-path="qywx-bot-post-image.js">
<tr>
<td id="file-qywx-bot-post-image-js-L1" class="blob-num js-line-number js-blob-rnum" data-line-number="1"></td>
<td id="file-qywx-bot-post-image-js-LC1" class="blob-code blob-code-inner js-file-line"><span class=pl-k>const</span> <span class=pl-s1>axios</span> <span class=pl-c1>=</span> <span class=pl-en>require</span><span class=pl-kos>(</span><span class=pl-s>'axios'</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-c1>default</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L2" class="blob-num js-line-number js-blob-rnum" data-line-number="2"></td>
<td id="file-qywx-bot-post-image-js-LC2" class="blob-code blob-code-inner js-file-line"><span class=pl-k>const</span> <span class=pl-s1>textToImage</span> <span class=pl-c1>=</span> <span class=pl-en>require</span><span class=pl-kos>(</span><span class=pl-s>'text-to-image'</span><span class=pl-kos>)</span><span class=pl-kos>;</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L3" class="blob-num js-line-number js-blob-rnum" data-line-number="3"></td>
<td id="file-qywx-bot-post-image-js-LC3" class="blob-code blob-code-inner js-file-line"><span class=pl-k>const</span> <span class=pl-s1>crypto</span> <span class=pl-c1>=</span> <span class=pl-en>require</span><span class=pl-kos>(</span><span class=pl-s>'crypto'</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L4" class="blob-num js-line-number js-blob-rnum" data-line-number="4"></td>
<td id="file-qywx-bot-post-image-js-LC4" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L5" class="blob-num js-line-number js-blob-rnum" data-line-number="5"></td>
<td id="file-qywx-bot-post-image-js-LC5" class="blob-code blob-code-inner js-file-line"><span class=pl-k>const</span> <span class=pl-s1>text</span> <span class=pl-c1>=</span> <span class=pl-s1>process</span><span class=pl-kos>.</span><span class=pl-c1>argv</span><span class=pl-kos>[</span><span class=pl-c1>2</span><span class=pl-kos>]</span> <span class=pl-c1>||</span> <span class=pl-s>'Hello World!'</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L6" class="blob-num js-line-number js-blob-rnum" data-line-number="6"></td>
<td id="file-qywx-bot-post-image-js-LC6" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L7" class="blob-num js-line-number js-blob-rnum" data-line-number="7"></td>
<td id="file-qywx-bot-post-image-js-LC7" class="blob-code blob-code-inner js-file-line"><span class=pl-k>const</span> <span class=pl-en>md5</span><span class=pl-c1>=</span> <span class=pl-kos>(</span><span class=pl-s1>str</span><span class=pl-kos>)</span> <span class=pl-c1>=></span> <span class=pl-s1>crypto</span><span class=pl-kos>.</span><span class=pl-en>createHash</span><span class=pl-kos>(</span><span class=pl-s>'md5'</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>update</span><span class=pl-kos>(</span><span class=pl-s1>str</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>digest</span><span class=pl-kos>(</span><span class=pl-s>'hex'</span><span class=pl-kos>)</span><span class=pl-kos>;</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L8" class="blob-num js-line-number js-blob-rnum" data-line-number="8"></td>
<td id="file-qywx-bot-post-image-js-LC8" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L9" class="blob-num js-line-number js-blob-rnum" data-line-number="9"></td>
<td id="file-qywx-bot-post-image-js-LC9" class="blob-code blob-code-inner js-file-line"><span class=pl-k>const</span> <span class=pl-s1>botUrl</span> <span class=pl-c1>=</span> <span class=pl-s1>process</span><span class=pl-kos>.</span><span class=pl-c1>env</span><span class=pl-kos>.</span><span class=pl-c1>TEST_BOT_URL</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L10" class="blob-num js-line-number js-blob-rnum" data-line-number="10"></td>
<td id="file-qywx-bot-post-image-js-LC10" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L11" class="blob-num js-line-number js-blob-rnum" data-line-number="11"></td>
<td id="file-qywx-bot-post-image-js-LC11" class="blob-code blob-code-inner js-file-line"><span class=pl-k>const</span> <span class=pl-en>test</span> <span class=pl-c1>=</span> <span class=pl-k>async</span> <span class=pl-kos>(</span><span class=pl-kos>)</span> <span class=pl-c1>=></span> <span class=pl-kos>{</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L12" class="blob-num js-line-number js-blob-rnum" data-line-number="12"></td>
<td id="file-qywx-bot-post-image-js-LC12" class="blob-code blob-code-inner js-file-line"> <span class=pl-k>const</span> <span class=pl-s1>dataUrl</span> <span class=pl-c1>=</span> <span class=pl-k>await</span> <span class=pl-s1>textToImage</span><span class=pl-kos>.</span><span class=pl-en>generate</span><span class=pl-kos>(</span><span class=pl-s1>text</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L13" class="blob-num js-line-number js-blob-rnum" data-line-number="13"></td>
<td id="file-qywx-bot-post-image-js-LC13" class="blob-code blob-code-inner js-file-line"> <span class=pl-k>const</span> <span class=pl-s1>byteString</span> <span class=pl-c1>=</span> <span class=pl-s1>dataUrl</span><span class=pl-kos>.</span><span class=pl-en>substr</span><span class=pl-kos>(</span><span class=pl-s1>dataUrl</span><span class=pl-kos>.</span><span class=pl-en>indexOf</span><span class=pl-kos>(</span><span class=pl-s>','</span><span class=pl-kos>)</span> <span class=pl-c1>+</span> <span class=pl-c1>1</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L14" class="blob-num js-line-number js-blob-rnum" data-line-number="14"></td>
<td id="file-qywx-bot-post-image-js-LC14" class="blob-code blob-code-inner js-file-line"> <span class=pl-k>const</span> <span class=pl-s1>buffer</span> <span class=pl-c1>=</span> <span class=pl-v>Buffer</span><span class=pl-kos>.</span><span class=pl-en>from</span><span class=pl-kos>(</span><span class=pl-s1>byteString</span><span class=pl-kos>,</span> <span class=pl-s>'base64'</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L15" class="blob-num js-line-number js-blob-rnum" data-line-number="15"></td>
<td id="file-qywx-bot-post-image-js-LC15" class="blob-code blob-code-inner js-file-line"> <span class=pl-k>const</span> <span class=pl-s1>md5Str</span> <span class=pl-c1>=</span> <span class=pl-en>md5</span><span class=pl-kos>(</span><span class=pl-s1>buffer</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L16" class="blob-num js-line-number js-blob-rnum" data-line-number="16"></td>
<td id="file-qywx-bot-post-image-js-LC16" class="blob-code blob-code-inner js-file-line"> <span class=pl-k>const</span> <span class=pl-s1>res</span> <span class=pl-c1>=</span> <span class=pl-k>await</span> <span class=pl-s1>axios</span><span class=pl-kos>.</span><span class=pl-en>post</span><span class=pl-kos>(</span><span class=pl-s1>botUrl</span><span class=pl-kos>,</span> <span class=pl-kos>{</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L17" class="blob-num js-line-number js-blob-rnum" data-line-number="17"></td>
<td id="file-qywx-bot-post-image-js-LC17" class="blob-code blob-code-inner js-file-line"> <span class=pl-c1>msgtype</span>: <span class=pl-s>'image'</span><span class=pl-kos>,</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L18" class="blob-num js-line-number js-blob-rnum" data-line-number="18"></td>
<td id="file-qywx-bot-post-image-js-LC18" class="blob-code blob-code-inner js-file-line"> <span class=pl-c1>image</span>: <span class=pl-kos>{</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L19" class="blob-num js-line-number js-blob-rnum" data-line-number="19"></td>
<td id="file-qywx-bot-post-image-js-LC19" class="blob-code blob-code-inner js-file-line"> <span class=pl-c1>base64</span>: <span class=pl-s1>byteString</span><span class=pl-kos>,</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L20" class="blob-num js-line-number js-blob-rnum" data-line-number="20"></td>
<td id="file-qywx-bot-post-image-js-LC20" class="blob-code blob-code-inner js-file-line"> <span class=pl-c1>md5</span>: <span class=pl-s1>md5Str</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L21" class="blob-num js-line-number js-blob-rnum" data-line-number="21"></td>
<td id="file-qywx-bot-post-image-js-LC21" class="blob-code blob-code-inner js-file-line"> <span class=pl-kos>}</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L22" class="blob-num js-line-number js-blob-rnum" data-line-number="22"></td>
<td id="file-qywx-bot-post-image-js-LC22" class="blob-code blob-code-inner js-file-line"> <span class=pl-kos>}</span><span class=pl-kos>,</span> <span class=pl-kos>{</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L23" class="blob-num js-line-number js-blob-rnum" data-line-number="23"></td>
<td id="file-qywx-bot-post-image-js-LC23" class="blob-code blob-code-inner js-file-line"> <span class=pl-c1>responseType</span>: <span class=pl-s>'blob'</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L24" class="blob-num js-line-number js-blob-rnum" data-line-number="24"></td>
<td id="file-qywx-bot-post-image-js-LC24" class="blob-code blob-code-inner js-file-line"> <span class=pl-kos>}</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L25" class="blob-num js-line-number js-blob-rnum" data-line-number="25"></td>
<td id="file-qywx-bot-post-image-js-LC25" class="blob-code blob-code-inner js-file-line"> <span class=pl-smi>console</span><span class=pl-kos>.</span><span class=pl-en>log</span><span class=pl-kos>(</span><span class=pl-s>'res is: '</span><span class=pl-kos>,</span> <span class=pl-s1>res</span><span class=pl-kos>.</span><span class=pl-c1>data</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L26" class="blob-num js-line-number js-blob-rnum" data-line-number="26"></td>
<td id="file-qywx-bot-post-image-js-LC26" class="blob-code blob-code-inner js-file-line"><span class=pl-kos>}</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L27" class="blob-num js-line-number js-blob-rnum" data-line-number="27"></td>
<td id="file-qywx-bot-post-image-js-LC27" class="blob-code blob-code-inner js-file-line">
</td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L28" class="blob-num js-line-number js-blob-rnum" data-line-number="28"></td>
<td id="file-qywx-bot-post-image-js-LC28" class="blob-code blob-code-inner js-file-line"><span class=pl-en>test</span><span class=pl-kos>(</span><span class=pl-kos>)</span><span class=pl-kos>.</span><span class=pl-en>catch</span><span class=pl-kos>(</span><span class=pl-s1>err</span> <span class=pl-c1>=></span> <span class=pl-kos>{</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L29" class="blob-num js-line-number js-blob-rnum" data-line-number="29"></td>
<td id="file-qywx-bot-post-image-js-LC29" class="blob-code blob-code-inner js-file-line"> <span class=pl-smi>console</span><span class=pl-kos>.</span><span class=pl-en>error</span><span class=pl-kos>(</span><span class=pl-s1>err</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L30" class="blob-num js-line-number js-blob-rnum" data-line-number="30"></td>
<td id="file-qywx-bot-post-image-js-LC30" class="blob-code blob-code-inner js-file-line"> <span class=pl-s1>process</span><span class=pl-kos>.</span><span class=pl-en>exit</span><span class=pl-kos>(</span><span class=pl-c1>1</span><span class=pl-kos>)</span></td>
</tr>
<tr>
<td id="file-qywx-bot-post-image-js-L31" class="blob-num js-line-number js-blob-rnum" data-line-number="31"></td>
<td id="file-qywx-bot-post-image-js-LC31" class="blob-code blob-code-inner js-file-line"><span class=pl-kos>}</span><span class=pl-kos>)</span></td>
</tr>
</table>
</div>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/686a103e704d16fe2456b0ff276f87da
2021-04-01T09:39:29Z
2021-04-01T09:39:29Z
软解公司监控
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/686a103e704d16fe2456b0ff276f87da#file-soft-crack-company-os-sensor-blog-md">soft-crack-company-os-sensor.blog.md</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-soft-crack-company-os-sensor-blog-md" class="file my-2">
<div id="file-soft-crack-company-os-sensor-blog-md-readme" class="Box-body readme blob p-5 p-xl-6 "
style="overflow: auto" tabindex="0" role="region"
aria-label="soft-crack-company-os-sensor.blog.md content, created by banyudu on 09:39AM on April 01, 2021."
>
<article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">软解公司监控</h1><a id="user-content-软解公司监控" class="anchor" aria-label="Permalink: 软解公司监控" href="#软解公司监控"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">今年公司强制要求所有人在工作电脑中安装了监控软件,不安装的话就会锁定账号。</p>
<p dir="auto">最可恨的是这个软件居然还要求开录屏权限,打算监控屏幕吗?</p>
<p dir="auto">想一想它可能会监控所有键盘输入,所有网络请求,就会浑身不自在。</p>
<p dir="auto">那有没有什么方式可以破解监控呢?</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">监控原理(猜测)</h2><a id="user-content-监控原理猜测" class="anchor" aria-label="Permalink: 监控原理(猜测)" href="#监控原理猜测"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/53e4b35bfeb4360f7c28ef68950d85661d370b668f3ca6dcba10953c7b40aca8/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303231303430313137313135343834312e706e67"><img src="https://camo.githubusercontent.com/53e4b35bfeb4360f7c28ef68950d85661d370b668f3ca6dcba10953c7b40aca8/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303231303430313137313135343834312e706e67" alt="image-20210401171154841" data-canonical-src="https://banyudu.github.io/images/image-20210401171154841.png" style="max-width: 100%;"></a></p>
<p dir="auto">具体流程:</p>
<ol dir="auto">
<li>有专门的进程采集行为数据,如键盘操作、屏幕显示。并上报</li>
<li>有专门的进程采集身份信息,并向后台上报。这些进程不能杀死,因为一旦身份信息没有及时上报,就会引起后台监控平台的报警</li>
<li>另有一个进程负责更新版本</li>
<li>多个进程之间互相唤醒</li>
</ol>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">难点</h2><a id="user-content-难点" class="anchor" aria-label="Permalink: 难点" href="#难点"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">如果直接杀死进程,并卸载相关软件,会因为没有定时上报身份信息,被“老大哥”查出来。</p>
<p dir="auto">即使它和采集行为的进程是两个相互独立的进程,因为彼此会相互唤醒,杀了个别进程也没用。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">思路</h2><a id="user-content-思路" class="anchor" aria-label="Permalink: 思路" href="#思路"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">我想到了下面几个思路:</p>
<ol dir="auto">
<li>
<p dir="auto">开两个计算机用户,专门新建一个,运行监控软件,后台上传身份信息,另一个正常工作。</p>
<p dir="auto">这种方式不可行,因为监控进程以root身份启动,可监控所有用户的操作。</p>
</li>
<li>
<p dir="auto">开虚拟机运行监控软件</p>
<p dir="auto">监控运行在虚拟机中,正常工作在宿主机中,有可能不可行,因为mac地址等用来校验身份的信息发生了变化。</p>
</li>
<li>
<p dir="auto">Docker,监控软件未提供linux版本,无法运行。</p>
</li>
<li>
<p dir="auto">开两台电脑,一个工作,一个跑监控</p>
<p dir="auto">取决于监控实现方式,如果检测的是mac地址是否匹配,就不可行。如果检测的是有没有上报,则可行。</p>
</li>
<li>
<p dir="auto">限制资源</p>
<p dir="auto">通过限制资源,使监控软件活在一片缺少算力的荒漠之中,虽然还能存活,但几乎无法正常工作。</p>
</li>
</ol>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">目前的方案</h2><a id="user-content-目前的方案" class="anchor" aria-label="Permalink: 目前的方案" href="#目前的方案"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">目前我采用的是限制资源的方式。</p>
<p dir="auto">首先找到监控软件对应的进程名,然后使用<a href="https://github.com/opsengine/cpulimit">cpulimit</a>限制其可用的cpu量。当它的cpu被限制到极低的时候,可能发生两种情况:</p>
<ol dir="auto">
<li>因cpu过低,虽然能够正常运行,但是采集数据掉帧严重,只能采取到一些片断的信息。</li>
<li>cpu过低,无法正常工作。进程反复挂掉,又被其它进程重新启动。</li>
</ol>
<p dir="auto">两种方式下,都有可能偶发地上传一些身份信息,使“老大哥”不会起疑心。</p>
<p dir="auto">同时因为可用资源低,掉帧严重的监控信息有可能已经不足为虑。</p>
<p dir="auto">参考脚本:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre><span class="pl-c"><span class="pl-c">#!</span>/bin/sh</span>
<span class="pl-c"><span class="pl-c">#</span> Get cpulimit from https://github.com/opsengine/cpulimit</span>
<span class="pl-c"><span class="pl-c">#</span> or `brew install cpulimit` if you are on macOS</span>
apps=(<span class="pl-s"><span class="pl-pds">"</span>进程1<span class="pl-pds">"</span></span> <span class="pl-s"><span class="pl-pds">"</span>进程2<span class="pl-pds">"</span></span> <span class="pl-s"><span class="pl-pds">"</span>进程3<span class="pl-pds">"</span></span> <span class="pl-s"><span class="pl-pds">"</span>进程4<span class="pl-pds">"</span></span> <span class="pl-s"><span class="pl-pds">"</span>进程5<span class="pl-pds">"</span></span>)
<span class="pl-c"><span class="pl-c">#</span> Set the respective limits here</span>
limit=0.1
<span class="pl-k">while</span> <span class="pl-c1">true</span><span class="pl-k">;</span> <span class="pl-k">do</span>
<span class="pl-k">for</span> <span class="pl-smi">app</span> <span class="pl-k">in</span> <span class="pl-smi">${apps[@]}</span><span class="pl-k">;</span> <span class="pl-k">do</span>
<span class="pl-k">for</span> <span class="pl-smi">pid</span> <span class="pl-k">in</span> <span class="pl-s"><span class="pl-pds">$(</span>pgrep <span class="pl-smi">${app}</span><span class="pl-pds">)</span></span><span class="pl-k">;</span> <span class="pl-k">do</span>
cpulimit -l <span class="pl-smi">${limit}</span> -p <span class="pl-smi">${pid}</span>
<span class="pl-k">done</span>
<span class="pl-k">done</span>
sleep 60s
<span class="pl-k">done</span></pre></div>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">后续可能的改进方向</h2><a id="user-content-后续可能的改进方向" class="anchor" aria-label="Permalink: 后续可能的改进方向" href="#后续可能的改进方向"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ol dir="auto">
<li>限制网络传输速度</li>
<li>每隔几分钟杀死所有监控进程,再过几分钟唤醒。</li>
<li>伪造信息,造成干扰</li>
</ol>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">PS</h2><a id="user-content-ps" class="anchor" aria-label="Permalink: PS" href="#ps"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">如果被发现限制了cpu,可以解释为cpu占用过高影响了正常工作。</p>
</article>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/e1786b9dff1533feb8ebfaa9a90e9379
2021-03-26T10:09:21Z
2021-03-26T10:09:21Z
Serverless跳过腾讯云的方法
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/e1786b9dff1533feb8ebfaa9a90e9379#file-bypass-tencent-cloud-in-serverless-cli-blog-md">bypass-tencent-cloud-in-serverless-cli.blog.md</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-bypass-tencent-cloud-in-serverless-cli-blog-md" class="file my-2">
<div id="file-bypass-tencent-cloud-in-serverless-cli-blog-md-readme" class="Box-body readme blob p-5 p-xl-6 "
style="overflow: auto" tabindex="0" role="region"
aria-label="bypass-tencent-cloud-in-serverless-cli.blog.md content, created by banyudu on 10:09AM on March 26, 2021."
>
<article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">Serverless跳过腾讯云的方法</h1><a id="user-content-serverless跳过腾讯云的方法" class="anchor" aria-label="Permalink: Serverless跳过腾讯云的方法" href="#serverless跳过腾讯云的方法"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">自从腾讯云和Serverless Components达成<a href="https://cloud.tencent.com/developer/article/1538221" rel="nofollow">战略合作</a>之后,国内用Serverless命令行工具就到处都是腾讯云的身影了。</p>
<p dir="auto">由于目前我的服务还是部署在AWS上的,所以这个事情对我来说多多少少地有些困扰,把系统语言换成英文也没好使,所以特意查了下Serverless中判断是腾讯云用户还是AWS用户的方法:</p>
<p dir="auto">关键代码如下:</p>
<div class="highlight highlight-source-js" dir="auto"><pre><span class="pl-c">/**</span>
<span class="pl-c"> * Detect if the user is located in China by looking at their settings</span>
<span class="pl-c"> */</span>
<span class="pl-k">const</span> <span class="pl-en">isChinaUser</span> <span class="pl-c1">=</span> <span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=></span> <span class="pl-kos">{</span>
<span class="pl-k">let</span> <span class="pl-s1">result</span><span class="pl-kos">;</span>
<span class="pl-k">if</span> <span class="pl-kos">(</span>
<span class="pl-s1">process</span><span class="pl-kos">.</span><span class="pl-c1">env</span><span class="pl-kos">.</span><span class="pl-c1">SERVERLESS_PLATFORM_VENDOR</span> <span class="pl-c1">===</span> <span class="pl-s">'tencent'</span> <span class="pl-c1">||</span>
<span class="pl-s1">process</span><span class="pl-kos">.</span><span class="pl-c1">env</span><span class="pl-kos">.</span><span class="pl-c1">SLS_GEO_LOCATION</span> <span class="pl-c1">===</span> <span class="pl-s">'cn'</span>
<span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-s1">result</span> <span class="pl-c1">=</span> <span class="pl-c1">true</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span> <span class="pl-k">else</span> <span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">process</span><span class="pl-kos">.</span><span class="pl-c1">env</span><span class="pl-kos">.</span><span class="pl-c1">SERVERLESS_PLATFORM_VENDOR</span> <span class="pl-c1">===</span> <span class="pl-s">'aws'</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
<span class="pl-s1">result</span> <span class="pl-c1">=</span> <span class="pl-c1">false</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span> <span class="pl-k">else</span> <span class="pl-kos">{</span>
<span class="pl-s1">result</span> <span class="pl-c1">=</span> <span class="pl-k">new</span> <span class="pl-v">Intl</span><span class="pl-kos">.</span><span class="pl-c1">DateTimeFormat</span><span class="pl-kos">(</span><span class="pl-s">'en'</span><span class="pl-kos">,</span> <span class="pl-kos">{</span> <span class="pl-c1">timeZoneName</span>: <span class="pl-s">'long'</span> <span class="pl-kos">}</span><span class="pl-kos">)</span>
<span class="pl-kos">.</span><span class="pl-en">format</span><span class="pl-kos">(</span><span class="pl-kos">)</span>
<span class="pl-kos">.</span><span class="pl-en">includes</span><span class="pl-kos">(</span><span class="pl-s">'China Standard Time'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span>
<span class="pl-k">return</span> <span class="pl-s1">result</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span><span class="pl-kos">;</span></pre></div>
<p dir="auto">从代码中可以看出,由于我当前用的是中国时区,所以被判断成了中国用户,就自动转到腾讯云相关服务中了。</p>
<p dir="auto">解决办法也很简单,在环境变量中加一个 <code>SERVERLESS_PLATFORM_VENDOR</code>,值为'aws'就好了。</p>
</article>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/5750c18aebb5e6e0a7934017e681f251
2021-03-26T09:44:22Z
2021-03-26T09:44:22Z
Git忽略已在仓库中的文件的改动
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/5750c18aebb5e6e0a7934017e681f251#file-git-ignore-file-changes-in-existing-files-blog-md">git-ignore-file-changes-in-existing-files.blog.md</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-git-ignore-file-changes-in-existing-files-blog-md" class="file my-2">
<div id="file-git-ignore-file-changes-in-existing-files-blog-md-readme" class="Box-body readme blob p-5 p-xl-6 "
style="overflow: auto" tabindex="0" role="region"
aria-label="git-ignore-file-changes-in-existing-files.blog.md content, created by banyudu on 09:44AM on March 26, 2021."
>
<article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">Git忽略已在仓库中的文件的改动</h1><a id="user-content-git忽略已在仓库中的文件的改动" class="anchor" aria-label="Permalink: Git忽略已在仓库中的文件的改动" href="#git忽略已在仓库中的文件的改动"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">在git中当我们想忽略一个文件的时候,只需要修改<code>.gitignore</code>文件,将要忽略的目录包含在内就可以了。</p>
<p dir="auto">典型的如<code>node_modules</code>等。</p>
<p dir="auto">但是有些情况下我们会既需要将文件提交到代码仓库中,又忽略它的后续改动。这种情况就不能使用<code>.gitignore</code>来解决了。</p>
<p dir="auto">什么情况下会有这种问题呢?如工程中的配置,可能每个人在开发调试过程中都会略做调整,但是不希望将这些改动提交到仓库中,以免影响持续集成或他人的配置。</p>
<p dir="auto">常规的解决方案,是将配置文件分成多套,如dotenv会有类似下面的文件:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>.env
.env.local
.env.production
.env.development</pre></div>
<p dir="auto">我们可以将 <code>.env.local</code>文件的优先级设置为最高,同时在<code>.gitignore</code>文件中忽略掉它。每个用户自定义的时候,只改<code>.env.local</code>文件,就可以做到互相不影响了。</p>
<p dir="auto">这种方法是比较好的,也是一般来说的推荐方法。但是并不是所有场景都能用这种方式解决,有一些工程不支持从多个位置读取配置,只能改动公共文件。这种情况就无法用上面的方式解决了。</p>
<p dir="auto">下面我介绍另一种解决方案,通过修改Git仓库配置,使其忽略掉指定文件的改动。在配置之后,即使指定的文件发生了变化,也不会体现在<code>git status</code>中,也不会被<code>git add</code>提交。</p>
<p dir="auto">具体的命令为:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>git update-index --assume-unchanged [<span class="pl-k"><</span>file<span class="pl-k">></span> ...]</pre></div>
<p dir="auto">如果以后想要提交改动,可以撤销这个设置:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>git update-index --no-assume-unchanged [<span class="pl-k"><</span>file<span class="pl-k">></span> ...]</pre></div>
<p dir="auto"><a href="https://git-scm.com/docs/git-update-index/" rel="nofollow">相关说明文档</a>:</p>
<blockquote>
<p dir="auto"><strong>--[no-]assume-unchanged</strong>
When this flag is specified, the object names recorded for the paths are not updated. Instead, this option sets/unsets the "assume unchanged" bit for the paths. When the "assume unchanged" bit is on, the user promises not to change the file and allows Git to assume that the working tree file matches what is recorded in the index. If you want to change the working tree file, you need to unset the bit to tell Git. This is sometimes helpful when working with a big project on a filesystem that has very slow <code>lstat(2)</code> system call (e.g. cifs).</p>
</blockquote>
<blockquote>
<p dir="auto">Git will fail (gracefully) in case it needs to modify this file in the index e.g. when merging in a commit; thus, in case the assumed-untracked file is changed upstream, you will need to handle the situation manually.</p>
</blockquote>
<p dir="auto">快来试试吧!</p>
</article>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/b5bac69767f49073e09985d82128e713
2021-03-24T07:49:45Z
2024-06-15T14:19:11Z
记一次Git仓库同步时大小超限问题的解决
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/b5bac69767f49073e09985d82128e713#file-fix-git-pack-exceeds-maximum-allowed-size-problem-blog-md">fix-git-pack-exceeds-maximum-allowed-size-problem.blog.md</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-fix-git-pack-exceeds-maximum-allowed-size-problem-blog-md" class="file my-2">
<div id="file-fix-git-pack-exceeds-maximum-allowed-size-problem-blog-md-readme" class="Box-body readme blob p-5 p-xl-6 "
style="overflow: auto" tabindex="0" role="region"
aria-label="fix-git-pack-exceeds-maximum-allowed-size-problem.blog.md content, created by banyudu on 07:49AM on March 24, 2021."
>
<article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">记一次Git仓库同步时大小超限问题的解决</h1><a id="user-content-记一次git仓库同步时大小超限问题的解决" class="anchor" aria-label="Permalink: 记一次Git仓库同步时大小超限问题的解决" href="#记一次git仓库同步时大小超限问题的解决"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">最近我尝试了将一个Git仓库上传到另一个Git服务器,本来是挺简单的一个步骤,但是遇到了一个大小超限的问题,搞得很麻烦:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>remote: fatal: pack exceeds maximum allowed size
error: remote unpack failed: unpack-objects abnormal <span class="pl-c1">exit</span></pre></div>
<p dir="auto">解决过程很是繁琐,涉及到了Git的一些高级应用,在这里记录一下。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">TL;DR</h2><a id="user-content-tldr" class="anchor" aria-label="Permalink: TL;DR" href="#tldr"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">本文是流水账形式,主要涉及到的技术点如下,对具体过程不感兴趣的只看此节即可:</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">问题</h3><a id="user-content-问题" class="anchor" aria-label="Permalink: 问题" href="#问题"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Git仓库中单个Commit中提交了多个视频文件(7 * 10MB),在原来的Git服务中正常提交,在新的Git服务器上限制了50MB,所以提交不上去。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">解决方案</h3><a id="user-content-解决方案" class="anchor" aria-label="Permalink: 解决方案" href="#解决方案"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">解决方案笼统地来说是分批上传,操作包括:</p>
<ul dir="auto">
<li>分批push,解决一次性推送Commit过多导致的超大问题</li>
<li>拆分commit,解决单个commit体积过大导致的超大问题
<ul dir="auto">
<li>定位到问题commit(du + git log,或 git bisect + git branch + git push)</li>
<li>获取commit内部详情 (git cat-file 或 git ls-tree)</li>
<li>截取commit内部分内容,生成新的tree(git mktree)</li>
<li>根据tree生成commit (git commit-tree)</li>
<li>推送commit & Merge 解决单次commit过大的问题</li>
</ul>
</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">具体过程</h2><a id="user-content-具体过程" class="anchor" aria-label="Permalink: 具体过程" href="#具体过程"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">问题产生</h3><a id="user-content-问题产生" class="anchor" aria-label="Permalink: 问题产生" href="#问题产生"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">因为工作需要,要将一个Git仓库同步到公司的另外一个Git服务器上。</p>
<p dir="auto">这个事情一般来说是比较简单的,涉及到的<a href="https://stackoverflow.com/questions/5181845/git-push-existing-repo-to-a-new-and-different-remote-repo-server" rel="nofollow">示例代码</a>如下:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>git remote rename origin upstream
git remote add origin URL_TO_GITHUB_REPO
git push origin master</pre></div>
<p dir="auto">但是在具体执行过程中,最后一步<code>git push</code>时,发生了错误:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>remote: fatal: pack exceeds maximum allowed size
error: remote unpack failed: unpack-objects abnormal <span class="pl-c1">exit</span></pre></div>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">原因剖析</h3><a id="user-content-原因剖析" class="anchor" aria-label="Permalink: 原因剖析" href="#原因剖析"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">从报错中可以看出,是pack的大小超出了限制。</p>
<p dir="auto">那首先要理解pack是什么东西,以及它的使用流程。</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/e63e570f25581de80ac78dbffb275c07007a662278a40335a5b197e74011614c/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f7061636b2d636c69656e742d7365727665722e706e67"><img src="https://camo.githubusercontent.com/e63e570f25581de80ac78dbffb275c07007a662278a40335a5b197e74011614c/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f7061636b2d636c69656e742d7365727665722e706e67" alt="Pack commands on client and server" data-canonical-src="https://banyudu.github.io/images/pack-client-server.png" style="max-width: 100%;"></a></p>
<blockquote>
<p dir="auto">图片来自<a href="https://stefan.saasen.me/articles/git-clone-in-haskell-from-the-bottom-up/" rel="nofollow">https://stefan.saasen.me/articles/git-clone-in-haskell-from-the-bottom-up/</a></p>
</blockquote>
<p dir="auto">Git在push操作中,是先将要推送的内容打包成一个pack,再调用<code>send-pack</code>传送到服务器上,服务器上使用<code>receive-pack</code>处理客户端发送上来的pack。</p>
<p dir="auto">客户端和服务端中都可以对pack文件的大小做出限制,上面的报错是因为pack文件超出了服务端允许的最大值。</p>
<p dir="auto">什么原因会导致pack文件超大呢?因为Git是在推送时将所有需要的内容(根据Commit决定)一次打包,然后上传的。所以当commit数量很多时(比如第一次同步仓库时)或者commit中包含的文件内容很多时(如带有视频文件等),就可能会导致pack超大。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">解决思路</h3><a id="user-content-解决思路" class="anchor" aria-label="Permalink: 解决思路" href="#解决思路"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">分批推送</h4><a id="user-content-分批推送" class="anchor" aria-label="Permalink: 分批推送" href="#分批推送"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Stackoverflow上有一个<a href="https://stackoverflow.com/a/51468389/2380603" rel="nofollow">相关回答</a>,它提到了一种解决思路,将Commit分批提交。</p>
<p dir="auto">核心代码为:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>git push remoteB <span class="pl-k"><</span>some previous commit on master<span class="pl-k">></span>:master
...
git push remoteB <span class="pl-k"><</span>some previous commit after the last one<span class="pl-k">></span>:master
git push remoteB master</pre></div>
<p dir="auto">可以按如下的方式来理解:</p>
<p dir="auto">从原来的一把梭推送:</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/6ff2acced486972e0a1f6dd09a96d4760791751759db2b3eb1f8476eaf49ee79/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303231303332343132323632373734332e706e67"><img src="https://camo.githubusercontent.com/6ff2acced486972e0a1f6dd09a96d4760791751759db2b3eb1f8476eaf49ee79/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303231303332343132323632373734332e706e67" alt="image-20210324122627743" data-canonical-src="https://banyudu.github.io/images/image-20210324122627743.png" style="max-width: 100%;"></a></p>
<p dir="auto">换成了多次推送:</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/7be06b355f25b593ac6db908b2f67b3ca3f8227e2aef278603b6ca0b31b205eb/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303231303332343132323635333936332e706e67"><img src="https://camo.githubusercontent.com/7be06b355f25b593ac6db908b2f67b3ca3f8227e2aef278603b6ca0b31b205eb/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303231303332343132323635333936332e706e67" alt="image-20210324122653963" data-canonical-src="https://banyudu.github.io/images/image-20210324122653963.png" style="max-width: 100%;"></a></p>
<p dir="auto">图中是用的创建tag的方式,不过tag并不是必须的,可以按示例代码中的方式来操作:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>git push origin <span class="pl-k"><</span>commit id<span class="pl-k">></span>:refs/heads/<span class="pl-k"><</span>分支名<span class="pl-k">></span></pre></div>
<p dir="auto">这种方式简单易操作,而且<a href="https://stackoverflow.com/a/51468389/2380603" rel="nofollow">StackOverflow</a>中还有现成的脚本,应该能解决大多数类似的问题了。</p>
<p dir="auto">然而我遇到的问题并不能用这种方式解决,因为它存在单条超大Commit。</p>
<p dir="auto">好在这个超大Commit是因为存在多个视频文件,而非一个独立大文件,所以还是有可操作空间的。</p>
<p dir="auto">我在解决单条超大Commit时有两种思路:</p>
<ol dir="auto">
<li>重写历史,将此Commit拆分成多个小Commit(如每个文件拆分成一个commit)</li>
<li>拆分上传,从更细的粒度,拆分上传此Commit,不重写历史。</li>
</ol>
<div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">重写历史</h4><a id="user-content-重写历史" class="anchor" aria-label="Permalink: 重写历史" href="#重写历史"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">重写历史是为了将大的commit拆分成多个小的commit,即从一个Commit中上传了7个文件,每个文件10MB,改成每一个或多个文件一个Commit,使每一个Commit的大小都小于最大限制。</p>
<p dir="auto">可以使用的工具是<code>git-filter-branch</code>,也可以考虑使用<a href="https://github.com/rtyley/bfg-repo-cleaner">bfg-repo-cleaner</a>。</p>
<p dir="auto">它的好处是步骤相对来说稍简单一些,也容易理解。坏处是修改了Git仓库的历史,在很多项目中可能是不可接受的。</p>
<p dir="auto">出于不想修改历史的原因,我并没有采用这个方案,而是尝试通过拆分上传解决这个问题。</p>
<div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">拆分上传</h4><a id="user-content-拆分上传" class="anchor" aria-label="Permalink: 拆分上传" href="#拆分上传"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Git存储的粒度是各种各样的Blob,存储在 <code>.git/objects</code>目录中。虽然上面提到的7个视频文件都在同一个commit之中,但是它们各自有自己的blob对象,那么是否能基于这一点,分批上传Blob对象,最终使得<code>git push</code>时需要推送的内容减少呢?</p>
<p dir="auto">原理图如下:</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/353dc75f91b04003c04da8c55c71e9aeab6b674e030451d2f5e02b071243fe1b/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303231303332343134343331323930302e706e67"><img src="https://camo.githubusercontent.com/353dc75f91b04003c04da8c55c71e9aeab6b674e030451d2f5e02b071243fe1b/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303231303332343134343331323930302e706e67" alt="image-20210324144312900" data-canonical-src="https://banyudu.github.io/images/image-20210324144312900.png" style="max-width: 100%;"></a></p>
<p dir="auto">我做了一次尝试,首先通过<code>git ls-tree</code> 或 <code>git cat-file</code>命令查找超大Commit中包含的blob对象。</p>
<p dir="auto"><code>git ls-tree <commit-id> path/to/files</code></p>
<p dir="auto">可以找到类似如下的结果:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>100644 blob 9687f4c6202de35256ak89sc1381497ec18c29fc video/1.mp4
100644 blob 5ffc1e655702a4898sakfkk90a116a37c819d684 video/2.mp4
100644 blob a2b785a998223e0918138akkfjla965b36fcb762 video/3.mp4
100644 blob dea68deeae131022kkf4a3eef7f5c89ab827d4de video/4.mp4
100644 blob ee5ff963d697571c4366a20f00d532937e3b4f82 video/5.mp4
100644 blob 6356akk0099aa5b7a11c1055118c351691f0927b video/6.mp4
100644 blob 55f62d2737c83dd9dc0987f7123kaka09j18baaf video/7.mp4</pre></div>
<p dir="auto">那么如何将这些blob分批上传呢?</p>
<p dir="auto">我的想法是分别为每个(或一组)blob创建一个commit,要求其中包含的blob对象小于服务端的pack限制,然后为这些commit创建tag或分支,然后推送到远程服务器。</p>
<p dir="auto">具体操作上来说,是用类似下面的脚本:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>git ls-tree <span class="pl-k"><</span>commit-id<span class="pl-k">></span><span class="pl-k">|</span>head -n 3<span class="pl-k">|</span>git mktree</pre></div>
<p dir="auto">这个命令是过滤了前3个文件,并将它们传递给<code>git mktree</code>命令,生成了只包含前3个文件的一个tree。</p>
<p dir="auto">此时控制台中会输出这个tree的id信息。</p>
<p dir="auto">然后再使用<code>git commit-tree</code>命令将其提交成一个commit:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>git commit-tree <span class="pl-k"><</span>上条命令中返回的tree id<span class="pl-k">></span> -p <span class="pl-k"><</span>parent commit id<span class="pl-k">></span> -m <span class="pl-s"><span class="pl-pds">'</span>commit message<span class="pl-pds">'</span></span></pre></div>
<p dir="auto">这样就会再生成一条commit。</p>
<p dir="auto">之后再基于这个commit做分支或tag,然后推送到服务端就好了。</p>
<hr>
<p dir="auto">理论是很好的,但是实际操作起来,发现并没有解决掉这个问题。即使先上传了部分的object,下次再回到原来的分支<code>git push</code>时,还是会出现相同的问题。</p>
<p dir="auto">查了一圈资料,原因是在于<code>git push</code>时,计算要推送的内容是基于commit维护的,而不是基于objects维度。所以虽然超大commit中包含的部分object已经存在于服务器上,git push的时候还是会把它们打包进去。</p>
<p dir="auto">这就很尴尬!!!</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/9c51787abb1e74cf7ddd587128d78b1fd35de1d15e3330ad7e41e10da3a6d24d/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f326435653565616636346130616339642e6a706567"><img src="https://camo.githubusercontent.com/9c51787abb1e74cf7ddd587128d78b1fd35de1d15e3330ad7e41e10da3a6d24d/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f326435653565616636346130616339642e6a706567" alt="img" data-canonical-src="https://banyudu.github.io/images/2d5e5eaf64a0ac9d.jpeg" style="max-width: 100%;"></a></p>
<div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">曲线救国</h4><a id="user-content-曲线救国" class="anchor" aria-label="Permalink: 曲线救国" href="#曲线救国"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">在知道<code>git push</code>时pack的最小维度是commit之后,问题就又回到了拆分commit上来了。</p>
<p dir="auto">但是我还是不太想重写历史,有没有办法曲线救国呢?</p>
<p dir="auto">这里我想到的办法是这样:</p>
<ol dir="auto">
<li>首先按上一步的做法,使用超大Commit内部的部分Object创造出一个或多个新的Commit</li>
<li>基于上一步创建出的commit,新建一个分支 tmp</li>
<li>将超大Commit所在的分支合并到此新分支中(如 master -> tmp)</li>
<li>将新分支推送到远程(git push origin tmp)</li>
<li>将原分支推送到远程 (git push origin master)</li>
<li>删除临时分支 (git push origin -d tmp)</li>
</ol>
<p dir="auto">它的原理如下图:</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/30f9bad0e3b8af5d835991be3b23f4b6c15421a1e06f94813d1d6b45bc508b53/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303231303332343135313335313433322e706e67"><img src="https://camo.githubusercontent.com/30f9bad0e3b8af5d835991be3b23f4b6c15421a1e06f94813d1d6b45bc508b53/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f696d6167652d32303231303332343135313335313433322e706e67" alt="image-20210324151351432" data-canonical-src="https://banyudu.github.io/images/image-20210324151351432.png" style="max-width: 100%;"></a></p>
<p dir="auto">tmp分支第一个新的Commit中包含了4个objects,它可以直接push,没有超过大小限制。然后再merge一次之后,产生了一个merge commit,它中间包含着剩余的3个object,也没有超出限制。待Merge Commit推送上去之后,Git会认为超大Commit已经在远程服务器中存在,因此再重新推送master分支时,将不再打包超大commit中的内容,也就曲线地将超大commit推送了上去。</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/d68369a6c2fdb8921656d304303248a059d73dc2a9e6bdfce2c109e7d38e3cba/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f326364343137396663323836653035322e6a706567"><img src="https://camo.githubusercontent.com/d68369a6c2fdb8921656d304303248a059d73dc2a9e6bdfce2c109e7d38e3cba/68747470733a2f2f62616e797564752e6769746875622e696f2f696d616765732f326364343137396663323836653035322e6a706567" alt="img" data-canonical-src="https://banyudu.github.io/images/2cd4179fc286e052.jpeg" style="max-width: 100%;"></a></p>
<p dir="auto">这种方式的好处是没有重写master分支的历史,操作完成后删除掉临时的tmp分支即可。</p>
<div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">新的尝试</h4><a id="user-content-新的尝试" class="anchor" aria-label="Permalink: 新的尝试" href="#新的尝试"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">在上面的方案成功之后,我开始思考能否使用更简便的方式来实现上述操作。</p>
<p dir="auto">之前使用<code>git mktree</code>和<code>git commit-tree</code>来生成commit的操作,是想直接利用git仓库中已有的object,那如果使用<code>git add && git commit</code>的方式提交部分文件,替代<code>git mktree && git commit-tree</code>的方式,能否实现相同的目的呢?</p>
<p dir="auto">这里我在服务端新建了一个空白仓库,重新复现问题。然后执行下面的操作:</p>
<ol dir="auto">
<li>添加新的远程仓库:git remote add test-upstream <git仓库地址></li>
<li>在master分支中执行 git push -u test-upstream master,返回失败信息</li>
<li>基于超大Commit之前的一条Commit,新建分支 tmp</li>
<li>使用 git push -u test-upstream tmp</li>
<li>在tmp分支的基础上,git add 超大分支中的部分文件,然后commit & push</li>
<li>在 tmp 分支上执行 git merge master,合并超大Commit</li>
<li>再次git push -u test-upstream tmp</li>
<li>此时切换到master分支,再将执行 git push -u test-upstream master,成功</li>
</ol>
<div class="markdown-heading" dir="auto"><h4 class="heading-element" dir="auto">原因分析</h4><a id="user-content-原因分析" class="anchor" aria-label="Permalink: 原因分析" href="#原因分析"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">为什么直接<code>git add 部分文件 && git commit</code>也能起到和<code>git mktree && git commit-tree</code>相同的效果呢?</p>
<p dir="auto">前者并没有使用原来的object id,而后者是直接用的原来的object-id的。</p>
<p dir="auto">这里我查了下,是因为<code>git hash-object</code>命令,对于同样位置中的相同的文件,它总能生成相同的 objectId,所以这里只要保证文件路径和文件内容不变,就可以直接复用之前的objectId,不必使用<code>git mktree</code>和<code>git commit-tree</code>人为同步。</p>
<p dir="auto">相关算法为:</p>
<div class="highlight highlight-source-shell" dir="auto"><pre>Commit Hash (SHA1) = SHA1(<span class="pl-s"><span class="pl-pds">"</span>blob <span class="pl-pds">"</span></span> + <span class="pl-k"><</span>size_of_file<span class="pl-k">></span> + <span class="pl-s"><span class="pl-pds">"</span>\0<span class="pl-pds">"</span></span> + <span class="pl-k"><</span>contents_of_file<span class="pl-k">></span>)</pre></div>
<p dir="auto">参见StackOverflow中的<a href="https://stackoverflow.com/questions/7225313/how-does-git-compute-file-hashes" rel="nofollow">相关问题</a>。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">总结</h2><a id="user-content-总结" class="anchor" aria-label="Permalink: 总结" href="#总结"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">在同步Git仓库时,如果遇到了大小超限的问题,可以分批上传、分拆上传等方式来解决。</p>
<p dir="auto">深入理解Git的内部原理,有助于解决此类复杂问题,如非必要,应尽量避免重写历史。</p>
</article>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/986a320ff9af0dc319962a7d8245cc93
2021-03-17T03:04:16Z
2021-03-17T03:04:16Z
OSX Dictionary Lookup History Service
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/986a320ff9af0dc319962a7d8245cc93#file-lookup-applescript">LookUp.APPLESCRIPT</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-lookup-applescript" class="file my-2">
<div itemprop="text"
class="Box-body p-0 blob-wrapper data type-applescript "
style="overflow: auto" tabindex="0" role="region"
aria-label="LookUp.APPLESCRIPT content, created by banyudu on 03:04AM on March 17, 2021."
>
<div class="js-check-hidden-unicode js-blob-code-container blob-code-content">
<template class="js-file-alert-template">
<div data-view-component="true" class="flash flash-warn flash-full d-flex flex-items-center">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
<span>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
<a class="Link--inTextBlock" href="https://github.co/hiddenchars" target="_blank">Learn more about bidirectional Unicode characters</a>
</span>
<div data-view-component="true" class="flash-action"> <a href="{{ revealButtonHref }}" data-view-component="true" class="btn-sm btn"> Show hidden characters
</a>
</div>
</div></template>
<template class="js-line-alert-template">
<span aria-label="This line has hidden Unicode characters" data-view-component="true" class="line-alert tooltipped tooltipped-e">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
<path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
</span></template>
<table data-hpc class="highlight tab-size js-file-line-container" data-tab-size="8" data-paste-markdown-skip data-tagsearch-path="LookUp.APPLESCRIPT">
<tr>
<td id="file-lookup-applescript-L1" class="blob-num js-line-number js-blob-rnum" data-line-number="1"></td>
<td id="file-lookup-applescript-LC1" class="blob-code blob-code-inner js-file-line"><span class="pl-k">on</span> <span class="pl-c1">run</span> {input, parameters}</td>
</tr>
<tr>
<td id="file-lookup-applescript-L2" class="blob-num js-line-number js-blob-rnum" data-line-number="2"></td>
<td id="file-lookup-applescript-LC2" class="blob-code blob-code-inner js-file-line"> <span class="pl-k">set</span> <span class="pl-smi">logPath</span> <span class="pl-k">to</span> <span class="pl-s"><span class="pl-pds">"</span>Dropbox/Dictionary/words.txt<span class="pl-pds">"</span></span></td>
</tr>
<tr>
<td id="file-lookup-applescript-L3" class="blob-num js-line-number js-blob-rnum" data-line-number="3"></td>
<td id="file-lookup-applescript-LC3" class="blob-code blob-code-inner js-file-line"> <span class="pl-k">set</span> <span class="pl-smi">lookUpWord</span> <span class="pl-k">to</span> <span class="pl-c1">quoted form</span> <span class="pl-k">of</span> (input <span class="pl-k">as</span> <span class="pl-c1">string</span>)</td>
</tr>
<tr>
<td id="file-lookup-applescript-L4" class="blob-num js-line-number js-blob-rnum" data-line-number="4"></td>
<td id="file-lookup-applescript-LC4" class="blob-code blob-code-inner js-file-line"> </td>
</tr>
<tr>
<td id="file-lookup-applescript-L5" class="blob-num js-line-number js-blob-rnum" data-line-number="5"></td>
<td id="file-lookup-applescript-LC5" class="blob-code blob-code-inner js-file-line"> <span class="pl-k">tell</span> <span class="pl-c1">application</span> <span class="pl-s"><span class="pl-pds">"</span>System Events<span class="pl-pds">"</span></span> <span class="pl-k">to</span> <span class="pl-k">tell</span> (<span class="pl-c1">process</span> <span class="pl-c1">1</span> <span class="pl-k">where</span> <span class="pl-c1">frontmost</span> <span class="pl-k">is</span> <span class="pl-c1">true</span>)</td>
</tr>
<tr>
<td id="file-lookup-applescript-L6" class="blob-num js-line-number js-blob-rnum" data-line-number="6"></td>
<td id="file-lookup-applescript-LC6" class="blob-code blob-code-inner js-file-line"> <span class="pl-k">set</span> <span class="pl-smi">windowTitle</span> <span class="pl-k">to</span> <span class="pl-c1">name</span></td>
</tr>
<tr>
<td id="file-lookup-applescript-L7" class="blob-num js-line-number js-blob-rnum" data-line-number="7"></td>
<td id="file-lookup-applescript-LC7" class="blob-code blob-code-inner js-file-line"> <span class="pl-k">try</span></td>
</tr>
<tr>
<td id="file-lookup-applescript-L8" class="blob-num js-line-number js-blob-rnum" data-line-number="8"></td>
<td id="file-lookup-applescript-LC8" class="blob-code blob-code-inner js-file-line"> <span class="pl-k">set</span> <span class="pl-smi">windowTitle</span> <span class="pl-k">to</span> windowTitle</td>
</tr>
<tr>
<td id="file-lookup-applescript-L9" class="blob-num js-line-number js-blob-rnum" data-line-number="9"></td>
<td id="file-lookup-applescript-LC9" class="blob-code blob-code-inner js-file-line"> <span class="pl-k">set</span> <span class="pl-smi">titleBar</span> <span class="pl-k">to</span> <span class="pl-c1">name</span> <span class="pl-k">of</span> window <span class="pl-c1">1</span></td>
</tr>
<tr>
<td id="file-lookup-applescript-L10" class="blob-num js-line-number js-blob-rnum" data-line-number="10"></td>
<td id="file-lookup-applescript-LC10" class="blob-code blob-code-inner js-file-line"> <span class="pl-k">end try</span></td>
</tr>
<tr>
<td id="file-lookup-applescript-L11" class="blob-num js-line-number js-blob-rnum" data-line-number="11"></td>
<td id="file-lookup-applescript-LC11" class="blob-code blob-code-inner js-file-line"> <span class="pl-k">end tell</span></td>
</tr>
<tr>
<td id="file-lookup-applescript-L12" class="blob-num js-line-number js-blob-rnum" data-line-number="12"></td>
<td id="file-lookup-applescript-LC12" class="blob-code blob-code-inner js-file-line"> </td>
</tr>
<tr>
<td id="file-lookup-applescript-L13" class="blob-num js-line-number js-blob-rnum" data-line-number="13"></td>
<td id="file-lookup-applescript-LC13" class="blob-code blob-code-inner js-file-line"> <span class="pl-c"><span class="pl-c">#</span> do shell script ¬</span></td>
</tr>
<tr>
<td id="file-lookup-applescript-L14" class="blob-num js-line-number js-blob-rnum" data-line-number="14"></td>
<td id="file-lookup-applescript-LC14" class="blob-code blob-code-inner js-file-line"> <span class="pl-c"><span class="pl-c">#</span> "cd; echo '{ word: \"" & lookUpWord & ¬</span></td>
</tr>
<tr>
<td id="file-lookup-applescript-L15" class="blob-num js-line-number js-blob-rnum" data-line-number="15"></td>
<td id="file-lookup-applescript-LC15" class="blob-code blob-code-inner js-file-line"> <span class="pl-c"><span class="pl-c">#</span> "\", windowTitle: \"" & windowTitle & ¬</span></td>
</tr>
<tr>
<td id="file-lookup-applescript-L16" class="blob-num js-line-number js-blob-rnum" data-line-number="16"></td>
<td id="file-lookup-applescript-LC16" class="blob-code blob-code-inner js-file-line"> <span class="pl-c"><span class="pl-c">#</span> "\", titleBar: \"" & titleBar & ¬</span></td>
</tr>
<tr>
<td id="file-lookup-applescript-L17" class="blob-num js-line-number js-blob-rnum" data-line-number="17"></td>
<td id="file-lookup-applescript-LC17" class="blob-code blob-code-inner js-file-line"> <span class="pl-c"><span class="pl-c">#</span> "\" },' >> " & quoted form of logPath</span></td>
</tr>
<tr>
<td id="file-lookup-applescript-L18" class="blob-num js-line-number js-blob-rnum" data-line-number="18"></td>
<td id="file-lookup-applescript-LC18" class="blob-code blob-code-inner js-file-line"> </td>
</tr>
<tr>
<td id="file-lookup-applescript-L19" class="blob-num js-line-number js-blob-rnum" data-line-number="19"></td>
<td id="file-lookup-applescript-LC19" class="blob-code blob-code-inner js-file-line"> <span class="pl-k">set</span> <span class="pl-smi">timeStamp</span> <span class="pl-k">to</span> <span class="pl-c1">do shell script</span> <span class="pl-s"><span class="pl-pds">"</span>date +%s<span class="pl-pds">"</span></span></td>
</tr>
<tr>
<td id="file-lookup-applescript-L20" class="blob-num js-line-number js-blob-rnum" data-line-number="20"></td>
<td id="file-lookup-applescript-LC20" class="blob-code blob-code-inner js-file-line"> <span class="pl-c1">do shell script</span> ¬</td>
</tr>
<tr>
<td id="file-lookup-applescript-L21" class="blob-num js-line-number js-blob-rnum" data-line-number="21"></td>
<td id="file-lookup-applescript-LC21" class="blob-code blob-code-inner js-file-line"> <span class="pl-s"><span class="pl-pds">"</span>cd; echo 'date:<span class="pl-pds">"</span></span> <span class="pl-k">&</span> timeStamp <span class="pl-k">&</span> <span class="pl-s"><span class="pl-pds">"</span><span class="pl-cce">\t</span>word:<span class="pl-pds">"</span></span> <span class="pl-k">&</span> lookUpWord <span class="pl-k">&</span> <span class="pl-s"><span class="pl-pds">"</span><span class="pl-cce">\t</span>windowTitle:<span class="pl-pds">"</span></span> <span class="pl-k">&</span> windowTitle <span class="pl-k">&</span> <span class="pl-s"><span class="pl-pds">"</span><span class="pl-cce">\t</span>titleBar:<span class="pl-pds">"</span></span> <span class="pl-k">&</span> titleBar <span class="pl-k">&</span> <span class="pl-s"><span class="pl-pds">"</span>' >> <span class="pl-pds">"</span></span> <span class="pl-k">&</span> <span class="pl-c1">quoted form</span> <span class="pl-k">of</span> logPath</td>
</tr>
<tr>
<td id="file-lookup-applescript-L22" class="blob-num js-line-number js-blob-rnum" data-line-number="22"></td>
<td id="file-lookup-applescript-LC22" class="blob-code blob-code-inner js-file-line"> </td>
</tr>
<tr>
<td id="file-lookup-applescript-L23" class="blob-num js-line-number js-blob-rnum" data-line-number="23"></td>
<td id="file-lookup-applescript-LC23" class="blob-code blob-code-inner js-file-line"> <span class="pl-c1">do shell script</span> <span class="pl-s"><span class="pl-pds">"</span>open dict://<span class="pl-pds">"</span></span> <span class="pl-k">&</span> lookUpWord</td>
</tr>
<tr>
<td id="file-lookup-applescript-L24" class="blob-num js-line-number js-blob-rnum" data-line-number="24"></td>
<td id="file-lookup-applescript-LC24" class="blob-code blob-code-inner js-file-line"> </td>
</tr>
<tr>
<td id="file-lookup-applescript-L25" class="blob-num js-line-number js-blob-rnum" data-line-number="25"></td>
<td id="file-lookup-applescript-LC25" class="blob-code blob-code-inner js-file-line"> <span class="pl-c1">return</span> input</td>
</tr>
<tr>
<td id="file-lookup-applescript-L26" class="blob-num js-line-number js-blob-rnum" data-line-number="26"></td>
<td id="file-lookup-applescript-LC26" class="blob-code blob-code-inner js-file-line"><span class="pl-k">end</span> <span class="pl-c1">run</span></td>
</tr>
</table>
</div>
</div>
</div>
</div>
tag:gist.github.com,2008:Gist/banyudu/e71239e1e49227734a12ae23bf9de4c1
2021-03-15T06:25:47Z
2021-03-15T06:25:47Z
公有云中的信任危机
Yudu
https://gist.github.com/banyudu
<a href="https://gist.github.com/banyudu/e71239e1e49227734a12ae23bf9de4c1#file-the-crisis-of-trust-in-public-cloud-service-blog-md">the-crisis-of-trust-in-public-cloud-service.blog.md</a>
<div class="js-gist-file-update-container js-task-list-container">
<div id="file-the-crisis-of-trust-in-public-cloud-service-blog-md" class="file my-2">
<div id="file-the-crisis-of-trust-in-public-cloud-service-blog-md-readme" class="Box-body readme blob p-5 p-xl-6 "
style="overflow: auto" tabindex="0" role="region"
aria-label="the-crisis-of-trust-in-public-cloud-service.blog.md content, created by banyudu on 06:25AM on March 15, 2021."
>
<article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 class="heading-element" dir="auto">公有云中的信任危机</h1><a id="user-content-公有云中的信任危机" class="anchor" aria-label="Permalink: 公有云中的信任危机" href="#公有云中的信任危机"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">相信大多数人都经历过一次或多次的离职,每次离职时都会涉及到一些权限的收回、账号的关闭等事项。</p>
<p dir="auto">在公司资源全部在内网时,这个问题比较简单,容易处理,基本上把员工连接内网的权限收回,就可以掩盖很多漏洞了,即使个别系统中的账号没有关闭,也无伤大雅。</p>
<p dir="auto">但是在公有云时代,很多事情变得不同了。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">常见的安全漏洞</h2><a id="user-content-常见的安全漏洞" class="anchor" aria-label="Permalink: 常见的安全漏洞" href="#常见的安全漏洞"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">多人共享账号</h3><a id="user-content-多人共享账号" class="anchor" aria-label="Permalink: 多人共享账号" href="#多人共享账号"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">在权限管理不够严格和标准的公司里,可能会有多人共享同一账号的情况存在。即使有人离职了,因为怕麻烦等原因,可能也并不会修改此公共账号的密码。</p>
<p dir="auto">当这些账号只在内网可用时,问题不算很大,而当这些账号是公网上的第三方服务账号时,就很麻烦了。恶意的员工可能会利用这种漏洞,篡改一些线上的数据,或者抓取一些机密信息等。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">权限集中</h3><a id="user-content-权限集中" class="anchor" aria-label="Permalink: 权限集中" href="#权限集中"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">权限集中在一个或少数几个账号时,如果这些账号泄露了,就会造成极恶劣的安全隐患。</p>
<p dir="auto">而且这个泄露的过程,并非是只有恶意员工才会泄露,一个正常的员工也可能会因为操作不当,中病毒等原因将一些重要的密码泄露出去。</p>
<div class="markdown-heading" dir="auto"><h3 class="heading-element" dir="auto">工具攻击</h3><a id="user-content-工具攻击" class="anchor" aria-label="Permalink: 工具攻击" href="#工具攻击"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">如<code>oh-my-zsh</code>这样的工具,安装命令是先下载一个远程的shell,然后本地执行。若<code>oh-my-zsh</code>的维护人员中有恶意人员,或其被恶意人员攻击,或传输过程被劫持等,都有可能造成开发者的计算机中执行了恶意代码。</p>
<p dir="auto">加上一般云服务有固定的鉴权存储位置,如AWS的<code>~/.aws/credentials </code>或环境变量,恶意软件很容易获取到相应的公有云密钥。</p>
<p dir="auto">其它各种软件包、镜像仓库等,如homebrew、npm、dockerhub等,都有可能存在类似的风险。</p>
<p dir="auto">在私有云时代这种问题不明显,在于即使盗了相关账号,也很难使用。但是公有云就大大地不同了。</p>
<div class="markdown-heading" dir="auto"><h2 class="heading-element" dir="auto">解决思路</h2><a id="user-content-解决思路" class="anchor" aria-label="Permalink: 解决思路" href="#解决思路"><svg class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">现在已经有一些第三方的解决方案,如将代码开源供审查,以及加密传输防劫持等。</p>
<p dir="auto">但是这些都是不可控的因素,解决不了根本的问题。</p>
<p dir="auto">要彻底控制这种问题的发生,或规模。就需要从企业内部角度,寻求更好的解决方案。</p>
<p dir="auto">下面是几个思路:</p>
<ol dir="auto">
<li>启用SSO单点登录,除了极个别的核心员工以外,大部分人都不持有任何公有云的密钥信息,所有权限的获取都通过公司内账号代理,且尽量自动将密钥填充到需要的地方,不将它显示给员工。</li>
<li>权限动态化、碎片化。使用类似AWS IAM之类的技术,将用户可用权限限制在最小范围内,根据需求动态地添加和删除角色等。</li>
<li>密钥自动过期(临时密钥):每隔一段时间(如一天)将密钥自动过期,重新生成新的密钥。与SSO相结合,对正常用户无感知。对恶意用户来说,则使其持有的密钥尽快失效,且能在重要员工离职后手动触发密码变更流程,不影响正常业务。</li>
<li>账号按业务线隔离。</li>
</ol>
</article>
</div>
</div>
</div>