如何比较版本号

前端代码需要运行在客户的浏览器中,但是有些版本的浏览器不支持某些特性,所以我们需要比较浏览器的版本号,然后根据版本号来决定是否使用某些特性。这里就涉及到版本号的比较问题。

# 版本号定义

版本号一般是由三个数字组成,分别是主版本号、次版本号和修订版本号。比如 1.2.3,其中 1 是主版本号,2 是次版本号,3 是修订版本号。版本号的比较是从左到右依次比较,如果左边的数字相等,再比较右边的数字。但是版本号有些只有两个数字,比如 1.2,这时候我们可以认为修订版本号是 0,所以 1.2 等价于 1.2.0

# 两个版本号比较

这里我们需要把版本号转换成数组,然后比较两个数组的大小。如果两个数组的长度不一样,我们可以认为长度短的数组后面都是 0。

const compareVersions = (v1: number[], v2: number[]): number => {
  for (let i = 0; i < Math.max(v1.length, v2.length); i++) {
    const num1 = v1[i] || 0;
    const num2 = v2[i] || 0;
    if (num1 > num2) return 1;
    if (num1 < num2) return -1;
  }
  return 0;
};
1
2
3
4
5
6
7
8
9

如果我们需要判断版本号是否在某个范围内,我们可以这样写:

const isVersionInRange = (
  version: number[],
  min: number[],
  max: number[]
): boolean => {
  return (
    compareVersions(version, min) >= 0 && compareVersions(version, max) <= 0
  );
};
1
2
3
4
5
6
7
8
9

# 总结

版本号比较虽然看起来简单,但是在实现过程中还是需要考虑各种情况,例如版本号的长度不一样,版本号的范围判断等。

编译 zbar 到 WebAssembly

工作上需要前端实现扫描二维码的功能,这部分属于图像识别的工作,图像识别相关的库基本都是 C 语言库,所以需要将这些库编译到 WebAssembly,然后在前端调用。

# 胶水函数

我们需要将 C 语言的函数暴露出来,这样前端才能调用这些函数,这部分代码称为胶水函数。下面列举了一个简单的例子。

#include <emscripten.h>
#include <zbar.h>
#include <stdio.h>
#include <stdlib.h>

#define EXPORT EMSCRIPTEN_KEEPALIVE
typedef int int32_t;
typedef unsigned int uint32_t;

EXPORT zbar_image_scanner_t* ImageScanner_create() {
  return zbar_image_scanner_create();
}
1
2
3
4
5
6
7
8
9
10
11
12

# 编写 Makefile

ZBAR_VERSION = 0.23.90
ZBAR_SRC = zbar-$(ZBAR_VERSION)

SRC = src
BUILD = build
DIST = dist

EM_VERSION = 3.1.44

# See https://emscripten.org/docs/tools_reference/emcc.html
EMCC = emcc
EMMAKE = emmake
EMCONFIG = emconfigure

ZBAR_DEPS = $(ZBAR_SRC)/make.done
ZBAR_OBJS = $(ZBAR_SRC)/zbar/*.o $(ZBAR_SRC)/zbar/*/*.o
ZBAR_INC = -I $(ZBAR_SRC)/include/ -I $(ZBAR_SRC)/

# See https://github.com/emscripten-core/emscripten/blob/main/src/settings.js
EMCC_FLAGS = -Oz -Wall -Werror -s ALLOW_MEMORY_GROWTH=1 \
	-s EXPORTED_FUNCTIONS="['_malloc','_free']" \
	-s MODULARIZE=1 -s EXPORT_NAME=zbarWasm

.PHONY: all build clean-build clean

all: build

build: $(BUILD)/zbar.js $(BUILD)/zbar.wasm

clean-build:
	-rm -rf $(DIST) $(BUILD)

clean: clean-build
	-rm $(ZBAR_SRC).tar.gz
	-rm -rf $(ZBAR_SRC)

$(BUILD)/zbar.wasm $(BUILD)/zbar.js: $(ZBAR_DEPS) $(SRC)/export.c
	mkdir -p $(BUILD)/
	$(EMCC) $(EMCC_FLAGS) -o $(BUILD)/zbar.js -sEXPORT_ES6 $(SRC)/export.c $(ZBAR_INC) $(ZBAR_OBJS)

