分类
技术 篮球

今天打篮球(二)

时间过得真快,一晃一周过去,今天又是篮球活动日。

分类
git 技术

Git 操作特定分支的小技巧

加入新的开发团队之后,难免有一些东西不适应。比如,我团队要求把所有代码,不分前后端放到一个仓库里;而且,还都不删分支,于是现在代码仓库有 3k+ 分支……

我很受不了,clone 一次代码大几个 G,我感觉我的固态硬盘在呻吟……

于是简单研究了一下,只 clone 特定分支的技巧,记录如下。

clone 特定分支最近一次提交

git clone -b 分支名 --single-branch --depth=1 仓库地址

上面这段命令可以只 clone 一个分支、最近一次提交,速度非常快。缺点是会损失代码提交记录,如果你要回顾历史代码,可能会比较麻烦。不过具体到我负责的这个项目,它的历史记录乱成一张蜘蛛网,git blame 出来全是各种 merged branch,实在没啥看头。

fetch 特定分支最近一次提交

只 clone 一个分支可能不太够,比如我要看别人的提交,就要把别人的分支拉下来。这个时候,如果你用上面的方式 clone 代码,再执行 git fetch 是拉不到其它分支的,因为本地 git 配置里就不存在别的分支。

所以这时候要:

git fetch --depth 1 origin 远程分支:本地分支
git checkout 本地分支

修改 .git/config

除了上面的方法,你还可以修改 .git/config。打开 .git/config,你会看到这样的配置:

[remote "origin"]
        url = git@github.com:username/repo.git
        fetch = +refs/heads/master:refs/remotes/origin/master

如果直接 git clone 不加参数得到的仓库,“master”那里会是 *。此时,你可以把想要增加的分支列在后面,然后照常 git fetchgit checkout 即可。修改后的配置文件大约是:

[remote "origin"]
        url = git@github.com:username/repo.git
        fetch = +refs/heads/master:refs/remotes/origin/master
        fetch = +refs/heads/bugfix:refs/remotes/origin/bugfix
        fetch = +refs/heads/release:refs/remotes/origin/release

此时你就可以只操作 masterbugfixrelease 三个分支了,其它分支不会干扰你。

分类
前端工具链

偶然发现 babel@6 + babel-preset-es2015 的 bug

这两天尝试做一套解决方案,能够只编译一套 ES2015+ 代码,在现代浏览器就正常使用,在 IE11 自动切换到 babel-standalone 实时编译。

然后偶然发现 babel@6 的一个 bug,如下:

class T {
  foo() {
    const {hasOn: o} = (() => {
      for (let e = 0; e < 1; e++) {
        if (o = 1, 2 >= o) return null;
      }
      var o;
    })() || {};
  }
}

t = new T();
t.foo();

上面这段代码其实是符合语法的。看起来,L3 使用 const 声明了变量 o,然后在 L5 又再次试图给它赋值,似乎有修改常量之嫌。但其实,因为 L5 在另外一个块域里,而且 L7 var o; 会产生变量提升,所以这个赋值操作的是 var o 声明的局部变量。

如果你把它放在 V8 里,比如 node.js 或者 Chrome 浏览器,它就能正常执行;如果你用 babel@6 + babel-preset-es2015 转译,就会报错,说 o is read only

产生这个错误的原因是 babel-plugin-transform-es2015-classes 解析上面这段代码时存在错误,导致 babel-plugin-check-es2015-constants 认为常量被修改。不过我也只查到这一步,暂时不知道怎么修复这个 bug,也不知道怎么在 babel@7 里检查这个问题。有兴趣的同学可以试一下。

分类
篮球

今天打篮球

离上次打篮球不知道过了多久,少说有三年吧。金山广州有篮球群,每周都有活动,所以我就跟着去打了。今天是数年来的第一次。

分类
招聘

从招聘方的角度理解求职

0. 起因

前些日子,有位同学找我咨询求职问题。他本科专业其实不错,但是第一份工作没找好,所以只好报了家培训班,想社招之路走得更稳一些。结业之前,他担心培训班就业辅导不够,也找了几位外部导师帮忙。其中就包括我。

