变更日志 Changelog 的书写方法

变更日志(Changelog)是软件工程中的一种常用手段,用于记录每个软件版本所发生的变更。一方面,可以方便下游及时兼容;另一方面,让协作开发者和用户了解项目进展。变更日志需要打版本号,但并不一定要遵循 Semantic Versioning。

最简单的形式

  1. 推荐使用 Markdown 语法,文件名为 CHANGELOG.md。
  2. 用二级标题标记版本号,建议标注发布日期,以便日后回溯。合并主干分支但尚未发布的,用“未发布”标题,放在开头。
  3. 对于不兼容的变化,用“破坏性变更”标记。
# 变更日志

## 未发布

- 修复 Button

## 2.0.0 - 2021-09-17

- **破坏性变更**: Table 组件重命名为 Grid

## 1.1.0 - 2021-03-21

- Card 组件支持 size (small/large/medium)
- 新增 List 和 Table 组件

## 1.0.0 - 2021-02-08

- 新增 Card 组件
- 新增 Button 组件

增加模块标记

对于模块比较多的库或应用,变更项目会很多,不容易阅读。比较好的方式是在开头标记这个变更属于哪个模块,不属于任何模块的用 Other 标记。比如说你的应用有很多页面,则可以按页面来划分。如果是组件库,则可以按组件来分。如果是单个复杂组件,则可以按属性或功能来分。

## 2.1.0 - 2021-09-17

- Card: 增加 size (small/large/medium) 属性
- List: 新组件
- Button: 修复 loading 样式
- Input: 增加 warning 图标
- Other: 修复 IE 11 兼容性

细分变更类型

实践中,分类越详细,开发者维护变更日志的工作量越大。不分类是完全可行的,只有当你有充足的时间和很高的追求的时候,再考虑。

有很多种方法可以给变更分类。

按特性(Feature)和修复(Fix)分类是最简单可行的,也更符合当下软件工程的模型,容易配合各种项目管理工具。最重要的是,通常没有什么争议。

## 2.1.0 - 2021-09-17

### 特性

- Card: 增加 size (small/large/medium) 属性
- List: 新组件
- Input: 增加 warning 图标

### 修复

- Button: 修复 loading 样式
- Other: 修复 IE 11 兼容性

如果你听说过 Keep a Changelog ,它提供的分类方法要复杂得多。

## 1.0.0 - 2022-02-02

### 新增

- List: 新组件
- Input: 增加 warning 图标

### 更改

- Card: large size 的 padding 从 12px 调整为 16px

### 移除

- Card: align 属性,用 headerAlign 和 footerAlign 替代

### 废弃

- Input: focus 属性,建议用 Ref 

### 修复

- Button: 修复 loading 样式
- Other: 修复 IE 11 兼容性

对大部分项目来说,这样做意义不大,会消耗开发者更多的精力。因此建议变更不做细分,或者按照上面特性+修复的简单分类方法。

为什么不用 Git 历史

大多数项目的 Git 历史 commit message 含义不清,很难去阅读。

为什么不用项目管理系统

JIRA 和 AONE 之类的项目管理系统,或者 NPM 这样的软件包平台,确实能够在一定程度上记录软件发布历史。但实际项目中,会出现不得不从一个平台迁移到另一个平台的情况。这些发布记录有时候并不能随代码迁移到新平台。另外,软件分发给客户或者下游之后,他们并不一定能访问我们内部系统的发布历史。

React 页面加载速度优化(非SSR)

React 应用页面首次加载通常比后端渲染静态页面慢,这对用户体验有着极大的负面影响,尤其是移动端。服务器端渲染(SSR)是非常有效的手段,但实现成本过高,不止要调整技术架构,也会增加服务器负载。那么在不使用 SSR 的前提下,如何优化 React 应用的首屏加载速度呢?

加载过程分析

使用 Firefox 开发者工具网络面板对页面加载过程中的网络流量进行监控,是最常用的分析方法。

