跳到主要内容

· 阅读需 10 分钟
熊滔

Git 对象,四种类型的对象:

  • blob 对象
  • tree 对象
  • commit 对象
  • tag 对象

blob 对象

所有的文件都会被存储为一个 blob 对象,需要注意的是,blob 对象只存储文件的内容,对于文件的其它信息,例如文件名,修改时间,权限等等信息均不存储(会被存储到 tree 对象中),因此,如果两个文件拥有相同的内容,那么只会有一个 blob 文件。

新建一个仓库,然后添加两个文件,拥有相同的内容:

# 创建目录,并初始化
xt in ~ λ mkdir git-demo
xt in ~ λ cd git-demo
xt in ~/git-demo λ git init
Initialized empty Git repository in /home/xt/git-demo/.git/

# 创建两个文件 a.txt b.txt 含有相同的内容
xt in ~/git-demo on master λ echo "Hello World" > a.txt
xt in ~/git-demo on master λ echo "Hello World" > b.txt
# 查看 Git 对象,此时没有 Git 对象
xt in ~/git-demo on master λ tree -a .git/objects
.git/objects
├── info
└── pack

将两个文件添加到暂存区,然后查看 Git 对象的数量

xt in ~/git-demo on master λ git add .
xt in ~/git-demo on master λ tree -a .git/objects
.git/objects
├── 55
   └── 7db03de997c86a4a028e1ebd3a1ceb225be238
├── info
└── pack

3 directories, 1 file

此时会发现产生了一个 55 的文件夹,包含一个 7db03de997c86a4a028e1ebd3a1ceb225be238 的文件,其实二者应该合为一体看 557db03de997c86a4a028e1ebd3a1ceb225be238,这串字符串就是这个 blob 对象的 id,我们统称为 oid(Object ID)。这个 oid 是使用算法生成的,它可以保证不同的内容有不同的 oid,相同的内容有着相同的 oid

可以通过 git hash-object 命令查看内容相对应的 oid

xt in ~/git-demo on master λ echo "Hello World" | git hash-object --stdin
557db03de997c86a4a028e1ebd3a1ceb225be238

为了防止一个文件夹下的文件更多,以 oid 的前两位划分文件夹,将对象划分到 00-ff 共 256 个文件夹中。

如果直接查看 blob 对象的内容,会发现不是以文本形式存储的

xt in ~/git-demo on master λ cat .git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238
xK��OR04b�H����/�I�A�I%

这是因为 Git 使用了 zlib 对文本内容和对象的头信息(header) 进行了压缩, 为了真实还原我们存储的文本内容, 我们可以使用 git cat-file 子命令来查看

# -p 查看文件内容,并以美观的方式对文件内容进行输出
xt in ~/git-demo on master λ git cat-file -p 557db03de997c86a4a028e1ebd3a1ceb225be238
Hello World
# -t 查看文件类型
xt in ~/git-demo on master λ git cat-file -t 557db03de997c86a4a028e1ebd3a1ceb225be238
blob

tree 对象

如果文件对应到 blob 对象,那么文件夹对应到的就是 tree 对象,文件夹中包含的文件信息(文件名、修改时间等)均存储在 tree 对象中。而我们的工作区就是一个 tree 对象,我们称为 root-treetree 对象中可以包含 tree 对象和 blob 对象。

当我们执行 git commit 的时候,Git 会生成一个 tree 对象来存储当前工作区的快照,这个 tree 对象就是 root-tree,可以根据 root-tree 推演出该仓库的所有内容

# 创建一个文件夹 src,并新增一个文件 hello.txt
xt in ~/git-demo on master λ mkdir src
xt in ~/git-demo on master λ echo "Hello" > src/hello.txt
xt in ~/git-demo on master λ tree
.
├── a.txt
├── b.txt
└── src
└── hello.txt

1 directory, 3 files

# 添加到仓库中
xt in ~/git-demo on master λ git add .
# 出现了一个新的 blob 对象,对应 hello.txt 的内容
xt in ~/git-demo on master λ tree -a .git/objects
.git/objects
├── 55
   └── 7db03de997c86a4a028e1ebd3a1ceb225be238
├── e9
   └── 65047ad7c57865823c7d992b1d046ea66edf78
├── info
└── pack

4 directories, 2 files

进行一次提交

# 进行一次提交
xt in ~/git-demo on master λ git commit -am "first commit"
[master (root-commit) 94f8403] first commit
3 files changed, 3 insertions(+)
create mode 100644 a.txt
create mode 100644 b.txt
create mode 100644 src/hello.txt
# 查看对象
xt in ~/git-demo on master λ tree -a .git/objects
.git/objects
├── 1f
   └── 8946efd983ef493e28de287f5af69f862a6728
├── 55
   └── 7db03de997c86a4a028e1ebd3a1ceb225be238
├── 8c
   └── 3c7fbcd903744b20fd7567a1fcefa99133b5bc
├── 94
   └── f840300469b623c45d1c92ae1edbc2c39c466d
├── e9
   └── 65047ad7c57865823c7d992b1d046ea66edf78
├── info
└── pack

7 directories, 5 files

可以看到新增了三个对象,分别是一个 commit 对象和两个 tree 对象

