0%

Hmm, 新博客的第一篇文章,说些什么好呢🧐

首先当然是庆祝自己找到了喜欢的博客框架 Hexo, it’s truly awesome! 之前尝试过wordpress, ghost之类的框架,但是nah,就凭他们的markdown文章支持不好来说,就觉得他们不是coder优先的设计。看到朋友 cchroot 的博客后,被hexo+next主题给惊艳到了:

  • 🎨 简洁大方的设计:Next的主题,黑白色调+舒服动效,信息传达高效优雅
  • ✏️ 完美markdown格式支持:尤其是代码块与高亮,太让人心动了😍
  • 📚 文章归档与时间线预览:自动记录发布与修改时间,以及生成timeline太舒服
  • ☁️ 分类与标签词云:自动文章分类好香啊
  • 🤖 完全编程能力:整个工程都是基于文件,由代码项目构建处理来的

于是我花了一个晚上按照官网、next文档把自己的hexo blog site装扮好了😆 对于程序员来说最趁手的写作格式非markdown莫属,我缺的只是能把markdown渲染得漂漂亮亮的解析器与管理框架,现在有了hexo,我再也没理由不写文章啦🐶

部署的话用的是云原生玩法☁️:github webhook + Coding CI + 腾讯云的COS对象存储(开启静态托管) + CDN加速,完全解放运维能力,每次更新只需 coding > git commit > git push,然后一切都交给 CI/CD 流水吧,冲一杯咖啡的时间网站就更新部署好了 ☕️ 未来一定是万物基于云上生长的时代 ☁️

最后想说的就是,2021年,我要积极学习&输出,打造个人品牌(IP) 🤩

在之前,其实我是属于那种闷头学不输出的人,一直觉得自己还不够资格写文章来“传道授业解惑”,所以自己的知识点都是偷偷做在笔记软件里。随着自己的工程经验增长,掌握的能力确实在逐步增强,周围的朋友遇到似曾相识的问题时候,我都能比较好地指导他们解决,在那时候我才意识到,自己还挺勇的嘛😌

还有一个让我觉醒的点:今年工作矜矜业业,在工程里做了很多微创新和实践,手头的项目从刚接手的雏形到能够稳定承载多个业务接入的平台,并且也确实为公司业务带来了降本增效的成果,结果半年评绩效的时候,我居然被领导打了一个Need to improve(差绩效)???其中一个在领导看来重要的点:技术产出与个人影响力不足(但我觉得这些都不是OKR主要矛盾,应该看工作是否为公司带来的效益,所以我非常地不爽和不服😡)。情绪安定过后,跟其他前辈沟通了很多,意识到其实自己这种埋头干活不输出的coding style是很吃亏的,在结果导向的制度下,尽可能地做到在输出上比别人有热情,你的才华才可能被看见。这是我职场第一道坎,也是成为社会人的第一课。

这些事让我意识到,酒香也怕巷子深,自己在不断地学习与输入的同时,也要积极地做输出,比如把自己觉得牛逼的组件上传到npm包分享给大家、把遇到问题+解决问题的过程写成文章发出来,或许能节省后来者的宝贵时间呢?一直很羡慕和尊敬那些技术强又会做分享的技术大pro,活跃在知乎、掘金,拜读过他们的文章后也是受益匪浅,相信他们的万丈高楼也是从平地起的吧。

所以在这内卷🗞的时代,人们在迷茫时很容易好高骛远、眼高手低,我认为唯一地解决办法就是脚踏实地、踏实地敲下每一个字符,记录自己的成长。Stay hungry, stay foolish ✍🏻

🔖 延伸阅读:
hexo docs
next docs
如何自定义next主题
腾讯云静态托管
腾讯子公司产品-Coding CI

本篇文章阅读时间:10min
读者预期的收获是:

  • 认识测试驱动开发
  • 非常简单开启你的 TDD 之旅
  • 可以编写自动化测试
  • 重构、重新设计旧的代码更加自信

引子

(压抑背景音乐渐入——)

旁白:为何深夜的办公室传来程序员的哀嚎?

为何说好的一刀 999,砍下去伤害为 0?

为何程序员好基友反目成仇,因代码调用出问题后甩锅大打出手?

当个程序员,好难!(捂着铮亮的脑门)

程序员甲:自从用了 TDD,测试驱动开发之后,每天下班早了,BUG 变少了,基友不吵了。

程序员乙丙丁:真的吗?有这么神奇吗?!(集体星星眼)

程序员甲:没错,让我来给你们安利吧!

(雪花屏)Beep——

在这里插入图片描述
Hi,我是 Bruski。开头的段子纯属瞎编,但其中描述的场景:代码不按预期执行、协作的接口不可靠等等,在我们日常工作中其实挺常见的。
原因可能千奇百怪,比如在犯困的午后工作,比如没想清楚就动手等等,而且在过程很糟糕的情况下,输出还没有自动化测试去保证,那线上在跑的程序很可能就是一颗不定时炸弹。

那有没有什么办法能最大程度避免以上情况呢?我会说,不妨试试极限编程(XP)中的优秀实践:测试驱动开发吧!
在这里插入图片描述

别问,先感受

那么到底什么是测试驱动开发呢?

别急,先来感受一道小题目,非常简单:FizzBuzz

题目模板地址: git clone https://github.com/bruceeewong/tdd-kata.git -b kick-start

题目来源:极客学院-测试驱动开发实战营


FizzBuzz 是一个简单的猜数字游戏。
在这里插入图片描述
想象你是个小学 5 年级的学生,现在还有 5 分钟就要下课,数学老师带全班同学玩一个小游戏。他会用手指挨个指向每个学生,被指着的学生就要依次报数:

第一个被指着的学生说“1”,第二个被指着的学生说“2”,如果一个学生被指着的时候,应该报的数是 3 的倍数,那么他就不能说这个数,而是要说“Fizz”。5 的倍数也不能被说出来,而是要说“Buzz”。

于是游戏开始了,老师的手指向一个个同学,他们开心地喊

着:“1!”,“2!”,“Fizz!”,“4!”,“Buzz!”……

终于,老师指向了你,时间仿佛静止,你的嘴发干,你的掌心在出汗,你仔细计算,然后终于喊出“Fizz!”。运气不错,你躲过了一劫,游戏继续进行。

为了避免在自己这儿失败,我们想了一个作弊的法子:最好能提前把整个列表打印出来,这样就知道到我这儿的时候该说什么了。

题目要求

写一个程序,打印出从 1 到 100 的数字,将其中 3 的倍数替换成“Fizz”,5 的倍数替换成“Buzz”。既能被 3 整除、又能被 5 整除的数则替换成“FizzBuzz”。

要求:

  • 代码整洁,没有重复代码
  • 有单元测试,单元测试覆盖率 100%
  • 5 分钟内完成

题目解析

相信大家应该都能很快地实现题目的要求,不过,关于单元测试部分,大家写的是否轻松呢?接下来我想给大家展示下我的做题思路——用 TDD 的方式。

测试驱动开发的要义是:测试先行,没有失败的测试,就不允许实现。所以,在动手前我们需要想清楚题目要实现什么,即拆解需求。再回顾下题目要求:

打印出从 1 到 100 的数字,将其中 3 的倍数替换成“Fizz”,5 的倍数替换成“Buzz”。既能被 3 整除、又能被 5 整除的数则替换成“FizzBuzz”。

打印出 1 到 100 的数字?也许会有人开始构思程序:一个 for 循环,if-else 一下,再 console.log 一下。等等,输出打印到控制台的话,我们怎么写测试验证输出是否正确呢?所以不妨转换下思路,沿着函数的本质:input -> process -> output来思考,其实我们要做的是:

实现一个函数

输入: 1~100 的数字

处理:

  • 3 的倍数替换成”Fizz”

  • 5 的倍数替换成“Buzz”

  • 3 和 5 的公倍数(或者 15 的倍数)替换成“FizzBuzz”

  • 其他数字则转换为字符串

输出:字符串

将需求完全拆解后,对应的测试用例也就信手捻来了,就让我们从最最简单的测试开始,函数就叫 fizzbuzz 吧,接收参数 1,返回字符串“1”。(这种直白的语法就叫断言(Assertion),即把预期输出与实际输出作对比以验证程序是否正确运行)

1
2
3
4
5
6
7
8
// 以下语法为Jest.js的测试写法
const fizzbuzz = require("./fizzbuzz");

describe("fizzbuzz", () => {
test("测正常数字返回", () => {
expect(fizzbuzz(1)).toEqual("1");
});
});

执行jest命令运行测试,结果不出所料地报红了:fizzbuzz is not a function,毕竟我们此时连函数都没声明。

在这里插入图片描述

那我们赶紧定义函数:

1
2
3
4
function fizzbuzz(num) {
return "1";
}
module.exports = fizzbuzz;