熟悉我的人都知道,我比较反对报培训班,因为:

  1. 性价比太低。
  2. 培训班里真正有实战经验的很少,大部分都是纸上谈兵。

这次咨询也基本印证了我的观点。所以我想再分享一下,到底招聘方的情况是怎样的,他们有哪些需求,准备简历的时候应该注意什么,面试的时候又应该注意什么。

1. 企业要招人

1.0 产生需求

某天,老板想做个产品。我们假设公司里已经有靠谱的技术团队和技术管理,那么,技术主管很快就会梳理需求,落实到岗位和个人,如果他发现,目前的人力无法应对满足需求,就会启动招聘。

1.1 产生岗位描述(JD)

岗位描述(简称 JD),即这个岗位职责是什么、需要哪些职业技能、工作地点和福利待遇等,是招聘前必须准备好的物料。

产品确定、需求确定,所以岗位需求和岗位职责基本也是确定的。此时技术主管一般会找来以前的 JD,改一改职责和需求,完成新的 JD。然后 ta 会把 JD 发给招聘负责人(多半是 HR),上传到招聘网站、开发者社区、公司招聘库等等。

比如 OpenResty Inc. 招聘前端工程师 就是一份很典型的 JD。

1.2 筛简历

一段时间后,HR、技术主管会从各种渠道获得一大批简历。于是他们就要从这一大堆简历快速筛选出能满足岗位需要的候选人。

怎么筛呢?一般是关键词。拿我某项工作来举例,我当时负责开发的 Showman 产品,是浏览器插件,界面部分采用 Vue,需要兼容 Puppeteer API。那么在我招聘的时候,就会很重视候选人这几方面的经历。因为匹配度越高,他能顺利接手工作、快速度过适应期的可能性就越大。

找到关键词之后,HR 可能会接受简历,转发给岗位负责人,即初筛。而岗位负责人则会结合候选人的教育经历、职业经理、项目经验等,判断这些关键词的可靠性与含金量,最终再筛选出其中最合适的一些人进入面试环节。

1.3 面试

招聘方从成百上千的简历中筛选出了若干的“看起来”比较合适的候选人,接下来,就要约过来面试聊聊,看看哪些人真正合适。

面试的过程,其实是验证简历的过程。张三简历里说,他开发过浏览器插件,但是这个插件有多少用户、运营过多久,在开发过程中解决过多少问题,并没有写得非常详细;即使写得很细,也未必跟他有关系。所以作为面试官,我就需要再面试中弄清楚,这项职业经历对他来说,是加分还是减分,甚至是一票否决。

除了验证简历,面试还可以让招聘方初步了解候选人的非职业特性,比如沟通能力、语言概括能力、接人待物能力,等等。这些东西在招聘中占比不大,不过在诸多候选人的技术能力拉不开差距的时候,也会是必要的判断依据。

1.4 多轮面试

单场面试时间有限,通常做不到面面俱到。所以面试通常不止一轮。每个面试官关注的点不一样,比如 A 关注项目经验、B 关注代码实现能力、C 关注职业规划等。最终 ABC 的观点会汇总到一起,给每个候选人一个总评。

接下来,面试进行到一定阶段,总评过关的人数积累到一定数目,公司就会给其中比较出色的几位发 offer,然后就是入职试用转正,略过不谈。

2. 我们应该怎么做

2.1 简历要有针对性

正如前文所说,企业收到的简历量很大,筛简历的压力也很大。通常来说,招聘方负责人会花在每个简历上的时间很短,基本上就扫一遍,有必要的关键词就再看仔细一点,没有就直接扔掉。

很多同学只做一套简历,投所有企业所有岗位。在这种情况下希望渺茫:A 公司招数据可视化,B 公司招小程序开发,C 公司主做移动端,三家公司的岗位要求差异很大,一套简历很难覆盖不同公司的不同需求。

有同学说那我简历里把所有技术关键词都写上可以么?很遗憾,招聘方也不是傻子,你的工作年限和项目经验、技术专长不符,也难逃直接扔掉的命运。或者简历太长,重点不突出,要费力查找关键词,招聘方多半也会直接扔掉。

