整理之前录的视频,发现一个漏掉没有上传的:
这个视频里,我演示了如何使用纯 HTML + CSS 制作工序流程图。涉及到的技术包括:
display:flexFlex 布局- 使用
order: N调整显示顺序,以实现响应式 - 使用
position:XX调整定位
虽然项目不大,不过大部分布局相关的技术都有所涉及,很适合刚入门和初级同学学习。
今年想继续在直播、视频方面发力,希望大家支持。如果有什么想听的想看的想学的,也欢迎点菜。
各种开发心得,包括语言、软件工程、开发工具等
整理之前录的视频,发现一个漏掉没有上传的:
这个视频里,我演示了如何使用纯 HTML + CSS 制作工序流程图。涉及到的技术包括:
display:flex Flex 布局order: N 调整显示顺序,以实现响应式position:XX 调整定位虽然项目不大,不过大部分布局相关的技术都有所涉及,很适合刚入门和初级同学学习。
今年想继续在直播、视频方面发力,希望大家支持。如果有什么想听的想看的想学的,也欢迎点菜。

欢迎来到我的博客,我是 Meathill,想了解我可以点 关于我。
除了程序员,我还是个兴趣使然的 Web 开发视频制作者,欢迎在 B 站观看我的作品:https://space.bilibili.com/7409098。我的 YouTube 频道:肉山全栈小课堂 – YouTube。
欢迎留言点播各种内容。
(更多…)
周五打算给客户发版,结果在这里卡了大半天,写篇博客记录下。
SSO,Single sign-on,单点登录,即统一处理用户登录、提供用户身份凭据的功能。使用 SSO,可以只维护一套用户体系,容易开发维护;对用户来说,只需要登录一次就能使用该开发商的全部产品,也很轻松方便。
一般来说,SSO 的流程是:
我厂的产品也是这么实现的。
本地调试一切正常,但是加载成扩展之后,从登录服务跳回扩展会遇到 ERR_BLOCKED_BY_CLIENT 错误,URL 也被重定向到 chrome://invalid/。我在这里卡了很久,主要是不知道该怎么定位问题和搜索答案。
后来经过反复尝试,我终于发现,只有从登录页面跳转回去插件页面的时候,即 location.href='chrome-extension://{id}/ui/index.html 的时候,才会报错,所以立刻换用 chrome extension href ERR_BLOCKED_BY_CLIENT 作为关键词,立刻找到了答案:redirect to chrome-extension:// results in ERR_BLOCKED_BY_CLIENT。
然后阅读文档:Manifest – Web Accessible Resources(可由 Web 访问的资源),得知需要在 manifest.json 里添加对应的配置:
{
... "web_accessible_resources": [
"ui/index.html"
],
...
}
添加后 SSO 就正常了。
不过我没想明白的是,这个配置意义何在?配置写在扩展里,防止 web 访问扩展里的文件,似乎并没有什么帮助,也没什么安全性的顾虑。也许是我还没遇到吧。

Chrome API 都是回调型,连续使用非常不方便,希望能改成 Promise 型。Chrome 本身不提供 promisify,不过可以自己写一个:
export default function promisify(original) {
return function (...args) {
return new Promise((resolve, reject) => {
original(...args, (...results) => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError.message);
} else {
resolve(...results);
}
});
});
}
}
这里有几个注意事项:
...args 进行拆解和合并,方便调用runtime.lastError,不然出错的时候浏览器会报错,影响体验使用的时候,我比较喜欢只修改需要使用的函数,不打算 promisify 全部函数,大概是这样:
import promisify from './index';
/* global chrome */
export const update = promisify(chrome.tabs.update);
export const remove = promisify(chrome.tabs.remove);
export const get = promisify(chrome.tabs.get);
// 我觉得 `close tab` 看起来更合理
export const close = remove;
// 封装一个 `goto` 的快捷方式
export const goto = function (tabId, url) {
return update(tabId, {url});
}
// 全部导入,好处是简单,坏处是不方便 tree-shaking
import * as ChromeTabs from '../chrome-promisify/chrome.tabs';
ChromeTabs.goto(tabId, 'https://blog.meathill.com');
// 需要哪个导入哪个
import {goto} from '../chrome-promisify/chrome.tabs';
goto(tabId, 'https://github.com/meathill');