有人会说,函数体返回常量,你在骗自己吗?别急,再执行一下 jest 命令运行测试:
在这里插入图片描述
Yes,测试通过,变为绿色!没错我是硬编码返回了,但这是 TDD 的第二个重要的要义:只写让测试恰好通过的代码。好吧我知道留着这样的代码,是不敢入睡的,那就再多加一条测试:

1
2
3
4
5
6
7
8
const fizzbuzz = require("./fizzbuzz");

describe("fizzbuzz", () => {
test("测正常数字返回", () => {
expect(fizzbuzz(1)).toEqual("1");
expect(fizzbuzz(98)).toEqual("98");
});
});

再运行测试,闭着眼睛都知道会失败,自动化的测试还非常贴心地帮我们指出了期待输出和实际输出的差异。
在这里插入图片描述
有了失败的测试,我们才开始动手写实现,实现也相当简单:

1
2
3
function fizzbuzz(num) {
return num.toString();
}

执行测试,OK,测试通过,结果又变回绿色。
在这里插入图片描述
这时候我们知道第一条需求已经被解决,无情划掉它:

  • 3 的倍数替换成”Fizz”
  • 5 的倍数替换成“Buzz”
  • 3 和 5 的公倍数(或者 15 的倍数)替换成“FizzBuzz”
  • 其他数字则转换为字符串

那就写下第二条测试用例:

1
2
3
test("测3的倍数返回", () => {
expect(fizzbuzz(3)).toEqual("Fizz");
});

执行测试,结果 3 原样返回,测试不通过:
在这里插入图片描述
又是一条失败的测试,快速实现它让它翻绿!

1
2
3
4
5
6
function fizzbuzz(num) {
if (num % 3 === 0) {
return "Fizz";
}
return num.toString();
}

再次运行测试:
在这里插入图片描述
那此时再加几条测试,结果肯定是正确的:

1
2
3
4
5
test("测3的倍数返回", () => {
expect(fizzbuzz(3)).toEqual("Fizz");
expect(fizzbuzz(6)).toEqual("Fizz");
expect(fizzbuzz(99)).toEqual("Fizz");
});

再划掉一项需求!

  • 3 的倍数替换成”Fizz”
  • 5 的倍数替换成“Buzz”
  • 3 和 5 的公倍数(或者 15 的倍数)替换成“FizzBuzz”
  • 其他数字则转换为字符串

接下来想必大家都知道了,复制一下 3 的测试用例,改成 5,然后执行测试,失败,然后复制一条 if 语句….等等!难道你忘了,Copy-Paste 是魔鬼吗?难道我是在教你成为一名 CV 工程师吗?好了,这里引出 TDD 又一条要义:消除所有重复。其实通过将运算抽象为函数,很容易就能消除重复的直白代码:

1
2
3
4
5
6
7
8
9
10
11
12
function fizzbuzz(num) {
function canDivideBy(num, divideNum) {
return num % divideNum === 0;
}
if (canDivideBy(num, 3)) {
return "Fizz";
}
if (canDivideBy(num, 5)) {
return "Buzz";
}
return num.toString();
}

运行测试,干净漂亮地通过了测试:
在这里插入图片描述
最后再补充一条 3 和 5 的公倍数测试用例,使用抽象好的函数实现,运行测试,测试通过后,那么整个需求就完成了。下面是完整的测试用例&实现&测试截图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// fizzbuzz.test.js
const fizzbuzz = require("./fizzbuzz");