注意右侧的进度瀑布图有两根线条,一根红色,一根蓝色:

  • 蓝色是 DOMContentLoaded 事件触发的时间点
  • 红色是 load 事件触发的时间点

但是对于 React 应用而言,以上两个事件并不标志着页面内容已加载完成。React 应用通常会需要请求一些 JSON 接口(比如商品信息),然后再加载 UI 组件(比如商品列表)。实际加载时间,我们可以通过 performance.now() 接口在组件实际挂载(componentDidMount 或 useEffect)时获取:

import React, { useEffect, useState } from 'react';

function App() {
  const [products, setProducts] = useState([]);
  useEffect(() => {
    fetch('api/products').then(res => res.json()).then(setProducts);
  }, []);
  if (products?.length > 0) {
    return <ProductList products={products} />
  } else {
    return <div>Loading...</div>;
  }
}

function ProductList({ products }) {
  useEffect(() => {
    console.log('ProductList loaded in ' + performance.now()/1000 + 's');
  }, []);
  return products.map(({name}) => <div>{name}</div>);
}

本地的测试分析不能反映实际用户的加载速度。建议将加载时长结果上报到服务器,进行统计分析,制作按日/小时的趋势图,以及按加载秒数的分布图。统计结果将有助于判断后续的优化工作的效用有多大。

数据加载优化

首屏展示依赖的数据,后端直接注入到 HTML 中,从而减少网络请求数量。

<script>
  window.user = {{ JSON.stringify(user) }};
  window.productInfo = {{ JSON.stringify(productInfo) }};
  window.translations = {{ JSON.stringify(translations) }};
</script>
<script src="path/to/js"></script>

要小心控制注入 HTML 中的数据数量,过大的 HTML 可能会导致页面加载失败的概率增加。首屏不展示的数据,或者不重要的数据要排除:Select 的数据源,弹窗中用到的数据,通知数据等。翻译数据最好按首屏需要做一次切分,且只透出当前用户语言。

从实践经验来看,这一项措施,对加载速度提升最为明显。

图标加载优化

icon font 是图标加载方式的主流,但加载字体文件会多发出一个请求。且字体请求通常是在 CSS 中触发,会阻塞页面加载。另外,字体文件很难根据首屏内容进行切分,不够灵活。某些大型应用,图标高达上百个,加载数 MB 的图标字体会让体验变得很差。

更好的方案是用 SVG Sprite 直接嵌入 HTML 中,或用 Inline SVG 直接嵌入 JS 中,减少一次网络请求,同时只加载首屏需要的图标。

打包尺寸优化

首先,建议所有使用 Webpack 4 的项目迁移到基于 Webpack 5 或者 Rollup 的构建工具,以使用 ES6 Module 的特性进行 Tree Shaking,去掉未被使用的部分,缩减产物尺寸。

对于 lodash 这种非 esm 包,建议用 lodash-es 替代或者采用分量引用:

// Bad
import _ from 'lodash';

// Good
import get from 'lodash/get';

最后,使用 Webpack Bundle Analyzer 插件分析产物尺寸,识别对产物体积影响较大的包。

  1. 存在多版本的包,考虑升级依赖版本进行优化。
  2. 尺寸过大的包,考虑轻量级替代品,比如 moment -> date-fns/day.js

代码分割

注意:不支持 IE,Edge < 16,Firefox < 60,Chrome < 61 和 Safari < 11

常见的构建工具都支持以下语法进行代码分割:

import React from 'react';
import Header from './Header';
const Description = React.lazy(() => import('./Description'));
const Comments = React.lazy(() => import('./Comments'));
const FeedbackDialog = React.lazy(() => import('./FeedbackDialog'));
const Footer = React.lazy(() => import('./Footer'));

function ProductDetailPage() {
  return <div>
    <Header/>
    <Description/>
    <Comments/>
    <FeedbackDialog/>
    <Footer/>
  </div>
}

对于首屏需要展示的组件,Header,我们用传统的引用方式打包,在页面加载时直接加载其 JS 和 CSS。而对于不会首屏展示的组件,用 ES6 模块动态加载。这样做之后,首屏加载的 CSS 和 JS 尺寸会缩小,从而加快首屏展示。

