<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>无声告白</title>
    <description>不定期分享一些日常开发中遇到的问题及解决方案，大部分和前端开发相关，如Angular、Vue、React等前端框架， 也会有些诸如如git、VSCode等开发工具的相关知识。 Aperiodically share some questions and anwsers in daily develop, almost releate to frontend develop, eg. Angular, Vue, React. Develop tools like git, VSCode will also touch.
</description>
    <link>/</link>
    <atom:link href="/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Tue, 28 Apr 2026 03:42:58 +0000</pubDate>
    <lastBuildDate>Tue, 28 Apr 2026 03:42:58 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>改变 wsl2 下的 Linux 子系统安装位置</title>
        <description>&lt;p&gt;wsl 的 Linux 子系统使得在 Windows 下的开发体验得到了极大的优化，但重度使用对 C 盘空间是个不小的挑战，所以当 C 盘空间不足的开发者来说最好将其虚拟磁盘(VHD)转移到非系统盘上。本文涉及到的操作步骤均在 PowerShell 或 CMD 中执行，且所有的路径均可根据自己的需要修改。&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;关闭 wsl 服务，能够避免一些资源占用的问题：&lt;/p&gt;

    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; wsl &lt;span class=&quot;nt&quot;&gt;--shutdown&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;找到要转移子系统的名称，即输出的 NAME 列：&lt;/p&gt;

    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; wsl &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt;

  NAME      STATE           VERSION
&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt; Ubuntu    Running         2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;下面所有命令中的 Ubuntu 即此处 NAME 列指示的名字。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;将子系统导出为 tar 包&lt;/p&gt;

    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 下面命令中的 Ubuntu 要换成上一步中的 NAME 列，下同&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; wsl &lt;span class=&quot;nt&quot;&gt;--export&lt;/span&gt; Ubuntu D:&lt;span class=&quot;se&quot;&gt;\T&lt;/span&gt;emp&lt;span class=&quot;se&quot;&gt;\U&lt;/span&gt;buntu.tar
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;此命令会在 D:\Temp 目录下生成 Ubuntu.tar 文件，感兴趣的可以使用 7-zip 或其他解压工具/命令打开看一下，里面是一个标准的 Linux 文件系统。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;注销当前子系统并删除跟文件系统&lt;/p&gt;

    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; wsl &lt;span class=&quot;nt&quot;&gt;--unregister&lt;/span&gt; Ubuntu
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;从 Ubuntu.tar 中导出文件系统到非系统盘&lt;/p&gt;

    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; wsl &lt;span class=&quot;nt&quot;&gt;--import&lt;/span&gt; Ubuntu D:&lt;span class=&quot;se&quot;&gt;\P&lt;/span&gt;rogram&lt;span class=&quot;se&quot;&gt;\w&lt;/span&gt;sl&lt;span class=&quot;se&quot;&gt;\U&lt;/span&gt;buntu D:&lt;span class=&quot;se&quot;&gt;\T&lt;/span&gt;emp&lt;span class=&quot;se&quot;&gt;\U&lt;/span&gt;buntu.tar &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt; 2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;此操作执行时间会稍微有些长，需耐心等待。结束后会在 D:\Program\wsl\Ubuntu 目录下生成 ext4.vhdx 文件，此时可将 D:\Temp\Ubuntu.tar 文件删除。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;切换默认账号（不一定需要）&lt;/p&gt;

    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; ubuntu.exe config &lt;span class=&quot;nt&quot;&gt;--default-user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;USER_NAME]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;我在转移之后，打开 Ubuntu 子系统发现默认账户变成了 root，不是我之前的账号了，故需要使用此命令重新设置一下，如果没有这种情形，可不必执行此操作。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Mon, 25 Apr 2022 20:23:12 +0000</pubDate>
        <link>/2022/04/25/wsl-linux-distro-change-drive.html</link>
        <guid isPermaLink="true">/2022/04/25/wsl-linux-distro-change-drive.html</guid>
        
        <category>Tools</category>
        
        
      </item>
    
      <item>
        <title>Private Network Access</title>
        <description>&lt;p&gt;&lt;a href=&quot;https://wicg.github.io/private-network-access/&quot;&gt;Private Network Access&lt;/a&gt; 是 Blink 内核中新引入的安全性限制，放在 CORS 大类下，对于 Chrome 浏览器来说，是在 94 版本后引入的。最近在处理微前端的接入时，遇到了此限制，访问页面时的报错大致如下：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;XX has been blocked by CORS policy: The request client is not a secure context and the resource is in more-private address space private.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;大致的意思是说，请求的某资源因为 CORS 策略被浏览器拦截了。当前请求方不是安全的环境，且请求的资源处于更私有的空间内。具体到我报错的场景是：微前端的主应用域名为 &lt;em&gt;http://main.scope.com&lt;/em&gt;，请求子应用的主页为 &lt;em&gt;http://172.11.12.13:8081&lt;/em&gt;。&lt;/p&gt;

