像 ry 这样的程序员,我觉得工程师更符合他的 title ,Node.js 是在2009 年 5 月 28 日 发布 0.0.1 版本的,已经有 16 年的历史了。在这期间除了一些对外的技术类型的分享和演讲之外,很难找到和他相关的资料。但是这并不妨碍我们从他的作品和这 16 年间做的事情去了解他。
这篇文章会顺着 Node.js: The Documentary | An origin story 的时间线总结和归纳下 ry 的经历。
早些年 ry 是纽约北部的一名数学研究生,并且准备攻读博士学。它在视频中讲到,他虽然喜欢数学这个领域,但是实际上他并没有做更多看得见的、能实践的事情。这和我们认知的数学这门学科是一致的。他说他想做一些事情是与人类正在发生的东西相关,然后他就退学了。
退学后他在 Craigslist (类似当年中国的黄页网站,百姓网之类的) 上找到了他的编程之路,当时他应聘了一家滑雪板公司,做一些营销网站,当然这并不是一些看起来很有意思的事情。他把注意力转向了更抽象的事情上,他使用 Ruby on Rails 实现了整个网站,发现它很慢,然后他就研究 nginx 模块,比较底层的 web 技术栈。
接着他遇到了自己自己的女朋友,并随她女朋友一起去了德国,在科隆生活了大约两年,因为科隆消费比较低,租房每月只要 400 刀,这让他有足够的空间和时间去思考一些事情,去做一些自己想做的项目。并且他认为这是一段 20 多岁时的愉快的时光。
从 V8 发布的时候他就在考虑一个问题:Javascript 与非阻塞。他在视频中也说到:这是一个在正确的时间思考正确的事情。
ry 呆在科隆的那段时间大概从 2 月~ 10 月的时候全职开发构建了第一个版本的 Node.js
Isaac Schlueter(izs) 在 Node.js 首次发布的时候是 yahoo 的工程师,还因为当时的工作总是要在 PHP 和 Javascript 之间切换而感到沮丧,所以他会考虑为什么不使用 Javascript 来做服务端的编程语言。当时也有一小部分人在试图将 Javascript 实现成服务端编程语言,比如:Server.js,Jaxer,RingoJS 。在当时 Javascript 服务端能力已经有一些端倪了。Node.js 的出现有点出乎意料。Isaac Schlueter 说他认为
ry 选择 Javascript 并不是因为他喜欢 Javascript ,而是因为 Javascript 很合适。
ry 说在选择 Javascript 之前也研究了像 Python,Lua, Haskell 这样的编程语言。但是有一天和朋友坐在一起,突然之间就有了一个想法:“我靠 Javascript ,就应该是 Javascript”。就在那一刻他非常清楚的确定了是 Javascript 。
当时在其它编辑语言中基本上都有了一定的范式。但 Javascript 还是空白。
非阻塞 IO 的实现在其它的编程语言实现都会有很大的阻力,比如在 Python 中打开一个文件,大家已经习惯了使用下面的同步 IO 范式来实现:
with open("filename.txt", "r) as file: cOntent= file.read()
所以使用 Python 来实现显然不是一个好的选择,因为这意味着 Python 开发者需要转换编程习惯。
后来越来越多的人知道了 Node.js ,但是当时还没有包管理系统,**izs 就创建了 [NPM](NPM)**,最开始 NPM 的源代码是一些 shell 脚本,很多代码来自于 Yinst - yahoo 内部用的的包管理器。
ry 在 JSConf EU 上的主题演 Ryan Dahl: Original Node.js presentation 首次对外公开 Node.js ,可以看出来当时的他还是很青涩、很紧张的。
ry 说他只是 JSConf 上的一个普通演讲者,但是他已经为了这个演讲提前几周做了充分的准备。每个人都在演示自己的 玩具 项目,而只有他写的 Node.js 是真正严肃的项目。它在现场展示了一个使用 Node.js 构建的 IRC 频道服务器,当场在同一个网络的观众也可以链接进去发消息。
在 Node.js 创建的初期,程序员还没有一些很好的沟通工具,没有 slack, discord, github 的功能还非常的原始,也没有任何持续集成的工具。大家都通过电子邮件将补丁发给 ry ,然后手动合并到代码仓库中,ry 就像是个人工的 CI 工具一样,手动打补丁,手动测试。
JSConf 演讲之后 ry 还在科隆,当时已经有很多公司联系他说对他的项目感兴趣。他就飞到旧金山去和对方聊,也是为了这个项目能继续下去找一些资金支持。最后 joyent 提出了好的方案。joyent 是一个云服务提供商,他们想在自己的服务上运行 node 应用程序。
然后 ry 就搬到了旧金山,全职从事 Node 工作。他在 joyent 时除了 Node 没有任何其它工作。
Bert Belder 在一家初创公司,为建筑公司做自动化,他们必须进行一些复杂的计算,他们为前端实现这些计算功能。使用 Node.js 在一夜之间完成了他们的数据迁移工作。他为解决了 node 在 window 平台上运行的问题。Bert Belder 是 libuv 的作者,libuv 是 libev 的集成者,它解决了不同平台异步 IO 模型的封装和实现。node 0.4 之前 libev ,一个 select 的包装,很老而且速度一般,只支持 macOS 和 Linux ,还不支持 window 。
在早期的开发过程中 ry 通常会引入了破坏性变更,比如:v0.0.3 中把 sys 模块更名为 util 。然后大家都惊慌失措了。
npm 是随着 node 0.0.8 版本同时发布的,但是下一个 node 版本上就没法用了。所以社区都想要知道 node 第一个稳定版本什么时候发布的时候。ry 说 1.0 版本还没有完整的路线图,因为这是一个很遥远的版本,目前还是专注于 0.0.6 版本,需要重新设计管道,然后再重新审视这个问题。
工作了 1 年后,joyent 想从 ry 手里买下 Node.js 这个项目。ry 说他还不确定 joyent 从他手中买下一个开源项目是好事还是坏事。当然 ry 知道 joyent 的目的的:joyent 是想管理这个项目,拥有商标、网站并且利用这个来推广他们的公司。ry 同意了这笔交易,因为他当时并没有因此失去些什么,所以他感觉很好。社区当然会有很多质疑,大家会觉得这对 Node 意味着,如果 joyent 变坏会怎么样?但是因为 ry 和 joyent 达成的协议是 node.js 还是会以 MIT 许可来发布源代码,实际上 joyent 买的只是一个名字。
后来 Node.js 的运营和一些管理上的工作 joyent 会决定,ry 把管理上的一些事务交给了 Isaac 并逐渐退出了 Node.js ,后来 Isaac 对于运营 Node.js 的工作感觉到无聊和厌倦,工作交给 TJ 后退出。
最后 joyent 也不怎么把精力投入到 node.js 中,node 代码仓库的迭代明显减少。更新明显放缓,社区觉得 Joyent 对新功能(比如 ES6 特性、模块系统、协程方案)的推进过于保守,维护效率不高。
Node Forward, 讨论 node 未来的发展,核心维护者向 joyent 提出开放式的管理。
mikreal 分叉 node.js 起名 iojs 。
joyent 换 CEO Scott Hammond 与核心开发者沟通。
三个月后
双方就 Node.js 项目治理模式达成一致:技术方向和技术决策真正由社区驱动的,从而确保项目在真正的共识模式下运行,而不代表任何组织特殊利益的模式。io.js 成了一个重大的警告,让 joyent 意识到他们在 node 中拥有的东西实际上危险之中。
同时建立 Node.js Foundion 。joyent 说为了我们的利益,我们不需要成为 Node 的管理者,但是我们需要 Node 作为一个统一的项目
node 4.0 发布合并了 io.js
2019 年 Node.js Foundation 和 JS Foundation 合并成 OpenJS Foundation 。
在这之后 ry 淡出 Node.js ,他花了几年时间在其它兴趣上:机器学习,分布式系统,几何,摄影等。
2018 年 JSConf EU 回归,发表演讲 10 Things I Regret About Node.js,此时的 ry 看起来更潇洒、时尚。甚至不像是一个上技术分享会的程序员的形象,虽然还是很紧张。
在他淡出的这段时间,前端或者说 Node.js 社区已经有很大变化了。Node.js 似乎也有一些瓶颈和问题。
但在这个视频中他坦率地讲出了自己在 Node.js 中的一些设计「缺陷」:
require("module")
时不写 .js
扩展名可以看到 ry 总结的这些问题非常精准的戳到了当时 Node.js 的一些核心问题。我想经历过那个时代的程序员一定会记得:callback hell, node_modules, node-sass, gyp, fsevent...
有意思的是实际上在 Node.js 出现之间 Javascript 回调地狱并没有那么臭名昭著,因为 Node.js 出现后使用了异步 IO 的模型,刚好回调函数的模式可以和异步 IO 很好的融合,写起来很自然。但是使用的太多了就会另人感到不适:
callback hell
doSomething(function(result1) { doSomethingElse(result1, function(result2) { doAnotherThing(result2, function(result3) { doFinalThing(result3, function(result4) { console.log('Done:', result4); }); }); }); });
Promise
doSomething() .then(result1 => doSomethingElse(result1)) .then(result2 => doAnotherThing(result2)) .then(result3 => doFinalThing(result3)) .then(result4 => console.log('Done:', result4)) .catch(err => console.error(err));
async/await
async function main() { try { const result1 = await doSomething(); const result2 = await doSomethingElse(result1); const result3 = await doAnotherThing(result2); const result4 = await doFinalThing(result3); console.log('Done:', result4); } catch (err) { console.error(err); } } main();
为了解决这些问题,他又发明了一个新东西:Deno - 一个基于 V8 的安全 TypeScript 运行时。
deno 的出现可以说解决了 Node.js 所有设计上的重大缺陷问题,并且引用了 TypeScript ,这使得使用 Javascript 编写严肃的程序、系统成为可能。
值得注意的是,早期的 deno 底层是 Go 实现的,在后来的迭代中换成了 Rust ,其中一个重要的原因是:Javascript 是一门高级程序语言,是有垃圾回收的。而 Go 也一样,如果用 Go 实现那 deno 的运行时就会有两个垃圾回收器。ry 在后来的演讲中说:有两个垃圾回收器那样不对。虽然不是不可以,但是出于程序员的直觉两个垃圾回收器是不对的。
2021-4 成立 deno 公司
2022-6 Deno 完成了红杉资本领投的 2100 万美元 A 轮融资,总融资额达到 2600 万美元,目标是开发一款商业产品 Deno Deploy。
我的博客也托管在 deno deploy 上,以前用过 github pages, hugo, hexo 等,但是多少还是有点问题,刚好因为自己对 Javascript 熟悉所以一直用免费版的 deno deploy 。
——
这就是 ry 到目前为止做到的事情,当然故事还在继续。毫无疑问 ry 是一个成功的程序员、工程师、老板、Node.js 社区的精神领袖。我想从我自己的视角总结几个关于他的问题,这会对我们的工作、生活有所启发。
在这之前我想有几个时间点在技术领域是非常重要的:
epoll 在 Linux 内核中之前,大部分网站使用的服务器还是 apache 。apache 服务器的模型是多线程的,一请求一线程,显然这是无法应对大量并发访问的。因为启动一个线程会有很多开销,假如:启动一个线程需要 5MB 的内存,那么 1G 内存的机器上就最多只能开 200 多个线程,也就意味着一台 1G 内存的电脑只能服务 200 个 HTTP 连接(用户)。
但是随着互联网的发展,大家在网上的活动越来越频繁,这才出现了大量的高流量网站,社交媒体、BBS 、搜索引擎、博客、个人网站等等。一时之间网络流行起来,大家在上网的时候越来越多。
Nginx 就应运而生了,他抛弃了 apache 线程驱动模式,使用事件驱动,异步非阻塞模式。Linux 下使用 epoll 实现异步 IO 。Nginx 设计之初就解决了 C10K 问题。对于 静态文件服务、反向代理、负载均衡应用场景展示出了极高的性能。
我第一次使用 Nginx 的反向代理的时候,感觉就是:哇,这是什么魔法,太神奇了。只需要一行配置就可以让 A 网站展示 B 网站的内容。
注意在这个时间节点,大概 2004 年的时候不没有任何编程语具备异步编程模型的默认范式。当时异步编程概念是很早就有了。我这里讲的默认范式可以理解成指定编程语言中的编程风格或者说语言内核。比如:Java 的 OOP ,Haskell 的 FP ,现在 Javascript 中的 Promise/async/await 。
显然 ry 知道 Nginx 的核心原理,他是想把异步 IO 这种模型植入到某个编程语言中去,你可以想象的到这个想法的威力有多大吗? Nginx 是一个应用层软件引入异步 IO 后有这么大的性能提升,如果把这个模型引入到一个编程语言中,那整个编程语言都是基于异步 IO 的,性能会比同步的高出很多倍,人们可以轻易的编写出高效的程序。
就像前文中讲到的,ry 也研究过其它编程语言,没有合适的。但是无意中发现 Javascript 很合适。
V8 的出现让 chrome 浏览器在 2008–2015 年期间,市场占用率从 0 到了 53%,让整个 WEB 加速,也让 PC 时代到达了发展的顶峰。那段时间每年都会出现新的流行的东西。网络聊天,论坛 BBS ,个人博客,微博,团购,电商。整个互联网是一片朝气蓬勃的样子。
我自己写博客也是当时受到了韩寒、徐静蕾新浪博客的排名的热度影响。
可以想象当时大家对浏览器一种什么样的需求,大家似乎感觉不太到浏览器有多重要,但是对于 Javascript 来讲却是暗流涌动。彼时的浏览器可以说是万花齐放:
最终,技术上异步 IO 模型被验证了正确性,V8 的出现也逐步把 Javascript 拉向了正经严肃的编程语言行列(当然目前看来很多地方还不够严肃)。然后 Node.js 的出现就显得很水到渠成。
当然如果只看到这些泛泛的趋势、苗头其实并不能很客观的解释最终为什么是 Javascript 而不是其它语言,因为在我的职业生涯中从事 Javascript 编程占大部分时间,所以我还是想从编程语言的角度来总结下为什么 Javascript 比较合适的原因。
主要原因有三个:
第一:Javascript 还很年轻(很初级)
选择 Javascript 不是因为 Javascript 这门语言好,而是因为 Javascript 这门编程语言还很初级,当时的 JS 还处于脚本语言的范畴,人们用它来编程基本上很多时候是调用浏览器这个宿主环境提供的一些 API ,比如:DOM/BOM/XHR 等。但是严肃的讲当时的 Javascript 还只是一个玩具脚本语言。
第二:Javascript 语言特性丰富
Javascript 语言是一门看起来啥功能都有的语言。我们可以看看《 Javascript 权威指南》中的一段关于 Javascript 的介绍
Javascript 是面向 web 的编程语言,是一门 高阶的( high-level )、 动态的( dynamic )、 弱类型的( untyped ) 解释型( interpreted )编程语言,适合面向对象( oop )和函数式的( functional )编程风格。Javascript 语法源自 Java 和 C ,一等函数( first-class function )来自于 Scheme ,它的基于原型继承来自于 Self
可以看出来 Javascript 啥特性都有,但实际上啥特性都不好用。这就给 ry 一个选择 Javascript 的理由,这门编程语言上没有什么特别好的东西,才不至于它有一些默认的范式而导致语言层面引入异步 IO 会产生很大的阻力。
第三:Javascript 的核心,单线程事件驱动
这个是 Javascript 这种脚本语言被设计之初就确定好的,因为脚本语言就是用来屏蔽底层复杂性的。你很难想象如果 Javascript 实现上提供多线程,同时又跑在浏览器里面它会把浏览器搞成什么鬼样子。
事件驱动这个好理解,因为 Javascript 被设计出来就是要处理用户 UI 界面上的事件的。比如:用户点击按钮,提交表单。
单线程事件驱动这一点可以说是技术上最合适的一点,因为当 ry 把这个理念和编码方式与异步 IO 集成后,编写出来的代码非常简单而且容易理解。
我们可以看看 Node.js 官网上一直存在的代码片段,实现一个简单的 HTTP 服务器:
import { createServer } from 'node:http'; const server = createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Hello World!\n'); }); server.listen(3000, '127.0.0.1', () => { console.log('Listening on 127.0.0.1:3000'); });
这个实现就是 Node.js 的精髓:异步非阻塞 IO 。8 行代码实现一个 HTTP Server ,性能可以和 Nginx 媲美,这已经足以惊艳所有人。
异步编程的这种范式正在植入到 Javascript 这门语言中。自从 Javascript 有了这种最佳实践,异步编程的标准模型:async/await 也慢慢渗透到了其它编程语言中,Python/Rust 都有所借鉴。当然异步编程在其它编程语言里面也有实现,但是都没有在 Javascript 中那么自然。
技术界总有一些人靠自己的本领过上了衣食无忧的生活,但是过上衣食无忧的生活这并没有什么意义,因为人生的意义总是在于创造一些东西而非享受一些结果。我想 ry 是这样的人,要不然他也不会在卖了 Node.js 得到钱之后走上一条结束自己人生的路。也正如他在自己人生关键时刻做出的选择一样:做看得见的、能实践的事情。
我一直认为任何事情,方向对了+人对了,那结果就是自然而然的成功。就算不成功也没有什么遗憾。
所以 Node.js 成功了,Node.js 的成功在于它开创了一个新的纪元,他为原来在前端的开发者打开了一扇门,这里大家才意识到:原来前端也可以写后端,也可以写服务端,前端也可以在更多领域实践,可以和更多的领域一起竞争。
参考资料:
共享模式
和独立模式
。首创 DTO 动态推断与生成能力,解放我们的双手,显著提升生产力。为什么敢说是首创,因为 Prisma 和 Drizzle 没有提供此能力,Java 系亦如是。 Vona ORM 不仅提供基于静态关系
的关联查询,还提供动态关系
,从而适应大型业务系统所要求的灵活性和扩展性。
此框架所规划的能力还有很多,就不再赘述,以免占用大家宝贵时间。因为功能多,所以,花了大量时间终于把文档肝出来了。感兴趣的,可以观摩一下,欢迎拍砖。👏
文档地址: https://vona.js.org/zh/guide/techniques/orm/introduction.html
]]>而 Vona ORM 就提供了非常便利的工具,使我们可以非常直观的动态推断出 DTO ,就像推断类型一样,从而解放我们的双手,显著提升生产力。甚至可以说,能够自动推断 DTO ,为 Nodejs 后端框架打开了一扇窗。
限于篇幅,这里不展开讲解 Vona ORM 所有的知识点,而是以目录树
为例,演示如何查询一棵目录树,以及如何动态生成 DTO ,并最终生成 Swagger 元数据。
在 VSCode 中,可以通过右键菜单Vona Create/Entity
创建 Entity 的代码骨架:
@Entity('demoStudentCategory') export class EntityCategory extends EntityBase { @Api.field() name: string; @Api.field(v.optional()) categoryIdParent?: TableIdentity; }
在 VSCode 中,可以通过右键菜单Vona Create/Model
创建 Model 的代码骨架:
import { EntityCategory } from '../entity/category.ts'; @Model({ entity: EntityCategory }) export class ModelCategory extends BeanModelBase<EntityCategory> {}
如果要创建一棵目录树,本质就是建立 Model 引用自身的递归结构。Vona ORM 同样支持 4 种关系:1 对 1
、1 对多
、多对 1
,多对多
。那么,在这里,我们就需要采用1 对多
来创建目录的自身引用关系。
import { EntityCategory } from '../entity/category.ts'; @Model({ entity: EntityCategory, + relations: { + children: $relation.hasMany(() => ModelCategory, 'categoryIdParent', { + autoload: true, + columns: ['id', 'name'], + }), + }, }) export class ModelCategory extends BeanModelBase<EntityCategory> {}
在 VSCode 中,可以通过右键菜单Vona Create/Controller
创建 Controller 的代码骨架:
@Controller() export class ControllerCategory extends BeanBase {}
接下来我们创建一个 Api ,用于获取目录树:
export class ControllerCategory extends BeanBase { @Web.get('getCategoryTree') async getCategoryTree() { } }
一般而言,我们还需要创建一个 Service ,从而实现以下调用链:Controller->Service->Model->操作数据库。为了简化起见,在这里,我们直接在 Controller 中调用 Model 方法:
export class ControllerCategory extends BeanBase { @Web.get('getCategoryTree') async getCategoryTree() { const tree = await this.scope.model.category.select({ columns: ['id', 'name'], }); return tree; } }
this.scope
取得 Category Model ,然后调用 select 方法由于前面我们设置 children 关系为autoload: true
,因此,查询结果tree
就是一棵完整的目录树。下面我们看一下tree
的类型推断效果:
现在我们自动推断 DTO ,并且设为 API 的返回数据的类型:
export class ControllerCategory extends BeanBase { @Web.get('getCategoryTree') + @Api.body(v.array(v.object($Dto.get(() => ModelCategory, { columns: ['id', 'name'] })))) async getCategoryTree() { const tree = await this.scope.model.category.select({ columns: ['id', 'name'], }); return tree; } }
同样,由于前面我们设置 children 关系为autoload: true
,因此,$Dto.get
生成的 DTO 就是一棵完整的目录树。下面我们看一下 API 的 Swagger 效果:
从示意图中,我们可以清晰的看到,这棵树引用的 children 类型是名称为demo-student.entity.category_2c7d642ee581efa300341e343180fbb0ecdc785d
的动态 Entity 的数组,从而形成一种递归的引用关系。
虽然我们已经实现了预期的目标,但是 Vona ORM 提供的能力还没有结束。我们可以创建一个新的 DTO ,将前面的代码$Dto.get(() => ModelCategory, { columns: ['id', 'name'] })
封装起来,从而用于其他地方:
在 VSCode 中,可以通过右键菜单Vona Create/Dto
创建 DTO 的代码骨架:
@Dto() export class DtoCategoryTree {}
然后我们通过继承机制来封装 DTO:
@Dto() export class DtoCategoryTree + extends $Dto.get(() => ModelCategory, { columns: ['id', 'name'] }) {}
现在,我们再使用新创建的 DTO 来改造前面的 API 代码:
export class ControllerCategory extends BeanBase { @Web.get('getCategoryTree') + @Api.body(v.array(v.object(DtoCategoryTree))) + async getCategoryTree(): Promise<DtoCategoryTree[]>{ const tree = await this.scope.model.category.select({ columns: ['id', 'name'], }); return tree; } }
DtoCategoryTree
Promise<DtoCategoryTree[]>
这是优化前的,findMany 足足有 308kB 因为用户所关联的其他数据的 id 和命名字段也查出来了。
优化后骤降到 3.4kB
而 prisma studio 需要 21.7kB
这是因为 prisma studio 虽然也会查询所有关联数据,但他只查询了 id ,而我之前为了友好的显示数据所以查询了一个用于显示的字段,所以会比他大许多
而现在我反而比 prisma studio 更小,这是因为我不再查询 id 了,而是通过 _count 来查询关联数量。所以能够比 prisma studio 更小。
而为了实现关联字段的编辑我也大刀阔斧的重构了我的代码,能够做到在不加载全量关联关系的情况下动态通过分页数据感知到被关联表和当前数据行的关联关系。
]]>补充一下,这个项目使用 bytenode 编译后文件在跑,不是源代码跑。
axios 也做了统一处理
const axios = require('axios'); const https = require('https');
// 全局 10 秒超时 axios.defaults.timeout = 10000;
// 创建自定义的 HTTPS 代理,限制连接数 const httpsAgent = new https.Agent({ keepAlive: true, keepAliveMsecs: 30000, maxSockets: 30, // 限制并发连接数 maxFreeSockets: 5, // 限制空闲连接数 timeout: 10000, // 连接超时 });
// 创建 axios 实例 const apiClient = axios.create({ httpsAgent: httpsAgent, timeout: 10000, // 请求超时 maxRedirects: 3, });
module.exports = apiClient
有没有大佬能出出主意,到底是哪里出问题了?
]]>当你想给一个 已经有 3 层继承的祖传 Class 加事件系统:
class 祖传 Class extends 爷爷 Class { /* ... */ } // 传统方案:硬着头皮再继承一层 class 我的 Class extends 祖传 Class, EventEmitter { /* 多重继承?不存在的!直接报错! */ }
痛点暴击:
现在只需一行魔法:
import { eventable } from 'events-ex'; class 祖传 Java 风 Class extends 爷爷 Class { /* ... */ } // 保持原样 // 注入事件能力,但只注入(暴露) on/emit 方法(深藏功与名) eventable(祖传 Java 风 Class, { include: ['on', 'emit'] // 像做外科手术一样精准 }); // 用法和 EventEmitter 一毛一样 const obj = new 祖传 Java 风 Class(); obj.on('data', handleData); obj.emit('data', payload);
核心理念:
更骚的是连方法都能挂事件:
class 数据库 { connect() { // 原始方法保持纯洁 console.log('建立连接'); } } // 给 connect 方法加前置事件 eventable(数据库, { methods: { connect() { this.emit('pre-connect'); // 触发事件 this.super(); // 调用原方法( this.super 是魔法关键字!) } } }); // 监听数据库连接事件 const db = new 数据库(); db.on('pre-connect', () => { console.log('我要开始连接了,各组件注意!'); }); db.connect()
输出结果:
我要开始连接了,各组件注意! 建立连接
灵魂发问:
你们项目里有那种 不敢动又必须增强 的类吗?快来试试这套无痛事件注入方案!
传送门:
👉 GitHub 地址 | 📚 完整 API 文档
投喂姿势:
npm install events-ex
讨论点:
相比其他事件库,首先是带类型,让用户能知道 payload 的类型;其次是函数式,不再需要手敲事件名。然后是 sub 函数自动返回 unsub 函数,不再必须传入 sub 时的 listener:
const misakaStateChange = createTypedEvent<{ selfDestructionInProgress: boolean }>() // returns unsub function without defining handler outside const unsub = misakaStateChange.sub((payload) => console.log(payload)) misakaStateChange.dispatch({selfDestructionInProgress: true}) unsub()
>>> { selfDestructionInProgress: true }
另外还自带 react hook ,完美桥接 react 响应式变量,如果你想你也可以轻松写出其他响应式框架的版本。
是新包,但是已经在各种内部项目里用了两三年了,用过的同事都说好。
]]>从多个依赖包到 1 个运行时,从复杂配置到零配置开发——这就是 Bun 带给 MCP Server 开发的革命性变化
在传统的 MCP Server 开发中,我们需要搭建一套复杂的工具链:
以 Figma-Context-MCP 为例,一个标准的 TypeScript MCP 项目需要:
构建工具链
tsup
- TypeScript 构建工具,编译 TS 到不同 Node 版本的 JStypescript
- 类型系统支持@modelcontextprotocol/sdk
- MCP 协议 SDK开发服务
nodejs
- 运行时环境node-watch
- 文件变化监听express
- Web 框架cross-env
- 跨平台环境变量dotenv
- 环境配置管理测试框架
jest
- 测试框架ts-jest
- TypeScript 测试适配器f2c-mcp 项目 展示了 Bun 的完美解决方案:
{ "build": "bun run bun.build.script.ts", "dev": "bun --watch run bun.build.script.ts" }
一个脚本搞定所有构建需求:
const script = process.env.npm_lifecycle_script || '' const isDev = script.includes('--watch') const result = await Bun.build({ entrypoints: ['src/stdio.ts', 'src/cli.ts', 'src/streamable-http.ts'], outdir: 'dist', format: 'cjs', target: 'node', sourcemap: 'linked', minify: !isDev, env: isDev ? 'inline' : 'disable', })
关键优势:
{ "test": "bun test src/test/api.test.ts", "e2e": "bun test src/test/e2e.test.ts" }
无需配置:
{ "http:dev": "bun --env-file=.env --watch run src/streamable-http.ts", "http:prod": "bun --env-file= run src/streamable-http.ts" }
Bun 1.2+的突破:
指标 | Node.js 工具链 | Bun 方案 | 提升幅度 |
---|---|---|---|
项目启动时间 | 3-5 秒 | 0.5-1 秒 | 5 倍提升 |
热重载速度 | 2-3 秒 | <500ms | 6 倍提升 |
测试执行速度 | 10-15 秒 | 2-3 秒 | 5 倍提升 |
内存占用 | 200-300MB | 50-80MB | 3 倍减少 |
依赖包数量 | 15+ | 1 | 极简化 |
{ "scripts": { "build": "bun run build.script.ts", "dev": "bun --watch run build.script.ts", "test": "bun test", "serve": "bun --watch run src/server.ts" } }
// build.script.ts const result = await Bun.build({ entrypoints: ['src/index.ts'], outdir: 'dist', target: 'node' })
# 可以移除的包 npm uninstall tsup typescript ts-jest jest node-watch cross-env
Bun 的交叉编译能力让部署变得极其简单:
# 编译为各平台可执行文件 bun build --compile --target=linux-x64 ./src/index.ts bun build --compile --target=windows-x64 ./src/index.ts bun build --compile --target=darwin-x64 ./src/index.ts
从 Node.js 到 Bun 的迁移不仅仅是工具的替换,而是开发哲学的升级:
在 MCP Server 开发的新时代,Bun 不仅仅是一个更快的 Node.js 替代品,它重新定义了全栈 Javascript 开发的可能性。
立即开始你的 Bun + MCP 之旅,体验 3 倍效率提升的开发快感!
]]>https://github.com/prisma/prisma/issues/101421
https://github.com/prisma/prisma/issues/10142#issuecomment-1835279273
https://github.com/prisma/prisma/issues/20128
时区的问题,对于查询,写入,始终以 UTC 时区,从而忽略了 db 本身的时区,且 client 无法调整 https://github.com/prisma/prisma/issues/5051
model 中的关联关系对于业务侧使用显得特别笨重,比如简单的连表查询,尤其是那种不同业务需要临时或一次性的连表查询需要 Prisma.sql 来进行,如果在复杂则要走 typedsql;但是 typedsql 不支持动态条件,这种场景是业务侧最最最最多的,因此即便就是简单的列表+总条数查询都需要结合 Prisma.sql 来做原生 SQL 的拼接。
在遇到上面 1,2 的时候硬着头皮继续,但是遇到 3 的时候彻底让我放弃了它。
提桶跑路了 🏃♀️💨💨💨💨💨💨💨
兄弟们,节约时间,原理 prisma
]]>一些可以称赞的地方
总结就是,设计得挺好,有想法。但是实际做得太糙,哪怕小项目都不敢用。最开始舒服了一下,现在还得老老实实改回 node 。
]]>时间原因没法慢慢排查;暂时换 NextChat 上 mcp 。
如果必须要用 CherryStudio ,但又不想用内置的 bun ,有啥解决方案?
]]>也可以使用 npx lexe build -i=index.js 快速体验一下
rt ,周末写的小玩具,魔改了 AWS 的 Javascript 运行时 llrt 。
llrt 提供了大多数关键的 Node.js API ,但因为没有 JIT ,所以这个工具适合轻量级服务和 cli 工具。
实现上参考了 deno compile 和 bun compile ,目前一个 hello-world 打包出来是 10M ,虽然还可以更小一些,但相较于 deno 和 bun 的 50~60M 已经算可以了。
]]>各位 V 友们,你们在使用包管理工具有什么使用优先级吗?它们的区别是什么?作为一个后端,有时候会做一些前端开发,会纠结这些。虽然是瞎纠结,但还是想听各位 V 友们讲讲。
]]>// JS 的内置发布订阅 // create custom events const catFound = new CustomEvent("animalfound", { detail: { name: "cat", }, }); const dogFound = new CustomEvent("animalfound", { detail: { name: "dog", }, }); const element = document.createElement("div"); // create a <div> element // add an appropriate event listener element.addEventListener("animalfound", (e) => console.log(e.detail.name)); // dispatch the events element.dispatchEvent(catFound); element.dispatchEvent(dogFound); // "cat" and "dog" logged in the console
// 我的自定义发布订阅,我的桌面应用移动应用网页应用都是用它驱动的。 export class StateManage<T> { private inValue: T private publishChangeCalls: (() => void)[] = [] constructor(v: T) { this.inValue = v } subscriptChange(subCall: () => void) { this.publishChangeCalls.push(subCall) } unsubscriptChange(subCall: () => void) { this.publishChangeCalls = this.publishChangeCalls.filter(sub => !Object.is(sub, subCall)) } set value(v: T) { this.inValue = v this.publishChangeCalls.forEach(call => call()) } get value(): T { return this.inValue } }
]]>前几天,我我也开发了一款 Code Runner MCP Server:
Code Runner MCP Server ,支持运行 39 种编程语言!
今天,我就把我开发 MCP Server 的经验和遇到的一些坑,分享给大家!
以 Node.js 为例,从零开始开发一个 MCP Server !
从 https://nodejs.org/en 安装 LTS 版的 Node.js 即可。
在命令行运行下面命令,安装 Yeoman Generator for MCP Server:
npm install -g yo generator-mcp
在命令行运行下面命令,创建 MCP Server 项目:
yo mcp -n 'Weather MCP Server'
generator-mcp 已经把全部需要的代码框架和依赖都生成和安装了。
你可以按需修改代码,或者利用已有的代码直接进行调试和测试。
generator-mcp 已经配置好了 VS Code 的调试配置文件:launch.json 和 tasks.json
在 VS Code 中打开项目,按 F5 就能一键启动调试!
稍等片刻,浏览器自动打开 MCP Inspector 后,就能进行测试了!
此外,你还可以在其他支持 MCP 的客户端中,测试你的 MCP Server 。
generator-mcp 已经默认创建了 .vscode\mcp.json 文件,这个文件定义了在 VS Code 运行的 MCP Server 。
注:需从 https://code.visualstudio.com/insiders/ 下载最新版本的 VS Code Insiders 。
安装好最新的 VS Code Insiders 版本,点击 “start” 按钮,就能在 VS Code Insiders 的 Agent Mode 调用你的 MCP Server 啦!
测试完成后,就可以把你的 MCP Server 发布到 npm registry 或者 Docker Hub 了!
关于 Dockerfile 怎么写,以及如何在 VS Code 、Claude Desktop 等客户端配置 MCP Server ,还有 npx 可能在 Windows 上运行失败的问题,都可以参考 Code Runner MCP Server 的 README 和源代码,完全开源:
]]>仓库地址 https://github.com/pickknow/chrome-extension-react-Tailwindcss-typescript React TypeScript Tailwind CSS Webpack Chrome Extension APIs DaisyUI
bookmark tagger https://chromewebstore.google.com/detail/bookmark-tagger/eibebfbmnojbnbadhhcnnioocejgfmpg?authuser=0&hl=en 一个添加书签的时候可以使用多标签的扩展,
]]>首先这个域名是最便宜的那种,一年 8 块钱,续费就比较贵了,所以我打算每年换个域名。但是换了之后就得去帮他修改订阅,很麻烦。
所以我想了个方案,就是找一个支持 redirect 的免费服务,类似于 xxx.github.io ,部署一下 redirect url 的服务,类似于
<!DOCTYPE html> <html> <head> <meta http-equiv="refresh" cOntent="0;url=https://fgfw.xxx.com"> <title>301 Moved Permanently</title> </head> <body> </body> </html>
我把订阅 url 改成 https://xxx.github.io/index.html 就行了。以后换域名了,我修改 github 的配置就行了。路由器那边不用动。
但是发现 openclash 是通过 curl 下载订阅的,而 github pages 不支持 301 跳转。
这让我想找一个支持 node js server 的站点,就又回到鸡生蛋蛋生鸡的问题了,cf worker 就是干这个,但是它给我的缺省域名被墙了,vercel 也是如此。
所以我需要一个能够 redirect 我 url 的服务,能提供免费域名,类似 xxx.github.io ,最好比较坚挺,比我的域名存活时间长。
]]>然后登陆官网看了一下,目前 LTS 是 22.x
想问大家平时会保持用最新版的 node 吗?
一般是什么时候会进行大版本更新的?
]]> //几个类型导入: import { stream } from "hono/streaming" import { Readable, Writable } from "node:stream" import { ReadableStream } from "node:stream/web" // ...... const fileStream = createReadStream(filePath) // 将文件流作为响应返回给客户端 return stream( c, async (stream) => { // Write a process to be executed when aborted. stream.onAbort(() => { console.log('Aborted!') }) // Write a Uint8Array. await stream.write( new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]) ) // Pipe a readable stream. // 这里出现了类型不兼容 // await stream.pipe(Readable.toWeb(fileStream)) await stream.pipe(ReadableStream.from(fileStream)) }, async (err, stream) => { // stream.writeln('An error occurred!') console.error('An error occurred!', err) } )
ts 提示: 类型“import("stream/web").ReadableStream<any>”的参数不能赋给类型“ReadableStream<any>”的参数. 属性“pipeThrough”的类型不兼容。
我该如何解决,或者说如何在 nodejs 环境使用...
谢谢大家了!🙇😘
]]>1.猜测是 node_modules 文件过多,建立索引的问题,设置-编辑器-文件类型-排除-node_modules 已经添加。 2.猜测内存分配少了,毕竟 java 开发的 webstorm ,可能会频繁垃圾回收,给分配大点 分配的内存也不少了 -Xms1024m -Xmx8192m -XX:ReservedCodeCacheSize=1024m 3.所有插件都禁用
以上都试过了,无果,目前 cpu 稳定在 200%+,怎么都降低不下来
]]>略微吐槽一下,简直是现实版茴字有几种写法.
1.把中间件拆成 Middleware,Guards,Interceptors,Exception,Pipes, 并且他们都能获取请求上下文(Request, Response), 并且有不同的顺序, 但同类别内也有顺序, 并且生命周期是如此的繁琐.
https://docs.nestjs.com/faq/request-lifecycle
2.封装一个三方 API 花样太多了.
单独的 Service, Providers, Dynamic Module, ConfigurableModuleClass
这里面还有分 Sync 和 Async 导入, Global 模块.
3.模块系统是 Angular 那一套, 注册繁琐, 还会出现循环依赖.
心智负担比 Spring Boot 高太多了.
JS 的后端框架不像前端那样卷, 真希望能出个好用的.
]]>找不到解决办法,特来求助大佬们,谢谢
]]>今天上去看了下也有 js 版本了
github: https://github.com/google-gemini/generative-ai-js
const { getGenerativeModel } = require('@google-gemini/generative-ai-js') async function generateText() { const model = await getGenerativeModel({ model: 'gemini-pro' }) const request = { prompt: '请写一个 js 版本的 websocket 服务' } const respOnse= await model.generateContent(request) // print response text console.log(response.text) } generateText()
]]>预览地址: https://mail.fakeact.fun/
使用了 Cloudflare 的邮件路由功能,添加 DNS 解析记录之后,把所以邮件都转发到 worker ,worker 操作把接收到邮件存到 KV ,并设置 7200 秒过期
Astro 全栈页面开发,只需要读取 KV 数据,在前端页面显示,也部署到 Cloudflare Pages 下
Cloudflare Email Routing 与 Cloudflare Workers 一起使用,只能向批准的邮件地址发送或转发,Cloudflare 已与 MailChannels 合作,通过 Worker 免费发送电子邮件的服务
以上服务只使用了 Cloudflare 免费服务,用于实践和学习,如果发送失败,可能是当天额度超了
ecosystem.config.js
来启动一个服务,大概长这样 module.exports = { apps: [{ name: 'app1', script: './app.js' }] }
如果要启动一个静态文件服务,这个配置要怎么写呢?
]]>只能下载 npm 安装后搜索吗
]]>nodejs 生态有没有类似的?
主要功能: 界面好看点 管理台界面 管理员登录 权限管理 菜单管理
]]>免安装使用:
npx fakeact
全局安装:
npm i fakeact -g
使用:
fakeact -m composer
刚开始还以为项目中更改了什么导致的
尝试创建新项目:
npx create-next-app@latest
之后执行 build
npm run build --verbose
~/D/t/my-app on main npm run build --verbose 16:25:45 npm verbose cli /opt/homebrew/Cellar/node/23.2.0/bin/node /opt/homebrew/bin/npm npm info using npm@10.9.0 npm info using node@v23.2.0 npm verbose title npm run build npm verbose argv "run" "build" "--loglevel" "verbose" npm verbose logfile logs-max:10 dir:/Users/xxx/.npm/_logs/2024-11-15T08_25_54_292Z- npm verbose logfile /Users/xxx/.npm/_logs/2024-11-15T08_25_54_292Z-debug-0.log > my-app@0.1.0 build > next build ▲ Next.js 15.0.3 Creating an optimized production build ... ✓ Compiled successfully ✓ Linting and checking validity of types Collecting page data ..Error: Failed to collect configuration for / at <unknown> (/Users/xxx/Downloads/tt2/my-app/node_modules/next/dist/build/utils.js:1131:23) at async Span.traceAsyncFn (/Users/xxx/Downloads/tt2/my-app/node_modules/next/dist/trace/trace.js:153:20) { [cause]: TypeError: e[o] is not a function at Function.t (/Users/xxx/Downloads/tt2/my-app/.next/server/webpack-runtime.js:1:127) at async getLayoutOrPageModule (/Users/xxx/Downloads/tt2/my-app/node_modules/next/dist/server/lib/app-dir-module.js:37:15) at async collectAppPageSegments (/Users/xxx/Downloads/tt2/my-app/node_modules/next/dist/build/segment-config/app/app-segments.js:50:45) at async (/Users/xxx/Downloads/tt2/my-app/node_modules/next/dist/build/utils.js:1129:28) at async Span.traceAsyncFn (/Users/xxx/Downloads/tt2/my-app/node_modules/next/dist/trace/trace.js:153:20) } > Build error occurred Error: Failed to collect page data for / at <unknown> (/Users/xxxx/Downloads/tt2/my-app/node_modules/next/dist/build/utils.js:1234:15) { type: 'Error' } npm verbose cwd /Users/xxx/Downloads/tt2/my-app npm verbose os Darwin 22.6.0 npm verbose node v23.2.0 npm verbose npm v10.9.0 npm verbose exit 1 npm verbose code 1
用不同的 node 版本也试过了依旧如此,有遇到类似的吗?
有时候是出现这个:全新项目什么都不改。
npm run build 16:32:55 > my-app@0.1.0 build > next build ▲ Next.js 15.0.3 Creating an optimized production build ... Failed to compile. ./src/app/page.tsx + 1 modules Unexpected end of JSON input > Build failed because of webpack errors
]]>