分类
前端工具链 技术

初试 Webpack 5,解决合并 CSS 时多余的 JS

在长文分享《GitChat: 使用 Webpack 开发企业官网》中,我使用 Webpack 重构了官网开发工具链,效果很好,现在的开发效率也很高。

当时遗留下来一个问题:

  1. 我会把所有页面的 CSS 打包到一起。因为 CSS 尺寸不大,打包到一起利用浏览器缓存,可以改善后续页面的打开速度。
  2. 打包+抽取 CSS+合并,使用 mini-css-extract-plugin,配合 splitChunks。这里遵循的是 官方文档
  3. 打包完成后,除了包含所有样式的 screen.css,还会生成 screen.js,里面包含 webpack 模块的初始化代码,只有一行,几十个字节,但是很重要,必须在其它模块前先完成加载。

于是我就面临一个选择:

  1. 留着这个东西,会略微影响网页打开速度。因为它毕竟是个 JS 文件,要单独加载和运行。
  2. 想办法去掉它,比如利用 html-webpack-plugin 提供的各种钩子,写个插件,把它的内容提取出来,放进 html 或者跟 app.js 合并,等等。但是这个时机很难找,我尝试过一段时间,无果。

最后综合考虑成本和收益,以及看到这个 issue:https://github.com/webpack-contrib/mini-css-extract-plugin/issues/151 之后,我决定再等等,webpack 5 的时候再说。

最近 Webpack 5 终于发布了,我就来试试升级解决这个问题。

升级 Webpack 5

首先升级 Webpack@5 和 webpack-cli@4。新版本虽然底层变化很大,API 层面还是尽量保持了兼容性,所以配置文件可以不改或者小改(理论上)……果然编译就报错了,错误信息很扯:Error: Can't resolve './src',非常莫名其妙。Google 半天无果,后来我突然想起,假如 Webpack 配置信息没加载成功,那么它会默认使用 ./src/index.js 作为入口文件,是不是这个问题呢?

然后搜了半天,没有 dump configuration 的选项,只好仔细看文档。迁移指导里并没有关于配置文件的内容。那就看文档,然后发现,之前 Webpack@4,配置文件可以输出 Object、Promise、或者函数,所以项目的配置文件就是直接输出的 Promise;如今,虽然标题仍然是“Exporting a Promise”,但实际上 module.exports 导出的是函数,只是函数的返回值可以是 Promise。

这里嫌疑很大,所以我就试着改了一下,用函数包裹 Promise,果然可以了。

移除不必要的 screen.js

我会把所有样式都打包到 screen.css 里,这是从 HTML5 boilerplate 学到的。然后 Webpack 就会生成 screen.js,这是个多余的文件,只有坏处,前面说过。

我以为 Webpack@5 之后这个问题就自然解决了,结果看了一眼,还有,只好继续寻找解决方案。之前的关键词忘记了,issue 也没关注,所以多花了不少时间。所幸最终找到了:https://github.com/webpack/webpack/issues/7300#issuecomment-702840962

  1. 升级 webpack@5,webpack-cli@4,自不用说
  2. 升级 mini-css-extract-plugin@1,新版本包含了需要的补丁
  3. 如下修改代码
{
  optimization: {
    splitChunks: {
      cacheGroups: {
        styles: {
          type: 'css/mini-extract', // 重点在这里
          chunks: 'all',
          // If you need this uncomment
          // enforce: true,
        },
      },
    },
  },
}

然后重新编译,screen.js 就不见了,非常爽。

分类
前端

Webpack 5 发布,Chrome 86 开始支持本地文件系统

早上起来日常刷技术新闻,看到两个对我和我厂影响比较大的消息,简单写几句。

Webpack 5 发布

Webpack 5 于今天(北京时间10月11日,美国时间10月10日)发布。这是个大版本更新,会有很多破坏性变更。所以不要轻易升级,否则可能遭遇各种问题。不过,对于像我这种三天两头主动升级依赖的人来说,是否可以平滑升级还未为可知。