describe("fizzbuzz", () => {
test("测正常数字返回", () => {
expect(fizzbuzz(1)).toEqual("1");
expect(fizzbuzz(2)).toEqual("2");
expect(fizzbuzz(98)).toEqual("98");
});
test("测3的倍数返回", () => {
expect(fizzbuzz(3)).toEqual("Fizz");
expect(fizzbuzz(6)).toEqual("Fizz");
expect(fizzbuzz(99)).toEqual("Fizz");
});
test("测5的倍数返回", () => {
expect(fizzbuzz(5)).toEqual("Buzz");
expect(fizzbuzz(10)).toEqual("Buzz");
});
test("测3和5的公倍数返回", () => {
expect(fizzbuzz(15)).toEqual("FizzBuzz");
expect(fizzbuzz(45)).toEqual("FizzBuzz");
});
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// fizzbuzz.js 实现
function fizzbuzz(num) {
function canDivideBy(num, divideNum) {
return num % divideNum === 0;
}
if (canDivideBy(num, 15)) {
return "FizzBuzz";
}
if (canDivideBy(num, 3)) {
return "Fizz";
}
if (canDivideBy(num, 5)) {
return "Buzz";
}
return num.toString();
}

module.exports = fizzbuzz;

在这里插入图片描述
划掉所有需求,爽,可以下班了!

  • 3 的倍数替换成”Fizz”
  • 5 的倍数替换成“Buzz”
  • 3 和 5 的公倍数(或者 15 的倍数)替换成“FizzBuzz”
  • 其他数字则转换为字符串

最后,执行 Jest 命令jest --coverage生成测试覆盖率报告:
在这里插入图片描述
怎么样?100%的测试覆盖率,没有重复、多余的代码,漂亮地完成所有需求。如果你不放心,多加几条测试用例,多运行几遍测试命令,这就是测试驱动开发产出的有质量保证的代码。

有了自动化测试做保障,测试通过,我就敢说在我所预见的情况中,他会一直通过,除非,除非产品经理的需求又变了…
在这里插入图片描述
总结一下,在做 FizzBuzz 题目的过程中,用 TDD 的节奏开发流程如下图:执行测试覆盖率检查

如果大家有关注到图中的颜色,那么请大家跟我念一句 TDD 的咒语,念三遍:

Red / Green / Refactor

Red / Green / Refactor

Red / Green / Refactor
在这里插入图片描述
这就是贯穿测试驱动开发的整个流程的循环,也是 TDD 的节奏。其中:

  • Red表示测试不通过时,IDE 的状态条报红,此时我们有了失败的测试;
  • Green表示测试通过时,IDE 的状态条回到绿色状态,此时通过快速小步地开发,让测试恰好通过;
  • Refactor表示重构代码,消除所有重复的逻辑。

接下来,让我们跟随 Kent 大叔深入地琢磨下测试驱动开发吧!

深入测试驱动开发

到底什么是测试驱动开发(Test-driven Development)呢?

按照 Kent 大叔的原话:

TDD is an awareness of the gap between decision and feedback during programming, and techniques to control that gap.

粗浅地翻译过来意思是:

TDD 是一种编程意识,关注着程序设计与实现结果反馈之间的间隙;同时 TDD 也是用以填平该间隙的一系列技巧。

是的,概念总是过于晦涩与抽象。作者还提供了不同角度的定义来帮助理解:

  • 测试驱动开发是一种管理编程中的恐惧的方式。(Test-driven development is a way of managing fear during programming. )
  • 测试驱动开发是一种开发风格:我们通过自动化的测试来驱动开发(we drive development with automated tests, a style of development called Test-Driven Development)

你一定要记住,TDD 的终极目标是:

Clean code that works. (产出干净且可用的代码)

这是《测试驱动开发》序章的第一句话,也是我编程的座右铭。

TDD 开发模式

首先我们要搞清楚 3 个问题:

  1. 什么是测试?
  2. 测什么?
  3. 什么时候测试?

什么是测试

测试作为动词,是“去验证”的意思。测试作为名词,是对预期得出可接受或者不可接受判断的一个过程

按 Kent 大叔的意思是:

测试是开发者的基石,也是将对程序运行结果从未知的恐惧转化为熟知的手段。

不喜欢写测试的程序员,通常将经历这样的消极循环:感到恐惧,因为没测试 -> 越恐惧,压力越大 -> 压力越大,越不会测试

而善用测试的程序员呢?正向循环应该像这样:越感到恐惧,越执行测试 -> 越测试,恐惧越小 -> 压力越小,越愿意测试

测什么

我们这里指的,是程序级别的单元测试(Program Level),主要关注逻辑数据

对于逻辑的测试,一般来说等同于需求,我们要对需求进行编程级的拆解,即要能拆解为可以动手编码的若干步骤,通过不断地写下你的期望与实际输出的测试语句(即断言),然后实现代码让其通过,从而一步步达成目的。

对于数据的测试,这里我也没有很多实践,有几点可以分享:

  1. 不要使用真实的数据(数据库数据、网络请求等)
  2. 按照预期的数据结构,构造直观的伪造数据来满足测试。

什么时候测试

按照测试驱动开发的节奏,每当:

  • 动手编程前,先写出一条会失败的测试
  • 重构前,保证测试通过

了解完前置概念后,又该怎么落笔我们的第一个测试用例?

Red Bar Patterns

Red Bar,顾名思义:因执行测试失败而显示红色的状态栏

要让测试失败,那首先要写下你的测试,我们上一节介绍了需求拆分,得到了一份 todo-list,那我们究竟该自顶向下实现(即从大问题的具体用例开始实现),还是自底向上实现(从小模块开始,再逐步聚合)呢?

其实这两者都不能很好描述 TDD 的实现过程,准确地说,实现顺序没有太大关系,因为 TDD 是基于 known-to-unknown 的模式进行,即用已知的来推出未知的。我们在拆分需求为一条条可编程验证的用例时,就是将未知的庞然大物拆解成不废力气就能达成的小目标,我们知道如果一步步实现了所有子测试,最终需求就能实现。

在 TDD 这里,万事开头难,但测试开头易。第一个测试应该写一条测什么都不做的操作的测试,这里看似没什么意义,但是它确实验证了:

  • 这个操作属于哪里?
  • 什么是正确的输入?
  • 什么是基于正确输入的正确输出?

Green Bar Patterns

Green Bar,顾名思义:因执行测试成功而显示绿色的状态栏

在 FizzBuzz 实现的过程中,我们用到了几种快速让测试通过的技巧,分别是:

Fake It (‘Til You Make It) 伪造数据

比如在 FizzBuzz 最开始的时候,为了让测试通过,直接在函数里返回常量。

为什么要写早晚要换掉的实现?原因有两点:

  • 心理暗示
    • 测试成功比测试失败好
  • 范围控制
    • 专注在解决当前测试上,避免过度设计
    • 保证当前代码始终可用

Triangulate 三角测量

  • 从不同角度测试代码,让伪造数据的代码失败,然后抽象、实现,让测试通过。例如我们前面用两条测试,宣告了硬编码返回”1”的代码实现的死亡。

Obvious Implementation 最简实现

  • 既然用例已经拆分成小步,一定可以快速实现,否则,反思步子是否迈大。

  • 写恰好实现的代码。

至此,结合 FizzBuzz 的解析,我们已经体验完测试驱动开发最核心的流程。

来总结下吧

再回到定义,测试驱动开发本质上是一种编程思考和实践的一种风格/方式,比起一开始的顶层设计,他更关注需求与实现之间的距离,要求程序员能拆解成若干可测试、可实现的步骤,然后借助自动化测试工具,按照一定的节奏Red / Green / Refactor、测试技巧和编程手法,从而产出干净的、可工作的代码。

  • 因为测试先行,倒逼我们必须思考清楚问题应该如何解决,避免了低效地走一步看一步的浑浑噩噩;
  • 因为测试先行,我知道做到什么程度算完成,并且自信地认为在我所预期的情况内,程序可以良好地工作。
  • 测试用例可以作为更棒的注释而存在,让协作的同事更清楚地知道函数的用途和用法。
  • 提交代码时,看着绿色的状态栏,心情愉悦,安心下班!

TDD 的挑战

TDD 更多的应用在程序级别的单元测试,这一块是开发人员完全自主掌控的部分。而在此之外的一些场景,TDD 也许就不那么合适,比如:

  • 对于 GUI 的测试(网页、App 级别的 UI 测试)
  • 对于依赖数据库的测试(通常我们使用 mock 对象测试)
  • 不要去测第三方的代码,那应该有他们的开发去保证(如框架等)
  • 不能测试编译器之类的东西。

写在最后

作为一名 Web 前端开发,在开发业务逻辑时,我都会有意识地使用 TDD 的方式来实现。(UI 方面的测试实践并不多,还要继续学习!)
TDD 测试驱动开发带给我的开发体验是:

  1. 享受可预测、尽在掌握的开发体验
    1. 当通过了所有测试、开发也就结束了
    2. 并且开发结束了,可预见的场景不会有太多 bug
  2. 给自己留一瓶后悔药
    1. 第一次的实现可以很烂,但只要有测试,再回过头来,只要测试是通过的,就可以放心地重构。
    2. 如果祖传代码没有测试,那就尝试找到程序输入输出的接缝处,给他补充测试,这样可以最大程度确保重构不会大刀阔斧地破坏原有逻辑。
  3. 保持代码年轻的秘诀?
    1. 如果是测试驱动出来的代码,将拥有输入输出清晰、幂等性特点。
    2. 每当添加新特性前,先思考清楚,先写测试,代码不会随乱涂乱改而腐败。
  4. 同事协作时之间更放心
    1. 你产出的代码值得信赖。
    2. 同事也用 TDD,看着测试用例就知道怎么用了,真香。

这篇文章只是展示 TDD 的基础玩法,想要深入了解测试驱动开发,去读下 Kent Beck 的 《Test-Driven Development By Example》,感受 Kent 大叔的幽默与智慧吧。
在这里插入图片描述
愿你的程序无 Bug,早点下班!
在这里插入图片描述

CSS 层叠样式表作为前端三剑客之一,通过各类选择器来解耦 HTML 结构与表现,让开发者拥有专注控制样式的能力,实现了关注点分离。通过层叠机制,为规则赋予不同的重要程度,让我们的样式代码能够灵活地继承与覆盖。

它就像精灵宝可梦里的百变怪 👾,拥有强大而奇妙的变化与适应能力,前端技术也因 CSS 的加入而变得漂亮与有趣 🤩

本文主要分享 CSS 技术中的一个切面概念:渐进增强,希望能够从原理+实例出发,给大家在设计、编写网页样式时带来一点启发 💡

渐进增强

随着 HTML 与 CSS 的发展,许多新特性陆续得到浏览器厂商们的支持,比如 HTML5 中的新增标签,CSS 中的 Flexbox,Grid、calc 等,给网页带来了很多新鲜而强大的能力。
但是由于时代推移、浏览器的厂商各自对规范实现的不一致,导致不同的浏览器以及版本存在新特性不兼容或者其他 Bug 问题,让开发者在新特性与兼容性之间放弃了尝试新特性的想法 🤪

但是!时代与技术一定是在进步的,而在新老交替的过渡期,我们可以采用渐进增强的策略,来平衡向后兼容性与最新的 HTML/CSS 特性。

什么是渐进增强 🧐

⛳️ 所谓渐进增强,即我们首先为最小公分母准备可用的内容,然后再为支持新特性的浏览器添加更多交互优化。

要实现渐进增强,意味着代码要分层,每一层增强代码都只会在相应特性被支持或被认为适当的情况下使用。听起来有点复杂,然而实际上 HTML 与 CSS 已经部分内置了这一策略。

HTML 的渐进增强策略

⛳️ 对于 HTML 而言,浏览器遇到未知元素或属性时并不会报错,也不会对页面产生什么影响。

假设表单中有输入邮件的控件:

1
<input type="text" name="field-email" />

我们就可以使用 HTML5 新增的 email 类型,可以拥有邮件格式检验以及移动设备中的特定格式软键盘的强化能力:

1
<input type="email" name="field-email" />

尚未实现这个新类型的浏览器就会想:”这是甚么玩意儿?不明白“🧐,然后给他退化成 type=”text”类型,就当无事发生。
这样,我们既渐进增强了页面,也不会对浏览器产生什么不好的影响。
类似的还有 HTML5 的文档声明,这样的语法也是向后兼容的。

CSS 的渐进增强策略

⛳️ 对于 CSS 来说,无法识别的属性/值都会被浏览器丢弃,所以只要提供合理的后备声明,使用新属性就不会带来不良后果。

例如,现代浏览器支持的颜色值 rgba 函数,我们可以这样定义红色:

1
2
3
4
.overlay {
background: #000;
background-color: rgba(0, 0, 0, 0.8);
}

对于旧的浏览器,它会丢 rgba 的声明,应用第一条规则;对于现代浏览器,第二条规则就会覆盖第一条,显示带透明度的红色。
那么即使不是所有浏览器都支持 rgba 函数,由于提供了合理后备,我们仍然可以大胆地使用它 😎

如何实现渐进增强 🧐

1. 厂商前缀

🛠 浏览器厂商也基于同样的原理为自家浏览器引入实验特性,并加上一串特殊前缀,这样其自家浏览器就能识别而其他浏览器忽略。
例如 transform 属性:

1
2
3
4
5
6
.thing {
-webkit-transform: translate(0, 10px);
-moz-transform: translate(0, 10px);
-ms-transform: translate(0, 10px);
transform: translate(0, 10px);
}

下面列举前缀对应的浏览器厂商:

-webkit-: 适用于 Webkit 内核的浏览器,包括 Blink 引擎的(基于 Webkit)

  • Safari
  • Chrome
  • Opera

-moz-: 基于 Mozilla 浏览器

  • Firefox

-ms-: 微软家

  • Internet Explorer

2. 条件规则与检测脚本

@supports
⚙️ 如果希望通过检测浏览器是否支持某个 CSS 特性来提供样式,可通过条件规则检测:

1
2
3
@supports (display: grid) {
/* 编写网格布局的规则 */
}

3. JavaScript 库

⚙️ 通过 Modernizr 这个 JS 库可以检测各种特性的支持情况。
原理是 modernizr 会根据检测情况给 html 根元素添加相应特性的类名,如 flexbox,然后在编写 CSS 时带上前缀,即可安全的增强样式。

更多关于新特性的浏览器支持情况,可以到 http://caniuse.com 查阅 🔎

🌰 例子 1:在浮动之上应用 Flex

这里有一个使用 float 布局实现的卡片布局,我们来看看如何在不破坏原有代码的情况下,用 Flexbox 特性增强这个页面。

这个页面的使用了 float 来实现网格布局效果,主要原理是

  • 💡 使用 col 类名定义浮动组件,row 类名定义清除浮动的父组件
  • 💡 加上 row-quartet 定义子项列宽,实现网格布局效果
  • 核心代码如下:
1
2
3
4
5
6
<div class="row row-quartet">
<div class="col"><article></article></div>
<div class="col"><article></article></div>
<div class="col"><article></article></div>
<div class="col"><article></article></div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
// index.css/* 行组件 */
.row:after {
content: "";
display: block;
clear: both;
height: 0;
}
.row-quartet > * {
width: 25%;
} /* 列组件 */
.col {
float: left;
}

通过图片可以看到上述代码并未实现列等高效果,然而要实现浮动元素的列等高效果比较麻烦,详情方案参考:https://juejin.cn/post/6844904048278290440#heading-22

而使用 Flexbox 可以轻松实现,原理是 Flex 的等高机制:父容器定义为 Flexbox 容器之后,控制辅轴的属性 align-items 默认为 strech,即自动拉伸子项的高度以填满空间。

那么此时我们想通过 Flexbox 来实现列等高效果,又不破坏已实现的部分,该怎么做呢 🧐
⛳️ 答案是:引入检测机制,在检测块内编写新特性的规则。

上面也提到有两种方式:

  1. 使用 @supports 块检测,但由于此检测语法本身就比较新,所以对于旧浏览器不友好。
1
2
3
@supports (display: flex) {
/* 编写 flexbox 布局的规则 */
}
  1. 使用 Modernizr 库检测,然后使用带.flexbox 前缀的类名来编写增强的规则。

首先我们将 Modernizr 的 script 写在所有 link 的前面(保证检测脚本先加载)。

添加后,Modernizr 检测完毕后,我们的标签上就将支持的特性名字生成 classname,测试浏览器是当前最新的 Chrome 89, 所以可以看到 flexbox 赫然在列。

接下来就基于此类名前缀,编写增强代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// index.css​/_ flexbox 增强代码 _/
.flexbox .row {
display: flex;
}
.flexbox .col {
display: flex;
flex-direction: column;
}
.flexbox .col > _ {
flex: 1;
}
​/_ float 实现相关代码 _/.row:after {
content: "";
display: block;
clear: both;
height: 0;
}
.row-quartet > _ {
width: 25%;
}
.col {
float: left;
}

我们来看下效果,这里用到浏览器测试工具:browserstack ,选择在 Chrome 下打开页面,展示了等高效果,说明应用增强代码:

而在 IE 10 浏览器上就降级为无 flexbox 的效果:

查阅 CanIUse 网站,看到 IE10 只支持带 -ms- 前缀的 flexbox 属性,所以也解释了上述表现。

总结一下 在浮动之上应用 Flex 方案,有几个关键点:

  • 不理解 flex 关键字的浏览器会忽略它。
  • 给标签定义了 display: flex 之后,浏览器会忽略该标签定义的 float,clear 与 display:inline-block 等声明。

举一反三,其他场景我们也可以遵循首先写一个适合任何场景的布局,然后再通过 新特性检测 + 运用 Flexbox / Grid 等新特性来渐进增强我们的网页。

详情代码:CodePen:

🌰 例子 2:响应式图片

响应式 Web 设计出现以后,何时加载合适的图片是前端开发者要面对的问题。很多开发者不管设备、屏幕大小,一律使用相同的图片,这种做法导致小屏幕看大图浪费带宽与内存、大屏幕展示小图看不清楚等问题。而浏览器会对网页进行预处理,图片等资源会在浏览器构建完页面或运行 JavaScript 之前就开始下载,这意味着不可能仅凭脚本就完美解决图片响应式的问题。所以 HTML5 提出了响应式图片的解决方案。

响应式图片(Responsive Image)是指给 HTML5 规范给标签添加的 srcset、sizes 新属性,旨在解决不同的条件下告诉浏览器加载不同的图片。

  • srcset: 哪个是当前图片的可替换源文件,其宽度是多少像素?
  • sizes: 在各个断点中,图片的 CSS 宽度是多少?

Hi,img 标签的新属性

来看个例子 🌰:

1
2
3
4
5
6
7
8
9
<img
src="http://dummyimage.com/300x150"
srcset="
http://dummyimage.com/300x150 300w,
http://dummyimage.com/600x300 600w,
http://dummyimage.com/1200x600 1200w
"
sizes="(max-width: 600px) 300px, (max-width: 1200px) 600px, 1200px"
/>

分析一下:

  • srcset 的值是一组图片 URL+实际像素宽度(不是 CSS 像素),定义了一组资源后,还要告诉浏览器怎么使用这些图片。
  • sizes 通过开头可选的媒体查询条件 + 图片展示宽度值(CSS 单位,可为 px, em, vw)。

如果某条媒体查询条件为真,浏览器将取得条件后面定义的宽度,然后去匹配到最接近尺寸的图片,执行下载。最后,图片将按照定义的宽度进行展示。

在 Chrome 上运行的效果:



需要注意的点:

  • 在有图片缓存的情况下,浏览器可能会加载较大的图片。
  • 在网速慢或者电量低的情况下,浏览器会加载较小的图片。
  • 浏览器知道当前设备是不是高分辨率屏幕,从而决定是否自动加载大图。

通过 srcset,我们渐进增强了原有的图片组件,让不同宽度的屏幕加载更合适尺寸的图片。

进一步加强:picture 标签

MDN 介绍:picture 元素除了在多个不同分辨率的图片间切换,还有几个很重要的响应式图片的应用场景:

响应式图片在大小屏幕分别需要不同的裁切方式,根据浏览器的支持加载不同格式的图片。如 Google 的 Webp 格式会比 JPG 体积小 25%-34%,可以优化网页的性能。

这些问题的标准解决方案是:picture 元素,他作为 img 元素的容器,同时扩展了 srcset 和 sizes 的能力。举个例子:

1
2
3
4
5
6
<picture>
<source
type="image/webp"
srcset="https://www.gstatic.com/webp/gallery/4.sm.webp" />
<img src="https://www.gstatic.com/webp/gallery/4.sm.jpg" alt="tree"
/></picture>

picture 元素包含 source 标签与 img 标签,在支持 picture 与 webp 格式的浏览器下,就会加载 webp 格式的图片;否则忽略将 picture 与 source 元素,应用 img 标签中的 jpg 格式图片。以下是在 browerstack 的测试情况:图一在 IE11 上测试,IE11 不支持 webp 图片,所以加载 jpg。

Chrome85 支持 webp,加载 webp 图片

除此之外,picture 还可以添加 media 属性,结合媒体查询进一步控制浏览器选择图片的逻辑:

1
2
3
4
5
<picture>
<source media="(max-width: 799px)" srcset="elva-480w-close-portrait.jpg" />
<source media="(min-width: 800px)" srcset="elva-800w.jpg" />
<img src="elva-800w.jpg" alt="Chris standing up holding his daughter Elva"
/></picture>

对于开发者来说,我们在不破坏 img 原有能力的基础上,大大增加了操控图片的可能性,也得感谢 HTML 的渐进增强策略。

总结一下

在前端新标准、新技术百花齐放的时代,我们应该积极关注、拥抱新技术,用渐进、可降级的方式去为旧项目赋能焕新 ✅ 其实渐进增强,在我看来是一种严谨、巧妙的思维方式,感受其中的分层、容错、可扩展等设计理念,并尝试运用在日后的编程中吧 🤓

辅助工具

这里就列举几个辅助工具,有助于我们写出有良好兼容性的样式代码:

  • PostCSS 预处理器插件 - Autoprefixer, 根据 CanIUse 网站与你项目所支持的浏览器,自动为你的 CSS 代码添加相应前缀。
  • 静态分析及 Linter- Stylelint,能检查语法错误,也能检查选择符/声明中的有问题的规则。
  • 浏览器测试工具:Browserstack,是一款基于 Web 的实时浏览器测试工具,支持虚拟机测试全平台&浏览器版本,收费 💰
  • HTML/CSS/JS 兼容性查询平台:CanIUse

相关资源

  • 《精通 CSS 高级 Web 标准解决方案》(第三版)——埃米尔·比约克隆德
  • MDN - Responsive images

定个小目标 ⛳️:实现用 VS Code 编辑默认 vue cli 创建的 vue2 项目,对.vue, .js 等文件有错误检查与代码风格检查(lint)、保存时自动修复(autofix)、按照 Prettier 风格进行格式化。

上网搜索 vscode 的 vue 项目 lint 和格式化,大多文章介绍的是其团队自己一揽子规范,很多规则不通用,还有很多过时、无效的配置也一并贴出来(当前为 2021 年),看得我头都大了 🤯

我只想要一个 vue、eslint、prettier 三者结合的官方默认配置,怎么这么麻烦??
于是我决定痛下决心,分别查询各个官网以寻求最简配置 ⚙️

✅Tips: 只关心配置的朋友,直接拉到最后。

问题的解决思路

经过一轮探索与思考,要想实现上述目标,我们其实要解决三个问题:

  1. 代码检查(Lint),包括错误检查与风格检查
  2. 在 Lint 之后做代码自动修复(autofix)
  3. 使用代码格式化工具(formatter)去做 autofix

具体到 VS Code 这款编辑器的配置,我们需要搞定以下概念以及解决方案:

  • 概念
    • Linter 与 Formatter 的区别?
  • VS Code 相关
    • VS Code 如何识别 Vue SFC(.vue 单文件) 文件?
    • VS Code 如何识别项目的 eslint 配置,并在编辑器中提示错误?
    • VS Code 如何在保存时自动修复 eslint 错误?
    • VS Code 如何通过 Prettier 这款格式化工具,来自动修复 eslint 错误?
    • 如何解决 ESLint、Prettier 之间的规则冲突?
  • 项目配置相关
    • vue cli 创建的默认 eslint 配置到底包含了哪些规则?
    • 如何设置 Eslint 的代码错误规则?
    • 如何设置 Eslint 的代码风格 Prettier 的规则?

Linter 与 Formatter 的区别?

我们首先讲下什么是 Linter:
Linter 是语法检查/代码质量检查工具,不同语言都有自己的版本,比如 JavaScript 的 Linter 有 ESLint,JSLint,Python 有 pylint…而 JS 中目前最常用的就是 ESLint,通过配置规则+ESLint 的命令行工具(CLI),可以实现代码检错、风格检错、自动修复错误等能力。

然后是 Formatter:
Formatter 是指代码格式化工具,这类工具会帮你把代码按照规则进行统一的格式化,保证项目代码的美观、统一。前端有代表性的 Formatter 有 Prettier、Beautify 等,他们都可以规范 HTML、CSS、JS 等前端语言。

那么 Linter 与 Formatter 有什么区别呢?参考 Prettier 官网的解释:Prettier vs. Linters · Prettier,我们可以得知 Linter 主要有两类配置:

  • 代码格式化相关(Formatting rules) :
    • eg: max-len, no-mixed-spaces-and-tabs, keyword-spacing, comma-style…
  • 代码质量相关(Code-quality rules):
    • eg no-unused-vars, no-extra-bind, no-implicit-globals, prefer-promise-reject-errors…
      而 Formatter 如 Prettier,只关心代码格式化的规则,完全不关心代码质量的规则。

所以我们应该发挥各自所长,在项目中:

  • 使用 Linter 来做代码质量检查
  • 在 Linter 中配置 Formatter 的规则,使用 Linter 来做代码风格检查
  • 使用 Formatter 来做代码的格式化

💪 实战:从零开始配置

环境说明:

  • Mac OS 10.15.7
  • vs code 1.50.1
  • @vue/cli 4.5.12
  • 有 eslint 配置的 vue2 项目

初始化 vue2 项目

场景是这样的,通过 @vue/cli 创建一个默认的 vue2 项目,包括 eslint、babel 都是最基础的。
在这里插入图片描述创建之后,用没装什么插件的 vs code 打开项目:
在这里插入图片描述

安装必要 vs code 插件

看到.vue 后缀的文件默认是没有语法高亮的,我们需要安装 vetur 插件来支持

安装完之后,vscode 即支持 vue SFC 文件的语法高亮:
在这里插入图片描述
什么??居然没有错误提示,明明 vue/cli 默认帮我们创建了 eslint 配置的!

代码质量错误检测

其实这里要安装 vs code 的插件 ESLint,他能读取项目中的 eslint 配置,并告诉 vs code,vs code 才可以在编辑器中找到错误并提示出来!
在这里插入图片描述
安装完之后,回到App.vue,可以看到错误的代码有红色波浪线提示,鼠标悬浮还有具体错误原因,eslint(no-undef),这里说明 vscode 已经可读取项目的 eslint 配置,并按照 no-undef(不允许存在未定义的变量)这条规则定位错误!注意,这是属于代码质量类错误
在这里插入图片描述
对于 vue 的 template,代码质量类错误也是可以检测出来:
在这里插入图片描述
可以看到这里错误规则来源于eslint-plugin-vue,这是因为 vue cli 创建的 eslint 规则中,包含了两条继承的规则:

  • eslint:recommended: 负责检查 JS 错误(eslint),来自 eslint

  • plugin:vue/essential:负责检查 vue 的错误(eslint-plugin-vue),来自 eslint-plugin-vue
    在这里插入图片描述

代码风格类错误检测

接下来我们测试下 代码风格类错误 ,在 template 和 script 中都打上多余的空格:
在这里插入图片描述
好家伙,这么丑的代码都不报错,还有没有王法了?!为什么呢,我们不是有配置 eslint 规则吗?怀疑到了我们的两条规则:eslint:recommendedplugin:vue/essential,查阅资料发现:

List of available rules - ESLint - Pluggable JavaScript linter 告诉我们:eslint:recommended 只包含了必要的代码质量规则,对于风格规则,完全不在意。

User Guide | eslint-plugin-vue告诉我们:plugin:vue/essential只包含了 vue 在 ESLint 的解析、以及 vue 错误的检查,对于风格规则也是完全不 care。如果我们想加上 vue 的代码风格规范,应该使用 “plugin:vue/recommended” 规则集。
我们只需改一下 eslint 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
+ "plugin:vue/recommended"
- "plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},

再回到编辑器,可以发现 template 的代码风格校验告警了,而 script 中仍然没有提示。
在这里插入图片描述
其实也很容易想到,当前 eslint 的规则集里缺少 JS 的风格校验规则,是 Prettier 登场的时候了! 我们希望 eslint 里能写入 prettier 的规则,这里需要在项目里安装 eslint-plugin-prettier-vue NPM 依赖,根据其 NPM 文档指引,我们在项目里安装:

1
2
3
4
5
6
npm install --save-dev \
eslint-plugin-prettier-vue \
eslint-plugin-vue \
eslint-config-prettier \
eslint \
prettier
  • prettier: 是 prettier 的核心包
  • eslint: 是 eslint 的核心包
  • eslint-plugin-vue:是 vue 的 eslint 插件,包含多个规则集
  • eslint-plugin-prettier-vue:是 prettier fo vue 的 eslint 插件,包含 prettier 风格检测规则集
  • eslint-config-prettier:禁用 linter 与 prettier 之间会产生冲突的部分规则,保证 Prettier 与 ESLint 不冲突

安装完后我们再为 ESLint 添加风格校验规则集:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/recommended",
+ "plugin:prettier-vue/recommended",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},

再回到编辑器,已经可以检测 template, script, style 所有的风格错误了!
在这里插入图片描述

开启 ESLint Autofix

检测出错误,我们该修复错误了,我们可以手动修复(太笨),也可以命令行执行 eslint --fix(太麻烦),有没有更舒服地方式,有,在保存文件时自动修复风格错误!

要开启此特性,我们在 vscode 中,打开 偏好设置preference,点击切换到 JSON 配置文件模式:
在这里插入图片描述
在配置中添加:

1
2
3
4
// 开启代码保存时,eslint执行fix动作
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},

