Rollup plugin 可能会为 virtual module id 添加 \0
Rollup 约定如果一个插件使用了 virtual module,则它的 id 需要以 \0 开头。
我发现这个,是因为在配置 manualChunks 时,出现了一些莫名其妙的问题,比如本是 dynamic import 的 bundle,却被提前 import 了。
在我的项目里,这是由于 commonjs 插件的 commonjsHelpers.js 被打包在了这个毫不相关的 bundle 里。而根本原因是我使用 id === 'commonjsHelpers.js' 比较 id,但实际的 id 是 '\0commonjsHelpers.js'。
这意味着,最好使用 id.includes('commonjsHelpers.js') 比较,或至少是 id.endsWith('commonjsHelpers.js') 而不是 startsWith。
话虽如此,某些插件创建的,如以 react/ 或 vite/ 开头的 module,依旧可以使用 startsWith,因为它们并不包含 \0。
#experience
Rollup 约定如果一个插件使用了 virtual module,则它的 id 需要以 \0 开头。
我发现这个,是因为在配置 manualChunks 时,出现了一些莫名其妙的问题,比如本是 dynamic import 的 bundle,却被提前 import 了。
在我的项目里,这是由于 commonjs 插件的 commonjsHelpers.js 被打包在了这个毫不相关的 bundle 里。而根本原因是我使用 id === 'commonjsHelpers.js' 比较 id,但实际的 id 是 '\0commonjsHelpers.js'。
这意味着,最好使用 id.includes('commonjsHelpers.js') 比较,或至少是 id.endsWith('commonjsHelpers.js') 而不是 startsWith。
话虽如此,某些插件创建的,如以 react/ 或 vite/ 开头的 module,依旧可以使用 startsWith,因为它们并不包含 \0。
#experience
Rollup 插件开发:让 Service Worker 中的 import 支持 Firefox
我有一个 packet module,它是一个实现了 binary communication protocol 的 common module,这意味着它不只在 SW 中使用,还会在通常的 browser context 中使用,因此可以将该 module 打包为独立的 bundle,以便能在这两个不同的地方分别 import 它。
但是,import statement in workers 在 Chrome、Edge、Safari 都得到了支持,而 Firefox 却不支持。要想让 SW 在 Firefox 也能正常工作,就必须考虑另外一个方案:将 packet module 分别为 SW 和 browser context 打包两份,也就是说 packet 一份代码抽象为两份存在,此时便不再需要 import。这没关系,因为它足够小且足够关键。
那么,问题是,在 Rollup 中每个 module 由一个 unique id 标识,这意味着 不可能让一份 id 产出两份代码。在这里,我选用更加灵活的 virtual module 实现这一目的。
操作步骤是,在 transform hook 中 parse SW entry 的 AST,traverse 里面所有的 import nodes,并分别将它们替换,如
替换后的模块名会进入 resolveId hook,在这里需要为其补全 extension,如 `sw-import:../utils/packet.ts`,并在随后的 load 中将其从 fs 加载即可。最后还需要在 manualChunks 中,检查 id 是否包含 `sw-import:`,如若包含,将其与 SW scripts 打包到一起,否则打包到 common bundle 并服务于常规的 browser context。
#experience
我有一个 packet module,它是一个实现了 binary communication protocol 的 common module,这意味着它不只在 SW 中使用,还会在通常的 browser context 中使用,因此可以将该 module 打包为独立的 bundle,以便能在这两个不同的地方分别 import 它。
但是,import statement in workers 在 Chrome、Edge、Safari 都得到了支持,而 Firefox 却不支持。要想让 SW 在 Firefox 也能正常工作,就必须考虑另外一个方案:将 packet module 分别为 SW 和 browser context 打包两份,也就是说 packet 一份代码抽象为两份存在,此时便不再需要 import。这没关系,因为它足够小且足够关键。
那么,问题是,在 Rollup 中每个 module 由一个 unique id 标识,这意味着 不可能让一份 id 产出两份代码。在这里,我选用更加灵活的 virtual module 实现这一目的。
操作步骤是,在 transform hook 中 parse SW entry 的 AST,traverse 里面所有的 import nodes,并分别将它们替换,如
import {pack, unpack} from '../utils/packet' 替换为 import {pack, unpack} from 'sw-import:../utils/packet'。替换后的模块名会进入 resolveId hook,在这里需要为其补全 extension,如 `sw-import:../utils/packet.ts`,并在随后的 load 中将其从 fs 加载即可。最后还需要在 manualChunks 中,检查 id 是否包含 `sw-import:`,如若包含,将其与 SW scripts 打包到一起,否则打包到 common bundle 并服务于常规的 browser context。
#experience
👍1
2202 年我的 React 搭配
状态管理:局部 jotai、全局 zustand
网络请求:swr
路由:React Router
CSS:emotion + Tailwind CSS
UI:MUI、Mantine
动画:react-spring
表单验证:React Hook Form + zod
日期:Day.js
状态管理:局部 jotai、全局 zustand
网络请求:swr
路由:React Router
CSS:emotion + Tailwind CSS
UI:MUI、Mantine
动画:react-spring
表单验证:React Hook Form + zod
日期:Day.js
汇总:强制创建 layer 的属性,及 side effects
一般使用 backface-visibility 或 will-change: opacity,副作用相对最小。
See also: https://t.me/handrush/151
#learning
will-change: transform:会创建新的 containing block,导致 position: fixed 或 position: absolute 异常transform: translateZ(0):与 will-change: transform 有同样问题。早期的 Chrome、Safari 会引发闪烁,因此使用 backface-visibility: hidden 作为替代backface-visibility: hidden:会把 backface 的内容隐藏,在 3D 空间旋转元素时会有问题will-change: opacity:会创建新的 stacking context,影响元素的呈现顺序,但可以通过 z-index 恢复到预期状态一般使用 backface-visibility 或 will-change: opacity,副作用相对最小。
See also: https://t.me/handrush/151
#learning
FPS 不是流畅动画的全部
今天测试 CSS 与 JS 动画的性能差异时,发现一个有趣的现象:使用 JS 创建的动画,虽然能持续跑满 60fps,但动画本身“看起来”并不流畅;而 CSS 动画很流畅,但它却并不总是保持在 60fps。
稍微研究了下,发现这跟一个叫 subpixel 的东西有关,具体来说,对于 sub-pixel animations,它会在像素与像素之间添加模糊,使人眼看起来更平滑。
由于并不存在“半个像素”,一些更细小的动画效果就需要通过 subpixel 达到,它让人看起来动画是在“动”,实际上物体并没有动,而只是像素之间的交替变换。
因此,动画应尽量使用 CSS,或 Animations API。CSS 动画不仅能更充分利用 GPU,还能为动画提供诸如 subpixel 的优化,以减少 paint/composite 次数,并让视觉感到流畅。此外,它还不会破坏现有文档结构,而招致的频繁 lay out。
#learning
今天测试 CSS 与 JS 动画的性能差异时,发现一个有趣的现象:使用 JS 创建的动画,虽然能持续跑满 60fps,但动画本身“看起来”并不流畅;而 CSS 动画很流畅,但它却并不总是保持在 60fps。
稍微研究了下,发现这跟一个叫 subpixel 的东西有关,具体来说,对于 sub-pixel animations,它会在像素与像素之间添加模糊,使人眼看起来更平滑。
由于并不存在“半个像素”,一些更细小的动画效果就需要通过 subpixel 达到,它让人看起来动画是在“动”,实际上物体并没有动,而只是像素之间的交替变换。
因此,动画应尽量使用 CSS,或 Animations API。CSS 动画不仅能更充分利用 GPU,还能为动画提供诸如 subpixel 的优化,以减少 paint/composite 次数,并让视觉感到流畅。此外,它还不会破坏现有文档结构,而招致的频繁 lay out。
#learning
👍1
CSS Pixel 与 Device Pixel
CSS Pixel 不与真实的 Device Pixel 一一对应。在具有高 Device Pixel Ratio (DPR) 的设备上,一个 CSS Pixel 可能由多个 Device Pixel 表示,如被排列为 2x2 的方格。
DPR 为方格的高度,或宽度,2x2 grid 的 DPR 是 2。对于图像,其宽高与 Device Pixel 相关,若 image 元素 width 为 200px,对应 Device Pixel 为 400px,则图像的清晰显示宽度应为 400px。
#learning
CSS Pixel 不与真实的 Device Pixel 一一对应。在具有高 Device Pixel Ratio (DPR) 的设备上,一个 CSS Pixel 可能由多个 Device Pixel 表示,如被排列为 2x2 的方格。
DPR 为方格的高度,或宽度,2x2 grid 的 DPR 是 2。对于图像,其宽高与 Device Pixel 相关,若 image 元素 width 为 200px,对应 Device Pixel 为 400px,则图像的清晰显示宽度应为 400px。
#learning
https://featurepolicy.info/
一个 Permissions-Policy 可用值的列表。在里边我发现了
#tools
一个 Permissions-Policy 可用值的列表。在里边我发现了
sync-script 这个从来没见过的东西,不过目前也还没任何浏览器支持#tools
👍1
原来 MIME 还定义了 'example' media type,如 image/example、text/example,一般作为例子占位用。
还发现
#learning
还发现
Content-type: text/plain; charset=utf-8 中的 charset 原来是 MIME 的一部分,并不是特定于 Content-type 的。#learning
👍1
Service Worker 最佳缓存实践
SW script 的文件名应该是固定的,而不该包含 hash 或其它任何动态内容,如
此外,在 SW 的 installation lifecycle,一般会将 index.html 加入缓存,这意味着 index.html 由 SW 提供(依赖于 SW),若 SW 具有非固定文件名,这需要体现在 index.html 中(依附于 index.html),这会引发 circular dependency。
因此最佳策略是,将整个项目构建的 unique id 添加到 SW script,并在下次请求时,浏览器检查到 SW script 变更,重新执行完整的 SW lifecycle,并对包括 index.html 在内的所有静态资源重新缓存。当然也可以配合
#experience
SW script 的文件名应该是固定的,而不该包含 hash 或其它任何动态内容,如
sw.5b6aeb1b.js 是不被提倡的。因为 Chrome 68 及更高版本,在检查 SW 更新时会忽略缓存。此外,在 SW 的 installation lifecycle,一般会将 index.html 加入缓存,这意味着 index.html 由 SW 提供(依赖于 SW),若 SW 具有非固定文件名,这需要体现在 index.html 中(依附于 index.html),这会引发 circular dependency。
因此最佳策略是,将整个项目构建的 unique id 添加到 SW script,并在下次请求时,浏览器检查到 SW script 变更,重新执行完整的 SW lifecycle,并对包括 index.html 在内的所有静态资源重新缓存。当然也可以配合
skipWaiting 使其提前进入 activation lifecycle。#experience
👍1
alpha-value 如果告诉我是 alpha channel 倒很好理解。如果说成是 transparency,每次都要在脑子里想一想。
因为,100%透明 = 不透明,而不是“完全透明”,0% 透明 = 完全透明 🤯
如果这里说成 opacity,100% 不透明 = 不透明,0% 不透明 = 完全透明。感觉会更符合直觉(
因为,100%透明 = 不透明,而不是“完全透明”,0% 透明 = 完全透明 🤯
如果这里说成 opacity,100% 不透明 = 不透明,0% 不透明 = 完全透明。感觉会更符合直觉(
👍2
JavaScript NaN 捡屎
NaN 是 Not a Number 的缩写,如果这么理解,很快会发现
比较
---
我更倾向 JS 的 NaN 是指 "Not a NaN”:试着检查一个值是否为 NaN,
在比较时,将关注点放在了 NaN,而不是 Number 上,或许它应该是一个独立类型,毕竟它作为独立属性出现在 top-level scope 中,而不像其它语言那样使用
好在,
这并不是全部,JavaScript 中还有许多为了 backwards compatibility 而保留至今的糟粕设计,如在一众语言中
---
UPDATED:调整了下叙述逻辑
#屎前考古
NaN 是 Not a Number 的缩写,如果这么理解,很快会发现
typeof NaN === 'number'比较
NaN === NaN,结果 false,这说明 "Not a Number" not "Not a Number",但它确实是 number,这有点反直觉---
我更倾向 JS 的 NaN 是指 "Not a NaN”:试着检查一个值是否为 NaN,
isNaN(NaN) === true,这很好,但很快又会发现 isNaN('foo') === true、isNaN([]) === true在比较时,将关注点放在了 NaN,而不是 Number 上,或许它应该是一个独立类型,毕竟它作为独立属性出现在 top-level scope 中,而不像其它语言那样使用
float('nan') 或 Float::NAN 等形式表示好在,
Number.isNaN() 被加到了 ES6,它只检查一个值是否 "Not a NaN",而不是 "Not a Number",“不是数字”的东西很多,而 NaN 只是 NaN这并不是全部,JavaScript 中还有许多为了 backwards compatibility 而保留至今的糟粕设计,如在一众语言中
pow(1, Infinity) == 1,而 JS 确是 NaN;如从 Java 1.0 引进的 Date 至今也仍在使用,而早在 JDK 1.1,Date 中的很多方法就被废弃了---
UPDATED:调整了下叙述逻辑
#屎前考古