这次的升级主要有以下几点变化:

  • 使用持久缓存提升性能
  • 通过替换更好的算法和缺省值,提升长期缓存的效果
  • 使用更好的 Tree Shaking 算法和代码生成方式,减少打包后的提及
  • 提升 web 平台的兼容性
  • 在不引入破坏性变更的前提下,清理掉为实现 v4 功能而遗留下的奇怪的内部架构
  • 现在的破坏性变更是为将来实现更多的功能打好基础,让我们可以在 v5 版停留尽可能长的时间
  • (我加的)多页面 css 合并的时候,不再需要 all.js

更详细的内容大家请移步官网了解:Webpack 5 release (2020-10-10)。英语苦手的同学稍微等两天,估计中译版和各种解读版也会很快问世。

我只有两个建议:

  1. 尽快升级你的项目到 webpack 5
  2. 不要再学/用以前的版本了

Chrome 86 开始支持本地文件系统

很久很久以前,我在上一家公司做创业项目肉大师(Web 创作工具),不小心踏入了 FileSystem API 这个大坑。还留下一篇长文《HTML5的File API应用》,可能是为数不多的中文资料。

时隔八年,如今文件系统 API 终于有了比较靠谱的实现,并且被 Chrome 正式支持。一般来说,Chrome 的统治地位会帮助这个 API 成为事实标准,所以如果你对操作本地文件有需求,那么就可以开始使用这个 API 了,将来它会慢慢普及到其它浏览器上。

简单来说,这个 API 允许用户选择若干文件或者目录,相当于用户主动授权某些文件或目录给当前网站,然后 JS 就可以从文件里读内容,或者把内容写入文件。当然,从安全角度出发,网页不能任意访问文件,一定要用户主动选择。

对于我厂来说,这意味着 QA 产品可以更容易的编辑、保存文件,可以大大提升用户体验。一些 Web 工具也可以直接保存内容到用户本地,感觉网页生态更强大了。

更详细的内容可以阅读这两篇文章:

总结

学无止境,勤为径苦作舟吧。

分类
前端

Boostrap 发布图表库 Bootrap Icons 1.0,该准备切换到 SVG 了

Bootstrap 团队一边开发 Bootstrap 5,一边发布了 Bootstrap Icons 1.0(以下简称 BI)。官方全文见此:Bootstrap Icons v1.0.0。这个版本里包含 1100 个图标,涵盖范围很广,全免费(MIT),可以自由使用、修改。