# 查看 commit 对象信息,包含 root-tree 对象的 oid
xt in ~/git-demo on master λ git cat-file -p master
tree 1f8946efd983ef493e28de287f5af69f862a6728
author LastKnightCoder <lastknightcoder@gmail.com> 1677897217 +0800
committer LastKnightCoder <lastknightcoder@gmail.com> 1677897217 +0800

first commit

# 查看 root-tree 对象的内容
xt in ~/git-demo on master λ git cat-file -p 1f8946efd983ef493e28de287f5af69f862a6728
100644 blob 557db03de997c86a4a028e1ebd3a1ceb225be238 a.txt
100644 blob 557db03de997c86a4a028e1ebd3a1ceb225be238 b.txt
040000 tree 8c3c7fbcd903744b20fd7567a1fcefa99133b5bc src
# 查看 src 对应 tree 对象的内容
xt in ~/git-demo on master λ git cat-file -p 8c3c7fbcd903744b20fd7567a1fcefa99133b5bc
100644 blob e965047ad7c57865823c7d992b1d046ea66edf78 hello.txt

commit 对象

tree 记录了仓库的内容快照,但是还缺少一些信息,比如提交者,提交信息,提交时间,关联快照。这就需要 commit 对象

xt in ~/git-demo on master λ git cat-file -p master
tree 1f8946efd983ef493e28de287f5af69f862a6728
author LastKnightCoder <lastknightcoder@gmail.com> 1677897217 +0800
committer LastKnightCoder <lastknightcoder@gmail.com> 1677897217 +0800

first commit
xt in ~/git-demo on master λ echo "# Readme" > README.md
xt in ~/git-demo on master λ git add .
xt in ~/git-demo on master λ git commit -am "second commit"
[master 90643a9] second commit
1 file changed, 1 insertion(+)
create mode 100644 README.md
xt in ~/git-demo on master λ tree -a .git/objects
.git/objects
├── 1f
&nbsp;&nbsp; └── 8946efd983ef493e28de287f5af69f862a6728
├── 55
&nbsp;&nbsp; └── 7db03de997c86a4a028e1ebd3a1ceb225be238
├── 68
&nbsp;&nbsp; └── 181b61e87b8766a6be442bf8ea52629308cbbf
├── 8c
&nbsp;&nbsp; └── 3c7fbcd903744b20fd7567a1fcefa99133b5bc
├── 90
&nbsp;&nbsp; └── 643a9ce0571aa37d5603cf861ae223796b84c8
├── 94
&nbsp;&nbsp; └── f840300469b623c45d1c92ae1edbc2c39c466d
├── e9
&nbsp;&nbsp; └── 65047ad7c57865823c7d992b1d046ea66edf78
├── f3
&nbsp;&nbsp; └── 954314c1026028e77ea3a765aadefa67b45195
├── info
└── pack

10 directories, 8 files
xt in ~/git-demo on master λ git cat-file -p master
tree 68181b61e87b8766a6be442bf8ea52629308cbbf
parent 94f840300469b623c45d1c92ae1edbc2c39c466d
author LastKnightCoder <lastknightcoder@gmail.com> 1677902228 +0800
committer LastKnightCoder <lastknightcoder@gmail.com> 1677902228 +0800

second commit

第二次提交发现新增了三个 Git 对象,分别是一个 commit 对象,一个 root-tree 对象,还有一个 blob 对象对应新建的 README.md 文件。从第二个 commit 对象中,我们可以看到提交的信息,包括作者,提交者,提交时间和提交信息,还能看到关联的提交 parent,指向了前一个 commit 对象。

如果用一张图解释当前的 Git 对象的关系,应当如下所示

Git 是基于快照的版本管理,而不是基于增量,这就意味着如果你修改了文件,然后进行了提交,那么会生成一个新的 blob 对象,相应的,包含该修改文件的文件夹对应的 tree 对象也会重新生成。这就会带来一个问题,如果频繁的进行 commit 的话,就会导致 blob 对象的数目急剧增大,虽然频繁 commit 是个好习惯,但是希望在进行 push 或合并之前,合并一下多个 commit,减少 commit 个数。

tag

tag 通常用来记录被认为很重要的提交, 例如软件的发行版本。我们可以创建一个不带有任何额外信息的 tag,Git 称这种类型的标签为轻量级 tag

xt in ~/git-demo on master λ git tag v1.0
xt in ~/git-demo on master λ git cat-file -t v1.0
commit

轻量级 tagcommit 对象无异,它不会保存额外的信息,就是一个 commit 对象。

另一种标签称为附注标签,可以添加额外的信息,比如标签作者,邮箱等等,它是一个 tag 对象

xt in ~/git-demo on master λ git tag -a v2.0 -m "v2.0"
xt in ~/git-demo on master λ git cat-file -t v2.0
tag
xt in ~/git-demo on master λ git cat-file -p v2.0
object b9e8d5d9952aea0baac5c19f4ebad48ba623b5f4
type commit
tag v2.0
tagger LastKnightCoder <lastknightcoder@gmail.com> 1677910987 +0800

v2.0

tag 对象中除了保存了指向的 commit 对象的 oid,还保存了标签的一些信息。

参考

· 阅读需 11 分钟
熊滔