当然,为每家公司单独准备一份简历成本太高,也不现实。所以,推荐的做法是:

  1. 准备一份比较通用的简历,不要太长,写上自己最擅长的东西,最能凸显自己特长的经历,用来海投,碰运气。
  2. 对自己比较中意、比较重视的公司,单独准备简历,突出该公司招聘岗位需要的知识、技能、项目经验等,专门投递。
  3. 同时精投的公司不宜过多,避免面试扎堆,影响准备时间。

2.1.1 特殊技巧

(我再想想要不要写……)

2.2 简历要尽可能真实,面试前也要做好准备

前文也提过,面试是验证简历的过程。能够走到面试这一步,说明招聘方认为候选人的履历可以满足岗位需求,接下来就是要对简历验真,以及判断候选人的发展潜力和综合排名。

所以简历里的内容可以适度美化,但一定不要做假。为什么呢?面试时间有限,面试官不会针对简历中的每一项进行审查,而是对自己关注的、擅长的领域盘问,也就是大概抽查 20% 的内容,给整份简历打分。

如果候选人简历有做假,面试时被面试官发现,ta 可不会只扣这一条的分,而是整份简历都显得不可靠,都要扣分。即使剩下的部分都是真实的,但是没有被抽检到,就不会改变面试官的判断。甚至,如果连问两条都不符合预期,可能就会直接中止面试。

面试前的准备也要尽量做好。比如你参考上一条建议,优化了简历内容,突出以前的某段项目经历以匹配特定关键词。那么这个时候最好回顾一下,审视一下当时的技术方案有何得失,哪里值得改进,自己的工作有何值得称道的地方,等等。然后搜搜看该领域目前的发展状况。不要面试官问起来,这个想不起那个不知道,好好的加分项直接被干成减分项。

2.3 项目经验要择优表现

通常来说,简历不要太长,因为筛简历的人不会有那么多时间认真仔细的读。有人说不要超过一页,我觉得不用这么极端,但是三页绝对是极限了。短简历更容易突出重点、突出优势。

所以通常来说,我们要对项目经验进行取舍,不要把每个项目都罗列出来,即使是海投简历,也要选择会增加自己竞争优势的项目,写到简历里。

比如,某位培训班同学的简历里,介绍 ta 做过的某个全栈项目,混用 MySQL + MangoDB,koa2 + express。这就很奇怪,这两组技术产品定位冲突,在实际生产中,几乎不会混用。所以这样的项目经验就只能减分。还有,在一些同学的简历里看到,他们这两年都还在用 easyui、jquery 做项目,我当然不否认这些传统框架也能完成产品需求,但是写到简历里,就不要指望它们还能帮你加分。

如果,极端情况,你的项目经验都很差,那我建议你抓紧时间参与一些能给自己加分的项目,不管是开源的、商用的、独立项目都可以,别憨憨的写一堆没价值的项目,然后抱怨拿不到面试机会。

2.4 职业经历/项目经历要能让面试官感知到你的优势

很多同学写简历写到职业经历和项目经历时会写的特别平铺直叙、没有重点、缺少关键信息。比如,做过 OA 系统,就把自己负责的模块都列举一下,或者把流程叙述一遍。这样的信息对面试官来说毫无意义:OA 系统大部分人都用过或了解,公司内的工作流程基本也大同小异。这样的简历基本难逃扫一眼直接丢掉的命运。

提升面试官感知有三个方法:

  1. 列数字。比如:“我使用了xx方法,使得首页打开速度提升了 40%”,或者“我们把测试覆盖率做到 100%”,等等,让面试官一下就能形成具体认知
  2. 举方案。有一些成型的优秀方案,但不是套个库就行,实施起来有一些难度,就很适合写到这里。比如:应用设计模式、分布式多线程、灰度增量,等等。如果能配合上面的“列数字”方法,效果更佳
  3. 引用其他人的评价。有时候我们想写数字,但是无奈不负责统计,拿不到数字,乱写又担心被面试官挑战,也可以写别人的评价,比如:“产品总监评价此功能至少带来 5% 的总访问量提升”

提醒大家,如果你要用这个方法吸引招聘方注意,那就要做好相应的准备,避免偷鸡不成蚀把米。比如,你要写数字,就要知道数字是怎么统计出来的;你要写方案,就要写有说服力的方案。