以前写过一篇笔记《树莓派4 安装 OpenResty + PostgreSQL》,记录如何在树莓派上装 PostgreSQL,不过那时候只是为了在上面做开发,没有考虑过对外服务。如今为了能够在别的机器上做开发,所以要想办法配置一下对外服务。
# 查看服务状态
sudo service --status-all
# [ + ] postgresql
# 查看端口
sudo netstat -plunt | grep postgres
# tcp 0 0 127.0.0.1:5432 0.0.0.0:* LISTEN 6629/postgres
服务在运行,端口也在侦听,直接连接,失败,被服务器拒绝。
猜测可能跟防火墙有关,iptables 我不熟,所以安装 ufw 帮忙:
# 安装
sudo apt install ufw
# 启动端口
sudo ufw allow 5432
sudo ufw allow from 10.0.0.10 # 我的 iMac
修改防火墙后还是连不上。使用 Telnet 工具可以本地连接,但不能远程连接,推断应该是侦听端口的问题。回去仔细看了一下端口状态,觉得应该是端口没配好,所以修改配置,侦听 0.0.0.0,然后重启 PostgreSQL 服务,再连接就成功了。
listen_addresses = '0.0.0.0'
port = 5432
host all all 0.0.0.0/0 md5
host all all ::/0 md5
参考链接:
慢慢记。
await Promise.all(items.map(item => ....))很容易造成 409 too many requests 问题。
最好用
const newItems = [];
for (const item of items) {
item = await doSomeAsyncJob();
newItems.push(item);
}
Pointer 时尽量用 query取单一对象的时候,方法有很多,比如 createWithoutData + fetch。不过如果如果对象内部属性有 Pointer,且我们希望一次性把 Pointer 取回来的话,最好用 query,因为只有它支持 .include(),可以一次性拉取全部需要的数据,减少请求次数,减少发生 too many requests 的可能。

早上起来,得知 PHP 8 正式发布了,作为曾经的半个 PHP 程序员,当然要去看一看。官方的 Release note 在这里,建议做 PHP 开发的各位同学都看一看:
接下来聊聊我的想法。
作为一个大版本,PHP 8 一定要有一些非常大的变化,JIT 就是这个非常大的变化。
JIT 是 just-in-time 的简写,意思是在运行时将部分代码编译成机器码,以便反复使用。执行机器码的速度会比执行一般的解释型代码快很多,所以 JIT 通常意味着可以大大提升语言的运行速度。
PHP 8 引入了两种 JIT 编译引擎,Tracing JIT 和 Function JIT,其中最值得期待的是 Tracing JIT。在基准测试中,速度有 3 倍提升;在一些长时间运行的应用当中,也有 1.5~2 倍的提升。参考下图,可惜,这个基于 WordPress 的博客提升只有一倍。
所有的功能都要以性能为基础,PHP 从 v7 开始就很努力地提升性能,加上它的功能一直封装的很好,所以我一直觉得 PHP 是服务器端开发最好的语言。
不知道是不是受了同为 Web 开发语言的 JS 的影响,v7 之后的 PHP 非常放飞,每个版本都引入一堆新语法和语法糖,什么箭头函数、类型系统,基本上只要有用,都给加上。v8 也不例外,有一些语法已经到了我看不懂的程度了……
比如这个 Attributes,我就没太明白,暂时把它理解成装饰器:
// PHP 7
class PostsController
{
/**
* @Route("/api/posts/{id}", methods={"GET"})
*/
public function get($id) { /* ... */ }
}
// PHP 8
class PostsController
{
#[Route("/api/posts/{id}", methods: ["GET"])]
public function get($id) { /* ... */ }
}
顺便说一下,前些日子 Laravel 也发布了 v8。不过不太一样的地方是,Laravel 的版本号跟 node.js、Ubuntu 采用同一种策略,即每年一个大版本,只有偶数版本会长期支持(LTS)。
活到老学到老,大家加油。