参考 React 官方文档

但实际项目中,构成项目产物的大部分来自第三方包,比如 React,Moment 和 Ant Design 这样的库,也就是 vendor chunk。如果你的业务代码产物 app chunk 在 gzip 压缩之后不超过 200KB,做代码分割的意义就不大了。

总结

在实际工作中,由于技术架构限制或是资源限制无法使用 SSR,我们还是有很多手段可以去快速优化 React 应用加载速度。

本文没有涉及 HTTP2,缓存策略,CDN 等优化传输速度的方法,因为这些是 DevOps 考虑的范畴,在大的 IT 公司中,都已经做得比较到位了,前端开发者不需要亲历亲为。

GKD 350H 掌机的游戏移植

开源掌机可以理解为是带手柄按键和屏幕的嵌入式开发板,软件开发的方法都是一样的。移植游戏时,我们不止要保证能够编译通过,运行通过,还要让游戏适配掌机的分辨率,以及映射掌机的按键。

我玩过的第一台开源掌机是借的同学的 Gameshell,首发售价 1200 RMB。它很强大,用的是全志的 ARM 芯片,运行完整的 Debian 系统。因此可以直接 SSH 进去,安装需要的开发工具,直接编译和修改游戏即可,非常方便。

第二台开源掌机是我淘的二手 GKD 350H,全新的首发售价 399 RMB。这台掌机性能就弱很多了,用的是君正的 MIPS 芯片,运行特制的精简 Linux 系统。掌机本身的系统无法 SSH,无法安装开发工具链,性能也不支持它从事复杂编译。只能在电脑上用 buildroot 构建好开发工具链,然后交叉编译。

老张的 GKD 350H 和 GKD mini 用的相同的芯片和系统,理论上它们的构建工具和移植过的游戏也是通用的。不过我手头没有 GKD mini,不能保证兼容性。

周哥的 RG 350/351/280 系列,我手头没有设备,以后入手了可能会移植一下。

构建 Buildroot

GKD 350H 官方并没有开源系统,但是这块芯片很常见,所以 GitHub 出现了很多为 GKD 350H 或 GKD mini 定制的 buildroot。我用的是这个:

https://github.com/gameblabla/gkdmini_buildroot

首先你的电脑需要安装至少以下工具:

  • git
  • make
  • gcc
  • bc

我的电脑是 openSUSE Linux 系统,只要用包管理器安装即可。不太了解 Windows 和 macOS 要怎么安装。

然后,我们下载并构建 buildroot (国内同学建议全程开启 VPN,耗时较长,可以晚上睡觉前开始构建):

git clone git@github.com:gameblabla/gkdmini_buildroot.git
cd gkdmini_buildroot
make gkdmini_defconfig
make

构建完成之后,我们打开 output/host 这个目录,它就很像 GNU/Linux 发行版的目录结构。在 output/host/bin 目录下面,我们能够找到交叉编译所需要的工具链,比如 mipsel-linux-gcc。buildroot 推荐的使用方法是把 output/host/bin 添加到 $PATH 环境变量中:

# ~/.profile
export PATH=~/git/gkdmini_buildroot/output/host/bin:$PATH

在我们编译游戏的时候,要把工具链配置成 buildroot 提供的工具链路径。

移植 2048

2048 是当年在程序员之间很流行的小游戏。最初版本是网页游戏(JavaScript),Libretro 项目有一个 C 版本。它足够简单,依赖的库也少,所以我把它作为第一个练手项目。

make HOST=mipsel-linux CC=mipsel-linux-gcc CXX=mipsel-linux-g++ platform=linux

移植 Craft

Craft 是一个简化的 Minecraft 类沙盒游戏。我

前端构建工具测评

2022-04-02 更新:增加测评 JS Compiler 的测试数据

2022-03-21 更新:增加测评 Bundler 和 JS Minifier 的测试数据