2.5 面试时找机会突出自己的优势

目前来看,招聘方占据优势,是买方市场。招聘方会在众多候选人当中选择最好的几个发出 offer。所以我们在面试时,不仅要证明简历上写的都是真实可靠的,还要抓住机会,展现自己的优势。不然,平平淡淡拿到一个中等分数,可能在招聘方的眼里,只是一块“鸡肋”。

别的岗位就不说了,只说技术开发岗。

通常来说,技术研发,简历外,通常需要在面试中展现的能力主要有:

  1. 沟通能力
  2. 业务理解能力
  3. 主动学习的习惯和能力
  4. 积极思考的习惯
  5. 解决问题的韧性和积极性

这些条件可能不足以形成一票通过/否决,但会影响你在同一批候选人里的排名,也要尽量好好表现。另外,不同的公司、职级、岗位,可能也有不同的权重,我就不详细解说了。

分类
职业

希望大家记住,求职面试也是双向选择

前几天在思否上答题,看到这个问题:

面试被问到:let不能在相同作用域重复声明的底层原理是什么?

简单总结一下:题主去参加面试,面试官问他 letconst 不能在同一作用域里重复声明的底层原理是什么。题主答不出来,面试官得意地教训他:“做一个合格的 JavaScript 开发者一定要掌握编程语言的基础原理。”

看得我狠从心头起,恶从胆边生。怒答道:

这个问题不好。

let、const 不能在相同作用域重复声明变量是规定,规范就是这么制定的,开发 JS 引擎的程序员就这么执行了。就像公司规定 9 点上班,员工 9 点之前就要到办公室打卡一样。没什么底层原理,我想怎么实现就怎么实现,想坐公交就坐公交、想开车就开车、想走路就走路。

也许某个实现方案会好一些,有些会差一些,但对于 JS 程序员而言,都是毫不相关的领域。如果想问,可以先问一下,候选人答不出来,面试官就应该把实现原理说出来,请候选人分析其中的道理。

这个面试官多半就是偶然看到篇讲解这方面知识的文章,奉为瑰宝,到处拿来卡人,跟孔乙己一样的。所以,没过是好事。题主收拾心情,再去面别家就是。

谁都年轻过,我当年也喜欢在工作中积攒一些很偏门的小知识小技巧,面试的时候拿来问别人,一旦候选人不知道,就沾沾自喜。随着后面知识渐广、经验渐丰,发现这种做法真的是愚蠢至极。开发领域的知识技能浩如烟海,谁都不敢说自己全知全能。关键问题在于,面对一个未知领域,一个没遇到过的问题,我们怎么能解决它,多快能解决它。

对应到面试,我们要确认的是:

  1. 候选人是否具备这个岗位的基本能力
  2. 候选人面对陌生问题时,能提出什么样可行有效的解决方案

如果面试官不理解这一点,做了蠢事还沾沾自喜,那么这个岗位的未来也岌岌可危,所以不去也罢。


我还在 V2 上看到这个帖子:

当招聘信息上的薪资与他的要求明显不符合时,通常如何提出自己的薪资要求

我猜楼主是个小朋友,一副天真烂漫的样子。看到一些企业的招聘启示,岗位要求和岗位薪资不太对的上,就想去面试,期望在面试的时候纠正对方,即拿到更多的 offer,还得到理想的薪资。

这里的核心问题在于,楼主假设所有公司都是靠谱的,他们只是一时糊涂,标错了薪资,而已。

企业招人,都有很明确的想法和定位。企业当然可能对市场、对招聘环境理解有误,导致落到招聘启事的纸面上,让行内人觉得奇怪。但这正说明这家企业有问题。他们或者对技术人员的价值理解有误、或者根本不清楚自己的需求应该怎么满足。这些问题都不是一场简单的面试能掰扯清楚的,更何况我们只是个陌生的候选人。

所以最简单也是最好的选择就是放弃这家公司。


简单总结一下,面试求职,是双向选择。不仅公司在我们几个候选人之间挑选,我们也要在几家候选公司之间挑选。要选择最靠谱、最有价值的公司,给自己的履历加分,让自己越走越好。