Babel 可以帮助我们把最新的 JS 语法转译为较低版本的 JS 代码,使得我们可以同时兼顾开发效率和兼容性。Babel 将代码转译为低级版本代码时包含两部分:

  1. 语法,例如箭头函数,结构语法
  2. API,例如 ES7 提出了 includes 方法,为了使得在低版本的浏览器中也可以使用该方法,我们需要 polyfill

a polyfill is used to make modern native functions work in old browsers.

本片文章是介绍使用 Babel 进行 polyfill 的各种姿势。

信息

实际上 Babel 只是一个编译器,也就是说它只能完成语法的转换,对于 polyfill 需要借助第三方库,有很多的库可以提供 polyfill,比较有名的有 core-jses-shimpolyfill.io,Babel 内置使用 core-js 进行 polyfill,使得我们只需要进行一些配置就可以接入 core-js,但同时完全可以使用其它的库进行 polyfill,只不过接入过程没有 core-js 这么容易。

preset-env与 useBuiltIns: 'entry'

配置文件:

{
presets: [
[
"@babel/preset-env",
{
targets: "chrome 30",
useBuiltIns: 'entry',
corejs: 3,
debug: true,
}
]
]
};

源码:

import 'core-js/stable';
const adder = (num1, num2) => num1 + num2;

const p = Promise.resolve();
class Person {

}

const pp = new Person();

编译后的代码:

"use strict";

