2026-07-01 00:00:00
All in AI!
前段时间,我写了一篇关于AI Agent的文章,虽然当时我说这并没有什么创新之处,但在那之后用着用着感觉还是挺好用的,至少相比以前在云端Vibe Coding来说又快又免费,所以我打算分享一下最近我用AI Agent的经历。
不过大多数情况下我写代码都可以直接让各路AI在对话中给我生成,那到底有什么样的场景才需要用AI Agent呢?一般应该就是需要边改边测的东西吧。
虽然说AI生成的东西可靠性并不是很高,但至少在网上已经有很多例子的代码应该不至于乱生成吧……其实我一直都想给我的博客增加全站无刷新的功能,但是每次看到PJAX都要自己重新调用当前页面的各种函数就懒得动手了😂。不过其实这也不是什么复杂的事情,主要是一个一个分析函数比较麻烦,所以既然有AI了,不如直接让AI帮我做吧。于是我让它克隆了我的仓库,分析并写出PJAX的代码。
用AI Agent最大的好处就是能让它了解整个项目的所有代码,如果是直接在对话里让AI修改的话就很难做到这一点。总之我告诉它之后等了一会它真的写出来了,我试了一下确实能用,虽然刚开始有不少问题,主要是因为我用了Live2D,主要问题也是这个引起的。不过只要我告诉它问题在哪里,它总是能给我改好,先不论它实现的代码怎么样,但至少从效果来说还是相当不错的。
最终生成的代码我看了一下,代码质量非常糟糕,把我在别处的代码到处复制粘贴到PJAX用的代码里了,但是体验效果不错……所以没办法,虽然代码很糟糕,但先用着吧。不过我觉得优化应该也能交给AI Agent,只是它有时候改来改去还会把正常代码改坏,所以优化看起来还是只能自己做……
最终的代码就是:pjax.js,虽然我对这个代码质量不是很满意,但是毕竟能用,效果也不错,就先这样用吧~
前段时间,我在Bilibili上看视频的时候发现了一个有趣的微信小游戏,叫做“雀谜”,它把立直麻将和Wordle两个玩法结合到一起,看起来挺有意思。不过它作为微信上的小游戏有些问题,一是看提示要看广告,二是换题也要看广告,而且看广告就算能换题也有次数限制,一天也就几次机会,让我有点不爽。
不过从功能上来说看起来并不复杂,感觉用AI直接Vibe Coding完全不是问题,不过AI Agent从零开始写比较考验模型的水平,所以我打算先让各路网页上的AI生成初版的Demo,之后再考虑怎么改。于是我对它们说:
帮我制作一款立直麻将Wordle网页小游戏,规则为:
- 系统随机生成一个有役能和的14张牌,必须按照万、筒、索、字的顺序排列,同花色内数字从小到大,但是第14张用来和的牌可以不遵守排列规则
- 让玩家猜6次来猜到这个牌型,每次猜的牌也必须是满足有役和牌的条件
- 位置和内容正确的牌用绿色标注、只有内容正确但是位置不正确的用黄色标注,不在生成范围内的牌用灰色标注
- 界面中所有的牌使用Unicode中的麻将字符,并且提供一组输入按钮用于输入这些麻将字符
- 输入按钮在游玩过程中也要标注颜色
- 随机生成的牌需要标注场风、自风和是否自摸,如果是荣和需要额外的役,需要考虑平和之类的役种
- 加一个提示键,可以查看答案牌型的役种
直接生成确实很考验水平,有很多AI直接生成出来的完全用不了,像DeepSeek生成出来的按钮按上去都没有反应😅,最终只有Gemini生成出来的感觉还不错,无论是界面还是输入的感觉都很好,于是接下来的修改我就打算以Gemini生成出来的代码为主。
刚生成出来的代码虽然能玩,但是每个AI直接生成都不想做完整的役种检测,说什么一个对话不够😥,明明各个都是几百k上下文的AI,有什么不够的……总之想一口气就完美做出来显然是不现实的,不过其实最开始我也不打算把所有役都做了,毕竟条件是“有役和牌”,而且没有副露,所以好多可以叠加的役其实都不需要做,尤其是役满那些,大多都可以用其他役的条件满足。
在这期间,“平和”算是实现起来最麻烦的,主要是这个役需要根据和牌判定,必须是顺子的两面听牌才可以……虽然AI理解这些知识,但是写的时候经常出问题,像边张之类的经常误判……但是不实现又不行,毕竟这是可以nomi的牌型。
最终写的差不多了,感觉还不错,于是在GitHub Pages上部署了一份,起名叫立直麻将 Wordle。
最开始我以为这个小游戏的玩法是他们原创的,于是把生成的代码又丢给AI,让它评价一下创新性,结果AI说已经有开源了😂,有个叫做Mahjong Handle的才是最早做这个玩法的……还好这代码不是我写的,要是我写了半天结果发现有现成的那就很无语了😆。
当然我让AI写的版本也有一些特色,Mahjong Handle那个和原版Wordle一样,每天只有一道题,而且这个题是从题库里抽的,我让AI Agent看了一下它的代码,似乎是用phoenix-logs这个脚本从天凤上下载的牌谱数据解析得到的题库,所以它的题目数量是有限的,而我的代码是完全随机生成的,所以结果是无限的。但AI似乎认为作为Wordle来说一天一题才是它的特色,无限就显得没意思了……既然如此,那干脆让它给我把两个游戏合并了,让它参考Mahjong Handle把一天一题的功能也给我加上,另外Mahjong Handle还有一个MPSZ快速输入法,也让AI给我合并进我的游戏里了。
这时候就体现了AI Agent的优势,如果直接用线上的AI把Mahjong Handle中的功能移植到我的代码,我还得完整描述要实现的功能,但是AI Agent能自己看人家仓库的代码,就知道要改什么东西了。
最终实现的效果也很不错,虽然我的代码没有题库,但是使用了当天日期作为随机种子,用mulberry32算法搞出了一天一题的功能,还挺有意思的。
另外在Mahjong Handle以及原版的Wordle中,还有一个困难模式,就是每次猜测必须使用之前的猜测条件,不过我觉得如果原模原样把这个功能抄过来似乎有点无聊,而且我发现玩这个东西一般直接一个国士+两个一气就基本上把要猜的牌找完了,有点简单,所以我想了一下,要不然限制一下提交的条件,提交的牌型必须包含目标的役种之类的……不过要是做这个功能就不能像之前那样只实现部分役种了,得把所有能用到的都实现一遍,还好这种事情肯定也不是我来做,肯定还得是让AI来😋。
实现的时候还不能让AI直接一口气全加上,还得一步一步的提示,而且每个牌型还得手动测一遍……最终虽然做的看起来似乎已经很完善了,不过逻辑的根本还是有点问题,AI设计役种计算的时候是按拆解后役种数量最多的情况来算,但事实上立直麻将算役种是按照高点法,要最终点数最大才行。虽然按照目前的方法绝大多数情况下都能正常工作,但偶尔也会遇到问题,比如像这副牌:
按照目前的方法计算的结果是:
然而正确答案是:
但是符数的计算又很麻烦……不过反正提示和验牌都是同一套程序,题倒是也能做出来……所以就懒得修了🤣。当然说到底还是因为AI没有用现有的计算库,像Mahjong Handle用的是现成的Riichi库,制作起来比我这个就简单多了。
在我制作“立直麻将 Wordle”这个小游戏的时候,我让AI使用的是Unicode中的麻将字符,毕竟有现成的字符总比去找牌的图片要方便,而且实现起来感觉更简单一些。不过用字符确实也会遇到一些问题,我不知道为什么在macOS和Android上其他牌都是黑白的,唯有🀄️是彩色的😅,Windows上使用的Segoe UI Emoji倒都是彩色的,但是牌很丑,像是小学生设计的……虽然不影响玩,但是看着很难受,至少牌的颜色统一一下也好吧?虽然也可以搞出黑白的🀄︎,但如果用这个,在Windows上又成了只有🀄️是黑白的😅。虽然Mahjong Handle是直接用了SVG格式的牌图片,但我觉得应该有现成的麻将字体,像这种问题应该有现成的解决方法才对吧?
于是我直接问AI有没有这样的字体,AI搜了一下看起来还真有,有个叫做Mahjong Colored就是基于那些SVG做的OpenType-SVG字体,我本来以为问题就会这样解决,然而当我应用了字体之后发现牌的位置变成了白色的方块,貌似只有Firefox才支持这种字体……然而现在谁还用Firefox啊?
那遇到这种问题怎么办?当然还是让AI解决,它说要在Safari和Chrome上使用彩色字体,用COLR或者SBIX格式比较好,COLR格式的兼容性最好,三个浏览器都能用,而SBIX的话Firefox没法用。不过Firefox既然基本上没人用了,我倒是不太在乎它的兼容性,但我肯定还是更希望能用兼容性更好的方案,于是我把OpenType-SVG字体转换为COLR的工作交给了AI Agent。
结果它整了半天也没整出来,貌似是因为SVG的特效太多,没办法转换为COLR格式,于是它选择了SBIX方案,不过COLR存储的是矢量图,而SBIX存储的是位图,大小要大一些,而且放大还会有锯齿。只是目前实在是没办法了,只能用这种格式。
最终我把它转换后的字体存了一份到仓库,但是看着它1MiB多的大小以及不太好的浏览器兼容性,再考虑到它还是位图,实在是不太想在我的游戏里应用它……
结果后来我又搜了一下,原来是有现成的麻将字体啊,有个叫做Mahjong Font的项目用的就是COLR格式,兼容性非常好,而且大小只有80KiB,算是最完美的解决方案了。垃圾AI给我推荐的Mahjong Colored真的是……浪费了不少时间,看来有的时候自己调查要比问AI好一些吧。
总的来看,目前Vibe Coding的体验确实是相当不错,只是问题也非常明显,完全让AI生成的代码要么实现的不优雅,要么就会出BUG,如果完全不懂要写的东西,纯粹由AI生成,那出问题的概率就非常高了。不过要是看不出来错误,完全被AI忽悠过去了,也许这时候的Vibe Coding在另一方面算是完美吧😂?至少对用的人来说也算是解决了问题呢。
2026-06-01 00:00:00
浪费硬盘空间是可耻的!
在几年前,我写过一篇在MacBook上玩游戏的文章,在那之后,我已经在我的Mac上下载了几十部游戏。只不过有个问题……我的Mac只有256GiB的硬盘存储空间,下载一堆游戏会让我的硬盘空间不够用,但是又不太想删,所以我该怎么尽可能让游戏占用更少的空间呢?
首先为了能在Mac上尽可能流畅地玩,我玩的游戏大多都是用跨平台能力很强的引擎编写的游戏,比如Ren’Py、RPG制作大师、Godot之类的,而像RPG制作大师这种引擎制作的游戏还有一个特点,开发者一般都会使用引擎自带的素材进行开发,有时候还会用不少第三方的罐头素材之类的(实际上甚至还有好多AVG为了蹭这些引擎的公用素材刻意用它们),所以这几十个游戏里应该有非常多的重复素材,如果能想办法把它们去个重,应该能节省相当多的空间吧……
如果想要对文件进行去重,我搜了一下,有个叫做jdupes的工具就很不错,它支持多种去重方式,比如使用硬链接,或者用一些文件系统的写时复制特性。不过如果用写时复制特性,jdupes在第二次执行的时候会认为去重后的文件还是单独的文件,就会重复去重了,而且最终也不好统计,反正对我玩的游戏来说,要去重的都是游戏素材,不存在后续修改的可能性,所以我打算全部用硬链接。
所以最终要执行的命令也非常简单,直接一句jdupes -r -L Game就可以了,这样以后每次下载了新的游戏之后重复执行这个操作,就可以将游戏中和其他游戏里有的素材去重了。
不过实际上很多游戏并不能直接用这种方式去重,因为它们的资源文件有些是打包成单个文件,有些进行了简单的加密,导致即使是相同的素材,文件也并不相同,所以我必须让所有的资源以单独原始的形态出现。对于不同的引擎也有不同的处理方式,所以接下来我需要对它们进行一些研究。
对于RPG制作大师MV/MZ开发的游戏来说,解密很简单,比较知名的是一个叫做RPG-Maker-MV-Decrypter的工具,它可以在浏览器中进行解密,但一个游戏的资源文件非常多……要是全上传给浏览器实在是太麻烦了……后来我又搜了一下,有一个用C#写的叫RPG Maker Decrypter工具也很不错,它作为命令行工具比在浏览器中执行简单多了,而且还能只把资源文件单独提出来,这样就可以剔除掉游戏自带的浏览器文件。不过他这个仓库的代码有个问题,它在选择文件的时候似乎会区分大小写,文件夹名中含有大写字母的似乎会被剔除……这样不太符合我的要求啊,当然我不会C#,于是我用AI改了一下,还给他提了个PR,不过这家伙看起来似乎不太喜欢AI写的代码,看起来不打算合我的PR😅。不过无所谓了,反正我也是自用,他爱合不合吧。
这个工具的用法也非常简单,一句RPGMakerDecrypter-cli [input] -p -o [output]就处理好了,处理完之后只需要把data/System.json中的hasEncryptedImages和hasEncryptedAudio设置为false就可以正常识别,以后在Mac中只要在游戏路径下执行python3 -m http.server就可以在浏览器中游玩了。
在这个过程中,我还发现有一些游戏喜欢把原画文件直接放到游戏里面,一张图片好几M,但RPG制作大师的引擎在渲染的时候根本不会渲染出那么高的分辨率,结果毫无意义地浪费一大堆存储空间,而且因为图片是加密的,对大多数人来说也没有收藏价值。所以在解密完之后我就想干脆把这些图片全部有损压缩一遍,估计能节省不少存储空间,于是让AI写了个简单的压缩脚本处理了一下:
#!/usr/bin/env python3
"""
图片压缩脚本(多进程版本)
将 pictures.orig 文件夹中的图片使用 WebP 格式进行高效压缩,
保持分辨率不变,肉眼看不出差异,压缩后的图片保存到 pictures 文件夹。
使用方法:
python3 compress_images.py
压缩策略:
- 保持原始分辨率不变
- 使用 WebP 格式(有损压缩,高质量)
- 质量设置为 85,在保持视觉质量的同时显著减小文件大小
- 文件名和后缀保持不变
- 多进程并行处理
- 处理失败时自动复制原文件
"""
import os
import shutil
from PIL import Image
from pathlib import Path
from multiprocessing import Pool, cpu_count
from functools import partial
# 配置路径
SOURCE_DIR = "pictures.orig"
OUTPUT_DIR = "pictures"
# WebP 质量设置 (0-100,数值越高质量越好,文件也越大)
# 85 是一个很好的平衡点,肉眼几乎看不出差异
WEBP_QUALITY = 85
# 对于带有透明通道的图片,可以设置不同的质量
WEBP_QUALITY_WITH_ALPHA = 80
# 并行进程数,默认为 CPU 核心数
NUM_WORKERS = cpu_count()
def compress_single_image(img_file: tuple[str, str, str]) -> tuple[str, bool, int, int]:
"""
压缩单个图片文件(用于多进程)
Args:
img_file: (源文件路径, 输出文件路径, 输出目录) 元组
Returns:
(文件名, 是否成功, 原始大小, 压缩后大小) 元组
"""
source_path, output_path_str, output_dir = img_file
source_path = Path(source_path)
output_path = Path(output_path_str)
original_size = source_path.stat().st_size
try:
img = Image.open(source_path)
# 检查是否有透明通道
has_alpha = img.mode in ('RGBA', 'LA', 'PA') or (img.mode == 'P' and 'transparency' in img.info)
# 确定使用的质量
quality = WEBP_QUALITY_WITH_ALPHA if has_alpha else WEBP_QUALITY
# 保存为 WebP 格式,但使用原始的文件扩展名
img.save(
str(output_path),
format='WEBP',
quality=quality,
method=6 # 压缩方法 0-6,6 是最慢但压缩率最高的
)
compressed_size = output_path.stat().st_size
return (source_path.name, True, original_size, compressed_size)
except Exception as e:
# 处理失败时,复制原文件到输出目录
try:
shutil.copy2(source_path, output_path)
compressed_size = output_path.stat().st_size
return (source_path.name, False, original_size, compressed_size)
except Exception as copy_error:
return (source_path.name, False, original_size, 0)
def main():
source_dir = Path(SOURCE_DIR)
output_dir = Path(OUTPUT_DIR)
# 检查源目录是否存在
if not source_dir.exists():
print(f"错误: 源目录 '{SOURCE_DIR}' 不存在")
return
# 创建输出目录
output_dir.mkdir(exist_ok=True)
# 获取所有图片文件(支持多种格式)
image_extensions = ('*.png', '*.jpg', '*.jpeg', '*.bmp', '*.gif', '*.tiff', '*.webp')
image_files = []
for ext in image_extensions:
image_files.extend(source_dir.glob(ext))
image_files = sorted(set(image_files)) # 去重并排序
if not image_files:
print(f"在 '{SOURCE_DIR}' 中没有找到图片文件")
return
# 构建任务列表
tasks = []
for img_file in image_files:
output_path = output_dir / img_file.name # 保持原文件名和后缀
tasks.append((str(img_file), str(output_path), str(output_dir)))
print(f"找到 {len(tasks)} 个图片文件")
print(f"源目录: {SOURCE_DIR}")
print(f"输出目录: {OUTPUT_DIR}")
print(f"WebP 质量设置: {WEBP_QUALITY}")
print(f"并行进程数: {NUM_WORKERS}")
print("-" * 70)
# 使用多进程池处理图片
success_count = 0
fail_count = 0
total_original = 0
total_compressed = 0
with Pool(processes=NUM_WORKERS) as pool:
for i, (filename, success, original_size, compressed_size) in enumerate(pool.imap(compress_single_image, tasks), 1):
total_original += original_size
total_compressed += compressed_size
if success:
success_count += 1
marker = "✓"
reduction = (1 - compressed_size / original_size) * 100 if original_size > 0 else 0
status_msg = f"{reduction:+.1f}%"
else:
fail_count += 1
marker = "✗"
status_msg = "复制原文件"
status = f"[{i}/{len(tasks)}] {filename}"
print(f"{marker} {status:50} {original_size/1024:>8.1f}KB -> {compressed_size/1024:>8.1f}KB ({status_msg})")
# 输出总结
print("-" * 70)
total_reduction = (1 - total_compressed / total_original) * 100 if total_original > 0 else 0
print(f"压缩完成!")
print(f" 成功处理: {success_count}/{len(tasks)} 个文件")
if fail_count > 0:
print(f" 失败(已复制原文件): {fail_count}/{len(tasks)} 个文件")
print(f" 原始总大小: {total_original / 1024 / 1024:.2f} MB ({total_original / 1024:.1f} KB)")
print(f" 压缩后大小: {total_compressed / 1024 / 1024:.2f} MB ({total_compressed / 1024:.1f} KB)")
print(f" 总压缩率: {total_reduction:.1f}%")
print(f" 节省空间: {(total_original - total_compressed) / 1024 / 1024:.2f} MB")
if __name__ == "__main__":
main()
最终压缩完之后我把原图上传到了EH画廊中,本地只留压缩后的图片,大小从原来的2GiB多下降到了300多MiB,可以说效果相当显著了。
除此之外还有一些游戏使用了Ogg FLAC背景音乐,这种音乐不仅占用磁盘空间很大,而且我在Safari上玩的时候浏览器根本没法解析(Chrome应该可以)。虽然我听音乐是会考虑HiFi,但玩游戏就没必要了吧……所以像这种音乐,就得用一句:
ffmpeg -i input.flac.ogg -c:a vorbis -strict -2 -q:a 10 output.ogg
转换为正常有损的Ogg音乐了。
对于RPG制作大师XP/VX/VA引擎开发的游戏来说,它们都是基于用Ruby语言开发的RGSS编写的,作为脚本来说,倒是有跨平台的条件,但因为官方并没有做跨平台,所以不能直接在Mac上运行。不过有一款叫做mkxp-z的工具允许跨平台运行使用RPG制作大师XP/VX/VA制作的游戏,因此这类游戏我也收集了一些。
这些游戏的资源通常会进行简单的混淆加密,一般会打包成单个RGSSAD文件,这个解包也很简单,用刚刚的RPG Maker Decrypter就可以。不过这种游戏还有个特点,有些游戏需要使用RTP才能运行,它这个RTP其实就是RPG制作大师自带的素材包,当时设计出来估计也是想着用来节约硬盘空间吧,就是不知道为什么到后来的MV/MZ却取消了这种方式……虽然mkxp-z是支持通过配置文件引入RTP的,但既然我已经选择了硬链接的方式,就没必要单独搞RTP了,我选择把RTP直接和游戏合并,然后让jdupes直接去重就好了,这样相比于RTP的方式还有一些好处就是XP/VX/VA可能有一些和MV/MZ使用相同的素材,这部分也可以不用占用重复的空间了。
对于Ren’Py来说,因为这个引擎并没有自带的公共资源,所以重复素材的问题并不是很大。不过在我之前对Ren’Py的探索中提到过,我玩的一些游戏是系列游戏,这种系列游戏有非常多的素材复用,但显然开发者并不会为了节约玩家硬盘空间而共享这部分资源,而且Ren’Py游戏也都是打包成单个文件的,所以接下来我们依然得要解包才能进行去重处理。
Ren’Py使用的rpa文件解包起来依然很简单,有一款现成的工具unrpa可以直接解包,用pip就能安装。不知道为什么这些引擎总是喜欢把资源文件都打成一个包,明明很容易就能解包……难道是为了性能吗?
不过也正是因为Ren’Py的公共资源不多,如果玩的不是系列游戏,就没有解包的必要了,解包之后一堆小文件有可能会比整个rpa文件更大,毕竟文件系统存在“簇”,有可能会消耗没对齐的空间。
最终进行完上述操作,可以通过执行du -sh和du -shl进行对比来验证节约的硬盘空间,我在这次游戏的瘦身中节约了:
~ % du -sh Game
33G Game
~ % du -shl Game
47G Game
看起来还是相当可观啊……尤其是在当下硬盘价格大涨的情况下,如果很多人能通过这些方式来节约硬盘空间,就能减少对硬盘容量的需求吧……不过说到底其实也都是网上能下到的资源,也许玩完之后就删掉才是最好的节约硬盘的方式吧😂。
2026-05-01 00:00:00
异地组网,有多少种选择?
最近我有一些放置在许多不同地方的机器,有一些东西需要让它们之间能够相互访问。虽然我很久以前写过一篇使用SSH进行互联的文章,但这样做每个服务都需要单独配置,也不方便管理。所以为了能让机器之间能够轻松通信,我打算组建一个虚拟局域网,让它们像在同一交换机下一样。不过这种组网的工具非常多,我应该选哪个比较好呢?
以前我用过一款用C写的叫做n2n的工具,它可以很轻松地组建一个P2P的二层虚拟网络,而且生态也不错,手机、电脑、路由器、服务器上都有可以用的客户端。使用起来非常简单,它的中继和穿透服务程序叫做Supernode,无需太多的配置,只要在有公网的服务器安装并使用-p指定一个端口就可以启动。而客户端配置也非常简单,用-l配置好Supernode的地址,然后让想要在同一个网络的机器使用相同的任意-k和-c就可以成功组网,可以说算是非常好用了。
唯一的问题就是它这个项目看起来似乎已经停止更新了……虽然大多数情况下用起来没问题,但是有时候还是会出现组网不太可靠的情况。如果两个机器都不经过NAT,可以通过公网IP连接,它的可靠性还可以。但如果是两个NAT后的机器之间,有时候会存在莫名掉线的情况,也许是因为穿透导致的不可靠?总之遇到这种情况之后重启又能正常工作,说明是软件本身的问题,但它停更了……所以对我来说它的可靠性不太够。(其实它还有个叫做n3n的继任者,不过知名度不高,所以生态也不太行)
其实在这之后我本来是打算用L2TP/IPSec进行组网的,但看了一下貌似配置有点复杂,而且不够现代,现在想要组网貌似大多都推荐WireGuard作为更现代的选择。只不过它和n2n相比来说是三层的虚拟网络,如果需要发送非TCP/IP协议的特别包,可能就用不了它吧,当然对我来说没有这种需求。它用起来也非常简单,不过正常情况下它设计是为了点对点传输,而且没有自带的NAT穿透功能,所以如果想要实现组网,就得搭一个星形网络,让互联网上的服务器作为虚拟的交换机,这个做起来倒也不复杂。首先,每个节点需要生成一个公私钥对作为身份证明,在安装好WireGuard之后执行wg genkey就能生成私钥。作为交换机的节点需要在/etc/wireguard/wg0.conf中写一个这样的配置:
[Interface]
PrivateKey = xxx
Address = 192.168.1.1/24
ListenPort = 51820
PostUp = iptables -A FORWARD -i wg0 -o wg0 -j ACCEPT
PostDown = iptables -D FORWARD -i wg0 -o wg0 -j ACCEPT
# 机器1
[Peer]
PublicKey = xxx
AllowedIPs = 192.168.1.2/32
# 机器2
[Peer]
PublicKey = xxx
AllowedIPs = 192.168.1.3/32
其中PrivateKey填写交换机自己的私钥,而作为使用者的Peer中的PublicKey可以用对应节点的私钥执行echo xxx | wg pubkey这个命令查看,然后每个Peer需要像这样配置:
[Interface]
PrivateKey = xxx
Address = 192.168.1.2/24
[Peer]
PublicKey = xxx # 交换机节点的公钥
Endpoint = xxx.xxx.xxx.xxx:51820 # 交换机节点的地址
AllowedIPs = 192.168.1.0/24
PersistentKeepalive = 25
最后全都配置好之后所有节点使用systemctl enable --now wg-quick@wg0启动就可以了,启动之后每个节点可以执行wg查看当前的连接状态。
当然这是在Linux上,至于其他系统大多都有GUI配置,填起来更简单。它的生态也非常好,基本上常见的操作系统都支持,具体可以在官网查看支持的系统和安装方法。不过由于它在Linux中优先使用内核模块,导致我在一些比较小众的环境中也是遇到了各种特别的问题。
我在这个网络中有几个安装了OpenWrt的路由器,在这其中使用联发科芯片的路由器基本上都没什么问题,官网能轻松下载到固件,也能很轻松地在软件包中找到WireGuard并安装,但我还有一台使用高通芯片的红米AX3000,似乎因为高通对资料管控得很严格,导致它没有官网的固件,最终我在GitHub上找了一个其他人自己编译的固件。虽然它整起来有点麻烦,不过倒也能用,但是在我尝试安装WireGuard的时候遇到了麻烦……
它的软件包里有WireGuard,也能找到对应的内核模块安装包,但安装完之后没法启动……随后我看了一下它下载的安装包,结果发现是空的😰,它这个固件的内核模块可能是在编译的时候遇到了一些问题。至于让我自己编译这个内核模块,难度似乎有点高了……那怎么办呢?要知道Linux的内核模块都是和内核挂钩的,没办法随便找一个别的模块使用。还好WireGuard倒也不止有内核模块,也有一些在用户空间中的实现,比如wireguard-go和wireguard-rs。只是官方似乎非常不推荐在Linux上使用它们,所以没有提供预编译的版本。不过遇到这种问题的人也许是比较多,所以有人做了在OpenWrt上使用的wireguard-go,安装好之后效果和使用内核模块的感觉基本上没什么区别,最终也能连通,唯一的区别就是在执行wg的时候,会显示“Interface: wg0 (userspace)”罢了。从效率上来说虽然肯定没有内核模块那么高,但它其实也用了“Tun”模块,理论上和使用“Tap”模块的n2n应该差不多吧。
在我使用的节点中,还有一台安装了openEuler 22.03 LTS操作系统的服务器,虽然openEuler和CentOS可以说基本上没什么区别,但毕竟它的内核是openEuler自己编译的,所以没办法直接使用CentOS的内核模块。并且openEuler的源中也完全没有提供和WireGuard相关的包,所以想要在openEuler上安装WireGuard还是有些挑战(当然如果觉得麻烦,它们倒是有一个兼容WireGuard的客户端TunSafe可以凑活用一下)。
后来我试了一下在这上面安装wireguard-tools倒是可以直接用CentOS 8EPEL源中的包,但openEuler的内核在编译的时候故意没有包含WireGuard内核模块……这该怎么办呢?用wireguard-go吗?虽然这样可以很简单地解决,但感觉这样就是认输了😂。后来我搜了一下,找到了一篇在openEuler安装WireGuard内核模块的文章,方法大致如下:
yum install elfutils-libelf-devel kernel-devel pkgconfig "@Development Tools"
yum install kernel-headers.x86_64 pkg-config ncurses-devel openssl-devel dwarves
yum install kernel-source.x86_64
/usr/src/下,找到之后在里面执行make menuconfig,然后勾选“Device Drivers -> Network device support -> Wireguard secure network tunnel”并保存。make开始编译,为了加速可以用-j参数加上CPU的核心数进行并行编译,当时编译就花掉了一整天😂,理论上应该可以只编译WireGuard和它依赖的几个模块,不过我不太清楚怎么做,还是费点时间按照文中说的做吧。make modules_install将编译好的结果安装到/lib/modules/5.10.0。
不过系统似乎不会去这个路径下找内核模块,所以还得把这里面的kernel文件夹复制到/lib/modules/$(uname -r)下,然后执行depmod -a更新模块依赖。modprobe wireguard验证模块是否能正常加载,如果没有报错并且可以在lsmod | grep wireguard中看到,就说明安装成功了,剩余的步骤和其他Linux系统一样。虽然WireGuard本身配置很简单,但每加一个节点还得在交换机节点上修改一下配置文件,稍微有些麻烦,所以有人开发了一些控制平面,让它可以被更规范地管理,比如Netmaker和Headscale。而Headscale主要是为Tailscale客户端开发的开源服务器端,因此功能会局限于Tailscale提供的功能。所以如果没有用过Tailscale,可以优先考虑Netmaker。
这两个控制平面支持的功能相当丰富,而且它们还支持让WireGuard进行NAT穿透,自动组建Mesh网络,不像我一堆在NAT后的设备还要直接使用WireGuard就只能搭成星形网络。只不过对我来说,我也用不到那么多企业级功能,这个服务端配置起来也有点麻烦,而且我也没有很多节点需要动态增减,我的云端服务器带宽也足够使用,所以就没有用这些东西了😆。
除了WireGuard之外,还有很多其他的组网工具,比如VNT和EasyTier,这俩用起来也非常简单,只需要加几个参数就能组网,和n2n一样。不过功能相比于n2n来说要强大不少,也支持NAT穿透,而且还都兼容WireGuard协议,另外不像WireGuard强制使用UDP传输,这两个还能用TCP和WebSocket,在特殊网络环境下应该比直接用WireGuard更好。另外它们都是Rust编写的,也许会更安全😋?可惜我已经配好WireGuard之后懒得再改了,如果以后有机会,可以尝试一下。
现在如果想要异地搭建虚拟局域网,还是有相当多的选择,而且无论是性能还是配置难度,都比以前好了不少。看来这种需求还是相当多啊,也正是因为有这些需求,所以才会出现这么多的方案可以用吧……总之我最后还是选择了纯WireGuard方案,主要还是简单够用,可靠性也不错,而且折腾了这么多再换也不太合适吧🤣。
2026-04-14 00:00:00
给AI添加手脚能有多少种方法?
最近像OpenClaw这样被叫做“AI个人助理”的Agent越来越火了,当然这种东西在我看来依然是新瓶装旧酒,整来整去还就是和AutoGPT一样。这种东西在当时GPT-3.5的时候就有了,现在只不过是增加了聊天软件交互的渠道便突然大火,和当年的Manus一样……实在是无法理解。
一年前我用过Devin.ai这个云端的Agent编写过用JS解析订阅源的脚本,体验还算不错,既然现在已经过了一年,那就让我看看现在又有了什么样的发展吧。
虽然感觉OpenClaw对我的意义不大,但我还是安装体验了一下。不过在国内安装它还是相对有点困难,毕竟国内无论是访问GitHub,还是NPM都有点麻烦,而且还需要有LLM提供商的信息……安装好之后使用起来感觉问题也非常多,经常出现执行一半就停止执行,在它执行的过程中看到它的操作不正确的时候也不能发言打断,而且很多时候最终任务执行的效果也不太好,这也可能是我用的国产开源模型推理能力有限,没舍得用Claude之类先进模型的锅😂?
另外我也尝试让它加入MoltBook、MomoClaw、InStreet、百度贴吧抓虾吧之类的AI社区让它帮我宣传我的博客,但效果也很差,它每次发的时候会忘掉之前发的内容,结果就是同一篇内容发了好几遍……不过在这期间,有个叫PushMeBot的家伙在Moltbook的帖子中让我的OpenClaw执行一个网络监视程序,最终安装好之后给我发了9USDC😝,还挺有意思。
总之按照我的体验,实在是想不出它能火的理由,体验不算很好,而且还要安装Node环境,完全不像是能让大众轻松使用的东西。
不过这个项目似乎本身就是Vibe Coding的产物,体验不好也能理解,就看火了之后能有多少人完善它吧。
国内好多大厂倒是看中了这个东西的爆火,像腾讯就出了几款这样的软件,比如QClaw。它可以不需要配置额外的环境,能像传统的软件一样直接安装使用,而且有自带的模型,有一定的免费额度可以用。配置技能也比较简单,直接点击就可以完成。而且可以直接扫码关联微信,直接通过微信和它进行交流,可以说是相当的傻瓜化了。不过QClaw给的免费额度虽然用来聊天之类的没问题,但对于开发软件还是有点少,所以他们还出了个叫做WorkBuddy的软件,它送的初始额度比QClaw要多不少,所以更适合用来开发。只不过为啥腾讯要出两个功能一样的软件?看起来应该是不同团队出的,可能是面向的用户群体不一样,所以搞了两套吧?
但要说开发的话,用作为“AI个人助理”的某些Claw其实并不合适,毕竟正常开发还是以人开发为主,全AI开发总会有些问题,所以开发的时候还是用编辑器集成的AI比较好。在三年前我就在用GitHub Copilot了,到现在我依然在用。现在的Copilot已经支持了Agent功能,开发相比之前也是强了很多,只不过现在的我没有学生身份,Copilot Free偶尔也会出现不够用的情况。不过对于Agent这类功能实现起来还是太简单了,所以有人开发这种功能的插件也很正常,比如Cline,Copilot只能用微软提供的几个模型,而Cline可以自定义模型,用起来也很方便。
前段时间,我闲来无事看了一下两年前买的Luckfox Pico Plus开发板的文档,偶然发现了一个很有意思的项目,叫做LuckClaw,这是一个基于nanobot用Golang重构的轻量个人AI助手,可以在仅仅64MiB内存的超有限环境下运行一个和OpenClaw功能几乎相当的AI个人助理,真的是非常厉害。
我在我的开发板上试了一下,体验很不错,安装不需要额外环境,直接下载就能使用,Go语言的程序确实方便。配置也很简单,直接执行luckclaw config就可以交互式进行模型等设置的配置,而且作为国产的应用,它也能很方便的对接国内聊天软件。只是限于开发板本身的能力,浏览器功能自然无法使用,所以搜索如果不借助那些需要API Key的AI专用接口,就基本上不能用……但总的来说效果已经非常不错了,至少有那些Claw的80%能力。
(2025.04.15补充:后来我发现这种超精简的Claw项目看起来还挺多,比如ZeroClaw和PicoClaw,甚至还有给单片机用的MimiClaw。而且有意思的是,PicoClaw是Luckfox的竞争对手开发的,但是LuckClaw中却包含PicoClaw字样的注释,结果功能也没PicoClaw强,关注度也更低,属于是没抄明白了🤣)
想到前段时间还有人为了OpenClaw专门买Mac Mini,就感觉很有意思😆,这个东西看起来应该是在路由器上都能跑。所以想要AI个人助理,硬件完全不是问题,只要整一个能24小时挂机的东西,就可以满足绝大多数人的需求了。
其实很多人也有比开发板和路由器性能更强的闲置设备,那就是手机,所以有人开发了一款叫做ApkClaw的软件,一样可以接入国内聊天软件。它既然能在手机上运行,当然和在其他平台运行的Claw相比有一个独特的优势,那就是操作手机应用。现在手机的应用相比电脑应用对于很多普通人来说功能更强大,所以它能做的事情可能比其他的Claw还多。我试了一下,配置也很方便,只不过能配置的项目太少了,看起来似乎没有安装Skill之类的功能,也许是因为它是相对早期的软件,所以功能还比较少吧。
总的来说,现在的Agent依然没有非常明显的进步,问题依旧很多,只是化身“AI个人助理”之后,增加了不少应用场景。这倒也是好事,在广泛传播的过程中,也能让很多对技术了解不多,但是很有想法的人参与其中,也许能对AI的应用化增添不少力量吧。
2026-03-01 00:00:00
用更多的方式探索AI!
在一年前,我整了张RTX4090 48GiB魔改版用来跑DeepSeek-R1 70B的4bit量化模型,不过都已经过了这么长时间,这个模型也已经是过时的东西了……我之前在Mac Studio M3 Ultra上试了一下OpenAI在半年前出的gpt-oss-120b模型,感觉效果还挺不错,只不过因为M3 Ultra的GPU实际性能比不上正经高端的独显,所以它在上下文很长的情况下还是有点慢,因此我又整了张RTX4090 48GiB,想整个双路试试更快的GPT-OSS模型,总共96GiB的显存应该够跑这个模型了。
既然现在我手头有两张4090了,那继续用i5-8400处理器的主机似乎不太合适,主要是那个主板就一个PCIe插槽,想插两张显卡也做不到,那买个新的不知道买啥……不管怎么说既然用这么高级的显卡,至少得让它跑满。在两张显卡上跑模型似乎卡间的通信速度比较重要,那最起码得整个支持2个PCIe4.0 x16的板U套装才行,这种级别的没有消费级产品,只能考虑服务器或工作站了。不过我对服务器和工作站了解得并不多,所以就问了问AI哪个支持2个PCIe4.0 x16的平台最便宜,结果AI推荐了TRX40+TR 3960X,于是就按照AI的说法整了一套。
这套板U差不多4000CNY,价格倒是还行,如果买现役的估计主板都比显卡贵了。但后来我发现这个并不是最便宜的😂,搜了一下买寨版+EPYC 7502还能再便宜1000CNY,而且通道数更多,插4张显卡都没问题……不过买都买了,就先用吧,看来AI的话不能随便信😥。
之前我跑模型为了方便,基本上都用的是Ollama,不过听说Ollama多卡运行的效率很低,而且多并发的效果不太好,所以这次换了新电脑之后我想试试vLLM,据说一般生产级的AI都用的是这个框架。
安装vLLM倒是比想象得简单很多,直接一句pip install vllm就可以了,其实并没有比Ollama复杂多少。我看了一下OpenAI和vLLM运行GPT-OSS的官方文档,发现启动也非常简单,一般来说直接执行vllm serve openai/gpt-oss-120b就可以。不过直接执行是对于单卡的,我用两张卡需要加个--tensor-parallel-size 2参数启用张量并行,不然会爆显存。另外考虑到这个模型本身占掉60多GiB的显存之后剩下30GiB还是看起来有点少,所以额外加了个--kv-cache-dtype fp8参数降低上下文对显存的占用,毕竟模型本身也就是4bit量化的,加了这个应该不会对它的能力有什么影响。除此之外AI还给我推荐了个--enable-chunked-prefill参数,说是也能避免爆显存的问题。
一切准备好之后直接执行,程序就自动开始下载模型了,过了几个小时,终于下载完成,顺便一说启动的时候还显示推荐安装torch_c_dlpack_ext库,虽然不知道是干啥的,但也顺手安装了。启动完成之后我试了一下,效果非常好,不并发的情况下直接用能达到接近190Tps,可以说是相当快了,而且这个模型的水平也算是开源中的上游水平,应该算是又快又好吧……看来多来一张4090还是挺划算嘛。只不过这个东西基本上就我一个人用,所以也没什么能测一下并发的场景……虽然很快,但还是有点浪费性能吧。
前段时间DeepSeek又出了新的模型,最高可以支持1M长的上下文,而且听说模型规模变小了,所以速度也很快。可惜的是到目前为止还没有开放权重。当然就算开放权重了用2张4090估计也没有足够的显存分配给上下文,至于Mac Studio感觉在长上下文的情况下运行速度应该会很慢……
不过我对这个1M上下文还是挺感兴趣,因为好久之前我写过一篇关于LLM能力上限的文章,在那篇文章中其实我遇到的问题基本上也就是由上下文不足导致的。那既然现在DeepSeek支持了1M的上下文,那我就应该试试之前因为局限性而妥协的一些东西了。
这次我没有用摘要,而是直接把包含整个博客内容的search.json文件上传到DeepSeek,然后向它问了问我的一些问题。试了一下效果非常不错,用摘要会省略的一些细节它基本上都可以展现出来,我试了试让它给我生成一份简历,它甚至在所有文章中找到了我的博客地址、GitHub和邮箱地址,之前用摘要显然是做不到这一点的,这个长上下文还是挺有用啊。
另外我还试了试让它根据文章内容分析十六型人格,并且我自己去答了一遍那个测试,结果也是相同的,说明它真的是在几秒内就读完了我的所有文章而且也完全理解了,真的是非常厉害。
只是拿AI分析我的文章也许只有我自己了😂,实际上根本没人对我感兴趣,也就只有我自己拿来给自己看……当然如果我的博客能比我活得长,不知道会不会有未来人会对我感兴趣呢……总之对于现在肯定是毫无意义了。
除了这些之外,我又试了一下让DeepSeek重构我的Mabbs,这次生成效果看起来很不错了,虽然代码我没细看,不确定能不能运行,但至少没有偷懒只写一点点,一口气写了80KiB多的代码,这也是长上下文带来的好处吧。总之目前这个长上下文的DeepSeek也算是突破了之前我认为的上限,看来LLM真的是前景无限啊。
另外我发现这次更新的DeepSeek居然了解我的博客,我问了一下它“你知道Mayx的博客是哪个博客吗?”,它居然知道,能说出域名,而且还知道我的博客是关于技术的😎,看来这次的训练样本中包含我的信息啊……所以我对这次的更新也挺有好感,毕竟我的知识如果能成为AI的一部分,也算是一种永恒吧。
在3年前,我在探索AI时,在我只有8GiB内存的MacBook Pro上运行了非常早期的LLM——Alpaca-7B,那时候7B的LLM虽然能回答一些问题,但答非所问的情况也非常多。不过最近我发现了一个有意思的LLM,叫做LFM2.5-1.2B-Thinking,它只用了12亿的参数就有思维链,而且水平据说还挺强。这么长时间过去之后我倒也想看看我的MacBook能运行多聪明的模型,所以就试着跑了一下它。
运行它也很容易,一般用Ollama就可以,但是Ollama只有TUI,不能渲染Markdown,我也不太想在我的Mac上整WebUI之类的东西……那有什么好的选择吗?我去制作这个模型的公司官网看了一下,他们制作这个模型本就是为了在端侧运行,所以也专门制作了一个软件运行他们的模型,叫做Apollo,在手机和Mac上都可以用。我在我的Mac上安装试了一下,效果很好,首先速度非常快,8bit量化正常情况下可以达到60多Tps,即使是省电模式,也能达到20多Tps。另外加上思维链它的思考能力也还不错,虽然一些脑筋急转弯的题不算擅长,但是正常对话,回答问题之类的表现都很不错,相比于之前7B的模型表现好太多了。当然考虑到都已经过去3年了,能有这样的进步也很正常,不过12亿参数就能有这样的智能还是相当可以啊。
这个模型之所以有这样的能力似乎是因为他们并不完全是Transformer架构,而是使用的一种叫做LFM2的混合架构,按照大家对他们公司(Liquid AI)以及这个架构名字的理解,可能会觉得这个模型基于液态神经网络,不过我让AI看了一下他们的代码似乎并不是,他们用的是一种类似于Mamba的架构,这种架构似乎就很擅长在小参数的模型下比Transformer模型表现的更好,所以说这种变化也是算法进步带来的。
顺便一说这个Apollo除了运行他们自己的模型之外也能连接其他兼容OpenAI接口的模型,正好可以用来连接我的GPT-OSS,这样我就可以不需要下载一些浏览器套壳的重型应用来用我的模型了😝。
自从ChatGPT之后,AI的发展真是越来越强了,而且能看出来目前甚至并不需要多新多好的硬件就能让一般人获得还不错的智能(当然训练也许还是要大量的硬件),这么看来AI软件的发展还是相当有潜力。目前来看既然优化软件就能做得越来越好,那也许在有限的硬件环境下可以期待无限的智能吧。
2026-02-08 00:00:00
即使没有了XSLT,也不能让读者看到光秃秃的XML!
在半年前,我写了一篇用XSLT美化博客XML文件的文章,自从那以后,每次我在浏览其他人博客的时候,都会看一眼对方博客有没有给自己的订阅文件做美化。不过就在前段时间,我在浏览某个博客的时候,发现他博客的订阅文件,甚至连最基本的XML文档树都没有显示出来。这时候我打开开发者工具看了一眼源代码,发现他也并没有使用xml-stylesheet之类的指令……而且控制台貌似报了些错,好像是出现了什么CSP错误……于是我就想,浏览器显示XML文档树的本质,会不会其实也是一种XSLT?之所以报错也有可能是浏览器在自动引用内置的XSLT时违反了CSP。所以我就问了问谷歌AI,结果似乎真的是这样,比如火狐浏览器就内置了一份XSLT文件,IE浏览器也有。正当我为XSLT的功能感到强大时,谷歌AI随后提到,Chrome浏览器决定弃用XSLT,所以以后不要再用XSLT了😰……
我给我的订阅文件加美化功能才半年,怎么就要不能用了?XSLT出现这么多年都还能用,结果等我加上就要废弃了?当时为了增加这个功能,还是费了不少劲的,怎么能让谷歌说没就没?于是我就开始对这件事进行了调查。
从上面Chrome的弃用XSLT文档中,可以发现,这件事的始作俑者是Mason Freed,他在WHATWG中发起了一个Issue,因为XSLT用的人很少,以及实现XSLT的库很老而且容易出漏洞,所以建议把XSLT从Web标准中删除。在这个Issue中可以发现,有很多人表示不满,毕竟这个功能对想要给自己订阅做美化的博主来说还是很有用的。为了对抗谷歌,还有人做了个网站: https://xslt.rip 。
而且XSLT虽然用的人占比也许不高,但从总量上应该还是挺多的,除了用XSLT美化博客订阅的,甚至还有用XSLT作为博客框架的,另外还有一些人提出一部分政府网站也有使用XSLT。
不过Freed看起来对这件事早有准备,他做了一个Polyfill库,通过WASM的方式让XSLT可以正常工作,为了方便大家使用这个库,我顺手给CDNJS发了个PR,以后可以用CDN引用它了。不过使用这个库的前提是需要在订阅中加一段引用JS的代码,像我博客中的Atom订阅,用的是jekyll-feed插件,里面的格式都是写死的,就用不了了……
只不过现在已经没办法阻止谷歌了……而且其他浏览器也表示会跟进,看来我们唯一能做的就是去适应了。
虽然XSLT不能用,但不代表xml-stylesheet指令就不能用了,除了XSLT之外,xml-stylesheet同样可以引用CSS。只是似乎完全没见过用CSS美化订阅源的,也许是因为光用CSS能做到的事比较少吧,想用CSS给XML文档加链接之类的估计就做不到了。
但目前能选择的也不多了,既然大家都没写过用CSS美化订阅源,那就让我来写一个吧!然而我并不会写😅……那就只好让AI来写了,我把需求说清楚之后,AI就写出来了:feed.css。试了一下效果还挺不错的,我让AI写的这个版本无论是RSS还是Atom都可以使用,如果有人感兴趣可以拿去用。可惜我的Atom订阅因为用的是插件的原因用不了😭,只能加到用纯Liquid实现的RSS订阅上了。
但用纯CSS的缺点也很明显,没办法操作文档的内容,像修改日期格式的就做不了了,而且也不能添加超链接……XML的标签本身对浏览器来说并没有内建的语义,正常情况下也没法让浏览器把某个标签当作超链接。那难道就没办法了吗?
如果完全不能修改XML内容,那确实就没有办法了,但如果能修改XML的内容那还是有办法的,简单来说就是混入XHTML,事实上Freed编写的Polyfill库原理上也是利用了XHTML,只要在能作为XHTML的标签中添加XHTML的命名空间,那么浏览器就可以理解它的语义并渲染,像刚刚用纯CSS美化的订阅没有链接,那就可以在根元素中添加命名空间:xmlns:xhtml="http://www.w3.org/1999/xhtml",然后在合适的位置写:
<xhtml:a href="https://example.com">Read more -></xhtml:a>
就可以了。只是这样有个缺点,这样写的订阅文件不够“纯粹”,用验证器验证会显示“Misplaced XHTML content”警告。对有洁癖的人来说可能会有点难受😆。
不过如果能接受这种“不纯粹”,那么其实xml-stylesheet指令也没必要了,link标签一样可以用,包括script也是,所以有人写了一个不使用XSLT美化XML的库。
只不过这种方法和XSLT相比还是有一些缺陷,要知道XSLT的本质是转换,是把XML转换为HTML,也就是说转出来的文档本质是HTML,所有的DOM操作都和操作HTML是完全相同的,但是在XML里混入XHTML标签就不一样了,它的本质依然是XML文档,只是嵌入了XHTML命名空间下的元素,所以相应的DOM操作会有一些不同。如果是自己写的纯JS可能还好,如果是用了jQuery之类假定DOM为HTML的库就会出现问题了,因此这也就是那个Polyfill库的局限性,用正常的XSLT执行document.constructor会显示HTMLDocument,而用这个Polyfill库执行完则是显示XMLDocument。因此,直接套用为浏览器原生XSLT编写的旧样式文件,就有可能会出问题,但如果要考虑改XSLT的话那还不如重新写JS,然后用XHTML引入呢。
虽然有一些技术会因为各种各样的原因消失,但这不代表我们就要妥协一些东西,总有一些不同的技术可以解决相同的问题,所以我们只需要用其他的技术去实现就好了。不过这也是没办法的事情,毕竟没人能改变浏览器厂商们的决策啊😂。