至于那些不靠谱的公司、不靠谱的团队、不靠谱的领导,早点认出来,跟他们说再见,别让他们耽误了自己的美好前程。

分类
技术

教程:搭建 SonarQube 服务并测试自己的项目

之前分享过 SonarQube 的使用体验,今天写篇教程介绍下如何部署自己的服务器实例,及测试自己的项目。

官方文档写得很详细,推荐大家阅读学习:Install the Server。这篇博文我主要分享实操的经验教训。

0. 安装 Docker

2021年即将过去,建议大家,部署各种服务能用 Docker 最好都用 Docker。省时省力,集中精力写业务代码。

Windows、macOS 用户推荐使用 Docker hub,Linux 用户可以命令行安装 Docker Engine——你都用 Linux 了,应该不需要我详细解释,参考 官方文档 即可。

1. 部署 SonarQube

1.1 保存配置文件

有了 docker 之后,部署就很简单。先找个地方,将下面的内容保存成配置文件 sonarqube.yml

version: "3"

services:
  sonarqube:
    image: sonarqube:community
    depends_on:
      - db
    environment:
      SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
      SONAR_JDBC_USERNAME: sonar
      SONAR_JDBC_PASSWORD: sonar
    volumes:
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_extensions:/opt/sonarqube/extensions
      - sonarqube_logs:/opt/sonarqube/logs
    ports:
      - "9000:9000"
  db:
    image: postgres:12
    environment:
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: sonar
    volumes:
      - postgresql:/var/lib/postgresql
      - postgresql_data:/var/lib/postgresql/data

volumes:
  sonarqube_data:
  sonarqube_extensions:
  sonarqube_logs:
  postgresql:
  postgresql_data:

注意第5行,“sonarqube:community”,是镜像的注册名称,默认是最新版本。如果你想使用长期支持(LTS)版,可以将其改成 sonarqube:lts-community。一般来说,LTS 版本新功能会落后于最新版本,但是会更稳定,安全系数也更高。目前这个时间点,如果你想部署中文版本,必须选择 LTS,也就是 8.9.x 版本,因为中文翻译插件最高只支持 9.1,在最新版 9.2 下会报错。

1.2 拉取镜像:

docker-compose -f sonarqube.yml pull

1.3 启动容器:

docker-compose -f sonarqube.yml up

第一次启动建议只用 up,方便看日志,判断状态。我的经验,不管是 Windows + WSL,还是云上小水管,都会报告:max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144] 错误。这是虚拟机内存配置不够导致的,所以调高。

Linux

  1. 永久修改配置:编辑 /etc/sysctl,增加 vm.max_map_count = 262144
  2. 修改当前环境:执行 sysctl -w vm.max_map_count=262144
  3. 重启 docker 服务:systemctl restart docker

WSL

先进入 docker 环境:

wsl -d docker-desktop

然后

sysctl -w vm.max_map_count=262144

1.4 启动为服务

第一次启动会自动创建 Volumes(相当于磁盘),安装软件、部署各种功能、执行初始化操作等,会比较久。最后停下来时,如果 DB 服务和 SonarQube 服务都成功启动,就算完成。

默认服务端口是 9000,启动浏览器访问 localhost:9000,此时应该可以看到下图所示的登录界面。

默认用户名密码是 admin/admin,登录后会被要求修改。如果要对公网提供服务,建议尽量改得复杂一些。

如果刚才启动 SonarQube 的时候只用了 up,建议先停掉,改为 up -d 再次启动为后台服务。

2.0 配置 nginx 反向代理

Docker 启动的服务只能内部访问,所以我们通常需要把它开放到外面,这里还是用 nginx 做反向代理:

server {
    server_name sonarqube.meathill.com;
    root /var/www/sonarqube;

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $http_host;
        proxy_pass http://127.0.0.1:9000;

    }
}

3.0 安装中文包

SonarQube 所有语言包都是以插件形式存在的,所以要汉化 SonarQube 只需要安装中文插件即可。

登录 SonarQube 后,进入 Administration > Marketplace,搜索 Chinese,安装简体中文包即可。安装之后需要重启,请耐心等待界面变化,按照提示一步一步走。

SonarQube 会根据浏览器语言配置自动选择语言,没有留出显式选项。