function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
require("core-js/modules/es.symbol.js");
require("core-js/modules/es.symbol.description.js");
require("core-js/modules/es.symbol.async-iterator.js");
require("core-js/modules/es.symbol.has-instance.js");
require("core-js/modules/es.symbol.is-concat-spreadable.js");
require("core-js/modules/es.symbol.iterator.js");
require("core-js/modules/es.symbol.match.js");
require("core-js/modules/es.symbol.replace.js");
require("core-js/modules/es.symbol.search.js");
require("core-js/modules/es.symbol.species.js");
require("core-js/modules/es.symbol.split.js");
require("core-js/modules/es.symbol.to-primitive.js");
require("core-js/modules/es.symbol.to-string-tag.js");
require("core-js/modules/es.symbol.unscopables.js");
require("core-js/modules/es.array.concat.js");
require("core-js/modules/es.array.copy-within.js");
require("core-js/modules/es.array.fill.js");
require("core-js/modules/es.array.filter.js");
require("core-js/modules/es.array.find.js");
require("core-js/modules/es.array.find-index.js");
require("core-js/modules/es.array.flat.js");
require("core-js/modules/es.array.flat-map.js");
require("core-js/modules/es.array.from.js");
require("core-js/modules/es.array.includes.js");
require("core-js/modules/es.array.index-of.js");
require("core-js/modules/es.array.iterator.js");
require("core-js/modules/es.array.last-index-of.js");
require("core-js/modules/es.array.map.js");
require("core-js/modules/es.array.of.js");
require("core-js/modules/es.array.reduce.js");
require("core-js/modules/es.array.reduce-right.js");
require("core-js/modules/es.array.slice.js");
require("core-js/modules/es.array.sort.js");
require("core-js/modules/es.array.species.js");
require("core-js/modules/es.array.splice.js");
require("core-js/modules/es.array.unscopables.flat.js");
require("core-js/modules/es.array.unscopables.flat-map.js");
require("core-js/modules/es.array-buffer.is-view.js");
require("core-js/modules/es.array-buffer.slice.js");
require("core-js/modules/es.date.to-primitive.js");
require("core-js/modules/es.function.has-instance.js");
require("core-js/modules/es.json.to-string-tag.js");
require("core-js/modules/es.map.js");
require("core-js/modules/es.math.acosh.js");
require("core-js/modules/es.math.asinh.js");
require("core-js/modules/es.math.atanh.js");
require("core-js/modules/es.math.cbrt.js");
require("core-js/modules/es.math.clz32.js");
require("core-js/modules/es.math.cosh.js");
require("core-js/modules/es.math.expm1.js");
require("core-js/modules/es.math.fround.js");
require("core-js/modules/es.math.hypot.js");
require("core-js/modules/es.math.log10.js");
require("core-js/modules/es.math.log1p.js");
require("core-js/modules/es.math.log2.js");
require("core-js/modules/es.math.sign.js");
require("core-js/modules/es.math.sinh.js");
require("core-js/modules/es.math.tanh.js");
require("core-js/modules/es.math.to-string-tag.js");
require("core-js/modules/es.math.trunc.js");
require("core-js/modules/es.number.constructor.js");
require("core-js/modules/es.number.epsilon.js");
require("core-js/modules/es.number.is-integer.js");
require("core-js/modules/es.number.is-safe-integer.js");
require("core-js/modules/es.number.max-safe-integer.js");
require("core-js/modules/es.number.min-safe-integer.js");
require("core-js/modules/es.number.parse-float.js");
require("core-js/modules/es.number.parse-int.js");
require("core-js/modules/es.object.assign.js");
require("core-js/modules/es.object.define-getter.js");
require("core-js/modules/es.object.define-properties.js");
require("core-js/modules/es.object.define-property.js");
require("core-js/modules/es.object.define-setter.js");
require("core-js/modules/es.object.entries.js");
require("core-js/modules/es.object.freeze.js");
require("core-js/modules/es.object.from-entries.js");
require("core-js/modules/es.object.get-own-property-descriptor.js");
require("core-js/modules/es.object.get-own-property-descriptors.js");
require("core-js/modules/es.object.get-own-property-names.js");
require("core-js/modules/es.object.get-prototype-of.js");
require("core-js/modules/es.object.is-extensible.js");
require("core-js/modules/es.object.is-frozen.js");
require("core-js/modules/es.object.is-sealed.js");
require("core-js/modules/es.object.keys.js");
require("core-js/modules/es.object.lookup-getter.js");
require("core-js/modules/es.object.lookup-setter.js");
require("core-js/modules/es.object.prevent-extensions.js");
require("core-js/modules/es.object.seal.js");
require("core-js/modules/es.object.set-prototype-of.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.object.values.js");
require("core-js/modules/es.parse-float.js");
require("core-js/modules/es.parse-int.js");
require("core-js/modules/es.promise.js");
require("core-js/modules/es.promise.finally.js");
require("core-js/modules/es.reflect.apply.js");
require("core-js/modules/es.reflect.construct.js");
require("core-js/modules/es.reflect.define-property.js");
require("core-js/modules/es.reflect.delete-property.js");
require("core-js/modules/es.reflect.get.js");
require("core-js/modules/es.reflect.get-own-property-descriptor.js");
require("core-js/modules/es.reflect.get-prototype-of.js");
require("core-js/modules/es.reflect.has.js");
require("core-js/modules/es.reflect.is-extensible.js");
require("core-js/modules/es.reflect.own-keys.js");
require("core-js/modules/es.reflect.prevent-extensions.js");
require("core-js/modules/es.reflect.set.js");
require("core-js/modules/es.reflect.set-prototype-of.js");
require("core-js/modules/es.regexp.constructor.js");
require("core-js/modules/es.regexp.exec.js");
require("core-js/modules/es.regexp.flags.js");
require("core-js/modules/es.regexp.to-string.js");
require("core-js/modules/es.set.js");
require("core-js/modules/es.string.code-point-at.js");
require("core-js/modules/es.string.ends-with.js");
require("core-js/modules/es.string.from-code-point.js");
require("core-js/modules/es.string.includes.js");
require("core-js/modules/es.string.iterator.js");
require("core-js/modules/es.string.match.js");
require("core-js/modules/es.string.pad-end.js");
require("core-js/modules/es.string.pad-start.js");
require("core-js/modules/es.string.raw.js");
require("core-js/modules/es.string.repeat.js");
require("core-js/modules/es.string.replace.js");
require("core-js/modules/es.string.search.js");
require("core-js/modules/es.string.split.js");
require("core-js/modules/es.string.starts-with.js");
require("core-js/modules/es.string.trim.js");
require("core-js/modules/es.string.trim-end.js");
require("core-js/modules/es.string.trim-start.js");
require("core-js/modules/es.typed-array.float32-array.js");
require("core-js/modules/es.typed-array.float64-array.js");
require("core-js/modules/es.typed-array.int8-array.js");
require("core-js/modules/es.typed-array.int16-array.js");
require("core-js/modules/es.typed-array.int32-array.js");
require("core-js/modules/es.typed-array.uint8-array.js");
require("core-js/modules/es.typed-array.uint8-clamped-array.js");
require("core-js/modules/es.typed-array.uint16-array.js");
require("core-js/modules/es.typed-array.uint32-array.js");
require("core-js/modules/es.typed-array.copy-within.js");
require("core-js/modules/es.typed-array.every.js");
require("core-js/modules/es.typed-array.fill.js");
require("core-js/modules/es.typed-array.filter.js");
require("core-js/modules/es.typed-array.find.js");
require("core-js/modules/es.typed-array.find-index.js");
require("core-js/modules/es.typed-array.for-each.js");
require("core-js/modules/es.typed-array.from.js");
require("core-js/modules/es.typed-array.includes.js");
require("core-js/modules/es.typed-array.index-of.js");
require("core-js/modules/es.typed-array.iterator.js");
require("core-js/modules/es.typed-array.join.js");
require("core-js/modules/es.typed-array.last-index-of.js");
require("core-js/modules/es.typed-array.map.js");
require("core-js/modules/es.typed-array.of.js");
require("core-js/modules/es.typed-array.reduce.js");
require("core-js/modules/es.typed-array.reduce-right.js");
require("core-js/modules/es.typed-array.reverse.js");
require("core-js/modules/es.typed-array.set.js");
require("core-js/modules/es.typed-array.slice.js");
require("core-js/modules/es.typed-array.some.js");
require("core-js/modules/es.typed-array.sort.js");
require("core-js/modules/es.typed-array.to-locale-string.js");
require("core-js/modules/es.typed-array.to-string.js");
require("core-js/modules/es.weak-map.js");
require("core-js/modules/es.weak-set.js");
require("core-js/modules/web.dom-collections.for-each.js");
require("core-js/modules/web.dom-collections.iterator.js");
require("core-js/modules/web.immediate.js");
require("core-js/modules/web.queue-microtask.js");
require("core-js/modules/web.url.js");
require("core-js/modules/web.url.to-json.js");
require("core-js/modules/web.url-search-params.js");
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var adder = function adder(num1, num2) {
return num1 + num2;
};
var p = Promise.resolve();
var Person = /*#__PURE__*/_createClass(function Person() {
_classCallCheck(this, Person);
});
var pp = new Person();