&lt;p&gt;两者对照来看，请求方是主应用的域名，是 http 协议的（非安全的环境），请求的资源为子应用的主页，是 ip:port 的方式（更私有的资源）。关于某资源是否是更私有的，Chrome 关于此特性的 flag(chrome://flags/#block-insecure-private-network-requests) 中如下解释：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Prevents non-secure contexts from making sub-resource requests to more-private IP addresses. An IP address IP1 is more private than IP2 if 1) IP1 is localhost and IP2 is not, or 2) IP1 is private and IP2 is public. This is a first step towards full enforcement of CORS-RFC1918: https://wicg.github.io/cors-rfc1918
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以看到其判断两个 IP 地址的私有程度遵循下面两个条件：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;如果 IP1 是 localhost 地址，而 IP2 不是，则 IP1 比 IP2 更私有&lt;/li&gt;
  &lt;li&gt;如果 IP1 是私有的地址，而 IP2 是公网地址，则 IP1 比 IP2 更私有&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;对于我遇到的场景，是符合了第 2 个条件（IP1 是 172.11.12.13，IP2 是 main.scope.com 对应的 IP）。此 flag 是默认开启的，我在接入阶段时，手动将其禁用了，但不能要求用户作此处理。还可以看到目前只是完全实现 &lt;a href=&quot;https://wicg.github.io/cors-rfc1918&quot;&gt;CORS-RFC1918&lt;/a&gt; 的第一阶段，后续仍会有一些相关的更新。&lt;/p&gt;

&lt;p&gt;对于引入这个特性后可能会产生的问题，Chrome 在其&lt;a href=&quot;https://developer.chrome.com/blog/private-network-access-update&quot;&gt;文档&lt;/a&gt;中也给出了几种方法进行适配，但适配难度不小，本文会先给出自己的解决方案，再解释文档中的适配方法。&lt;/p&gt;

&lt;h2 id=&quot;我的方案---为子应用申请域名&quot;&gt;我的方案 - 为子应用申请域名&lt;/h2&gt;

&lt;p&gt;由于业务针对的是私有云服务，强制用户使用 https 是不现实的，但上文说道如果 IP1 比 IP2 更私有，从 IP2 请求 IP1 时就会出现此问题，故两者地位对等应该就不会存在此问题。为子产品申请域名后，此问题就不存在了。在用户的环境下可能是都是 ip:port 的方式，也不会存在此问题。&lt;/p&gt;

&lt;h2 id=&quot;chrome-方案-1---双方都使用-https-服务&quot;&gt;Chrome 方案 1 - 双方都使用 https 服务&lt;/h2&gt;

&lt;p&gt;此方案不必赘述，优点很明显，安全性很强。缺点也很明显，成本比较高。&lt;/p&gt;

&lt;h2 id=&quot;chrome-方案-2---webtransport-服务&quot;&gt;Chrome 方案 2 - WebTransport 服务&lt;/h2&gt;

&lt;p&gt;此方案要求被请求者方的服务器上运行 &lt;a href=&quot;https://w3c.github.io/webtransport/&quot;&gt;WebTransport 服务&lt;/a&gt;（基于 http3 稍加改动）。此方案不需要 https 中的证书成本，目标设备上有自签名的证书即可建立到其的安全链接。WebTransport 链接允许双向数据传输。此方案暂时没理解，这里暂不班门弄斧了。&lt;/p&gt;

&lt;h2 id=&quot;chrome-方案-3---反向嵌入&quot;&gt;Chrome 方案 3 - 反向嵌入&lt;/h2&gt;

&lt;p&gt;该方案不需要对网络有较强的理解和改动，而且可以在目标服务器无法运行 https 时使用。大致的做法如下：&lt;/p&gt;

&lt;p&gt;请求者网站拆分为二，其本身的资源通过 CDN 等公网手段请求，其骨架放在和被请求者的私有服务器上，这样在请求私有服务器时，因为是同域，不会存在 Private Network Access 问题。使用此方案甚至可以请求其他私有服务（私有请求私有也不会有问题）。&lt;/p&gt;

&lt;h2 id=&quot;总结&quot;&gt;总结&lt;/h2&gt;

&lt;p&gt;可以看出，Private Network Access 确实能够增强安全性，但目前仍处于初级阶段，后续仍会更新和改动，业务方接入也会存在不同的难度和方案。目前也会遇到比较多的问题，如果各位也遇到了类似的问题，可首先查看相关的 &lt;a href=&quot;https://bugs.chromium.org/p/chromium/issues/list?q=component%3ABlink%3ESecurityFeature%3ECORS%3EPrivateNetworkAccess&amp;amp;can=2&quot;&gt;chromium issue&lt;/a&gt; 及 &lt;a href=&quot;https://github.com/WICG/private-network-access/issues&quot;&gt;GitHub issue&lt;/a&gt;。&lt;/p&gt;

</description>
        <pubDate>Tue, 11 Jan 2022 20:23:12 +0000</pubDate>
        <link>/2022/01/11/private-netework-access.html</link>
        <guid isPermaLink="true">/2022/01/11/private-netework-access.html</guid>
        
        <category>Tools</category>
        
        
      </item>
    
      <item>
        <title>icon font 渲染乱码</title>
        <description>&lt;p&gt;最近维护一老项目，在部署后发现部分字体图标渲染出现乱码，且不是必现，在控制台查看元素，发现伪元素的 content 属性为乱码：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/posts/iconfont/iconfont-render-messy-code.PNG&quot; alt=&quot;iconfont-render-messy-code.PNG&quot; /&gt;&lt;/p&gt;

&lt;p&gt;而 scss 源码为：&lt;/p&gt;

&lt;div class=&quot;language-scss highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// icon.scss&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;.icon-order&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;:before&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;\e261&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;而经过 webpack 构建，通过 sass-loader（dart-sass 实现）、css-loader 处理后的样式为：&lt;/p&gt;

&lt;div class=&quot;language-css highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;/* icon.css */&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;.icon-order&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;:before&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;如果使用的是 &lt;a href=&quot;https://github.com/sass/node-sass&quot;&gt;node-sass&lt;/a&gt;，虽然可能不会出现此问题，但其已经不再维护了，故不推荐使用。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;经过查询资料，发现此问题有如下几种方案：&lt;/p&gt;

&lt;h2 id=&quot;方案-1---使用-utf-8-编码&quot;&gt;方案 1 - 使用 utf-8 编码&lt;/h2&gt;

&lt;p&gt;通过 webpack 构建后的文件默认是 utf-8 编码的，但最好在响应头部中显式地指定当前文档使用的是 utf-8 编码：&lt;/p&gt;

&lt;div class=&quot;language-http highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;err&quot;&gt;# Response Header
Content-Type: text/css; charset=utf-8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样即使 css 文件中显示的是乱码，浏览器也应能够正常渲染。而出现问题页面的服务器并未给 css 文件设置 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;charset=utf-8&lt;/code&gt; 属性，想来这也是偶现的原因。&lt;/p&gt;

&lt;p&gt;一般在 index.html 都会有如下代码：&lt;/p&gt;

&lt;div class=&quot;language-html highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nt&quot;&gt;&amp;lt;meta&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;charset=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;utf-8&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但此属性只是指示了 index.html 的编码，并不能影响其中引用资源文件的编码。关于此配置的文档可查看&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset&quot;&gt;MDN 文档&lt;/a&gt;。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;服务器配置方面，应该为 js、css 等资源文件的 Content-Type 头部中指定 utf-8 编码。&lt;strong&gt;此方案能否解决此问题未经实际验证&lt;/strong&gt;，可配合后续的方案使用更加保险。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;方案-2---在-sass-loader-配置中为-dart-sass-指定-sassoptions&quot;&gt;方案 2 - 在 sass-loader 配置中为 dart-sass 指定 sassOptions&lt;/h2&gt;

&lt;p&gt;使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;outputStyle&lt;/code&gt; 配置为 &lt;em&gt;expanded&lt;/em&gt; 后，上文生成的 css 代码将不会是乱码，而是正常的 &lt;em&gt;content: “\e261”;&lt;/em&gt;：&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// webpack.config.js&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nl&quot;&gt;loader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;sass-loader&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;implementation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;sass&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;sassOptions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;// 不进行代码压缩&lt;/span&gt;
      &lt;span class=&quot;nl&quot;&gt;outputStyle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;expanded&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;outputStyle&lt;/code&gt; 默认配置为 &lt;em&gt;compressed&lt;/em&gt;，表示尽可能地删除多余的字符，将所有样式写作一行（即会进行代码压缩），关于 outputStyle 配置的信息，可查阅&lt;a href=&quot;https://sass-lang.com/documentation/cli/dart-sass#style&quot;&gt;dart-sass 文档&lt;/a&gt;。虽然对于 dart-sass 来说，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;outputStyle&lt;/code&gt; 的默认是 &lt;em&gt;expanded&lt;/em&gt;，但 sass-loader 会在生产模式下将默认值改为 &lt;em&gt;compressed&lt;/em&gt;。可查阅 &lt;a href=&quot;https://github.com/webpack-contrib/sass-loader/blob/babe42a1144e201cb17e3b076a677b167a7c2d41/src/utils.js#L174&quot;&gt;sass-loader 源码&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;将 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;outputStyle&lt;/code&gt; 配置修改后，最终生成的样式文件是未经压缩的，这会对文件体积有影响，此时可结合 &lt;a href=&quot;https://github.com/webpack-contrib/css-minimizer-webpack-plugin&quot;&gt;css-minimizer-webpack-plugin&lt;/a&gt; 使用，具体用法可查看其文档，此处不再赘述。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;css-minimizer-webpack-plugin@1 版本适用于 webpack@4；css-minimizer-webpack-plugin@2 以上适用于 webpack@5。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;方案-3---定义-scss-函数替代直接书写-unicode-字面量&quot;&gt;方案 3 - 定义 scss 函数，替代直接书写 unicode 字面量&lt;/h2&gt;

&lt;p&gt;此方案详情，可查看此 &lt;a href=&quot;https://stackoverflow.com/a/30421654/4526557&quot;&gt;stack overflow 回复&lt;/a&gt;。&lt;/p&gt;

</description>
        <pubDate>Tue, 11 Jan 2022 20:23:12 +0000</pubDate>
        <link>/2022/01/11/iconfont-render-messy-code.html</link>
        <guid isPermaLink="true">/2022/01/11/iconfont-render-messy-code.html</guid>
        
        <category>Tools</category>
        
        
      </item>
    
      <item>
        <title>lerna / Yarn Workspaces 中使用 Jest 可能存在的路径问题</title>
        <description>
&lt;p&gt;最近在 Yarn2+ Workspaces 中使用 Jest 发现可能会找不到本地的依赖，结构如下：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# product 依赖于 utils&lt;/span&gt;
├── package.json
├── node_modules
│   ├── lwz-utils -&amp;gt; ../packages/lwz-utils/
│   ├── lwz-product -&amp;gt; ../packages/lwz-product/
│   ├── jest
├── packages
│   ├── lwz-utils
│   ├── lwz-product
├── README.md
└── yarn.lock
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;lwz-product&lt;/em&gt; 依赖于 &lt;em&gt;lwz-utils&lt;/em&gt;，而它们通过软链的方式被提升到顶层的 &lt;em&gt;node_modules&lt;/em&gt;，且 Jest 也被安装/提升在了顶层，此时如果在 &lt;em&gt;lwz-product&lt;/em&gt; 执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jest&lt;/code&gt; 进行测试，Jest 会抛出找不到 &lt;em&gt;lwz-utils&lt;/em&gt; 依赖的错误：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;yarn workspace lwz-product &lt;span class=&quot;nb&quot;&gt;test

&lt;/span&gt;FAIL  src/__tests__/index.test.ts
  ● Test suite failed to run
Cannot find module &lt;span class=&quot;s1&quot;&gt;&apos;lwz-utils&apos;&lt;/span&gt; from &lt;span class=&quot;s1&quot;&gt;&apos;src/index.ts&apos;&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 其他错误&lt;/span&gt;
at Resolver.resolveModule &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;../../node_modules/jest-resolve/build/resolver.js:327:11&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; at Object.&amp;lt;anonymous&amp;gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;src/index.ts:1:1&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以猜测出，这是 Jest 在查找依赖的时候因为路径的原因导致查找不到 &lt;em&gt;lwz-utils&lt;/em&gt; 依赖。&lt;/p&gt;

&lt;h2 id=&quot;解决方案&quot;&gt;解决方案&lt;/h2&gt;

&lt;p&gt;此时可以在 Jest 的配置中使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;moduleNameMapper&lt;/code&gt; 主动配置类似依赖的查找路径，像上面的问题，就可以像下面这样进行配置：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// lwz-product/jest.config.js&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;testEnvironment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// 其他配置&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;moduleNameMapper&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;^lwz-utils$&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;__dirname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;../../node_modules/lwz-product/lib/index.js&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;要注意的是，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;moduleNameMapper&lt;/code&gt; 配置中的 key 值是作为正则处理的，可以像书写正则字符串一样书写，而且在 key 值中用到的捕获组，在 value 部分可以使用 &lt;em&gt;$1&lt;/em&gt;、&lt;em&gt;$2&lt;/em&gt; 等语法进行引用，这里就不再赘述了，具体可查看 &lt;a href=&quot;https://jestjs.io/docs/configuration#modulenamemapper-objectstring-string--arraystring&quot;&gt;官方文档&lt;/a&gt;，里面也有一些例子。&lt;/p&gt;

&lt;h2 id=&quot;参考&quot;&gt;参考&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://jestjs.io/docs/configuration#modulenamemapper-objectstring-string--arraystring&quot;&gt;moduleNameMapper API文档&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#specifying_a_string_as_a_parameter&quot;&gt;MDN 字符串替换中捕获组的引用&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Tue, 30 Nov 2021 08:23:12 +0000</pubDate>
        <link>/2021/11/30/jest-in-yarn-workspaces.html</link>
        <guid isPermaLink="true">/2021/11/30/jest-in-yarn-workspaces.html</guid>
        
        <category>Tools</category>
        
        
      </item>
    
      <item>
        <title>重置 wsl2 中的 Linux 子系统的账户密码</title>
        <description>&lt;p&gt;重置密码的操作也很简单，经过查询资料，可将步骤大致分为 3 步：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;将 Linux 子系统的默认账户改为 root&lt;/li&gt;
  &lt;li&gt;利用 root 账户重置 Linux 子系统账户的密码&lt;/li&gt;
  &lt;li&gt;将 Linux 子系统的默认账户修改回之前的账户&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;很简单吧，大部分的博文都是这样搞的，但是在我这却第一步都没走通，遇到的问题和解决方法在后面会提及。&lt;/p&gt;

&lt;h2 id=&quot;默认账户改为-root&quot;&gt;默认账户改为 root&lt;/h2&gt;

&lt;p&gt;首先查看自己当前安装了哪些 Linux 子系统，找到忘记了密码的子系统，使用管理员身份打开 cmd 执行（下同）：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; wsl &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt;
适用于 Linux 的 Windows 子系统分发版:
Debian &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;默认&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
Ubuntu
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;下面就以 Debian 为例说明如何将默认账户修改为 root：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; debian config &lt;span class=&quot;nt&quot;&gt;--default-user&lt;/span&gt; root
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这一步我就出现了问题：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;The system cannot find the file C:\Users[USERNAME]\AppData\Local\Microsoft\WindowsApps\debian.exe.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;但是我执行 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;where debian&lt;/code&gt; 得到的结果确是是上面那个路径，我又用 unbuntu 试了一下也是不行，让我很是不能理解，于是我打进敌人内部，到达那个路径下，发现 &lt;em&gt;debian.exe&lt;/em&gt; 文件和 &lt;em&gt;ubuntu.exe&lt;/em&gt; 文件确实都是存在的，但它们的文件大小都是 0：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/posts/tools/wsl-linux-subsystem-exe-not-work.png&quot; alt=&quot;wsl-linux-subsystem-exe-not-work.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;但是我的 wsl2 中的 Linux 子系统确实也没问题，于是想到它不是我的菜，于是我使用 Everything 搜索了一下 “debian.exe”，发现了另一个结果，路径类似于：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;C:\Program Files\WindowsApps\TheDebianProject.DebianGNULinux_1.11.1.0_x64__[HASH]\debian.exe&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;所以它才是执行命令的主体，于是我在 cmd 中定位到此路径下，再次执行上面的命令后结果正常：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;C:&lt;span class=&quot;se&quot;&gt;\P&lt;/span&gt;rogram Files&lt;span class=&quot;se&quot;&gt;\W&lt;/span&gt;indowsApps&lt;span class=&quot;se&quot;&gt;\T&lt;/span&gt;heDebianProject.DebianGNULinux_1.11.1.0_x64__[HASH]
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; debian config &lt;span class=&quot;nt&quot;&gt;--default-user&lt;/span&gt; root
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;此时打开 Debian 子系统，可以发现账户变成了 root。&lt;/p&gt;

&lt;h2 id=&quot;变更普通账户密码&quot;&gt;变更普通账户密码&lt;/h2&gt;

&lt;p&gt;这一步是 Linux 中基础的密码重置操作，注意需要在 Debian 子系统中操作，除此之外没什么幺蛾子：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/posts/tools/wsl-linux-subsystem-reset-passwd.png&quot; alt=&quot;wsl-linux-subsystem-reset-passwd.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;将默认账户从-root-修改为普通账户&quot;&gt;将默认账户从 root 修改为普通账户&lt;/h2&gt;

&lt;p&gt;最后一步也很简单，相当于重复第一步，也是在 cmd 中执行，只不过将账户名改一下即可：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 还是在切换到之前那个文件夹&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;C:&lt;span class=&quot;se&quot;&gt;\P&lt;/span&gt;rogram Files&lt;span class=&quot;se&quot;&gt;\W&lt;/span&gt;indowsApps&lt;span class=&quot;se&quot;&gt;\T&lt;/span&gt;heDebianProject.DebianGNULinux_1.11.1.0_x64__[HASH]
&lt;span class=&quot;c&quot;&gt;# 将 lwz 换成自己的账户名&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; debian config &lt;span class=&quot;nt&quot;&gt;--default-user&lt;/span&gt; lwz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;到此就大功告成了。&lt;/p&gt;

&lt;h2 id=&quot;其他注意事项&quot;&gt;其他注意事项&lt;/h2&gt;

&lt;p&gt;除了第一步可能出现的问题之外，可能还会遇到多个版本的问题，如多个 Ubuntu 版本，如 18.04、20.04版本等，在 cmd 中切换默认账户时，不能使用 &lt;em&gt;ubuntu.exe&lt;/em&gt; 命令，而是要使用 &lt;em&gt;ubuntu1804.exe&lt;/em&gt; 和 &lt;em&gt;ubuntu2004.exe&lt;/em&gt; 等：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; ubuntu1804 config –default-user root
&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; ubuntu2004 config –default-user root
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;参考&quot;&gt;参考&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;https://itsfoss.com/reset-linux-password-wsl/&lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Wed, 20 Oct 2021 08:23:12 +0000</pubDate>
        <link>/2021/10/20/wsl-reset-password.html</link>
        <guid isPermaLink="true">/2021/10/20/wsl-reset-password.html</guid>
        
        <category>Tools</category>
        
        
      </item>
    
      <item>
        <title>老项目使用 dayjs 替代 moment 的注意事项</title>
        <description>
&lt;p&gt;&lt;a href=&quot;https://day.js.org/&quot;&gt;Day.js&lt;/a&gt; 作为 &lt;a href=&quot;https://momentjs.com/&quot;&gt;Moment.js&lt;/a&gt; 的极简替代品，具有体积小、API 类似 、TypeScript 支持良好等优点。最近在优化项目时，发现项目中存在 dayjs 和 moment 混用的情况，决定将 moment 全面替换掉，&lt;strong&gt;但 Day.js 其并不是 Moment.js 的直接替代品&lt;/strong&gt;，在替换过程中发现了一些需要注意的地方。&lt;/p&gt;

&lt;h2 id=&quot;dayjs-的-immutable-vs-momentjs-的-mutable&quot;&gt;Day.js 的 immutable VS Moment.js 的 mutable&lt;/h2&gt;

&lt;p&gt;熟悉函数式编程或 Redux 中 Reducer 的同学，对于 immutable 和 mutable 应该有很深刻的了解，这里仅简单说下区别：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;immutable 会直接操作当前对象，造成当前对象的属性变化&lt;/li&gt;
  &lt;li&gt;mutable 不会直接操作当前对象，而是返回处理后新的对象&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;下面以项目中遇到的 “根据调度周期和首次调度时间需要计算出最近 10 次的调度时间” 这个场景为例，之前使用 Moment.js 的代码（为简单起见，调度周期的单位假定为天）：&lt;/p&gt;

&lt;div class=&quot;language-ts highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;moment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;moment&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getNext10ScheduleTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;firstSchedule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;period&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;timeEntity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;moment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;firstSchedule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;scheduleTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;timeEntity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;valueOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;scheduleText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;timeEntity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;YYYY-MM-DD HH:mm:ss&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;timeEntity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;period&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// A&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;此时便不能简单地直接将 &lt;em&gt;moment()&lt;/em&gt; 的方法调用改为 &lt;em&gt;dayjs()&lt;/em&gt;，因为 A 行利用了 Moment.js 的 mutable 特性，直接对 &lt;em&gt;timeEntity&lt;/em&gt; 进行了操作，以便下次循环时取得新值。如果使用 Day.js 改写，则应为：&lt;/p&gt;

&lt;div class=&quot;language-ts highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dayjs&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;dayjs&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;getNext10ScheduleTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;firstSchedule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;number&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;period&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;number&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;timeEntity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dayjs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;firstSchedule&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;scheduleTime&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;timeEntity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;valueOf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;scheduleText&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;timeEntity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;YYYY-MM-DD HH:mm:ss&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;timeEntity&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;timeEntity&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;period&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;d&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// B&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Day.js 默认是 immutable 的，所以 B 行的 &lt;em&gt;add()&lt;/em&gt; 调用后 &lt;em&gt;timeEntity&lt;/em&gt; 本身没有变化，所以需要将返回值重新赋值。其实对于 Moment.js 来说，&lt;em&gt;add()&lt;/em&gt; 也会返回一个值，但这个值是对 &lt;em&gt;timeEntity&lt;/em&gt; 的直接修改后的结果。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;所有涉及到日期对象的操作修改，如 &lt;em&gt;add()&lt;/em&gt;、&lt;em&gt;subtract()&lt;/em&gt;、&lt;em&gt;year()&lt;/em&gt;、&lt;em&gt;set()&lt;/em&gt;、&lt;em&gt;startOf()&lt;/em&gt;、&lt;em&gt;endOf()&lt;/em&gt; 等操作，都符合上文所述，需要注意，也正是因为 Moment.js 的 mutable 特性，所以才想要使用 Day.js 将其替换。如果你的项目中大量依赖此类逻辑的话，Day.js 通过插件提供了一种&lt;strong&gt;&lt;a href=&quot;https://day.js.org/docs/en/plugin/bad-mutable&quot;&gt;不推荐的方案&lt;/a&gt;&lt;/strong&gt;用以适配此类情况。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;dayjs-的插件系统&quot;&gt;Day.js 的插件系统&lt;/h2&gt;

&lt;p&gt;Day.js 默认情况下包含了常用的大部分 API，但有些不常用的功能是通过插件提供的，一个插件包含了某些功能的实现及声明文件的提供。如 &lt;em&gt;dateOfyear()&lt;/em&gt;、通过提供代表日期的对象构造 dayjs 实例等，这些都需要手动引入：&lt;/p&gt;

&lt;div class=&quot;language-ts highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// dayjs-facade.ts&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dayjs&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;dayjs&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dayOfYear&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;dayjs/plugin/dayOfYear&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;objectSupport&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;dayjs/plugin/objectSupport&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// configure plugin like this&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;dayjs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dayOfYear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;dayjs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;extend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;objectSupport&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;dayjs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以将插件的配置都放置于这一个文件中，使用时从此文件引入即可。&lt;/p&gt;

&lt;p&gt;目前的最新版本（1.10.7）&lt;em&gt;objectSupport&lt;/em&gt; 插件提供的声明文件有些问题，在使用对象构造 dayjs 实例时 ts 的语法检查会提示 &lt;em&gt;没有与此调用匹配的重载&lt;/em&gt;：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/posts/javascript/dayjs-objectSupport-declaration-error.png&quot; alt=&quot;dayjs-objectSupport-declaration-error.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;社区中已有用户提供了 &lt;a href=&quot;https://github.com/iamkun/dayjs/pull/1647&quot;&gt;pr&lt;/a&gt; 解决，不过目前还没合入发布版本，所以可以暂时使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;as any&lt;/code&gt; 将参数类型做一下强制类型转换，或者单独使用 &lt;em&gt;set()&lt;/em&gt; 方法进行分别设置。&lt;/p&gt;

&lt;h2 id=&quot;替换过程中需要必要的测试&quot;&gt;替换过程中需要必要的测试&lt;/h2&gt;

&lt;p&gt;在替换过程中，遇到不能确定会不会引起差异的情形，最好针对自己的情形书写用例进行验证，避免可能因版本或者是某些 API 实现上存在差异的情况。可查看在替换过程中&lt;a href=&quot;https://github.com/liuwenzhuang/algorithm/blob/main/src/sundry/moment-dayjs.test.ts&quot;&gt;我书写的用例&lt;/a&gt;以做参考。当然如果项目本身就有比较健全的单元测试，这一步也可以直接在项目中验证。&lt;/p&gt;
</description>
        <pubDate>Wed, 20 Oct 2021 08:23:12 +0000</pubDate>
        <link>/2021/10/20/differences-between-dayjs-moment.html</link>
        <guid isPermaLink="true">/2021/10/20/differences-between-dayjs-moment.html</guid>
        
        <category>JavaScript</category>
        
        
      </item>
    
      <item>
        <title>工程前后端分离实践</title>
        <description>
&lt;h2 id=&quot;现状&quot;&gt;现状&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;代码处于同一个仓库，前后端版本、分支等由后端确定。&lt;/li&gt;
  &lt;li&gt;前后端代码是独立存放的，前端代码存放于其中一个子目录。
    &lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;bdms
  ├── bdms-webserver/src/main/webapp - 需要分离的前端子目录
  ├── ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;用户访问页面过程：&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/img/posts/tools/目前前后端交互流程.png&quot; alt=&quot;目前前后端交互流程.png&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;目标&quot;&gt;目标&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;前端代码拆分为独立的代码仓库，版本由前端控制&lt;/li&gt;
  &lt;li&gt;前端应用独立部署，使用 Node.js 启动单独的服务&lt;/li&gt;
  &lt;li&gt;前端代码调整，支持用户信息通过接口（校验登录）获取&lt;/li&gt;
  &lt;li&gt;Nginx 配置，API 接口映射到后端服务，其他映射到前端 Node.js 服务&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/img/posts/tools/前后端分离交互流程.png&quot; alt=&quot;前后端分离交互流程.png&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;将前端子目录拆分&quot;&gt;将前端子目录拆分&lt;/h2&gt;

&lt;p&gt;git 本身是存在 filter-branch 命令来做这个工作的，但由于性能等原因，已经不推荐使用了。目前推荐使用 &lt;a href=&quot;https://github.com/newren/git-filter-repo/&quot;&gt;git filter-repo&lt;/a&gt; 工具做这个操作。不过这个工具需要独立安装一下。&lt;/p&gt;

&lt;p&gt;所需依赖：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;git &amp;gt;= 2.24.0&lt;/li&gt;
  &lt;li&gt;python3 &amp;gt;= 3.5&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;python3 的安装和配置这里不再赘述，对于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git filter-repo&lt;/code&gt; 来说有&lt;a href=&quot;https://github.com/newren/git-filter-repo/blob/main/INSTALL.md&quot;&gt;很多种安装方式&lt;/a&gt;，但使用 pip3 安装最方便：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pip3 &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;git-filter-repo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;下面的命令从当前 &lt;em&gt;bdms&lt;/em&gt; 这个 git 仓库中分离出其中的 &lt;em&gt;bdms-webserver/src/main/webapp&lt;/em&gt; 目录，将其放置到 &lt;em&gt;bdms-frontend&lt;/em&gt; 工程中，&lt;em&gt;bdms-frontend&lt;/em&gt; 是个空的 git 仓库。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git filter-repo &lt;span class=&quot;nt&quot;&gt;--source&lt;/span&gt; bdms &lt;span class=&quot;nt&quot;&gt;--target&lt;/span&gt; bdms-frontend &lt;span class=&quot;nt&quot;&gt;--subdirectory-filter&lt;/span&gt; bdms-webserver/src/main/webapp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--source&lt;/code&gt; 指定要操作的 git 仓库目录，这里是指当前路径下的 &lt;em&gt;bdms&lt;/em&gt; 目录&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--target&lt;/code&gt; 指定要分离出去文件的 git 仓库目录，这里是指当前路径下的 &lt;em&gt;bdms-frontend&lt;/em&gt; 目录&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--subdirectory-filter&lt;/code&gt; 指定要分离出的子目录&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果不指定&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--source&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--target&lt;/code&gt;，也可以在要操作的 git 仓库目录下直接操作，但这是一个原地替换的操作。&lt;/p&gt;

&lt;p&gt;拆分后 &lt;em&gt;bdms-frontend&lt;/em&gt; 仓库内的 git 提交历史仅仅保留了 &lt;em&gt;bdms-webserver/src/main/webapp&lt;/em&gt; 目录相关的提交。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;拆分后，能保留 tag 信息，但不能保留分支信息，所以在进行操作前可以把需要的分支先切出来。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;前端代码调整&quot;&gt;前端代码调整&lt;/h2&gt;

&lt;p&gt;之前的方案，后端随着 Serve index.html 进行 Cookie 的设置，前端的入口 js 中肯定能从 Cookie 中拿到用户的信息，相当于是同步的操作。分离之后，需要前端发起&lt;strong&gt;获取用户信息&lt;/strong&gt;的操作（后端会进行登录的验证），需要将路由渲染的主体逻辑放于获取用户信息的操作返回之后，并进行代码的调整：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/posts/tools/前后端分离代码调整方案-异步请求-代码转换.png&quot; alt=&quot;前后端分离代码调整方案-异步请求-代码转换.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;需要进行代码转换的原因在于，之前的代码中，&lt;em&gt;userToken&lt;/em&gt; 是从 COOKIE 中同步解析而来，所以能够直接 export，但通过接口获取用户信息后，不能对 export 的 &lt;em&gt;userToken&lt;/em&gt; 设置值，但可以对一个 object 设置属性。&lt;/p&gt;

&lt;p&gt;转换前的全局常量文件及用法：&lt;/p&gt;

&lt;div class=&quot;language-ts highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 原来的 constant.ts&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;userToken&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;parseCookie&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;cookie&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 使用者：comp.ts&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;userToken&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;constant.ts&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;userToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// &apos;lwz@abc.com&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;转换后：&lt;/p&gt;

&lt;div class=&quot;language-ts highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// constant.ts&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;userToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 会在 用户信息接口返回后被设置&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 使用者：comp.ts&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;constant&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;constant.ts&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;constant&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;userToken&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// &apos;lwz@abc.com&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;关于转换使用到的 babel 插件，可查看 &lt;a href=&quot;https://gist.github.com/liuwenzhuang/730b495aa1e4f9c4e3a122240e4a1e6c&quot;&gt;gitlab&lt;/a&gt;。&lt;/p&gt;

&lt;h2 id=&quot;前端独立的服务&quot;&gt;前端独立的服务&lt;/h2&gt;

&lt;p&gt;每个团队都有自己的部署方案，如果有运维能力，推荐使用 Node.js 服务。可考虑成熟的框架（如 Egg.js）或者自己通过 Koa 启动服务。优点是可以方便的集成配套的生态，如配置 SSR，服务报警等。这部分就不再赘述了。&lt;/p&gt;

&lt;h2 id=&quot;nginx-配置&quot;&gt;Nginx 配置&lt;/h2&gt;

&lt;p&gt;Nginx 转发规则和其他前后端分离的应用大致相同，除了 API 的 url 转发到后端，其他的都应该转发到独立的前端服务：&lt;/p&gt;

&lt;div class=&quot;language-nginx highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kn&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Host&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;X-Forwarded-For&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;X-From-IP&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$remote_addr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$scheme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Upgrade&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$http_upgrade&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Connection&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;upgrade&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;proxy_pass&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http://server-frontend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# 前端服务的 upstream&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;kn&quot;&gt;location&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;~&lt;/span&gt; &lt;span class=&quot;sr&quot;&gt;^/(v1|v2|v3|v4)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Host&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$host&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;X-Forwarded-For&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$proxy_add_x_forwarded_for&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;X-From-IP&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$remote_addr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;proxy_set_header&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;X-Forwarded-Proto&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;$scheme&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;kn&quot;&gt;proxy_pass&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;http://server-backend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;# 后端服务的 upstream&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Wed, 13 Oct 2021 12:23:12 +0000</pubDate>
        <link>/2021/10/13/split-frontend-backend.html</link>
        <guid isPermaLink="true">/2021/10/13/split-frontend-backend.html</guid>
        
        <category>Tools</category>
        
        
      </item>
    
      <item>
        <title>Yarn2+ 在确定版本范围时不会参考版本上的标签</title>
        <description>&lt;h2 id=&quot;问题描述&quot;&gt;问题描述&lt;/h2&gt;

&lt;p&gt;事情的起因是我在安装 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;antlr4-c3@^1.1.16&lt;/code&gt;（依赖 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;antlr4ts@^0.5.0-alpha.3&lt;/code&gt;） 时得到的 antlr4ts 的版本为：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;antlr4ts@0.5.0-dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在 npm 上可以看到 antlr4ts 的版本分布：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/posts/tools/antlr4ts-versions-with-tag.png&quot; alt=&quot;antlr4ts-versions-with-tag.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;当使用 npm 安装 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;antlr4-c3@1.1.16&lt;/code&gt; 包时，下载它的依赖 antlr4ts 的结果是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;antlr4ts@0.5.0-alpha.4&lt;/code&gt; 版本，因为 npm 在进行版本范围确定时会参考版本上的 &lt;strong&gt;latest&lt;/strong&gt; 标签（上图中红圈），具体可查看此 &lt;a href=&quot;https://github.com/npm/feedback/discussions/109&quot;&gt;npm feedback&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;但是当使用 yarn 2+ 时，得到的结果会是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;antlr4ts@0.5.0-dev&lt;/code&gt; 版本，因为按照 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;semver&lt;/code&gt; 的规范，此版本确实符合版本范围的：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/posts/tools/antlr4ts-semver-satisfied.png&quot; alt=&quot;antlr4ts-semver-satisfied.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;而使用 &lt;a href=&quot;https://semver.npmjs.com/&quot;&gt;npm 版本计算工具&lt;/a&gt;进行版本确定工具得到的结论会不同：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/posts/tools/antlr4ts-version-semver-of-npm.png&quot; alt=&quot;antlr4ts-version-semver-of-npm.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;原因是 yarn 2+ 不会像 npm 一样参考版本上的标签进行范围版本确定&lt;/strong&gt;。具体可参照我提的 issue 中 yarn 2+ &lt;a href=&quot;https://github.com/yarnpkg/berry/issues/3345#issuecomment-906310176&quot;&gt;维护者 arcanis 的回复&lt;/a&gt;。&lt;/p&gt;

&lt;h2 id=&quot;方案&quot;&gt;方案&lt;/h2&gt;

&lt;p&gt;一般情况下，此类情况不会有什么问题，但对于 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;antlr4ts&lt;/code&gt; 包来说，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;antlr4ts@0.5.0-dev&lt;/code&gt; 版本和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;antlr4-c3&lt;/code&gt; 依赖的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;antlr4ts@0.5.0-alpha.4&lt;/code&gt; 版本文件结构上有差异，导致不能正常使用。&lt;/p&gt;

&lt;p&gt;虽然在安装依赖时使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-E&lt;/code&gt; 或 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--exact&lt;/code&gt; 标志能够锁定依赖的具体版本，但不能锁定其内层依赖的版本。好在 Yarn（包括 Yarn 1.x） 也考虑到了此种情况，提供了 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;resolutions&lt;/code&gt; 的能力帮助我们确定具体的版本，我们需要在工程根目录下的 package.json 中增加如下配置来进行版本的确定：&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nl&quot;&gt;&quot;resolutions&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;antlr4ts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;0.5.0-alpha.4&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样我们再安装 antlr4-c3 依赖后得到的结果将是：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;antlr4-c3@1.1.16
antlr4ts@0.5.0-alpha.4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;关于 Yarn2+ 的更多配置，可直接到&lt;a href=&quot;https://yarnpkg.com/configuration/manifest&quot;&gt;官网&lt;/a&gt;查询。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;总结&quot;&gt;总结&lt;/h2&gt;

&lt;p&gt;原则总有例外，但作为开发者来说例外条件其实越少越好，一些隐藏的操作经常令人措手不及，对于版本范围的确定上只使用版本号是完全足够的。&lt;/p&gt;
</description>
        <pubDate>Fri, 27 Aug 2021 07:23:12 +0000</pubDate>
        <link>/2021/08/27/yarn2-version-range-not-consider-version-tag.html</link>
        <guid isPermaLink="true">/2021/08/27/yarn2-version-range-not-consider-version-tag.html</guid>
        
        <category>Tools</category>
        
        
      </item>
    
      <item>
        <title>RESTful API 设计指南 - 最佳实践</title>
        <description>&lt;p&gt;可查看 Medium 上的&lt;a href=&quot;https://medium.com/hackernoon/restful-api-designing-guidelines-the-best-practices-60e1d954e7c9&quot; target=&quot;_blank&quot;&gt;原文&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;Facebook、谷歌、Github、Netflix 和其他几家科技巨头都提供了开发者和产品通过 API 消费他们数据的能力，这些公司成为了一个数据平台。&lt;/p&gt;

&lt;p&gt;即使你编写 API 不是为了向其他开发者或产品提供，设计出精美的 API 对你自己的应用也大有裨益。&lt;/p&gt;

&lt;p&gt;长期以来，互联网上都存在着关于设计 API 的最佳实践的辩论，这是最微妙的辩论之一。可能是因为在这方面没有一个官方指引吧。&lt;/p&gt;

&lt;p&gt;API 是一个接口，开发者通过它与数据进行交互。一个设计精美的 API 总是非常容易使用的，开发者能够非常顺畅地进行开发。API 是开发者的 GUI，如果它令人困惑或者功能不足（not verbose），那么开发者就会寻找替代方案或干脆不再使用。开发者体验是衡量 API 质量最重要的指标。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;API 就像舞台上表演的艺术家，它的用户就是观众。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;术语&quot;&gt;术语&lt;/h2&gt;

&lt;p&gt;下面列出的是 REST API 相关的重要术语&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Resource（资源）是客体或对非客体的一种表达，它有一些与之相关的数据，并且可以有一组方法对其进行操作。例如，动物、学校和员工是资源，&lt;em&gt;删除&lt;/em&gt;、&lt;em&gt;添加&lt;/em&gt;、&lt;em&gt;更新&lt;/em&gt;是对这些资源执行的操作。&lt;/li&gt;
  &lt;li&gt;Collections（集合）是 Resource（资源）的集合，例如多个公司（Companies）是公司（Company）资源的集合。&lt;/li&gt;
  &lt;li&gt;URL（Uniform Resource Locator 统一资源定位符）是一个路径，通过它可以定位资源并可以对其执行一些操作。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;api-端点&quot;&gt;API 端点&lt;/h2&gt;

&lt;p&gt;为了更加深入的理解，这里我们编写一些 API，针对的是多个公司，多个员工的场景。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/getAllEmployees&lt;/code&gt; 是一个 API，它将返回员工列表。公司针对员工相关的 API 如下所示：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;/addNewEmployee&lt;/li&gt;
  &lt;li&gt;/updateEmployee&lt;/li&gt;
  &lt;li&gt;/deleteEmployee&lt;/li&gt;
  &lt;li&gt;/deleteAllEmployees&lt;/li&gt;
  &lt;li&gt;/promoteEmployee&lt;/li&gt;
  &lt;li&gt;/promoteAllEmployees&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;并且还会有很多其他 API 端点用于不同的操作。而所有这些 API 都包含了许多冗余操作。因此，随着 API 数量增加时，维护这些 API 端点就会愈发麻烦。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;哪里出了问题呢？&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;URL 应该只包含资源（名词）而不涉及动作或动词。&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/addNewEmployee&lt;/code&gt; 这个 API 端点同时包含了动作 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;addNew&lt;/code&gt; 和资源名称 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Employee&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;那么正确的做法是什么呢？&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/companies&lt;/code&gt; 端点就是一个很好的例子，它不包含任何动作。但随之而来问题是我们要怎样告诉服务器要对 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;companies&lt;/code&gt; 资源执行什么操作呢？是添加、删除还是更新操作呢？&lt;/p&gt;

&lt;p&gt;在这种场景下，不同的 HTTP 方法（GET、POST、DELETE、PUT），也称为动词，就有了用武之地。&lt;/p&gt;

&lt;p&gt;API 端点中的资源应该总是复数，而如果我们想访问资源中的一个实例，我们可以在 URL 中设置 id。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;方法 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET&lt;/code&gt; 路径 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/companies&lt;/code&gt; 应该获取所有公司的列表&lt;/li&gt;
  &lt;li&gt;方法 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET&lt;/code&gt; 路径 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/companies/34&lt;/code&gt; 应该获取 34 号公司的详细信息&lt;/li&gt;
  &lt;li&gt;方法 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DELETE&lt;/code&gt; 路径 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/companies/34&lt;/code&gt; 应删除 34 号公司&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;还有一些复合的场景，如我们需要获取某资源下的资源，例如获取某公司员工，那么一些示例的 API 端点是：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /companies/3/employees&lt;/code&gt; 应该从 3 号公司获取所有员工的列表&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /companies/3/employees/45&lt;/code&gt; 应该获取 3 号公司 45 号员工的详细信息&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DELETE /companies/3/employees/45&lt;/code&gt; 应该删除 3 号公司的 45 号员工&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST /companies&lt;/code&gt; 应该创建一个新公司并返回创建的新公司的详细信息&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;现在这些 API 不是更加精确和一致吗？&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;结论：&lt;/strong&gt;路径应该包含复数形式的资源，HTTP 方法应该和对资源执行的操作相对应。&lt;/p&gt;

&lt;h2 id=&quot;http-方法动词&quot;&gt;HTTP 方法（动词）&lt;/h2&gt;

&lt;p&gt;HTTP 本身就定义了一些方法来表示要对资源执行的操作类型。&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;如果将 URL 看作一句话，那资源就是名词，而 HTTP 方法就是动词。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;重要的 HTTP 方法如下所示：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET&lt;/code&gt; 方法从资源请求数据，不应产生任何副作用。
例： &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/companies/3/employees&lt;/code&gt; 返回 3 号公司所有员工的列表。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST&lt;/code&gt; 方法请求服务器以在数据库中创建资源，大部分的场景是在提交 Web 表单。
例：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST /companies/3/employees&lt;/code&gt; 创建 3 号公司的新员工。
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;POST&lt;/code&gt; 方法是非幂等的，这意味着多个请求将产生不同的效果。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PUT&lt;/code&gt; 方法请求服务器更新资源，如果资源不存在，则创建资源。
例：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PUT /companies/3/employees/john&lt;/code&gt; 将请求服务器对 3 号公司下的 员工 &lt;em&gt;john&lt;/em&gt; 进行更新，如果 员工 &lt;em&gt;john&lt;/em&gt; 不存在则新建。
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PUT&lt;/code&gt; 方法是幂等的，这意味着多个请求将产生相同的效果。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DELETE&lt;/code&gt; 方法请求应该从数据库中删除资源或其实例。
例：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DELETE /companies/3/employees/john&lt;/code&gt; 会请求服务器从 3 号公司的员工集合中删除员工 &lt;em&gt;john&lt;/em&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;还有&lt;a href=&quot;https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods&quot; target=&quot;\_blank&quot;&gt;一些其他的方法&lt;/a&gt;，我们将在另一篇文章中讨论。&lt;/p&gt;

&lt;h2 id=&quot;http-响应状态码&quot;&gt;HTTP 响应状态码&lt;/h2&gt;

&lt;p&gt;当客户端通过 API 向服务器发出请求后，客户端应该得到反馈，请求的结果是失败、通过还是请求错误。HTTP 状态码是一堆标准化的代码，在不同的场景下有不同的解释。服务器应当始终返回正确的状态码。&lt;/p&gt;

&lt;p&gt;下面是 HTTP 响应状态码的重要分类：&lt;/p&gt;

&lt;h3 id=&quot;2xx成功类别&quot;&gt;2xx（成功类别）&lt;/h3&gt;

&lt;p&gt;此类状态码表示请求的操作已被服务器接收并成功处理。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;200 Ok&lt;/strong&gt; 代表 GET、PUT 或 POST 请求 成功的标准 HTTP 响应。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;201 Created&lt;/strong&gt; 每当创建新实例时都应返回此状态码。例如，在使用 POST 方法创建新实例成功时，应始终返回 201 状态码。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;204 No Content&lt;/strong&gt; 表示请求处理成功，但没有返回任何内容。
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DELETE&lt;/code&gt; 请求就是一个很好的例子：API &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DELETE /companies/43/employees/2&lt;/code&gt; 将删除 43 号公司的 2 号员工，而我们不需要这个 API 响应正文中的任何数据，因为我们明确要求系统删除。如果有任何错误，比如如果 2 号员工在数据库中不存在，则响应码不应该是 2xx 类别的，而应该属于 4xx Client 类别。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;3xx重定向类别&quot;&gt;3xx（重定向类别）&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;304 Not Modified&lt;/strong&gt; 表示客户端可以从其缓存中获得响应。因此无需再次传输相同的数据。&lt;/p&gt;

&lt;h3 id=&quot;4xx客户端错误类别&quot;&gt;4xx（客户端错误类别）&lt;/h3&gt;

&lt;p&gt;此类状态码表示客户端发出了错误的请求。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;400 Bad Request&lt;/strong&gt; 表示客户端的请求未被处理，因为服务器无法理解客户端的请求。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;401 Unauthorized&lt;/strong&gt; 表示不允许客户端访问资源，应使用所需的凭据重新请求。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;403 Forbidden&lt;/strong&gt; 表示请求有效，客户端也通过了身份验证，但是客户端因为某些原因不能访问某些页面或资源。例如，服务器有时不允许授权的客户端访问服务器上的目录。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;404 Not Found&lt;/strong&gt; 表示请求的资源现在不可用。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;410 Gone&lt;/strong&gt; 表示请求的资源不可用，且是被有意移动。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;5xx服务器错误类别&quot;&gt;5xx（服务器错误类别）&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;500 Internal Server Error&lt;/strong&gt; 表示请求有效，但服务器发生错误，此时服务器需要提供一些错误信息。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;503 Service Unavailable&lt;/strong&gt; 表示服务器宕机或无法接收和处理请求。大多数情况下可能是服务器正在进行维护。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;字段名称大小写约定&quot;&gt;字段名称大小写约定&lt;/h2&gt;

&lt;p&gt;可以遵循任何大小写约定，但要确保在整个应用中保持一致的规则。如果请求体或响应类型为 &lt;a href=&quot;https://en.wikipedia.org/wiki/JSON&quot;&gt;JSON&lt;/a&gt;，请遵循驼峰命名法以保持一致性。&lt;/p&gt;

&lt;h2 id=&quot;排序过滤搜索和分页&quot;&gt;排序、过滤、搜索和分页&lt;/h2&gt;

&lt;p&gt;这些所有的操作都只是对一个数据集进行的简单查询。不应该有新的 API 来处理这些操作。这里我们可以使用带附加查询参数的 GET 方法 API 来处理这些操作。&lt;/p&gt;

&lt;p&gt;让我们通过几个例子来了解如何实现这些操作。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;排序&lt;/strong&gt; 如果客户端想要获得排序的公司列表，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /companies&lt;/code&gt; 端点应该能够在查询参数中接受多个排序参数。例如，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /companies?sort=rank_asc&lt;/code&gt; 将按升序排列公司。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;过滤&lt;/strong&gt; 为了过滤数据集，我们可以通过查询参数传递各种选项。例如，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /companies?category=banking&amp;amp;location=india&lt;/code&gt; 将从公司列表中过滤出印度的银行公司。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;搜索&lt;/strong&gt; 在公司列表中搜索公司名称时，API 端点应为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /companies?search=Digital Mckinsey&lt;/code&gt;。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;分页&lt;/strong&gt; 当数据集太大时，我们可以将数据集分成更小的块，这有助于提高性能并且更容易处理响应。例如，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GET /companies?page=23&lt;/code&gt; 表示获取在第 23 页的公司列表。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果在 GET 方法中添加许多查询参数导致 URL 太长，服务器可能会以 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;414 URI Too long&lt;/code&gt; 状态码响应，在这种情况下，也可以使用 POST 请求，将请求参数放在请求体中。&lt;/p&gt;

&lt;h2 id=&quot;版本控制&quot;&gt;版本控制&lt;/h2&gt;

&lt;p&gt;当你的 API 被外界使用时，一些重大的 API 改造或升级可能会导致使用你的 API 的现有产品或服务出现问题。&lt;/p&gt;

&lt;p&gt;API &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://api.yourservice.com/v1/companies/34/employees&lt;/code&gt; 是一个很好的例子，它在路径中包含了 API 的版本号。如果有任何重大的突破性更新，我们可以将新的 API 集命名为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v2&lt;/code&gt; 或 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;v1.x.x&lt;/code&gt;。&lt;/p&gt;
</description>
        <pubDate>Thu, 17 Jun 2021 07:23:12 +0000</pubDate>
        <link>/2021/06/17/resetful-api-best-practices.html</link>
        <guid isPermaLink="true">/2021/06/17/resetful-api-best-practices.html</guid>
        
        <category>Tools</category>
        
        
      </item>
    
      <item>
        <title>webpack-dev-server 配置 websocket proxy</title>
        <description>
&lt;p&gt;最近在本地调试时，发现针对 websocket 的 proxy 不生效了，原因是后端开启了登录验证（Cookie 验证），而通过配置 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;onProxyReq&lt;/code&gt; 竟然不能生效，通过调试发现可以设置 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;headers&lt;/code&gt; 重写 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Cookie&lt;/code&gt; 头部，避免被后端登录校验拦截。&lt;/p&gt;

&lt;h2 id=&quot;环境信息&quot;&gt;环境信息&lt;/h2&gt;

&lt;p&gt;本文在 webpack@4 和 webpack-dev-server@3 下测试。&lt;/p&gt;

&lt;h2 id=&quot;具体配置&quot;&gt;具体配置&lt;/h2&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// webpack.config.js&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;module&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;mode&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;development&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// 其他配置...&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;devServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;hot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;proxy&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/websocket&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 换成自己的 websocket 路径 path&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;BACKEND_API&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;ws&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;changeOrigin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;headers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;cookie&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;COOKIE_AUTH_KEY=COOKIE_AUTH_VALUE;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;将上面的 context、target 以及 headers.cookie 中的值设置为自己环境的相应配置即可。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;原理&quot;&gt;原理&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http-proxy&lt;/code&gt; 中在转发请求之前会进行配置的合并工作：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/img/posts/tools/http-proxy-ws-headers.png&quot; alt=&quot;http-proxy-ws-headers.png&quot; /&gt;
&lt;em&gt;http-proxy 配置合并&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;这里是 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http-proxy@1.18.1&lt;/code&gt; 的代码截图，其他版本可能略有差异。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;服务重启造成的-read-econnreset-异常&quot;&gt;服务重启造成的 read ECONNRESET 异常&lt;/h2&gt;

&lt;p&gt;在开发过程中，由于后端服务会经常重新部署重启，发现会出现 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Error: read ECONNRESET&lt;/code&gt; 异常：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;at TCP.onStreamRead &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;internal/stream_base_commons.js:183:27&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  errno: &lt;span class=&quot;s1&quot;&gt;&apos;ECONNRESET&apos;&lt;/span&gt;,
  code: &lt;span class=&quot;s1&quot;&gt;&apos;ECONNRESET&apos;&lt;/span&gt;,
  syscall: &lt;span class=&quot;s1&quot;&gt;&apos;read&apos;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;我的本地开发环境信息：&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nl&quot;&gt;&quot;webpack&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;^4.44.1&quot;&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;webpack-dev-server&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;^3.11.0&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;

&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;v&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;12.6&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;npm&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;6.9&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;此异常会造成 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;webpack-dev-server&lt;/code&gt; 启动的服务挂掉，暂时还没时间查找具体的原因，但因为是未捕获的异常造成的，我们可以在全局处理一下 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;uncaughtException&lt;/code&gt; 事件：&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// webpack.config.js&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;uncaughtException&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 开发时，websocket proxy 会因服务重新部署而报错中断本地 server，这里拦截一下&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;拦截未处理异常:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;更多&quot;&gt;更多&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2018/03/24/nodejs-debug.html&quot; target=&quot;\_blank&quot;&gt;Node.js 调试&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://v4.webpack.js.org/configuration/dev-server/#devserverproxy&quot; target=&quot;\_blank&quot;&gt;webpack@4 proxy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Mon, 31 May 2021 08:23:12 +0000</pubDate>
        <link>/2021/05/31/webpack-proxy-ws.html</link>
        <guid isPermaLink="true">/2021/05/31/webpack-proxy-ws.html</guid>
        
        <category>Tools</category>
        
        
      </item>
    
  </channel>
</rss>