前端构建工具已经形成了一个或多个复杂的生态系统。从最基础的 webpack, rollup 和 babel, esbuild, swc 等工具库,衍生出各种各样的插件,封装组合成 crate-react-app, vite, parcel, snowpack 这样通用解决方案,进而发展出 Next.js, ICE.js, UMI.js 这种特异化的行业解决方案。

这个生态中的每个部分,都有其替代品。比如 swc 可以替代 babel, rollup 可以替代 webpack。swc 确实比 babel 快得多,但是插件生态不如 babel 全面,两者互有胜负。项目的实际需求,决定了构建工具的选型。

随着前端工程越来越大,构建性能和热加载性能逐渐下降,大型项目不得不面临两个选择:

  1. 对仓库/应用进行拆分,采用微前端等复杂技术整合多个子应用。这样固然能够减少每次发布构建的时间,但是会增加开发人力开销。由于影响了项目架构,严重依赖人工,无法大批量复用到多个项目。
  2. 提升构建工具的性能,比如用 esbuild 这种原生程序替代 babel 这种 Node 程序,用 Vite 这种 Bundleless 工具替代 webpack-dev-server。相比迁移项目架构,改造构建工具的成本更低,完全可以通过迁移脚本自动化。

这篇文章会介绍和评测当前各种流行的构建工具,从底层库到完整解决方案,帮助大家选择和定制最适合自己的方案。

Bundler

Bundler 是能够通过 import/export 将很多 JavaScript 文件,打包成一个/几个文件的工具。在前端开发中,也经常会 Bundle 其他文件类型,比如 CSS/LESS/SCSS,以及图片和字体。这些部分通常是以 Bundler 插件的形式提供。因此 Bundler 通常是一个平台,可以通过插件来扩展特性。

Webpack 是最流行的 Bundler。Rollup,Parcel 则是后起之秀。以编译器见长的 esbuild 和 swc 也提供了 Bundle 能力,但是插件生态不足。

特性比较

spack 还处在实验阶段,功能缺失非常多,因此不建议在任何实际项目中使用。

esbuild 稍好一些,如果你不需要 CSS Modules,不用代码分割,不需要支持 IE,也不需要输出 UMD,那么 esbuild 是可以满足你的需求的。 对于个人项目来说,这完全是可行的。对于企业项目,esbuild 作为 bundler 显然还不够全面,但它作为编译器和 Rollup 配合使用还是很好用的。

parcel,rollup 和 webpack 是功能最为全面的,能够应对绝大多数前端项目需求。webpack 的插件生态最丰富,兼容性也最好。rollup 和 parcel 则更加轻量化。Parcel 的 UMD 打包格式缺失,不适合用于组件库或微前端构建,但是对普通应用还是够用的。

webpackrollupparcelesbuildspack
CSS Modules✔️✔️✔️✔️
LESS✔️✔️✔️✔️
Sass✔️✔️✔️✔️
代码分割✔️✔️✔️
CJS 输出格式✔️✔️✔️✔️✔️
ESM 输出格式✔️✔️✔️✔️✔️
UMD 输出格式✔️✔️

性能比较

测试打包 antd + lodash + react + react-dom + three.js 的速度。为了控制变量,统一采用 esbuild 作为 minifier(spack 不支持除外)。

详细测试数据见 https://github.com/guoyunhe/benchmark-bundlers

webpackrollupparcelesbuildspack
ThinkPad T4805.347s14.548s6.069s0.249s1.082s
MacBook 16, 20194.229s12.790s5.225s0.227s1.012s

esbuild 是用 Go 语言编写,spack(swcpack)是用 Rust 语言编写。两者都编译成二进制文件运行,速度比其他 JS 编写的 Bundler 快几十倍。从 20s 到 0.2s 已经是一个质的飞跃了。

相比之下,传统的 JS Bundler 就慢得多了。令人意外的是,被很多人认为过时了的 webpack 居然比 parcel 和 rollup 更快。尤其是 rollup 的性能,令人意外地差。我们分析是因为 lodash 和 react 都是 CJS 模块,rollup 需要将其转化成 ESM 才能使用,因此比 webpack 慢了一倍多。