特点:

  • 全局导入,污染全局变量
  • 产生了很多辅助函数 _createClass _classCallCheck,并且每个被处理的且用到相同特性文件都会产生相同的辅助函数,产生了重复,增大了包体积
  • 将引入的 core-js/stableregenerator-runtime/runtime 的所有的 polyfill 导入,即使你没有用到
    • core-js/stable 在整个项目中只能被导入一次,否则会产生错误,一般在入口文件导入
    • 在 7.4.0 以前,会将 @babel/polyfill 这个包的所有 polyfill 导入

preset-env 与 useBuiltIns: 'usage'

配置文件:

{
presets: [
[
"@babel/preset-env",
{
targets: "chrome 30",
useBuiltIns: 'usage',
corejs: 3,
debug: true,
}
]
]
};

源码:

const adder = (num1, num2) => num1 + num2;

const p = Promise.resolve();
class Person {

}

const pp = new Person();

编译后的代码:

"use strict";

require("core-js/modules/es.symbol.to-primitive.js");
require("core-js/modules/es.date.to-primitive.js");
require("core-js/modules/es.symbol.js");
require("core-js/modules/es.symbol.description.js");
require("core-js/modules/es.number.constructor.js");
require("core-js/modules/es.object.define-property.js");
require("core-js/modules/es.symbol.iterator.js");
require("core-js/modules/es.array.iterator.js");
require("core-js/modules/es.string.iterator.js");
require("core-js/modules/web.dom-collections.iterator.js");
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.promise.js");
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var adder = function adder(num1, num2) {
return num1 + num2;
};
var p = Promise.resolve();
var Person = /*#__PURE__*/_createClass(function Person() {
_classCallCheck(this, Person);
});
var pp = new Person();

特点:

  • 按需导入,而不是像 entry 一样所有的 polyfill 全部导入,并且不需要手动在入口导入 core-js/stable
  • 全局导入,污染全局变量
  • 同样产生了很多辅助函数,多个文件会同时生成这些辅助函数,会产生重复

@balel/plugin-transform-runtim

除了通过 preset-env 来进行 polyfill,还可以通过插件 @balel/plugin-transform-runtime 来进行 polyfill

配置文件:

const options = {
presets: [
[
"@babel/preset-env",
{
targets: "chrome 30",
debug: true,
}
]
],
plugins: [[
'@babel/plugin-transform-runtime', {
corejs: 3
}
]]
};

源码:

const adder = (num1, num2) => num1 + num2;

const p = Promise.resolve();
class Person {

}

const pp = new Person();

编译后的代码:

"use strict";

var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
var adder = function adder(num1, num2) {
return num1 + num2;
};
var p = _promise.default.resolve();
var Person = /*#__PURE__*/(0, _createClass2.default)(function Person() {
(0, _classCallCheck2.default)(this, Person);
});
var pp = new Person();
  • 按需导入,只导入用到的 polyfill
  • 非全局注入,不会污染全局,如 _promise
  • 所有的辅助函数均通过 @babel/runtime-corejs3 导入,而不是每个文件都生成,可减少文件体积
  • 没有 target 选项,忽视目前浏览器的版本,即使浏览器已经内置支持,还是会使用 polyfill

配置文件(修改 target: "chrome 100"):

{
presets: [
[
"@babel/preset-env",
{
targets: "chrome 100",
debug: true,
}
]
],
plugins: [[
'@babel/plugin-transform-runtime', {
corejs: 3
}
]]
}

编译后的代码:

"use strict";


var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
const adder = (num1, num2) => num1 + num2;
const p = _promise.default.resolve();
class Person {}
const pp = new Person();

Chrome 100 已经内置支持了 Promise,但是还是使用了 polyfill

level-top targets

如果 @babel/plugin-transform-runtime 如果能读取 targets 然后条件编译就好了,现在我们可以将 targets 选项配置到 level-top 中,而不仅仅是 preset-env 的选项。

配置文件:

{
targets: "chrome 100",
presets: [
[
"@babel/preset-env",
{
debug: true,
}
]
],
plugins: [[
'@babel/plugin-transform-runtime', {
corejs: 3
}
]]
};

源码:

const adder = (num1, num2) => num1 + num2;

const p = Promise.resolve();
class Person {

}

const pp = new Person();

编译后的代码:

"use strict";


const adder = (num1, num2) => num1 + num2;
const p = Promise.resolve();
class Person {}
const pp = new Person();

特点:

  • 按需引入
  • 非全局引入
  • 辅助函数从 runtime 导入,而非在文件中定义(当前例子看不出来,将浏览器版本改低一点就可以)

babel/babel-polyfills

preset-envtransform-runtime 都可以进行 polyfill,但是在使用上二者是冲突的,如果同时使用,由于 plugin 的执行顺序先于 presettransform-runtime 插件生效。