4.0 分析 FastTest

全部配置完成之后,可以开始第一次分析了。

首先我们新增项目(Add Project),选择手动(Manually)即可。当然,如果你是团队内部使用,也可以配置为以 GitHub、GitLab 为代码仓库。

输入 token 名称,创建一个 token,将 token 复制下来。

接下来你可以选择使用打包好的二进制文件,那就点击“继续”(Continue)然后按照提示下载二进制文件,再输入命令执行即可。我比较喜欢用 Docker,所以推荐用下面的命令,会自动拉取镜像并且执行:

docker run \
    --rm \
    -e SONAR_HOST_URL="http://${SONARQUBE_URL}" \
    -e SONAR_LOGIN="${myAuthenticationToken}" \
    -v "/path/to/repo:/usr/src" \
    sonarsource/sonar-scanner-cli

上面的参数中:

  • SONAR_HOST_URL 即部署后提供服务的地址
  • SONAR_LOGIN 即上一步中复制的 token
  • /path/to/repo 需要是绝对路径,如果是 Windows 系统,正常使用 \ 即可,比如 C:\works\fasttest,不需要转义
  • 如果你的 SonarQube 跑在 docker 里,又使用 docker 启动分析,那么需要增加 --network=host 参数,不然两个 container 之间无法互相访问

最终得到的分析结果大约是这样:

报告内容我这次就不详细解析了,回头慢慢分享。

总结

好,SonarQube 的部署和分析过程就分享完了,建议大家都学习使用一下,能学到很多安全性、可维护性的知识,能很好的提升团队的代码质量。如果绑定到 GitHub、GitLab 等代码仓库,还可以自动分析 PR、MR,成为工作流程的一部分。

感兴趣又不想自己动手(或没条件)的同学可以联系我,我可以在我的服务器上给你开通账号试用。

注意:不要上传公司的业务代码!

注意:不要上传公司的业务代码!

注意:不要上传公司的业务代码!

分类
前端工具链

使用 webpack-rpc-inline-plugin 打包内联函数体

使用 Puppeteer 的时候,我们常常要使用 page.evaluate() 或者 page.evaluateHandle() 函数到目标页面的环境里执行一段代码。

如果是简单的代码,比如返回页面标题、改变某个节点的属性,那么直接写在 .evaluate() 里面就行了;但实际生产中,尤其是前厂的 Showman 产品里,要执行的函数往往非常复杂,经常需要组合多个函数:

page.evaluate(() => {
  function func1() {}
  function func2() {}
  // ...
  function funcN() {}

  func1();
  func2();
  // ...
  funcN();
});

这种场景下,我们必须用上面这种写法,而不是下面这种我们更熟悉的写法:

import func1 from './func1';
import func2 from './func2';
// ...
import funcN from './funcN';

page.evaluate(() => {
  func1();
  func2();
  // ...
  funcN();
});

因为被执行的函数会被转换成字符串,传输到目标环境,然后重新实例化成函数,再执行。所以上面这种写法,引擎会在全局环境下查找需要的函数,而那些函数都没传递过去,就会找不到。

如果开发时按照方案一组织代码,会遇到几个问题:

  1. 子函数放在主函数体内部,不方便独立开发、调试、测试
  2. 每个主函数内部都需要写死子函数,不方便共享复用

所以我就想从工具链入手,写一个专用工具,可以继续用方案二的形式组织代码,但是编译打包之后,就恢复到方案一的状态。

我选择了 webpack 插件,原因有二:

  1. 我比较熟悉 webpack
  2. 这种情况不能用 loader

最后我选择在 compilation.afterProcessAssets 钩子处理 JS。此时 JS 已经打包了所有资源,并且经过 terser 压缩。所以我会先将 bundle 解开(直接用 string.substring),然后 return webpack 对象,从中找到目标函数替换。

具体的代码在 GitHub 仓库里,我就不详细解释了(困了),感兴趣的同学可以看看。

欢迎需要在 rpc 环境下执行 JS 的同学使用,欢迎反馈需求和问题。

分类
前端工具链

babel@6 升级到 babel@7,兼容性代码膨胀的原因