真正引起我注意的是,这个仓库不再使用 Webfont,而是全部使用 SVG。官方文档 介绍了三种使用方式:

  1. 直接嵌入 SVG 代码
  2. <svg> 中引用 BI 库并配合 <use> 使用
  3. 通过 bootstrap-icons/icons/*.svg 直接使用

如果你非常怀念 Webfont 方式,可以用(3)结合 CSS background-image 的方式近似的模拟。

其实,GitHub 很早以前就开始从 Webfont(Icon font) 向 SVG 切换了,他们还特地写了篇博客解释这件事情:Delivering Octicons with SVG,在里面列举了六个理由:

  1. Icon font 本身存在一些渲染问题,Webkit 内核的浏览器会使其边缘模糊,不利于辨识
  2. Icon font 一般会延后加载,而字体在完成加载之前无法判定大小,所以加载完成之后,需要重新计算布局并且重新进行渲染,造成不必要的性能损耗
  3. 可用性。参考这篇 Slide:Death to Icon Fonts,每十个人当中就有一个人存在阅读障碍,他们很难使用普通字体,必须使用专为他们设计的特殊字体,而这个时候,icon font 就会变成没有意义的方块。对使用阅读器的人更甚:icon font 是一种 hack,通常来说,我们并不会真的写文字进去,所以在他们看起来,图标会变得完全不可读。
  4. 合适的图标尺寸
  5. 方便重构字体文件
  6. 方便制作动画

所以,如今 Bootstrap 放弃 webfont,使用 SVG 作为主要格式,应该说是大势所趋。这几年使用 Webfont,的确很方便,给开发带来很大的效率提升,但与此同时,也存在不少问题:

  1. fontawesome 体积越来越大,现在一组字体要 1M+,加载很耗时,但是拆分非常困难,即使只用几个图标,也需要加载整个字体库
  2. 无法对图标进行稍微复杂的操作,比如双色,或者特定部位的动画
  3. 放大之后,图标比较丑,也不好加工
  4. 前面说过的那些问题

所以未来我和我厂也要慢慢从 webfont 切回 SVG。这样也有助于提升我厂产品的可用性。其实,互联网行业的可用性一直都做得不错,软件开发目前也算是残障人士最有机会获得体面收入的机会,所以,我辈仍需努力呀。

分类
前端

零宽空格的问题与使用

今天有同学在群里提问:

“我的请求路径是这样的”
“路径上面自动多加东西了,好奇怪”

我的第一反应是“零宽空格”,然后该同学试着手动敲了一遍 url 地址,问题果然解决了。看起来,就是因为原来的 URL 里含有零宽空格,直接复制过来,虽然看起来没问题,但是在发起请求时,非标准字符被 encode 之后就出错了。

我们姑且不管为啥他的复制来源里会有零宽空格,聊一聊什么是“零宽空格”,以及“零宽空格”能干什么。

零宽空格定义

零宽空格是空格的一种空格,但是它的宽度为零,即不显示,所以看起来跟没有一样。我们可以在浏览器里启动开发者工具,然后切换到 Console 面板,输入以下代码:

> a = '\u200b'; // 即零宽空格
"" // 其实是有内容的,只是看不到
> a.length
1 // 长度为 1,说明有东西
> encodeURIComponent(a)
"%E2%80%8B" // encode 之后跟截图里一样,破案了

维基百科的解释:

零宽空格(zero-width space, ZWSP)是一种不可打印的Unicode字符,用于可能需要换行处。

它的用法:

在HTML页面中,零宽空格可以替代 <wbr>。但是在一些网页浏览器(例如 Internet Explorer的版本6或以下)不支持零宽空格的功能。

MDN 上没有零宽空格的定义,但是有 <wbr> 的内容。之前我也写过一篇文章:使用 <wbr> 解决长 URL 的换行问题,里面介绍了 HTML 换行算法,以及我选择 <wbr> 的思路,建议大家看一下。

用途一:在特定位置换行

比如一首古诗:

锄禾日当午,汗滴禾下土。谁知盘中餐,粒粒皆辛苦。

我们有时候会希望:

  1. 在宽度足够的时候,放一行
  2. 如果宽度不够,就在标点符号处换行

这个时候,我们可以先设置这段文字 word-break: keep-all,避免在汉字后断句换行;然后在每个标点后面加上零宽空格,这样,一行的时候就不会看到奇怪的空格,而宽度不够的时候,又能根据 white-space 属性正常换行。

用途二:特殊标记

我厂有一个产品,要输出大量日志,包含大量数字。为方便阅读,需要给数字添加千位分隔符;为了方便复制,又希望剪贴板上的是纯数字,不要千位分隔符;但是如果本来就是千位分隔符的,比如在别的软件里格式化的数字,就原样复制,不需要去掉千位分隔符。

这个时候就可以用到零宽空格。我先找出来足够长的数字,然后添加千位分隔符,然后在两头加上零宽空格。这样在用户眼里,看到的是千位分隔过的数字;等他们复制的时候,我就检查两端的零宽空格,如果有的话,就复原数字;如果没有的话,就原样返回。

其它零宽字符

除了零宽空格之外,还有很多零宽字符,可以用来在页面中加入特殊标记,或者实现一些控制功能。大家如果发现 url encode 之后的内容和之前肉眼看到的不符,那么多半是存在零宽字符,可以试着干掉它们,多半问题就能解决。

分类
前端

Font Awesome 发布 Duotone 字体,支持双颜色

无意中看到 Font Awesome 发布了 Duotone 双色字体,感觉很神奇,我还不知道 WebFont 竟然支持多种颜色,于是赶紧看了看。

首先,这是旧闻,实际上,去年,也就是2019年7月30日,Font Awesome 就发布了 Duotone。

关于 Duotone 的介绍,大家可以看这篇官方博客:Introducing Duotone。借张图直观地展现 Duotone 的效果:

然后我就很好奇它们是怎么实现的,据我所知 WebFont 不支持多颜色。研究了半天,发现原来是这样:

2019年5月30日,Font Awesome 发布了 Font Awesome Kits(工具包)。Kits 是一个 JS,用来取代之前直接使用 CSS 引用字体的方式。这样做有几个好处:

  1. 根据字体使用情况按需加载
  2. 可以在客户端管理缓存,避免重复下载图标
  3. 可以自动更新图标,跟 Font Awesome 发布同步
  4. 可以帮助开发者处理可用性问题(关于这一点,GitHub 曾经发布过一篇博客,解释他们为什么要从 Icon Font 切换回 图标:https://github.blog/2016-02-22-delivering-octicons-with-svg/
  5. 可以更快应用新技术,比如 HTTP2,SRI 等

上面几个好处都是 Font Awesome 官博《Introducing Font Awesome Kits》中列出来的,还有一个好处它们没说:可以修改 DOM。Duofont 正是藉此实现双色的。打开开发者工具,我们可以看到,所有图标 <i class="fad fa-*"></i> 都被替换成了 inline <svg>,包含两个图层,自然可以通过 CSS 控制颜色等样式。

这个思路很有意思,布局也一步一步规划的很好,虽然不知道最终效果如何,但还是学到不少东西。

Font Awesome 提供免费版,我一直在用。付费版一年 $99,我觉得还是贵了点,所以一直没用,有需求的同学应该试一下。

分类
技术 招聘

代友招聘:珠海/远程 APISIX 前端

另外我们 OpenResty Inc. 也在持续招聘后端工程师/系统工程师,可选北京、深圳,也支持远程,详情请点击:OpenResty Inc. 招聘后端工程师


支流科技(APISIX)是一家开源基础软件创业公司,围绕着 Apache APISIX 构建下一代微服务,用户包括:中航信、海尔优家、腾讯云、空中云汇、NASA、欧盟数字工厂等 100 多家国内外知名公司和科研机构。

招聘中高级前端工程师,主要负责:

  1. 企业官网开发
  2. Dashboard 型产品开发

主要待遇:

  1. 全职工作,有五险一金
  2. 珠海上班最好,远程亦可
  3. 有(丰厚)期权

需要:

  1. 独立工作能力
  2. 掌握 Web 前端技术
  3. 掌握前端工具链
  4. 熟悉软件工程体系
  5. 至少熟练使用一个前端框架

加分项:

  1. 有开源项目参与经验
  2. 有长期博客
  3. 有远程工作经验
分类
技术 招聘

代友招聘:广州垂直领域知识付费

公司从 2018 年开始做相关的产品研发,到现在已经经历了两轮迭代,目前正在招聘新鲜血液,准备在 2020 年再搏一搏。

招聘链接在这里,长期招聘:https://www.zhipin.com/job_detail/?query=硅碳鼠&city=101280100&industry=&position=,目前主要招聘中级以上的前端和 PHP。

感兴趣的同学可以直接联系,或者找我推荐。

分类
js 技术

使用 JS 模拟元素被 click

需求来自于我厂的 QA 产品。在这个产品中,我需要在浏览器插件里模拟用的各种行为,比如:点击。

click 事件前后发生了什么

最初我觉得点击嘛,能有啥问题,就直接广播 click 事件呗。结果发现并非如此。实际上,一次 click 背后,其实有一大套的逻辑:

  1. 移动到按钮上时,会依次触发 mouseovermouseenter 事件,前者冒泡,后者不冒泡
  2. 鼠标按下时,广播 mousedown 事件
  3. 如果此时其它元素有焦点,那么该元素会先失去焦点,并广播 blur 事件
  4. (下一节补充)
  5. 按钮获得焦点,广播 focus 事件
  6. 如果因此影响到 DOM,那么会等待 DOM 变更
  7. 如果鼠标没有离开按钮,按钮广播 mouseup 事件
  8. 最后广播 click 事件
  9. 在移动设备上,可能会有 300ms 延迟

其实(5)并不准确,每一次事件广播都是独立的 event loop ,所以上面每一步都可能产生 DOM 变化和其它次生变化,也可能导致一些操作或功能不符合预期。比如:

  1. 一个搜索框。输入后自动搜索,结果以 dropdown 形式展示在下面
  2. 点击 dropdown 里的条目可以跳转
  3. 搜索框 blur 时 dropdown 移除

此时,dropdown 里的条目可能无法点击。因为点击时,输入框先 blur,之后 dropdown 隐藏,接下来 mouseup 事件触发在别的元素上,于是不会有 click 事件。

最简单的解决方案,给 blur 事件加延迟,10ms 就够。

(关于上面的事件触发顺序,可以用这个的 codepen 尝试)

click 事件对 change 事件的影响

接下来,回到 QA 产品。

我真正踩的坑,是 change 事件没有按照预期触发。大家知道,Vue 组件通过 $emit('input') 可以更新绑定在 v-model 里的值。触发的时机一般通过侦听 DOM input 或者 change 来决定。如果 change 无法正确触发,那么 QA 产品自然而然就无法正常工作。

实际上,在上一节的 click 逻辑中,第(4)步可以展开为:

如果失去焦点的元素是 <input> 或者 <textarea>,则该元素会广播 change 事件。

模拟 click 动作的代码

最后,演示一下最终代码:

const options = {
  bubbles: true,
  cancelable: true,
  view: window,
};
let event = new MouseEvent('mousedown', options);
elem.dispatchEvent(event);
elem.focus();
event = new MouseEvent('mouseup', options);
elem.dispatchEvent(event);
elem.click();

相关链接

分类
服务器端

树莓派4 安装 OpenResty + PostgreSQL

为了给 OpenResty.org 添加论坛链接,在本地搭建开发环境,选择用树莓派搞。记录一下,以备将来回顾。

分类
css

CSS 网页保持全屏并自动伸长

其实是个小需求,以前也搞过很多次,没想到前几天被坑了一下,记一笔。

以前,如果想要页面在内容任意多的时候都能占满浏览器,可以简单设置:

html,
body {
  height: 100%;
}

但是这样设置,在 Safari 浏览器上,会将 <body> 固定为窗口高度,如果内容多,就会被底部挡住内容。解决方案是 body 高度用 min-height:100%

如果是三行一列的结构,即上面是导航条,下面是脚部,中间随内容自适应,可以用:

body {
  display: flex;
  flex-direction: column;
}

这个时候,不能用 flex-basis,在 Safari 上会失去弹性,也要用 min-height。所以,最终样式大概就是:

html {
  height: 100%;
}

body {
  min-height: 100%;
  display: flex;
  flex-direction: column;
}

#nav {
  height: 4rem;
}
#bottom {
  height: 10rem;
}
#content {
  flex-grow: 1;
  min-height: 40rem;
}

另外,因为基本只有桌面浏览器需要这个功能,所以可以考虑加一个 @media (min-width: 576px) 做限制。