综合评价

目前 webpack 仍然是最全面的 Bundler 选择,支持特性最丰富,生态最为庞大。而且它的性能并不差,在应用打包场景要优于 rollup 和 parcel。

而放眼未来,esbuild 的潜力最大。它的性能无与伦比,且具有成熟的插件接口。它的代码分割能力已经在测试中,前途一片光明。甚至 Vite 也希望在 esbuild 成熟之后,移除 rollup 而直接用 esbuild 打包。

JS Minifier

Production 构建需要对 JS 文件和 CSS 文件进行 Minify 处理,让生成的文件更小,加载更快。JS 的 Minifier 包括 terser,esbuild,swc。衡量它们优劣的主要指标是速度和压缩率。

以下测试用 webpack + terser/esbuild/swc 打包和压缩 antd + lodash + react + react-dom+three.js。详情见 https://github.com/guoyunhe/benchmark-js-minifiers

no-minifyterseresbuildswc
产物尺寸4.7MB1.8MB1.9MB1.9MB
运行时间 ThinkPad T4804.677s17.046s5.615s8.997s
运行时间 MacBook 16, 20193.556s13.834s4.052s6.892s

esbuild 和 swc 在速度上大幅度领先 terser。对于这类高计算量任务,Node.js 的性能比原生程序差得多。同为原生程序,esbuild 比 swc 的性能更好,说明它的设计更优秀。而从压缩率上来看,terser 略好于 esbuild 和 swc 但差距不大。因此综合来看,我认为 esbuild 是最佳的 minifier。

JS/TS Compiler

我们在开发中喜欢使用最新的 ES 语法,但在发布时需要兼容旧的浏览器。JSX 和 Vue 文件还包含了特殊语法,即使最新的浏览器也无法识别和运行。因此越来越多的前端项目需要用 JS Compiler 或者叫 Transpiler 来转换源码。这也是一项非常耗时的任务。目前最广泛使用的编译器是 babel,它功能全面,但性能堪忧。而 esbuild 和 swc 则是以速度见长的面向未来的编译器。TypeScript 有自己的编译器 tsc,支持 TypeScript 的类型系统检查,但是它的效率极差。因此 Babel, esbuild 和 swc 都提供了自己对 TypeScript 转译的支持。

特性比较

从特性上来看,babel 仍然是最全面的。esbuild 和 swc 则注重对最新 ES 标准的支持,因此少了对非标准的 JS 修饰器语法(但是支持 TS 修饰器语法)和 Flow 语言的支持。esbuild 不兼容 ES5 和 IE 浏览器,但某些项目仍然有这方面的需求,开发者需要平衡利弊。

babelesbuildswc
ES Next
ES5, IE11
JS 修饰器语法
Flow 语言
TypeScript 语言
TS 修饰器语法
JSX

性能比较

以下测试用 Webpack + babel-loader/swc-loader/esbuild-loader 转译 antd + three.js 源码到 ES5 语法,不进行 minify。详见

babelesbuildswc
运行时间 ThinkPad T48010.133s0.464s0.549s
运行时间 MacBook 16, 2019

综合评价

如果不需要支持 IE11,那么 esbuild 是最好的选择,它的性能非常好。

如果需要支持 IE11,那么推荐 swc,性能接近 esbuild 且浏览器兼容性更好。

只有当 esbuild 和 swc 都无法支持你的项目需求时(比如用到了 JS 修饰器语法),再考虑 babel。babel 虽然功能最为全面,但性能比 esbuild 和 swc 慢几十倍,已经无法支持大型项目了。

Dev Servers

Webpack Dev Server