保存后回到编辑器,按ctrl+s保存,可以发现代码根据prettier的规则,一口气格式化好了!
在这里插入图片描述
至此,ESLint 将怀揣 vue 与 js 的质量规则+prettier for vue 的风格规则,帮我们检测代码异常,并自动修复风格问题 🤩

正式宣布,我们完成了在 vscode 下,编辑有 eslint 配置 的 vue2 项目,拥有代码质量检测+代码风格检测以及自动根据 prettier 规则修复的目标了!

其他说明

autofix 的范围

能自动修复的只有代码风格类错误(缩进,换行),代码质量类是没法自动修复的(你当他 AI 吗?自动帮你修 bug)

在这里插入图片描述
如上图,输入 aaa 检测出一条质量问题(no-undef),一条风格问题(缺少;号),点击保存,只会帮你把分号加上 😥
在这里插入图片描述

关于 prettier 风格覆盖

在 eslint 配置中的 rules 字段配即可(更多字段查询https://eslint.org/docs/rules/),例如想覆盖 prettier 的双引号规则为单引号:

1
2
3
4
5
6
7
8
9
10
rules: {
'prettier-vue/prettier': [
'error', // 不符合规则的设为错误,好让eslint修复
{
// Override all options of `prettier` here
// @see https://prettier.io/docs/en/options.html
singleQuote: true,
},
],
},

疑问总结

  • VS Code 相关

    • VS Code 如何识别 Vue SFC(.vue 单文件) 文件?
      • 通过 vetur 插件
    • VS Code 如何识别项目的 eslint 配置,并在编辑器中提示错误?
      • 通过 Eslint 插件
    • VS Code 如何在保存时自动修复 eslint 错误?
      • 偏好配置中开启 :editor.codeActionsOnSave -> "source.fixAll.eslint": true
    • VS Code 如何通过 Prettier 这款格式化工具,来自动修复 eslint 错误?
      • 项目安装 prettier, eslint, eslint-plugin-prettier-vue, eslint-config-prettier
      • eslint 配置中继承 plugin:prettier-vue/recommended 规则
    • 如何解决 ESLint、Prettier 之间的规则冲突?
      • 安装 eslint-config-prettier 依赖,禁用冲突的规则,需要搭配其他包使用,如 eslint-plugin-prettier-vue
  • 项目配置相关

    • vue cli 创建的默认 eslint 配置到底包含了哪些规则?

      • eslint:recommended: 负责检查 JS 错误(eslint),不含风格检测,来自 eslint

      • plugin:vue/essential:负责检查 vue 的必要错误(eslint-plugin-vue),不含风格检测,来自 eslint-plugin-vue

    • 如何设置 Eslint 的代码错误规则?

      • 继承已有的规则集,或通过 rules 手动配置
    • 如何设置 Eslint 的代码风格 Prettier 的规则?

      • 通过plugin:prettier-vue/recommended继承 Prettier 的规则集

配置总结

VS Code 插件:

  • Vetur: 让 vscode 识别 vue SFC 文件,语法高亮、代码检测等
  • ESLint 让 vscode 读取项目 eslint 配置,并在编辑器中提示语法错误、应用 autofix 等

NPM 开发依赖:

  • prettier: 是 prettier 的核心包
  • eslint: 是 eslint 的核心包
  • eslint-plugin-vue:是 vue 的 eslint 插件,包含多个规则集
  • eslint-plugin-prettier-vue:是 prettier fo vue 的 eslint 插件,包含 prettier 风格检测规则集
  • eslint-config-prettier:禁用 linter 与 prettier 之间会产生冲突的部分规则,保证 Prettier 与 ESLint 不冲突

ESLint 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/recommended",
"plugin:prettier-vue/recommended",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {
"prettier-vue/prettier": [
"error",
{
"singleQuote": true
}
]
}
}

