🌸
357 subscribers
127 photos
17 videos
197 links
记录一些 学习笔记,工具,和其它奇怪的东西
Download Telegram
这写法太鬼畜了,既不想JS报错,又不想TS报错。既要又要了属于是
Vite 多入口踩坑

想给项目加多一个入口,并可以通过 /admin 请求(与前端的路由保持一致),按照 Vite 的目录结构,应该在 projectRoot 创建一个 admin 目录。

于是我创建了 projectRoot/admin/index.html 文件,并在浏览器中通过 /admin 测试,屡屡失败,但请求 /admin/index.html 却可以。

这一旦让我怀疑是自己配置的不对,因为 文档 确实是这么写的,我开始了在配置与配置之间反复调整,然后陷入不断的自我怀疑。

直到 ── 这时时间已经过去很久,我在偶然中发现,/admin 与 /admin/ 是不同的,请求 /admin/ 竟然可以!!这时我既喜悦,又气愤,这 TM 算什么 J8 设计??

此前我确实反复读了文档,文档也确实写的 `/nested/`,这没毛病,但却没写 /nested/ 和 /nested 是不同的,就连顺带一提 “注意最后的 /” 都没有,这让人下意识觉得只是书写风格的不同 ── 毕竟其它“常规”的 webserver 都应该默认认为这二者是等价的。

退一步说,如果 /nested/ 表示目录,/nested 表示文件,但是你能在同一个目录里建两个完全同名,但类型不同的东西?所以添加 “/” 的意义是什么,我是真的不理解。

---
补充内容:

写完这篇 post,我找到了 这个5个月前的 issue,它被标记为 bug,目前还是 open 状态。然而我并不确定这是不是 bug,因为 Vite 确实被实现为区分 “/”,而且文档也与这种行为保持了一致(最后带了 “/”),因此至少在写文档时,是很清楚 Vite 会有这种行为的。

所以我更倾向这是“先天性设计问题”,直到后来有像我这样的傻瓜踩到坑,被提出来,才被定性为“bug”。但是因为这个,我浪费了大量宝贵时间,实属不值!
😁1
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
我懂了,元宇宙的尽头是飞机杯🤣
【广东省应急管理厅、水利厅、水文局】提醒您:据监测预报,6月22-23日,我省北江干流出现超百年一遇特大洪水,沿江多个水文站超警,芦苞涌、西南涌等支流也将迎来较高洪水位。请广大市民群众提高防灾避险意识,危险时段人不下河、船不过境,出外野炊露营不选择江河滩地,特别是加强学生、儿童安全教育和日常看护,不到危险、陌生的江河游泳,谨防溺水。(广东预警信息发布中心2022年6月22日发布)

#sms “超百年一遇特大洪水”,看这几个字就恐怖,又是百年一遇
重新续了 GitHub Copilot,需要重新授权下 GitHub 帐号,在 VSCode 找了半天 sign out 都没找到,Google 了一下才发现要通过 activity bar 的 icon 登出。

但是 activity bar 常年是被我隐藏的,因此我需要先把它显示出来,登出,然后再隐藏回去。

感觉这设计太蠢了,为什么不把登出这种操作集成到 Command Palette (Cmd-Shift-P),隔壁的 IDEA (Cmd-Shift-A) 我就是这么登出的。

#吐槽
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,并分别将它们替换,如 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
汇总:强制创建 layer 的属性,及 side effects

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
👍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
https://featurepolicy.info/

一个 Permissions-Policy 可用值的列表。在里边我发现了 sync-script 这个从来没见过的东西,不过目前也还没任何浏览器支持

#tools
👍1
原来 MIME 还定义了 'example' media type,如 image/example、text/example,一般作为例子占位用。

还发现 Content-type: text/plain; charset=utf-8 中的 charset 原来是 MIME 的一部分,并不是特定于 Content-type 的。

#learning
👍1
一个最小的 CSS 压缩实现

它并不完备,也不能保证 cover 所有 cases,但它足够小,且对我来说够用。
👍1
Service Worker 最佳缓存实践

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
学到两个新词:
Elegant degradation 优雅退化
Progressive enhancement 渐进式增强
alpha-value 如果告诉我是 alpha channel 倒很好理解。如果说成是 transparency,每次都要在脑子里想一想。

因为,100%透明 = 不透明,而不是“完全透明”,0% 透明 = 完全透明 🤯

如果这里说成 opacity,100% 不透明 = 不透明,0% 不透明 = 完全透明。感觉会更符合直觉(
👍2