为了减少心智负担,官方决定使用一个统一的插件来处理 polyfill,那就是 babel-polyfills。有两个优势:

  • 可以选择 polyfill provider,由于历史原因,babel 一直用的都是 core-js,但是还有其他的方案,例如 es-shims
  • 可以选择插入方式,分为三种
    • entry-global:相当于 preset-env + useBuiltIns: 'entry'
    • usage-global:相当于 preset-env + useBuiltIns: 'usage'
    • usage-pure:相当于 top-level targets 选项 + transform-rumtime

配置文件:

{
targets: "chrome 30",
presets: ["@babel/preset-env"],
plugins: [
'@babel/plugin-transform-runtime',
[
'babel-plugin-polyfill-corejs3',
{
method: 'usage-pure'
}
]
]
}

源码:

const adder = (num1, num2) => num1 + num2;

const p = Promise.resolve();
class Person {

}

const pp = new Person();

编译后的代码

"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _index = _interopRequireDefault(require("core-js-pure/stable/promise/index.js"));
var adder = function adder(num1, num2) {
return num1 + num2;
};
var p = _index.default.resolve();
var Person = /*#__PURE__*/(0, _createClass2.default)(function Person() {
(0, _classCallCheck2.default)(this, Person);
});
var pp = new Person();

特点:

  • 灵活选择 polyfill provider 以及插入方式

更多用法可以参考 babel/babel-polyfills

· 阅读需 3 分钟
熊滔

Drag And Drop

Tauri 默认不支持 H5 Drag And Drop API,需要在 tauri.config.json 中禁止 fileDropEnabled,二者不能同时工作。

{
// ...
"tauri": {
// ...
"windows": [{
"fileDropEnabled": false
}]
}
}
危险

我还以为我的用法不对,浪费我好多时间,淦。

自动更新

Tauri 自动更新需要在 tauri.updater 中将 dialog 设置为 false,否则调用 checkUpdate 时一直在等待状态

{
"tauri": {
"updater": {
"dialog": false
}
}
}

窗口圆角

Tauri 目前不能通过配置实现这个功能,因此要实现圆角一定不能使用自带的 titlebar,需要在页面中实现,首先干掉自带的 titlebar,并且将窗口设置为透明

{
"tauri": {
"windows": [{
"decorations": false,
"transparent": true
}]
}
}

然后在页面的根元素设置圆角

#root {
border-radius: 12px;
overflow: hidden;
/* 要设置背景色,否则整个窗口都是透明的 */
background: var(--primary-bg-color);
}

/* 最大化的时候不使用圆角 */
#root.maximized {
border-radius: 0;
}

窗口阴影

使用插件 windows-shadows

[dependencies]
window-shadows = "0.2.2"
use window_shadows::set_shadow;

fn main() {
// ...
tauri::Builder::default()
.setup(|app|) {
// ...
let window = app.get_window("main").unwrap();
set_shadow(&window, true).expect("Unsupported platform!");
}
}
备注

tauri@v2 中,已经原生支持开启和关闭阴影了。

引用本地文件

在 Tauri 中要引用本地文件,需要使用 convertFileSrc 将本地路径转化为特殊的 HTTP 路径,需要先 tauri.conf.json 文件中设置 asset 协议

{
"tauri": {
"allowlist": {
"protocol": {
"asset": true,
"assetScope": [
"**"
]
},
}
}
}
import { convertFileSrc } from "@tauri-apps/api/tauri";

// -> https://asset.localhost/C%3A%5CUsers%5Cpc%5C.editor%5Cresources%5Cimage_87146a94-78a7-40ce-a9ea-23710c9e3f56.png
convertFileSrc('C:\\Users\\pc\\.editor\\resources\image_87146a94-78a7-40ce-a9ea-23710c9e3f56.png ')

vite-plugin-wasm

我使用了 tinypng-lib 这个库来对图片进行压缩,该包使用 wasm,需要使用 vite-plugin-wasm 进行处理,按照文档进行了安装和使用

pnpm add vite-plugin-wasm vite-plugin-top-level-await
import wasm from "vite-plugin-wasm";
import topLevelAwait from "vite-plugin-top-level-await";

export default defineConfig({
plugins: [
wasm(),
topLevelAwait()
]
})

在开发模式下是没有问题,但是打包构建后就没法打开软件,现在还不知道什么原因,不想折腾了。

· 阅读需 14 分钟
熊滔

ESLint 是代码检查工具,主要包含代码逻辑和代码风格:

  • 代码逻辑:

    • 使用了未声明的变量
    • 存在未使用的变量
    • 在函数式组件以外使用 React Hook
    • ... ...
  • 代码风格:

    • 是否使用分号
    • 使用单引号还是双引号
    • ... ...
信息

自 v8.53.0 开始,由于维护成本的原因,ESLint 官方已经放弃了对于代码风格规则的维护与开发,之前有关代码风格的规则都已经迁移到仓库  @stylistic/eslint-plugin-js  中,由社区进行维护。

配置

配置文件

ESLint 中代码逻辑和代码风格的检查是通过规则实现的,规则定义了如何检查代码以及如何修复代码,有如下几种方法可以对 ESLint 进行配置:

  1. package.json 中通过 eslintConfig 字段进行配置
  2. 在项目的根目录下,通过 .eslintrc.{(c)js,y(a)ml,json}进行配置
  3. 在命令行通过 --config 指定配置文件

