油猴脚本创建webworker踩坑记录

起因是我在使用vite-plugin-monkey编写油猴脚本,用ts编写webworker脚本,然后在一些网站创建webworker准备做一些耗时任务时,webworker一直没生效。我一直以为new Worker()梭哈就好了,没想到里面的门道这么多,整理了一些问题。

1.webworker不能直接使用非同源

假设谷歌有一个w.js脚本,在你的网站里面,不能直接new Worker('https://www.google.com/w.js')去加载。注意,这里是限制非同源,和跨域CORS没关系。

2.worker导入方式

1.new Worker()

传统worker的导入方式是new Worker(''), new Worker做了什么?它会发起一个http请求,请求自身站点的这个worker文件。(要求这个文件真实存在)

2.new Worker(BlobURL)

传统worker导入方式又有另一种变体,就是获取到js的字符串,通过new Blob([jsCodeString], { type: "application/javascript" })得到blob,然后再通过URL.createObjectURL(blob)生成blob url,最后new Worker(blob url), 就不需要文件真实存在了。

js 复制代码
const jsContent = `const a = 1;console.log(a)`
const blob = new Blob([jsCodeString], { type: "application/javascript" })
const url = URL.createObjectURL(blob)
new Worker(url)

3.new Worker(new URL('./w.js', import.meta.url).href, { type: 'module' })

new Worker('')是以浏览器地址栏页面 URL 为基准解析,可是在vue/react等应用出现后,这些SPA的History 多层路由(/user/detail/123)会拼接错误路径,导致找不到这个worker文件,所以Chrome在2020年推出用import.meta.url来查找worker文件,无论页面路由多深、页面在哪一级,永远以当前脚本文件目录查找 worker。但是一定要在script module里面执行

js 复制代码
<script type="module">
new Worker(new URL('./w.js', import.meta.url).href, { type: 'module' })
</script>

4.import Worker from './worker?worker&inline'

这是vite的语法,用来支持将外部worker文件内联到代码中的场景。

原理就是第二种方式,先把外部js文件转成code strig,再用Blob内联,只不过这个过程不用你手动处理了,vite帮你处理了。

3.代码实战

按照以上顺序,我让AI写了4组代码,并且分js/ts来测试。

代码具体实现如下:

A-js

js 复制代码
const w = new Worker('./fib-worker.js');

A-ts

ts 复制代码
const w = new Worker('./fib-worker.ts');

B-js

js 复制代码
import jsWorkerCode from './fib-worker.js?raw';

const blob = new Blob([jsWorkerCode], { type: 'text/javascript' });
const url = URL.createObjectURL(blob);
const w = new Worker(url);
URL.revokeObjectURL(url);

B-ts

ts 复制代码
import tsWorkerCode from './fib-worker.ts?raw';

const blob = new Blob([tsWorkerCode], { type: 'text/javascript' });
const url = URL.createObjectURL(blob);
const w = new Worker(url);
URL.revokeObjectURL(url);

C-js

js 复制代码
const url = new URL('./fib-worker.js', import.meta.url).href;
const w = new Worker(url, { type: 'module' });

C-ts

ts 复制代码
const url = new URL('./fib-worker.ts', import.meta.url).href;
const w = new Worker(url, { type: 'module' });

D-js

js 复制代码
import FibWorkerJS from './fib-worker.js?worker&inline';

const w = new FibWorkerJS();

D-ts

ts 复制代码
import FibWorkerTS from './fib-worker.ts?worker&inline';

const w = new FibWorkerTS();

以下是用vite-plugin-monkey掘金的页面开发环境下跑的:

然后来解释一下:

A-js是向掘金发起了一个http请求,去请求这个worker文件,而这个文件不存在,掘金返回了一个200的html <!DOCTYPE html><html>。然后Worker尝试去执行,发现第一行是<,于是报出具体的语法错误:

注意,不是说worker文件不存在就会返回html,是要看网站的处理,如果面对资源不存在,网站返回html,那就是这个。如果网站不处理,那么这个请求就会一直pending等待中,然后触发浏览器的报错。

A-ts和A-js一样

B-js就是标准的内联js worker,所以可以运行

B-ts是因为导入了ts源码,里面含有一些类型,而浏览器不支持解析这些类型,所以报错了。(有时候有人导入ts也成功,是因为里面不含类型,浏览器按js来解析了)

C-js和A-js差不多都是new Worker(), 为什么报错不一样呢?问题在于这个import.meta.url,此时worker不是向掘金发起http请求,而是向vite-plugin-monkey本地的vite服务器发起请求,实际上是new Worker('http://127.0.0.1:5173/src/fib-worker.js'),违反了第一条规则webworker不能直接使用非同源