$(ZBAR_DEPS): $(ZBAR_SRC)/Makefile
	cd $(ZBAR_SRC) && $(EMMAKE) make CFLAGS=-Os CXXFLAGS=-Os \
		DEFS="-DZNO_MESSAGES -DHAVE_CONFIG_H"
	touch -m $(ZBAR_DEPS)

$(ZBAR_SRC)/Makefile: $(ZBAR_SRC)/configure
	cd $(ZBAR_SRC) && $(EMCONFIG) ./configure --without-x --without-xshm \
		--without-xv --without-jpeg --without-libiconv-prefix \
		--without-imagemagick --without-npapi --without-gtk \
		--without-python --without-qt --without-xshm --disable-video \
		--disable-pthread --disable-assert --host=x86_64-linux-gnu

$(ZBAR_SRC)/configure: $(ZBAR_SRC).tar.gz
	tar zxvf $(ZBAR_SRC).tar.gz
	touch -m $(ZBAR_SRC)/configure

$(ZBAR_SRC).tar.gz:
	curl -L -o $(ZBAR_SRC).tar.gz https://linuxtv.org/downloads/zbar/zbar-$(ZBAR_VERSION).tar.gz
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

Makefile 中定义了编译 zbar 的流程,我们首先使用 curl 下载 zbar 的源码,然后解压,然后配置编译参数,最后编译生成 zbar 的调用 JS 和 WASM 文件。注意在配置参数中我们禁用了一些功能,因为这些功能在 WebAssembly 中不需要,并且在指定 host 的时候需要根据运行环境设置正确的参数,如果是运行在 Github Actions 中,需要设置为 x86_64-linux-gnu。如果是 M1 芯片的 Mac,需要设置为 arm

# 函数封装

虽然我们从 zbar 暴露出来关键的图像处理相关的函数,但是用起来还不是那么方便,所以我们需要写一点 Typescript 的代码来封装这些函数。然后使用 rollup 打包成 JS 文件,并生成对应的 TS 类型文件。这部分实现细节就不赘述了。

# 总结

编译 zbar 到 WebAssembly 的过程并不复杂,但是需要注意一些细节,比如编译参数的设置,胶水函数的编写等。这部分工作需要一定的 C 语言基础,如果没有的话,可以参考一些现成的例子,然后根据自己的需求进行修改。我也是参考网上的例子,然后根据自己的需求进行修改的。

# 参考

undecaf/zbar-wasm (opens new window)

acgotaku/zbar-wasm (opens new window)

前端的缓存策略

前端资源也需要配置缓存策略,以提高网站的访问速度。由于网站的流量增大,我们需要合适的缓存策略才能让 CDN 发挥最大效果保证网站的访问速度。

# Cache-Control

Cache-Control 用来设置资源的缓存策略,常见的的值有:no-store 、no-cache、public、private、max-age、immutable。

# no-store

不缓存资源,每次都需要重新请求。

# no-cache

需要先验证资源是否过期,如果没有过期,可以使用缓存。

# public

资源可以被所有的用户缓存,包括 CDN。

# private

资源只能被用户缓存,不能被 CDN 缓存。

# max-age

设置资源的最大缓存时间,单位是秒。

# immutable

资源不会改变,可以永久缓存。

# 前端网站的缓存策略

现在的前端网站一般都是 SPA 或者 PWA,index.html 是一个入口文件,其他的资源都是通过 index.html 加载的。所以我们可以设置 index.html 的缓存策略为 no-store,保证每次都是最新的资源。

其他的都是静态资源,并且文件名包含 hash,所以我们可以设置这些资源的缓存策略为 immutable,保证这些资源不会改变,可以永久缓存。

Cache-Control: no-store

Cache-Control: public, max-age=604800, immutable
1
2
3

# 总结

index.html 文件是网站入口,这个文件不能缓存,每次都需要重新请求,否则不能及时更新网站。不过这个文件一般也不大,所以不会影响网站的访问速度。其他的静态资源文件名包含 hash,所以这些资源不会改变,可以永久缓存,这样可以提高网站的访问速度。

# 参考

MDN Cache-Control (opens new window)

Prevent unnecessary network requests with the HTTP Cache (opens new window)