当没有主动指定配置文件,而同时存在多种配置文件时,优先级如下:

  1. .eslintrc.js
  2. .eslintrc.cjs
  3. .eslintrc.yaml
  4. .eslintrc.yml
  5. .eslintrc.json
  6. package.json
信息

在 v9 版本,存在新的配置系统,在新的配置系统中,将只会存在一个配置文件 eslint.config.js,且配置方式与现有系统不同,新的配置系统将采用扁平式的方式。

rule

ESLint 每一个检查项都是一个规则(rule),ESLint 为我们提供了大量的 rule,通过 rules 字段去配置每个具体的 rule,包括 违反该 rule 的错误等级,以及该 rule 需要的参数。

一个参考的示例如下:

{
rules: {
// 必须写分号,否则报错
semi: ['error', 'always'],
// 使用双引号,否则警告
quotes: ['warning', 'double'],
// 关掉禁止使用 console 的规则
'no-console': ['off']
}
}

配置的数组的第一个参数表示如果违反了该条规则,将会产生什么级别的错误,错误等级分为三类:

  • error或者 2:错误,不可忽视
  • warn或者 1:警告,可处理可不处理
  • off或者 0:不启动该规则

第二个参数表示该规则接收的选项,例如对于 semi 规则接受一个选项,取值为 always 或者 neveralways 表示始终有分号,而 never 表示不应该有分号。

提示

选项也可以不传递,当不传递选项时,将会使用默认值,例如

{
rules: {
semi: ['error'], // 选项没有传递,默认值是 always
}
}

当不传递选项时,可以不使用数组,仅配置错误等级即可

{
rules: {
semi: 'error', // 选项没有传递,默认值是 always
}
}

extends

ESLint 内置了几百个规则,如果我们一个个去了解每个规则,然后决定是否开启的话,我觉得对于每个开发者几乎都是不可能的事情,成本太高了,最好是具有某种开箱即用的方案,比如社区存在某种最佳实践,我们只要直接复用就好了。

基于这样的想法,ESLint 提出了 extends 配置项,它可以让我们通过 npm 包直接复用已有的最佳实践,ESLint 官方有一个推荐的规则配置,可以通过如下方式复用该配置:

{
extends: ['eslint:recommended']
}
信息

extends 出现之前,对于 eslint:recommended的规则,eslint 一开始是将其内置,即内置了一些规则,但是这会让用户很迷惑,因为我什么都没有配置就拥有了一些规则。

后来 eslint 将这个内置行为去掉了,但是这样用户就得从零开始配置,加重了用户的负担,直到 extends 出现才解决这个问题,对于需要内置行为的用户,只要 extends eslint:recommended 即可,完全由用户自己决定。

参考 ESLint's new config system, Part 1: Background

plugin

虽然 eslint 内置了几百条规则,但是这些规则也不能完全满足我们的需求,我们需要自己自定义规则,比如 eslint 内置的规则都是针对 js 的,对于 jsxts 没有相应的规则进行检查,这个时候就需要插件来直接自定义规则。

插件一般都命名为 eslint-plugin-xxx,如果符合这样的命名规则,那么在使用时就可以省略前面的 eslint-plugin-。在使用插件定义的规则之前,我们首先需要在 plugins 配置项中先进行声明,然后才可以在 extendsrules 使用插件提供的配置和规则。

以 React Hooks 为例,使用 React Hooks 有两个必须遵守的规则:

  1. 只能在函数式组件或自定义 hook 中使用
  2. 不能在分支中使用 hook

为了确保在开发时遵守以上规则,React 官方提供了 eslint-plugin-react-hooks 插件

npm install --save-dev eslint-plugin-react-hooks
{
"plugins": [
"react-hooks"
],
"rules": {
"react-hooks/rules-of-hooks": "error", // Checks rules of Hooks
"react-hooks/exhaustive-deps": "warn" // Checks effect dependencies
}
}

parser

ESLint 默认使用 espree 这个 parser 来解析 js 为 AST,但是该解析器针对 jsxts(x) 无法解析,万幸的是 ESLint 支持自定义解析器,我们可以通过 parser 选项来指定解析器。

以常见的解析 TypeScript 的 @typescript-eslint/parser 为例

npm install --save-dev @typescript-eslint/parser
module.exports = {
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
parserOptions: {
project: './tsconfig.json'
}
};

env 和 global

JavaScript 可以运行在不同的运行时中,最典型的两个就是浏览器和 Node.js,在不同的运行时,会存在各自独有的全局变量,比如在浏览器中,可以通过 document 来访问页面上的 DOM 元素,在 Node 中有 setImmediate 来调度任务。

为了防止 ESLint 将这些全局变量识别为未定义的变量,我们需要配置 env 来声明所处的环境,配置相应环境后即可支持该环境对应的全局变量

{
env: {
browser: true,
node: true,
jest: true
}
}

ESLint 目前支持 env 配置列表参见 Specifying Environments

如果你的代码使用到了指定环境未定义的全局变量,比如通过 script 引入了一个库,它在全局上挂载了一个变量,这个时候为了防止 ESLint 报错,你可以在 global 中对该变量进行声明