VS Code 配置

1
2
3
4
// 开启代码保存时,eslint执行fix动作
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}

以上就是 Vue 项目在 vscode 中 lint/autofix/format 的最简配置 🤓 装备好武器,开始撸代码吧 👨🏻‍💻

参考网站:
Prettier vs. Linters · Prettier: https://prettier.io/docs/en/comparison.html
ESLint rules: https://eslint.org/docs/rules/
eslint-plugin-vue: https://eslint.vuejs.org/user-guide/#usage
eslint-plugin-prettier-vue: https://www.npmjs.com/package/eslint-plugin-prettier-vue

读到博文Hexo常用插件介绍 hexo-symbols-count-time, 发现hexo-symbols-count-time这个好用的插件,能自动统计每篇博客的字数计算估计阅读时间 ,赶紧下载下来玩玩。

1
$ npm install hexo-word-counter

_config.yml 中配置

1
2
3
4
5
6
7
8
9
symbols_count_time:
symbols: true
time: true
total_symbols: true
total_time: true
exclude_codeblock: false
awl: 2
wpm: 300
suffix: "mins."

其中关键参数解释:

  • awl 平均字符长度
    • CN ≈ 2
    • EN ≈ 5
    • RU ≈ 6
  • wpm 每秒阅读的单词数
    • Slow ≈ 200
    • Normal ≈ 275
    • Fast ≈ 350
  • 更多参数