我一直想把录制教学视频的过程变得更稳定可控,而不需要依赖一时的状态。后者常常受到各种影响:比如家里狗叫了、孩子闹了、邻居装修了;或者录到一半突然遇到调不通的 bug;又或者只有 10分钟想录一段,但找不到感觉;等等。
加入我厂后,见识到各种小语言的威力;另一方面,计算机语言经过祛魅,我也不觉得有多难实现。所以我希望能够把录制教学视频的过程语言化,这样会带来几个好处:
能够实现这个小语言,自然需要依靠整个技术环境的成熟与健全。大概有这么几项:
语音合成(TTS)经过 AI 加成,效果相较于过去提升不少,足够为用户接受。再加上难度不大,所以支持的平台很多,价格也不贵,随便选一家即可。
首先可以选择 OBS。除了 UI 之外,它也提供 API,不过语言只有 Python 或 Lua,我都不是很熟。用它的好处是支持场景配置,我们可以先配好几组场景,然后在需要的地方切换。
如果是 macOS 可以使用 aperture-node,它借助 macOS 的原生 API 实现录屏,性能非常好。不过兼容性不行,考虑到新推出的 M1 芯片性能好功耗低,买一台 Mac mini 专门用来跑生成也不错。
也可以选择 ffmpeg,好处是什么平台都能跑,坏处是什么平台都一般。
有 webpack-dev-server 在,效果演示不成问题。
目前最大的挑战就在这里——我还不太确定怎么实现代码的自动输入。初步考虑使用 AppleScript 配合 VSCode,如果不行的话就浏览器里跑 VSCode online,然后用 JS。
语言本身肯定要开源,编译器计划也直接 MIT,随便用。然后提供一组自动化的编辑、生成、转码的基础设施,作为服务收费。

在长文分享《GitChat: 使用 Webpack 开发企业官网》中,我使用 Webpack 重构了官网开发工具链,效果很好,现在的开发效率也很高。
当时遗留下来一个问题:
于是我就面临一个选择:
最后综合考虑成本和收益,以及看到这个 issue:https://github.com/webpack-contrib/mini-css-extract-plugin/issues/151 之后,我决定再等等,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.css 里,这是从 HTML5 boilerplate 学到的。然后 Webpack 就会生成 screen.js,这是个多余的文件,只有坏处,前面说过。
我以为 Webpack@5 之后这个问题就自然解决了,结果看了一眼,还有,只好继续寻找解决方案。之前的关键词忘记了,issue 也没关注,所以多花了不少时间。所幸最终找到了:https://github.com/webpack/webpack/issues/7300#issuecomment-702840962。
{
optimization: {
splitChunks: {
cacheGroups: {
styles: {
type: 'css/mini-extract', // 重点在这里
chunks: 'all',
// If you need this uncomment
// enforce: true,
},
},
},
},
}
然后重新编译,screen.js 就不见了,非常爽。

早上起来日常刷技术新闻,看到两个对我和我厂影响比较大的消息,简单写几句。
Webpack 5 于今天(北京时间10月11日,美国时间10月10日)发布。这是个大版本更新,会有很多破坏性变更。所以不要轻易升级,否则可能遭遇各种问题。不过,对于像我这种三天两头主动升级依赖的人来说,是否可以平滑升级还未为可知。
这次的升级主要有以下几点变化:
更详细的内容大家请移步官网了解:Webpack 5 release (2020-10-10)。英语苦手的同学稍微等两天,估计中译版和各种解读版也会很快问世。
我只有两个建议:
很久很久以前,我在上一家公司做创业项目肉大师(Web 创作工具),不小心踏入了 FileSystem API 这个大坑。还留下一篇长文《HTML5的File API应用》,可能是为数不多的中文资料。
时隔八年,如今文件系统 API 终于有了比较靠谱的实现,并且被 Chrome 正式支持。一般来说,Chrome 的统治地位会帮助这个 API 成为事实标准,所以如果你对操作本地文件有需求,那么就可以开始使用这个 API 了,将来它会慢慢普及到其它浏览器上。
简单来说,这个 API 允许用户选择若干文件或者目录,相当于用户主动授权某些文件或目录给当前网站,然后 JS 就可以从文件里读内容,或者把内容写入文件。当然,从安全角度出发,网页不能任意访问文件,一定要用户主动选择。
对于我厂来说,这意味着 QA 产品可以更容易的编辑、保存文件,可以大大提升用户体验。一些 Web 工具也可以直接保存内容到用户本地,感觉网页生态更强大了。
更详细的内容可以阅读这两篇文章:
学无止境,勤为径苦作舟吧。