Webpack Dev Server 的出现,极大提升了 React 应用开发的效率:修改代码时,组件会自动被替换,无需手动刷新,也不会重新加载页面。然而早期的 Webpack 和 Webpack Dev Server 每次修改文件都会重新编译打包,给人越来越慢,又无能为力的痛苦感受。Webpack 5 和 Webpack Dev Server 4 已经在性能方面有了极大的改善。中小型项目的瓶颈大多不是 Webpack 本身造成,而是上面介绍过的 Compiler 和 Minifier。如果结合 swc 和 esbuild,Webpack Dev Server 仍然很能打。

但是在大型应用场景,Webpack Dev Server 还是有不足的。想象一下,你有一个包含几万行代码的 SPA 应用,且没有做代码分割。Webpack Dev Server 会将所有代码编译打包之后才能启动。这样一来,启动速度就会变得很慢。但实际上,我们每次只开发项目中很小的一部分,余下的页面/弹窗等组件并不需要提前编译加载。

但是这也并不是我们一定要抛弃 Webpack Dev Server 理由。大型应用场景,也极少硬着头皮上 SPA,而是用微前端框架将应用做进一步的切分。开发时 Webpack Dev Server 只需要运行子应用即可。

但是如果我们就是要做巨石 SPA 应用呢?在本地开发场景下,可以利用浏览器原生的 ES Module 能力动态地按需加载模块,而不需要编译打包所有内容。这样是不是就可以解决我们上面遇到的那个问题了呢?理论上行得通,但实际效果不尽如人意。

Snowpack

Snowpack 就是这样一个设计。理论上动态加载本地 JS 文件几乎没有延迟。但是这些模块并不都能直接在浏览器中运行。比如有很多 NPM 依赖是 CJS 而非 ESM 模块,我们写的代码有 JSX 和 TypeScript,更不要说还有 Sass,LESS,CSS Modules 这些要处理。因此动态加载每个文件时,都有一定的等待时间,而且无法并行处理。结果是在冷启动过程奇慢。在中小型项目开发中,不及 Webpack Dev Server 有效率。大型项目。而且,它并不能构建部署生产环境,仍然需要使用 Webpack 或 Rollup 来补足,无疑增加了项目维护成本。

因此在实际项目中,很难看到 Snowpack 的身影。Snowpack 本身的开发也陷入了很长时间的停滞不前。不过,Snowpack 的思路被 Vite 和 Parcel 2 所继承并改进,取得了成功。

Vite

Vite 给出了更好的答案:将依赖模块预先用 esbuild 进行 Bundle 以快速加载,项目模块采用 Unbundle 模式动态编译加载。

但是,Vite 对不同前端框架的支持程度是不同的,实际运行效率也差别很大。它的 Vue 插件用 esbuild 编译,速度极快。而它的 React 插件用的是 Babel 编译,速度一般。

另外我们发现,Vite 的缓存校验机制存在比较大的问题。更新 node_modules 中的依赖之后,Vite 缓存并没有更新。在开发过程中只能禁用缓存,这导致开发效率进一步降低。

Parcel 2

Parcel 2 和 Vite 的思路类似,但是有两个重要改进:

  • 缓存范围更广,效率极高,热启动在 1s 内
  • 采用了 swc 而非 babel 编译,构建和刷新速度更快

这两个优势使得 Parcel 2 在几乎各个性能指标上都领先于其他竞争对手。

性能比较

TODO

TypeScript 枚举 Enum

JavaScript 本身是没有枚举的,Enum 是 TypeScript 带来的特性。

虽然早在 TypeScript 2.4 就已经提供了 Enum 但是并没有被广泛使用。TypeScript 一开始就有 Union Type,且语法更简单。因此 Enum 在 TypeScript 并不是必需的,出现较晚,且语法更加复杂,阻碍了它的流行。

但是,在大型项目中,Enum 有很多不可替代的优势,结合编辑器和IDE的功能,让类型的使用和维护更加轻松。

和 Union Type 比较

在 Enum 诞生之前,我们经常使用 Union Type 来表示枚举类型:

type Role = 'superadmin' | 'admin' | 'editor' | 'contributor';

const myRole: Role = 'editor';

如果改写成 Enum 的话:

