简单介绍 Worker
简单介绍下 Web Worker ,随着 Web
应用日益发展迭代,性能需求越来越高,经典的事件循环(单线程)逐渐成了我们应用性能的瓶颈,Web Worker
就是可以让 Web
应用突破这个瓶颈的一个好手段,不管是在浏览器还是在 Node.js
中,Web Worker
都可以为开发者开辟出一个 OS
级别的新线程来执行一些耗时操作来充分利用多核 CPU
设备的能力,并且避免了耗时操作对主线程的阻塞。前端开发者应该都不陌生,或多或少写过或者用过,我们就不多赘述,这次的主题是要聊一下 ESModule
的出现给 Worker
的使用带来了哪些改变。
ESModule 是什么
ESModule,全称 ECMAScript 模块,是 JavaScript 的一种标准模块系统,它在 ES6(ES2015) 中被引入。 ESModule 提供了一种结构化的方式来组织和重用 JavaScript 代码,使得代码更易于维护和扩展。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16365.html
主要特点
- 模块化: 通过
import
语句导入其他模块的代码,通过export
语句将模块中的代码暴露给外部。ESModule 允许将代码拆分成独立的模块,每个模块负责特定功能,并通过import
和export
语句进行相互调用。 - 异步加载: ESModule 可以异步加载,提高了网页性能,避免阻塞页面渲染。
- 范围: ESModule 中的变量和函数默认是私有的,只有通过
export
才能被外部访问,提高了代码的安全性。
优势
- 代码组织/重用: 允许将代码拆分成独立的模块,允许模块之间共享代码,提高代码的可读性、可维护性和代码的重用率。
- 依赖管理: ESModule 提供了明确的依赖关系管理机制,方便代码维护和更新。
- 性能: ESModule 允许异步加载,提高了网页性能。
随着 IE11
的落幕和现代浏览器的更新迭代,浏览器逐渐对 ESModule
的支持完善,在浏览器处理 Worker
的代码组织和加载使用可以有更加优雅的手段。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16365.html
ESModule 之前如何创建和使用 Worker
在 ESModule
出现之前我们要在代码中使用 Worker
解决一些特定问题,需要单独写一个 js
文件, 在项目中用 Worker
的构造方法 new Worker(workerUrl)
来初始化一个 Worker
,这个过程其实是有限制的,构造方法的参数必须是可访问且遵循同源策略,这就意味着你编写的 Worker
代码要么放在项目中,使用构建工具比如 Webpack、Vite
等把 Worker
代码最终和项目代码一起打包构建,要么把代码放到远程网络,比如 CDN
上,并且你的Worker
代码无法通过 import
和 export
复用三方代码或者暴露给外部代码使用。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16365.html
ESModule 让 Worker 变得更加安全合理
ESModule
的出现让 Worker
代码变的模块化,通过设置构造 Worker
参数的第二个参数就可以告诉浏览器或者 Nodejs
,你要初始化一个 module
的 Worker
: new Worker(xxx.js, { type: "module" })
。文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16365.html
假设你有一个 Worker
希望根据传递参数的类型决定调用其内部不同的方法解决不同的问题,看下组织代码的差异:文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16365.html
不使用 { type: "module" }
的 worker.js
文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16365.html
文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16365.html
不使用 { type: "module" }
的 main.js
文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16365.html
文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16365.html
worker.js
和 main.js
中都用到了 WorkerHandleType
,但是在两个文件中没法复用文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16365.html
使用 { type: "module" }
的 worker.js
使用 { type: "module" }
的 main.js
通过使用 { type: "module" }
我们可以复用 WorkerHandleType
。
但是,仅此而已吗?
module 的 Worker 同样的可以用 import
尽管在 ESModule
之前可以 Worker
通过 importScripts
导入外部代码,但是这样的导入方式总和业务中其他的代码组织风格不同,而且这样的导入是同步的 。既然 Worker
可以是 { type: "module" }
了,可以用 export
暴露功能给其他模块使用,同样可以通过 import
复用外部代码,通过相对路径引用当然可以,但是发布到 npm
的三方包同样可以?。
这里用 consola 来优化我们上面的例子,让日志更醒目(记得先安装依赖 pnpm install consola
)。
使用 { type: "module" }
并且引入了 consola
的 worker.js
可以尝试自己跑一下,会看到控制台 Worker
中的日志的变化:
大胆想象,是否可以把 Worker 代码到 npm 包里
之前提到的 Worker
在通过构造函数初始化的时候需要文件的有效路径,这个限制的存在就约束开发者初始化 Worker
必须要有一个可以访问到 Worker
文件的 url
,并且传递进去,我们要么在项目中组织这样的逻辑(依托于开发服务器的能力),要么把这个文件放到 CDN
等的远程服务器,如果我们想要发布一个 npm
包,内部包含构造 Worker
并使用的能力,并且还可以在项目中引用这个包,该怎么做呢?
为什么要把 Worker 代码放到 npm 包里发布
有些场景我们需要使用 Worker
处理耗时/计算密集型的业务逻辑,但是如果想复用这个能力,直接 copy
代码当然可以解决问题,但是随着项目功能的迭代,不同项目之间的变化会让这个逻辑的维护逐渐失控,最好是把这块逻辑进行封装,让使用方调用设计好的 API
来解决特定问题,尽量降低对使用方代码的入侵。不应让使用方感知其内部的细节,降低使用的心智负担,使用方也不用考虑内部对 Worker
的复杂处理。这种情况下通常会考虑构建一个 npm
包并进行发布。
解决路径问题
import.meta 是一个给 JavaScript
模块暴露特定上下文的元数据属性的对象。它包含了这个模块的信息,比如说这个模块的 URL
。
可以使用 new URL()
借助 import.meta
上的 url
属性来构造出构造 Worker
需要的 url
:
js
// main.js
const workerUrl = new URL('./worker/posenet.js', import.meta.url)
const worker = new Worker(workerUrl, { type: 'module' })
这样我们就可以在不借助开发服务器帮我们请求路径的情况下通过浏览器的 ESModule
原生能力加载到我们的 Worker
了。
让我们发一个这样的包试试
还是复用之前的例子,先创建个项目 pnpm init
,然后更改 package.json
(这里就放我发的演示包的代码了:luvletterldl/esm-worker --- luvletterldl/esm-worker (github.com)):
然后创建 src
文件夹,里面放入 main.js
和 worker.js
代码
esm-worker/src/main.js
esm-worker/src/worker.js
最后记得安装下依赖然后 pnpm publish
发布这个包。
验证
我们用 vite
起个项目安装我们的包试一下,记得 vite.config.ts
的 defineConfig
中加上这样的一个配置,因为 vite
默认会对依赖的包做预构建优化,我们的包内部含有对同级目录文件的动态加载,如果不排除会报找不到依赖的文件 :
json
optimizeDeps: {
exclude: [
'esm-load-worker'
]
}
感兴趣的同学可以试一下安装 esm-load-worker
这个包的 0.0.1
版本在自己项目中试一下, 这里我替大家试一下看下结果:
到这里,我们要介绍的最核心内容就结束了,但是还有最后一个技巧交给大家,如虎添翼。
如果说我们的 Worker 可以用 TypeScript 来写就好了,安排?
我们前面的例子中代码都是用 js
来完成的,可以用 ts
重写我们的代码,借助 tsup
来打包我们的代码,并且输入类型声明文件,这对于大型复杂项目是很有必要的,关于 tsup
的内容,如果大家感兴趣可以留言,我考虑后续专门出一期来介绍发布 npm
包使用到的一些流行工具和模式。
tsup 是干什么的
可以简单理解为使用 esbuild
把你的 ts
代码通过很少的配置进行打包构建并输入类型声明文件的一个工具。
改造之前的代码
如果你嫌麻烦,直接看 ts
分支的代码: luvletterldl/esm-worker at ts (github.com),这里为了做区分,包版本改为了 1.0.0
, 之前非 ts
的包是 0.0.1
package.json
可以看到我们增加了构建脚本和对 tsup
的依赖,我们现在来配置下 tsup
的构建:
增加 tsup.config.ts 文件
增加了 tsconfig.json
改造我们的 main.js 为 main.ts
改造我们的 worker.js 为 worker.ts
最终,我们执行 pnpm build
,可以看到有一个 dist
文件夹,其中有4个文件:
bash
dist
├── main.d.ts
├── main.js
├── worker.d.ts
└── worker.js
其中两个以 .d.ts
结尾的分别是对应 js
文件的类型声明文件,这样我们在项目中使用的时候就可以有类型,并且我们的源代码也更易维护!
再次验证
感兴趣的同学可以试一下安装 esm-load-worker
这个包的 1.0.0
版本,在自己项目中试一下, 这里我替大家试一下看下结果:
总结
代码都在 github
的 luvletterldl/esm-worker
这个仓库中有体现,想体验的也可以分别安装 esm-load-worker
的 0.0.1
的 js
版本和 1.0.0
的 ts
版本。
ESModule
的逐渐流行和普及给 Worker
代码的组织带来了革命性的革新,往小了说:开发者在后续开发过程中可以更好的维护代码,整个生态都将逐渐的模块化和规范化;往大了说:开发者之后在使用多线程解决性能问题的门槛逐渐降低,可以更大的推动 Web
应用的发展,可以让开发者尽可能利用用户的多核 CPU
的设备特性实现更好的产品。
评论