ESModule如何让前端更合理的组织Worker

零 JavaScript教程评论65字数 4254阅读14分10秒阅读模式

ESModule如何让前端更合理的组织Worker

简单介绍 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 的 Workernew 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

without-module-workerjs.svg文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16365.html

不使用 { type: "module" } 的 main.js文章源自灵鲨社区-https://www.0s52.com/bcjc/javascriptjc/16365.html

without-module-mainjs.svg文章源自灵鲨社区-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

with-module-workerjs.svg

使用 { type: "module" } 的 main.js

with-module-mainjs.svg

通过使用 { type: "module" } 我们可以复用 WorkerHandleType 。

但是,仅此而已吗?

module 的 Worker 同样的可以用 import

尽管在 ESModule 之前可以 Worker 通过 importScripts 导入外部代码,但是这样的导入方式总和业务中其他的代码组织风格不同,而且这样的导入是同步的 。既然 Worker 可以是 { type: "module" } 了,可以用 export 暴露功能给其他模块使用,同样可以通过 import 复用外部代码,通过相对路径引用当然可以,但是发布到 npm 的三方包同样可以?。

这里用 consola 来优化我们上面的例子,让日志更醒目(记得先安装依赖 pnpm install consola)。

使用 { type: "module" } 并且引入了 consola 的 worker.js

with-module-workerjs-consola.svg

可以尝试自己跑一下,会看到控制台 Worker 中的日志的变化:

image.png

大胆想象,是否可以把 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)):

packagejson.svg

然后创建 src 文件夹,里面放入 main.js 和 worker.js 代码

esm-worker/src/main.js

npm-mainjs.svg

esm-worker/src/worker.js

npm-workerjs.svg

最后记得安装下依赖然后 pnpm publish 发布这个包。

验证

我们用 vite 起个项目安装我们的包试一下,记得 vite.config.ts 的 defineConfig 中加上这样的一个配置,因为 vite 默认会对依赖的包做预构建优化,我们的包内部含有对同级目录文件的动态加载,如果不排除会报找不到依赖的文件 :

json

复制代码
optimizeDeps: {
  exclude: [
    'esm-load-worker'
  ]
}

感兴趣的同学可以试一下安装 esm-load-worker 这个包的 0.0.1 版本在自己项目中试一下, 这里我替大家试一下看下结果:

esm-load-worker.gif

到这里,我们要介绍的最核心内容就结束了,但是还有最后一个技巧交给大家,如虎添翼。

如果说我们的 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

ts-packagejson.svg

可以看到我们增加了构建脚本和对 tsup 的依赖,我们现在来配置下 tsup 的构建:

增加 tsup.config.ts 文件

tsup-config.svg

增加了 tsconfig.json

tsup-config.svg

改造我们的 main.js 为 main.ts

ts-main.svg

改造我们的 worker.js 为 worker.ts

ts-worker.svg

最终,我们执行 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 版本,在自己项目中试一下, 这里我替大家试一下看下结果:

esm-load-worker-ts_1.gif

总结

代码都在 github 的 luvletterldl/esm-worker 这个仓库中有体现,想体验的也可以分别安装 esm-load-worker 的 0.0.1 的 js 版本和 1.0.0 的 ts 版本。

ESModule 的逐渐流行和普及给 Worker 代码的组织带来了革命性的革新,往小了说:开发者在后续开发过程中可以更好的维护代码,整个生态都将逐渐的模块化和规范化;往大了说:开发者之后在使用多线程解决性能问题的门槛逐渐降低,可以更大的推动 Web 应用的发展,可以让开发者尽可能利用用户的多核 CPU 的设备特性实现更好的产品。

零
  • 转载请务必保留本文链接:https://www.0s52.com/bcjc/javascriptjc/16365.html
    本社区资源仅供用于学习和交流,请勿用于商业用途
    未经允许不得进行转载/复制/分享

发表评论