enum Role {
  SuperAdmin = 'superadmin',
  Admin = 'admin',
  Editor = 'editor',
  Contributor = 'contributor',
}

const myRole = Role.Editor;

乍一看是不是代码繁琐了很多?那为什么我们还需要用 Enum 呢?Enum 和 Union Type 相比有两个优点:

  1. 可以对每个枚举成员添加注释
  2. 可以快速且精准地修改值和重命名

项目越大,类型也就会越多,相应的术语也就会越多。对每个枚举值成员添加注释,能够帮助新加入的开发者快速熟悉项目。尤其是国内的项目,中文的项目文档和英文代码之间的联系,注释必不可少。

/** 用户角色 */
enum Role {
  /** 超级管理员,可以增删管理员,配置和删除项目 */
  SuperAdmin = 'superadmin',
  /** 管理员,可以管理除了管理员外的成员角色 */
  Admin = 'admin',
  /** 编辑,可以直接编辑内容 */
  Editor = 'editor',
  /** 贡献者,可以参与讨论和提交补丁 */
  Contributor = 'contributor',
}

const myRole = Role.Editor;

当我们需要修改枚举值时,比如将 superadmin 改为 owner,Enum 就会非常简单。只需要将枚举值更改,而不需要去更改其他引用的地方。然而对于 Union Type,我们要小心翼翼地查找和替换每个出现的地方。加入我们想把 SuperAdmin 替换成 Owner,也只需要用 IDE 和编辑器提供的重命名/重构功能,快速而精确地重命名。这种维护的便利,也是 Enum 最大的魅力。

墓地

在大山的深处,有一个与外界少有往来的村庄,村民们过着自给自足的生活。一天,落难的传教士们在逃亡时误入此地。传教士们不擅长劳作,却会讲故事。他们编造了一个谎言,称村民们若不捐银钱求一块被祝福过的墓地,便会在死后受尽无数的折磨,灵魂不得安宁。村民们自此更加拼命劳作,开垦更多的土地,种植更多的粮食。即使如此,也少有人能捐得起一块墓地。久而久之,越来越多的人因辛劳而早亡。墓地越来越满,价格也水涨船高。人们开始互相抢夺和厮杀,而传教士们满载着钱财和粮食离开了。

单元测试:从 Jest 到 Karma+Mocha+Chai

我所参与过的 Web 项目大部分都使用 React 框架。Jest 是我们曾经最主要使用的单元测试 (Unit Test) 框架。Jest 配置足够简单,功能也足够丰富。但是随着前端开发逐渐进入深水区,Jest 的短板开始逐渐显现。我在经过探索和尝试后,迁移到了 Karma+Mocha+Chai 的解决方案。在此分享这两种技术方案的区别和取舍,希望对遇到同样问题的同学有所帮助。

继续阅读 →

前端必学&勿学清单(2022更新)

前端技术发展很快,有些技术如常青树,有些则已日渐式微。不断涌现的新技术,有的是真创新,有的是换汤不换药。本文希望帮助大家避免在没有学习价值的技术上浪费事件。

需要注意的是,仅凭个人经历去判断一项技术是不是不流行,是很片面的。很多人在整个职业生涯中都没有遇到过 Angular 项目,但这并不代表 Angular 没有人用。有人觉得 Vue 小众,是玩具,但是其生态仍然在蓬勃发展。本文尽量从技术架构和统计数据来分析,而非个人经验。

更新于 2022 年二月。

继续阅读 →

编程英语基础

编程是一门语言艺术。要写出赏心悦目的代码,最重要的便是恰当的命名变量,函数,类和包等。每种编程语言都自己独特的语法和代码风格。比如 C 语言常用 snake_case 命名,而 Java 语言用 camelCase 命名。这篇文章将不去探讨具体语言的特有规范,而是去介绍通用的命名的英语语法,比如变量类型与词性的关系,词组和短语的顺序,省略的用法,近义词的辨析等。示例以 Java/TypeScript 为主。正确示例标记为✔️,错误示例标记为❌。

本文有待完善,欢迎读者意见。

继续阅读 →