中文的话推荐配置 awl = 2, wpm = 300

在你的Next主题配置文件 _config.next.yml 中配置:

1
2
3
4
5
6
post_meta:
item_text: true

symbols_count_time:
separated_meta: true
item_text_total: false

最后启动博客前记得清除以前的构建数据:

1
2
$ hexo clean
$ npm run server

效果如图:

hexo-plugin-count-time

是不是很有用,继续鼓捣吧!

在网上搜索了一下文章标题,找到一篇博客:在 Next 主题中添加友情链接,这里记录一下步骤,也方便后来者~

找到你的hexo next配置文件,我的next配置文件是根目录下的 _config.next.yml,搜索 Blog rolls 注释,配置下方的 Links 即可:

1
2
3
4
5
6
7
8
9
10
11
12
# Blog rolls
links_settings:
icon: fa fa-user-friends
title: My friends
# Available values: block | inline
layout: block

# 这里添加友链~
links:
Airing的小屋: https://me.ursb.me/
Hedwig: https://hedwig.pub/
Wall-E Paradise: https://www.qirencloud.com/

效果如图:

hexo-add-friend-link.png

快去添加你的好友吧~

Ayy yo what’s up,这里是Brrruski aka. 搞程序的Gatsby👨🏻‍💻

作为第一篇正式对外的文章,想了很久要分享什么主题才会比较有意思,还要易上手,还要接地气🧐 那最近也是看到朋友的博客(基于Hexo搭建的),眼馋里面的markdown代码块、时间线timeline整理以及自动分类与标签词云呀🤩 (天知道我作为程序员是怎么忍受wordpress / ghost默认的markdown支持的🐶

于是我兴致勃勃地鼓捣了一番Hexo博客,在本地已经装饰的漂漂亮亮了✨ 。到了该部署的环节,我一拍脑袋💡,不如摒弃我的小水管server🚰,玩一次地道的云原生部署玩法吧?

经过一早上的踩坑,终于在云上建好属于自己的一亩三分地了,简直比在深圳买了房子装修完还开心呢(醒醒,你哪来的房子

所以我决定,不如就分享一下我是怎么把我的Hexo博客拎到云上去的吧☁️

网站托管(Serving)

让自己的网站能被大家访问到,我们需要进行名为网站托管的一系列操作。我们先简单回答一下关于上网冲浪🏄‍♂️ 的两个灵魂发问:

  1. 🧐 网页的本质是什么?
  2. 👨🏻‍💻 我们为什么能在浏览器上搜到并看到网页?

1. 网页的本质是什么?

网页的本质其实就是一堆按格式书写的字符,即我们常说的HTML(超文本标记语言),文本的内容大概长这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!doctype html>
<html>
<head>
<title>Bruski's Website</title>
</head>
<body>
<h1>My Intro</h1>
<p>Yo whatsup, this is Brrruski aka Coding-Gatsby</p>
<img src="/image/handsome-selfie.jpg" />
<audio src="/audio/handome-bgm.jpg" autoplay />
<video src="/video/me-skating.mp4" controls />
</body>
</html>

看到里面的标签了吗,我们通过书写这类浏览器能识别的标签,来创建内容、引用其他资源,经过浏览器处理后渲染到屏幕上,就变成我们看到的色彩缤纷、能够交互的网页啦。

2. 我们为什么能在浏览器上搜索并看到网页?

设想我们在网上买衣服,我们先按名字搜到某个牌子的衣服,如果找到了提供该衣服的商铺,购买下单,商家处理好之后发货,不久后你能穿上心仪的衣服啦。

同理,当我们在浏览器的地址栏中输入某个网址的时候,浏览器会发出寻找该网址对应服务的请求,如果找到了,提供该网站服务的服务器会把相应的网页内容返回给浏览器,浏览器解析后,网页内容就呈现在我们眼前了🤩

所以一个网页要想能被别人访问到,需要具备满足以下条件:

  1. 一个能找到你网页的地址(IP+端口或者域名)
  2. 一个能处理浏览器的请求,把资源返回去的服务(HTTP Web服务)

所以网站托管做的事情就是:

  1. 📦 把网页等资源上传到某个地方。
  2. 🤖 启动一个能对外提供服务的HTTP Web服务,把我们的网页内容发送给请求方。
  3. ⛳️ 设置这个服务的访问地址,可以是IP+端口,也可以起别名(域名)。

网站托管的方式

通过提供HTTP Web服务进行网站托管的方式大致可以分为:

  1. 借助能处理静态资源的Web Server,如Apache,Nginx。
  2. 由Web Server动态生成HTML内容,如JSP。

由于我们今天的主题是博客托管,我们只讨论第一种,只提供静态资源的方式。

云原生(Cloud Native)

cloud native

首先我们简单快速了解一下云原生这个概念,这里引用了掘金的文章《什么是云原生?这回终于有人讲明白了》——华为云开发者社区

云原生是一种基于云计算技术的构建和运行应用程序的方法,是一套技术体系和方法论。

云原生(CloudNative)是一个组合词,Cloud+Native。

  • Cloud表示应用程序位于云中,而不是传统的数据中心;
  • Native表示应用程序从设计之初即考虑到云的环境;

Pivotal公司的Matt Stine于2013年首次提出云原生(CloudNative)的概念,对云原生的定义总结为4个要点 DevOps + 持续交付 + 微服务 + 容器。一句话概括:原生为云而设计,在云上以最佳姿势运行,充分利用和发挥云平台的弹性+分布式优势。

图片来自掘金-华为云开发者社区的文章

其实结合实际理解,云原生已经具象化地存在于各大云服务厂商的官网中:云服务器、云存储、云容器、DevOps流水线等等,他就是一个船新的软件开发和部署的新生态,帮助开发者们更简单、更快地打造、发布与维护产品🔫

传统网站托管 VS 云原生网站托管

传统网站托管 VS 云原生网站托管

传统模式托管和云原生托管最大的不同在于:资源部署的维度不同

传统模式按硬件资源为单位部署,云托管按功能服务为单位部署,两者带来的服务架构设计、实际操作与效果都有着很大的差别。

传统网站托管: 我们需要自己维护服务器,把文件上传到服务器的具体路径,接着设置Web Server啊,安装证书 ¥&!# ,一顿操作之后才能完成网站托管。

云原生托管:文件打包后,上传到对象存储服务,设置一下存储桶为静态网站托管模式,嗯就可以了,什么域名啊、证书啊全部自动生成。什么,你想让你的网站在全国各地的访问速度都更快一点?那再到网页上点击配置一下CDN加速服务,让它将你的网页分发到全国各个边缘节点中,通过统一的加速域名来访问,用户访问速度杠杠的。

由此可见云原生托管不仅简单便捷、灵活按需、省心省钱,而且服务的效果和质量都比传统模式强😎 既然云托管这么香,那我们赶紧进入实操环节体验一把🤨

实战: 把这只Hexo博客拎到云上吧

⛳️ 明确我们的目标:将Hexo博客项目快速地部署,可以通过HTTPS域名访问。

注意:实战的云服务商选择腾讯云,其他云服务商操作同理

前置准备

  • 安装好gitNodeJS与Npm的环境
  • 一个hexo博客工程,并设置好github仓库关联(其他代码托管服务同理)
  • 一个腾讯云账户

Hexo博客工程可以下载示例工程:

1
2
3
4
# Github
git clone https://github.com/bruceeewong/hexo-demo.git
# Gitee码云
git clone https://gitee.com/bruceeewong/hexo-demo.git

安装npm依赖

1
npm install

预览页面效果

1
npm run server  

通过浏览器访问的本机地址 http://localhost:4000,查看效果:

最后测试打包命令是否正常:

1
npm run build

如果在当前目录下新增public目录,说明打包命令可用,就进入下一步。

开通网站托管相关的云资源

我们需要开通以下资源:

开通对象存储服务&上传静态资源

根据指引,为了存储我们的静态资源,我们需要创建一个存储桶资源。

注意存储桶的访问权限我们先设置为 公有读私有写,方便在没有接入CDN服务前直接访问网页。

接下里来两步直接按默认的来,点击创建。


存储桶创建好之后,我们找到文件列表>上传图片按钮,挨个把本地构建好的public下的文件夹&文件上传(好累,这里只是让你体会一下没有自动化工具的辛苦😂


到这里,我们已经把静态资源都传到存储桶中了,接下来就是设置其访问方式。

开启静态网站托管模式

到这里,网站的托管就完成了,是不是不敢相信?我们把访问节点的URL复制到浏览器试试:

怎么样,我们是不是已经完成了定下的目标:将Hexo博客项目快速地部署,并可以通过HTTPS域名访问。

你能做的,岂止如此

复盘一下刚才的操作,最费时的就是手动上传静态文件了(可能还不如 scp 传到服务器快呢)如何摆脱手动上传文件?解决的办法无疑就是将这部分操作自动化,让我们接入Coding CI DevOps服务,创建一条CI/CD流水线,来拉开跟手工部署的差距。

搜索Coding CI服务:

创建项目,这里只勾选 构建流水线 即可:

选择流水线模板 React + COS(我们要的只是对接COS上传的部分)

代码仓库选择 Github 或 码云(需完成授权),选中我们的仓库;关掉的单元测试选项(我们的hexo项目没有此命令)

接下来的上传COS Bucket配置部分参考下图。注意不勾选“创建后触发构建”,还有一些要配置的地方。


修改一下环境变量>产物的路径名(hexo的产物路径叫public

如果选择的是github,触发的分支注意有可能需要设置为 main(不知道微软为啥要改掉master)

最后点击构建,短短23秒流水线就执行完成了。

这意味着我们以后只需编辑与提交代码,构建和部署上传的工作交给流水线去做就好了😆

🚀 最后一步,配置CDN加速服务

CDN内容分发网络的工作方式大致如下,通过CDN服务的接入,把源站的文件分发至各个边缘节点。

为了能让用户能从最近的CDN节点获取资源,我们应该只对外开放CDN域名,隐藏存储桶的访问路径(可以设置为私有读写)

落到腾讯云这,有两种方案:

  • 使用COS提供的默认CDN加速域名
    • 优点:简单快捷,一键生成带ssl证书的域名
    • 缺点:域名太长
  • 配置自定义加速域名
    • 优点:可定制域名
    • 缺点:配置稍微复杂一些,需要前往CDN控制台配置

限于篇幅有限,就只介绍默认CDN加速域名的配置:

鉴于我们的hexo博客是将markdown生成html文件,为了防止因为缓存而导致用户不能看到最新更新的文章,我们还需要设置CDN的缓存配置。

配好默认CDN域名后,把html文件的CDN节点与浏览器缓存都设为 不缓存



最后,我们通过CDN加速域名 https://hexo-demo-1257249827.file.myqcloud.com/ 来访问一下我们的页面:

快速地打开了,没有任何问题 🚀

总结

至此,我们的Hexo博客就已经正式在云上托管了☁️,开罐啤酒庆祝吧,Hooray🍺

我们不仅完成了基础目标:快速地部署,并可以通过HTTPS域名访问,还通过添加devops服务与CDN服务,让我们的开发与访问速度都提升了🚀

其实能折腾的东西还有很多,像我还做了Hexo网站主题定制、自定义加速域名、强制HTTPS转换、SEO优化、ICP+公安备案等,这些操作留给你们去探索吧,我只是那个把你们带上云端的男人😆

今天就写到这了,还有更多有趣的云原生玩法,一边实践再一遍分享吧🔥

作为一个程序员,要是因为网络原因,不能享受全世界程序员的发展成果,那就真的太亏了=。=

比如 github 看到一个很棒的项目,激动地打下git clone,结果…

1
2
Cloning into 'awesome-project'...
fatal: unable to access 'https://github.com/god/awesome-project.git/': LibreSSL SSL_read: SSL_ERROR_SYSCALL, errno 60

由于网络连不通,又一位少年/少女失去了编程梦想,可惜!可惜!

本文就以作者实际探索,介绍 mac 设置命令行代理的 2 种方案:手动设置变量 与 使用proxychains工具 ,希望能帮到正在阅读本文的你,一起起飞 🚀

✅ 前置准备

首先,你的 mac 得有一个 socks 代理工具,以及可用的 socks 代理服务

本文重点不是这里,but sadly,这里又是全流程的重点,得靠你自己探索了,少年。

作者的 mac 有 socks 代理工具,它自动运行着两种协议的代理端口:socks5 代理端口与配套的 http 代理端口,都可用作请求的代理转发:

1
2
socks5 127.0.0.1 1086
http 127.0.0.1 1087

如果你的 mac 也满足条件,那么继续往下看吧 🤓

🚀 方案一:直接设置终端代理环境变量

这是最简便的方案,无需安装任何工具

每个终端会话都可以设置 $http_proxy$https_proxy环境变量,来控制终端的 http 请求代理。

首先保证当前终端会话没有设置代理:

image.png

我们来试下拉取一个别人分享的 mysql 命令行 gist(即文本笔记 📒),保存为 mysql_gist 文件。

链接:https://gist.github.com/hofmannsven/9164408

1
curl -o mysql_gist https://gist.github.com/hofmannsven/9164408

image.png

嗯,Connection refused,早已被拒绝习惯了 😢

擦干眼泪,我们接下来在命令行输入代理配置(=号左右不要有空格):

1
export http_proxy=http://127.0.0.1:1087; export https_proxy=http://127.0.0.1:1087;

我们再次检查 http 环境变量:

image.png

Yes,生效了,那再下载一遍试试看?

image.png

芜湖!成功把 github gist 下的配置下载下来了!不再是绝望的 Connection refused 或者 3kb/s 了 🚀

别急,还没完

上边的设置只是针对当前会话,如果关掉当前终端再开一个,悲剧依旧会重演 💔

我们可以把配置命令作为别名(alias),这里就叫 proxy 吧,写到.bash_profile里吧,方便以后取用:

1
alias proxy="export http_proxy=http://127.0.0.1:1087;export https_proxy=http://127.0.0.1:1087;"

那么以后在命令行,如果遇到下载不通的情况,可以在原始命令前加上 proxy 即可:

1
proxy curl -o mysql_gist https://gist.github.com/hofmannsven/9164408

image.png

Nicely done!

不建议在 bash_profile 中写死 export http_proxy=xxx,一是显示声明总比隐式声明好,二是节省你代理服务的流量,我们大多数场景使用直连就行了。

取消代理

当 export 了之后,当前终端会话的 http 请求都会走代理,如果要取消,需要手动输入:

1
unset http_proxy https_proxy

这样的方式可行,但还不够方便,不能按需使用,比如我只想某一条命令的请求走代理,而不是全局都走,我们把这个问题留在下面的方案解决吧。

方案总结

优点:

  1. 简单、无依赖:只需设置环境变量http_proxyhttps_proxy,即可为当前终端会话的 http 请求设置代理啦!

缺点:

  1. 无法做到按需使用:设置了代理后,当前会话之后的所有 http 请求都会走代理,如要取消需要手动 unset http_proxyhttps_proxy

🚀 方案二:使用 proxychains 工具

参考文章:

📚 Mac OSX 使用 proxychains-ng

📚 故事:试图不关闭 SIP 在 macOS Sierra 上使用 proxychains-ng

proxychains 是一个 UNIX 程序,可以帮助我们代理终端发出的请求,支持 http, https, socks4, socks5 的代理类型。

ProxyChains is a UNIX program, that hooks network-related libc functions in dynamically linked programs via a preloaded DLL and redirects the connections through SOCKS4a/5 or HTTP proxies.

📦 安装

macOS 可以通过 brew 来安装,不过巨慢(一个怪圈:慢是因为没配好终端代理,而配置终端代理又要到外网下载工具 🤪

1
brew install proxychains-ng

还可以尝试源码安装,这里参考 Mac OSX 使用 proxychains-ng 的安装步骤,我没试验过.

1
2
3
4
5
6
7
$ git clone https://github.com/rofl0r/proxychains-ng

$ cd proxychains-ng
$ ./configure --prefix=/usr --sysconfdir=/etc
$ make
$ make install
$ sudo make install-config # 安装proxychains.conf配置文件

注:mac 上 make install 会报错。因为 Mac 下用 Homebrew 安装的默认为/usr/local/etc/proxychains.conf

解决方法:

1
2
3
4
5
6
7
8
cd configure
vi config.mak
将:
bindir = /usr/bin
libdir = /usr/lib
修改为:
bindir=/usr/local/bin
libdir=/usr/local/lib

🔖 配置 proxychains

vim 打开 /usr/local/etc/proxychains.conf 配置文件:

在[ProxyList]下添加 socks5 代理 (115 行,vim 快捷跳转 :115回车 )

1
2
3
4
5
# 代理端口一定要和shadowsocks中的保持一致
# 如果有不明白的可以查看93~110
[ProxyList]
socks5 127.0.0.1 1086 # 配socks5
# http 127.0.0.1 1087 # 也可以配为http

具体代理类型配了 socks5 和 http 有什么性能上的不同,我不清楚,有知道的大神可以在评论科普一下

🎼 使用前的小插曲

如果不是 macOS 10.11 或更新,则跳过本节 🤪

Mac OSX 使用 proxychains-ng 提到:

macOS 10.11 后下由于开启了 SIP(System Integrity Protection) 会导致命令行下 proxychains-ng 代理的模式失效,如果使用 proxychains-ng 这种简单的方法,就需要先关闭 SIP。

想想苹果设置这个策略肯定是有安全考虑,有没有更优雅地解决方案,网上冲浪找到这篇文章 故事:试图不关闭 SIP 在 macOS Sierra 上使用 proxychains-ng ,他提到 SIP 策略的细节:

根据苹果的 官方说明,以下路径受到保护:

  • /System
  • /usr (不包含 /usr/local)
  • /bin
  • /sbin
  • Apps that are pre-installed with OS X

于是解决方案是,将工具如 curlssh 的二进制文件拷贝到非受保护路径:

我们在当前用户目录下创建自定义目录:

1
mkdir -p ~/local/bin

~/.bash_profile中加入 PATH:

1
export PATH=/Users/xxx/local/bin:$PATH

接着拷贝要用的工具即可

1
2
cp $(which ssh) ~/local/bin/ssh  # 拷贝 ssh
cp $(which curl) ~/local/bin/curl # 拷贝 curl

🚀 使用 proxychains

使用方法巨简单,在原始命令前加上 proxychains4 即可:

1
proxychains4 curl -o mysql_gist https://gist.github.com/hofmannsven/9164408

image.png

成功,并且不污染全局代理配置,舒服。

同样我们为 proxychains 配置 alias,就可以简便地使用了!

1
2
3
# ~/.bash_profile

alias pc='proxychains4'

方案总结

优点:

  1. 使用简便,直接在原始命令前加 proxychains4 前缀即可
  2. 支持多种代理的类型
  3. 按需使用,不污染全局代理配置

缺点:

  1. 安装可能受阻,配置相对麻烦
  2. macOS 10.11 之后的版本需要用小聪明绕考 SIP 策略。

🍳 总结

两种方案都是作者亲测可行,如果有疑问可以下方留言,或者 📧 邮件我 bruskiwang@outlook.com

已经没有什么能阻碍你了,享受全世界的开源代码吧 🚀

明天就是儿童节咯,写篇五月总结作为自己的儿童节礼物吧~如果要给五月设一个关键词的话,我想大概是:浮躁地成长着。
2021年还剩1个月就要过去一半了,虽然没有虚度光阴,但总觉冲劲不足,好多事说到没做到。怎么说呢,就感觉浮躁的心态又卷土重来,不太愿意慢下来做一些事,比如写作,比如反思。在这五月的尾巴,稍微给点时间自己。

五月开头是假期,过得相当开心,先是和KTV四少去长沙探店、美食、听说唱音乐节,再是前往武汉——度过我最灿烂的青春的城市,回母校和KS的老炮儿们再聚首,跳舞、跑卡丁车、校园电驴瞎逛,借用结扎的话:“好久没感觉这样放肆的开心了”。

去了趟武汉结果得了感冒,回来蓝瘦的雅痞,手头拍摄的长沙武汉旅游的素材一直没剪成VLOG,过了感冒后,又不愿意花时间在VLOG上了…一面羡慕着国外的人该工作工作,该玩玩,多会享受生活;一面自己莫名地不愿把时间花一点在这些看起来浪费时间无意义的生活美好上,多奇怪哦。

五月中又是广州之旅,和弯弯、脑子&他朋友一起听M_DSK音乐节,同时打卡融创雪世界,继神农架后第二次滑雪。开着脑子的宝马顶配530,一路吹B一路吃,尝试了单板滑雪,也算是好好陪弯弯玩了一次(话说跟我在一起真的会无聊吧?毕竟我是连自己都不愿意多给享受生活的时间的人)

其实这个月成长上倒是收获蛮多的,比如第一次完成了直播组的活动页+挂件需求,终于能说自己是“直播组”的前端了哈哈哈;比如理财上把微淼前十周的课学完了,初步建立了投资的框架,也把资金都聚拢,真正有计划地去在股票、基金、REITs、债券领域建仓;项目上也是独立完成了许多有意思的尝试:状态机在前端的应用、对React Hook的使用和封装有了更深的认识、尝试用数据结构去完成前端的组件能力;滑板上Ollie高度突破两立,并顺带练会了50-50……其实成长还是可喜的。

困扰我的点在于,月初指定的目标是什么,现在已经忘了…并且每个周末的大好时光,我都有点迷茫该做什么,等到定好计划,又效率很低导致只能完成10-20%…明明觉得时间紧,却把整块时间花在看视频、打游戏上…想努力又觉困乏,说搞个人IP、小程序,也只是雷声大雨点小,最后不了了之。好讨厌的感觉。

究其原因,我觉得有几点吧:休息不好导致精神不好;心态变得浮躁,什么都想做,什么都做不好;注意力的弦不懂松弛有度,学不好、玩不好;计划不甚清晰,不好落地,甚至忘了自己最初的目标;最后就是,执行力太差了。

与其说一直绷着,什么都想做,不如在六月做减法,只专注不超过三个目标,给自己留时间思考、写作。像明天要上线的摩尔庄园的宣传片:“小时候,快乐很简单;长大后,简单很快乐”,或许我的不快乐,就是因为我把心态弄得太复杂了吧。

晚安,明天起,做一个简单的小孩,儿童节快乐。