最近尝试把厂里项目的依赖从 babel@6 升级到 babel@7,发现打包之后体积大了很多。于是打开 webpack-bundle-analyzer,果然大部分代码都是 corejs 引入的,项目本身的逻辑只占少部分。

从报告来看,虽然目标浏览器的版本均高于 Promise 的启动版本(比如 Chrome 32),但 es.promise 仍然会被打包进来。于是以 es.promise 为突破口开始分析,找到答案:因为 JavaScript 引擎 V8 直至 v6.6 版本时,在 Promise 实现方面都存在严重 bug,所以 babel@7 保守地选择 Chrome 66/67 作为临界点。

想来其它体积多半也是这么加上来的,就不再一个一个排查了。 ​​​

这就很难处理。不升级吧,新特性兼容不了;升级吧,包体积变大,公司上层又未必同意。

下一步试试 esbuild 吧,或者回头手动打包一套 polyfill。目前设想的方案是:

  1. 用 babel 之类的工具提取出所有特性
  2. 根据 caniuse 生成必须的特性列表
  3. 像上面说的 Promise,因为 bug 所以必须兼容,我们就不考虑了,可以反过来加一条 eslint 规则规避
  4. 最终生成新的 polyfill 打包进来
分类
前端工具链

使用 caniuse-lite 检查目标浏览器的特性支持情况

起因

之前得知 loading="lazy" 新特性,正巧在学习如何使用 html-webpack-plugin,于是就写了个 lazyload-webpack-plugin,可以给页面里所有 <img><iframe> 加上 loading="lazy" 属性,以启动原生 lazyload。

不过当初写得很简单,只会不分青红皂白加属性,甚至可能会覆盖已有的 loading="eager" 属性,引发 bug。所以这几天就想找时间升级一下:

  1. 不再覆盖 loading 属性
  2. 根据 browserslist 得到的目标浏览器器列表采取不同策略
    1. 支持 loading="lazy" 就延续之前的做法
    2. 不支持的话,用 data-src 替换 src,然后在页面里根据浏览器特性处理

caniuse-lite

没想到这个需求还挺难满足,找了一圈竟然没有成型的教程,只好自己摸索一下,还好并不复杂。

以下代码实现了根据环境配置检查目标浏览器是否支持 loading="lazy" 的功能。我用在新版本的 lazyload-webpack-plugin 中,现在可以实现前面说的功能了。

// caniuse-lite 是官方提供的 caniuse 仓库封装,方便我们查询特性支持
const lite = require('caniuse-lite');
const browserslist = require('browserslist');
// `features` 是特性支持列表,`feature()` 可以将其转换成好用的 json 格式
const {features, feature: unpackFeature} = lite;

// 这一步,用特性名 'loading-lazy-attr' 获取支持列表
const feature = unpackFeature(features['loading-lazy-attr']);
// 直接声明 browserslist 实例,它会自动查找本地 `.browserslistrc` 或环境变量 `BROWSERSLIST` 来生成浏览器列表
const browsers = browserslist();
const {stats} = feature;
// 遍历浏览器列表,根据名称、版本验证对 `loading="lazy"` 的支持情况
const isSupport = browsers.every(browser => {
  const [name, version] = browser.split(' ');
  const browserData = stats[name];
  const isSupport = browserData && browserData[version] === 'y';
  if (!isSupport) {
    console.log(`[lazyload-webpack-plugin] target browser '${browser}' DOES NOT supported \`loading="lazy"\`.`);
  }
  return isSupport;
});

module.exports = isSupport;

问题

现在的情况是,如果知道特性名称(如“loading-lazy-attr”),可以判断目标浏览器是否支持;但是如果不知道准确名称,就没法判断。如果我想在项目当中使用,比如检查当前代码仓库用到哪些特性不被目标浏览器兼容,并生成 polyfill 套件,就很难操作。

有待进一步学习。

自荐

欢迎有制作静态页面需求的同学使用 lazyload-webpack-plugin<img><iframe> 添加 loading="lazy"。关于使用 webpack 制作多页面站点的经验分享,可以阅读我的这篇文章《使用 Webpack 开发多页面站点》。

有任何问题、意见、建议,欢迎通过各种方式提给我。