C-ts同上

D-js不是说会内联worker吗?为什么还是和A-js一样,请求不存在的worker?我们可以打开面板,看到这条请求

里面的内容是这样的

也就是说,因为vite在开发环境下是不打包的,所以只是对其简单的包装了一下,由于这样写,实际上还是向掘金发起了http请求,然后由于掘金对这个请求没有处理,一直pending,所以触发了浏览器的错误事件。

4.打包结果

我们可以看到CD变了,C-js是因为tampermonkey打包出来的是普通script,通过iife运行,所以外层script没有module,导致import.meta.url在这里是undefined,从而构造URL失败。

而D则是正确的内联在code里面,所以在打包后能够正常运行。

5.怎么在开发环境下使用ts+webworker?

我们可以看到,在开发环境下,只有一种方式,那就是使用js内联。但是我就是想让它支持ts丰富的类型提示,总不能每次修改一点,就打包测试吧?这样调试起来太麻烦了,我之前试过,每次改一点->打包->删除旧的油猴脚本->替换新的脚本,非常繁琐。

其实也有看到社区自己写了一些plugin,在检测到?worker&inline时,就先打包,直接内联,这样就ok了,但是感觉还是不够优雅。

终于被我找到了另一种方式:

ts 复制代码
const url = new URL('./fib-worker.ts', import.meta.url).href
const res = await fetch(url)
const jsCodeString = await res.text()
const blob = new Blob([jsCodeString], { type: 'application/javascript' })
const blobUrl = URL.createObjectURL(blob)
const w = new Worker(blobUrl)

这里涉及到vite的服务器原理了。我们在开发vue/react项目的时候,在开发环境下,你会发现浏览器发起了很多请求,像vue,ts,tsx等等。

可是浏览器怎么会认识vue、ts呢?那是因为浏览器向vite请求vue文件,先不直接返回vue文件,而是通过@vue/compiler-sfc先把vue文件转成js文件,再返回。

同样的,浏览器向vite请求ts文件时,vite会先做一遍类型擦除,类型擦除后不就是js文件吗?所以也不用编译,直接返回。

那么回到刚才的问题,const url = new URL('./fib-worker.ts', import.meta.url).href,这里得到的其实就是vite服务器的ts文件的地址: http://127.0.0.1:5173/src/fib-worker.ts

而刚才说了,向vite服务器请求ts,vite会自动做一次类型擦除,所以得到的是纯净的js文件

既然能得到纯净的js文件,那么我们就要得到它。通过fetch去获取,而vite服务器设置的是允许跨域,所以我们就得到了纯净的jsCodeString。既然得到了jsCodeString,上面开发环境演示的几个demo中,只有B-js是成功的,那么就只需要转Blob url,就能在开发环境下使用webworker了。

完整封装如下:

ts 复制代码
import TSWorker from './worker?worker&inline'

async function createWorker(): Promise<Worker> {
    if (import.meta.env.DEV) {
        const url = new URL('./worker.ts', import.meta.url).href
        const res = await fetch(url)
        if (!res.ok) throw new Error('获取worker代码失败')
        const jsCodeString = await res.text()
        const blob = new Blob([jsCodeString], { type: 'application/javascript' })
        return new Worker(URL.createObjectURL(blob))
    }
    return new TSWorker()
}

await createWorker()

开发环境下走fetch,打包时走vite的worker inline

相关推荐
原则猫2 小时前
前端基础大厦
前端
陈随易4 小时前
编程语言级别的Skill市场,AI Agent 的未来形态
前端·后端·程序员
SoaringHeart4 小时前
Flutter进阶:基于 EasyRefresh 的下拉刷新封装 n_easy_refresh_mixin.dart
前端·flutter
IT_陈寒6 小时前
Vite的热更新突然不香了,排查三小时差点砸键盘
前端·人工智能·后端
子兮曰7 小时前
Agency-Agents 深度解析:400+ AI 专家的"梦之队"如何重塑开发工作流
前端·后端·vibecoding
山河木马7 小时前
渲染管线-计算得到gl_Position(顶点着色器)之后续GPU流程
javascript·webgl·图形学
竹林8187 小时前
用 The Graph 查询链上数据实战:从手搓 RPC 到 Subgraph,我的 NFT 项目数据加载快了 10 倍
前端·javascript
妙码生花8 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(十九):点选验证码代码逐行目检
前端·后端·go
Awu12278 小时前
⚡从零开发 Agent CLI(五)实现一个可治理、可扩展的工具系统
前端·人工智能·claude