{
globals: {
"jQuery": "readonly",
"$": "readonly"
}
}

globals 中的值可以配置为 writable 或者 readonlywritable 表示可以修改该全局变量,而 readonly 禁止修改,只能访问该变量。

危险

由于历史原因,globals 中的值还可以配置为布尔值,truewritablefalsereadonly,但是不推荐使用。

overrides

在使用 ESLint 进行检查时,我们希望对于某些文件,使用不同的规则,比如对于测试文件,我们允许使用 console 进行打印。可以在 overrides 中配置与外层 rules 不同的规则

module.exports = {
rules: {
"no-console": "error"
},
overrides: [{
files: ["__test__/**/*.js"],
rules: {
"no-console": "off"
}
}]
}

通过 files 来配置对哪些文件进行覆盖。

使用

ESLint 有两种使用方式,一种是通过命令行,另一种是通过其提供的 Node.js API 接入到自己的应用中,比如在编辑器中接入 ESLint

CLI

如果是全局安装的 eslint,则可以直接输入 eslint 来运行命令,如果仅在项目中安装了,则可通过 npx eslint 来运行命令,为了简洁,后续命令都将以全局的形式运行。

eslint 的用法如下

eslint [options] [file|dir|glob]*

最简单的用法,不使用任何选项

# 检查文件 file1.js 和 file2.js
eslint file1.js file2.js

# 检查 src 文件夹下的所有文件
eslint src/

# 检查 src 目录下的任意文件夹下的所有 js 文件
eslint "src/**/*.js"
信息

当使用 src/**这样 glob 的语法时,其结果依赖于你 shell 的解析,如果你想使用 nodeglob 语法,建议为其添加双引号。

由于 eslint 的选项太多,并不是每个都常用,因此下面只介绍常用的几个选项,更详细的选项可以参考官方文档 Command Line Interface Reference

-c/--config

在不显示指定配置文件的情况下,会默认读取项目根目录下的 .eslintrc.{(c)js,y(a)ml,json}配置文件,配置文件的路径可以通过 -c--config 来进行显式指定。

eslint --config eslint.config.js index.js

-o/--output-file

默认情况下,eslint 的检查结果会输出在当前命令行窗口中,可以通过 -o--output-file 将结果输出到指定文件中

eslint --output-file result.txt index.js

--ext

默认情况下 eslint 只会检查 .js 文件,这一行为可以通过 --ext 改变,它可以指定要检查的文件的后缀名,可以同时指定多个后缀名

# 通过多个 --ext 选项指定多个后缀名
eslint --ext .js --ext .ts

# 通过 , 分隔多个后缀名
eslint --ext .js,.ts
信息

--ext 当且仅当指定的模式为文件夹时才生效,如果指定的模式为文件,--ext 是不生效的,如 eslint --ext .ts src/*--ext 选项是不生效的,因为 src/* 表示 src 下的所有文件,eslint --ext .ts src/ 是生效的,因为 src/ 表示文件夹。

eslint --ext .json ./

~\eslint-demo\package-lock.json
2:9 error Parsing error: Unexpected token :

~\eslint-demo\package.json
2:9 error Parsing error: Unexpected token :

2 problems (2 errors, 0 warnings)

--fix

使用了 --fix 选项,eslint 将会尽可能的帮你修复错误,如果错误可以被修复,那么该错误将不会在输出结果中显示

// .eslintrc.js
module.exports = {
rules: {
semi: ['error']
}
}
# 前两行未添加分号
λ cat index.js
var a = 1
var b = 2
var c = a + b;

# 只报告了 c 未使用的错误,没有报告 a 和 b 后面没有分号的错误
# 因为这个错误被自动修复了
λ npx eslint --fix index.js

~\eslint-demo\index.js
3:5 error 'c' is assigned a value but never used no-unused-vars

1 problem (1 error, 0 warnings)

# 前两行的分号被自动的添加上了
λ cat index.js
var a = 1;
var b = 2;
var c = a + b;

编辑器集成

VSCode

首先需要在 VSCode 插件市场下载 ESLint 插件

接着需要全局或者在项目中安装 eslint

npm install eslint

然后再编码的过程中就可以得到编辑器的提示了

这里再推荐另外一个插件,Error Lens,可以直接把行内显示错误,而不需要把鼠标放上去才看到具体的错误

效果如下

WebStorm

在 WebStorm 中不需要下载 ESLint 插件,WebStorm 默认集成 ESLint,但是这个功能默认是关闭的,需要手动开启

使用 Automatic ESLint configuration,WebStorm 会使用该项目下 node_modules 中的 eslint,配置文件会使用项目下的 .eslintrc 文件;而 Manual ESLint configuration 则需要手动配置使用的 eslint 和配置文件。

· 阅读需 13 分钟
熊滔

这本书不是教大家写作技巧的,它比较强调写作前的准备。

写作的困难是什么,是你对着一张白纸难以下手,不知道写什么,或是写的过程很困难,因为你在绞尽脑汁靠记忆在写作。此时想象如果你有一个素材库,里面包含了文章的观点、论点,你要做的只是对素材的组织,那是不是很容易呢,这本书的内容就是在讲如何构建这么一个素材库,即卡片笔记盒。