added TypeScript-and-Service-Worker
All checks were successful
Build & Depoly / Depoly blog (push) Successful in 1m40s
All checks were successful
Build & Depoly / Depoly blog (push) Successful in 1m40s
This commit is contained in:
parent
a1dacac023
commit
729704984c
1 changed files with 99 additions and 0 deletions
99
source/_posts/TypeScript-and-Service-Worker.md
Normal file
99
source/_posts/TypeScript-and-Service-Worker.md
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
---
|
||||||
|
title: TypeScript和Service Worker
|
||||||
|
tags:
|
||||||
|
- Web
|
||||||
|
date: 2024-10-16 22:58:32
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
在Service Worker中使用TypeScript还蛮麻烦的,因为Service Worker的类型属于内建类型库WebWorker下,默认情况下WebWorker的`self`是`WorkerGlobalScope & typeof globalThis`。但是Service Worker的`self`确实提供了一些Worker没有的服务,比如install事件,用`WorkerGlobalScope`确实不够。
|
||||||
|
|
||||||
|
<!--more-->
|
||||||
|
|
||||||
|
````json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["ESNext", "WebWorker"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
````
|
||||||
|
|
||||||
|
所以实际上,WebWorker也提供了`ServiceWorkerGlobalScope`……这个设计让人感觉自然又诡异。ServiceWorker确实是Worker的一种,但是:因为默认情况下你不能用不同类型覆盖值的类型(但是可以合并!这个不是本章重点,TS就是这么神奇),按照直觉的:
|
||||||
|
|
||||||
|
````ts
|
||||||
|
declare let self: ServiceWorkerGlobalScope & typeof globalThis;
|
||||||
|
````
|
||||||
|
|
||||||
|
很有可能会被报错:不能用不同类型覆盖值的类型声明。当然,这是一个很有必要的安全措施,否则类型就会随着声明的顺序变化。用`// @ts-ignore`可以隐藏这个错误,但是在代码联想中它仍然是`WorkerGlobalScope & typeof globalThis`,这不是我想要的。
|
||||||
|
|
||||||
|
在[TypeScript项目相关的Issue](https://github.com/microsoft/TypeScript/issues/14877)中很多人提到了一种绕过的方法:类型强转加上重命名。
|
||||||
|
|
||||||
|
````ts
|
||||||
|
const sw = self as unknown as (ServiceWorkerGlobalScope & typeof globalThis);
|
||||||
|
````
|
||||||
|
|
||||||
|
然后使用`sw`来做Service Worker专门的工作。这个workaround非常简单有效,但是我觉得它有点不够漂亮。首先是类型强转有风险,其次是我们明明有`self`,却要用`sw`,像是这串代码不是在Service Worker中工作的一样。那么,我们可以直接用一个局部新`self`覆盖掉全局`self`吗?
|
||||||
|
|
||||||
|
````ts
|
||||||
|
const self: ServiceWorkerGlobalScope & typeof globalThis = self;
|
||||||
|
let self: ServiceWorkerGlobalScope & typeof globalThis = self;
|
||||||
|
````
|
||||||
|
|
||||||
|
上面的代码都不可以用。因为JavaScript的词法作用域在同一个作用域内是没有shadowing的,所以你只能得到一个“块级变量不能在声明之前引用“,包括这种:
|
||||||
|
|
||||||
|
````ts
|
||||||
|
const self0 = self;
|
||||||
|
const self: ServiceWorkerGlobalScope & typeof globalThis = self0;
|
||||||
|
````
|
||||||
|
|
||||||
|
也属于这种错误。
|
||||||
|
|
||||||
|
那我们可以用一个函数把`self`抓住吗?
|
||||||
|
|
||||||
|
````ts
|
||||||
|
const captureSelf = () => self;
|
||||||
|
const self: ServiceWorkerGlobalScope & typeof globalThis = captureSelf();
|
||||||
|
````
|
||||||
|
|
||||||
|
大成功!可以通过语法和类型检查。但是workbox构建时会出错:
|
||||||
|
|
||||||
|
````text
|
||||||
|
Error: Unable to find a place to inject the manifest.
|
||||||
|
This is likely because swSrc and swDest are configured to the same file.
|
||||||
|
Please ensure that your swSrc file contains the following: self.__WB_MANIFEST
|
||||||
|
````
|
||||||
|
|
||||||
|
静态分析中一般会把函数当作受副作用影响的部分,它们的返回值不会被看作是不变的。可能就是因为这样workbox-build就不再认识我们的`self`了。
|
||||||
|
|
||||||
|
那词法作用域不行,函数作用域呢?这样写并不会有语法错误:
|
||||||
|
|
||||||
|
````ts
|
||||||
|
var self: ServiceWorkerGlobalScope & typeof globalThis = self;
|
||||||
|
````
|
||||||
|
|
||||||
|
但这样做只会得到`undefined`。你可以尝试在浏览器的开发者工具执行下面的代码:
|
||||||
|
|
||||||
|
````js
|
||||||
|
(function () {
|
||||||
|
var window = window;
|
||||||
|
console.log(window);
|
||||||
|
})()
|
||||||
|
````
|
||||||
|
|
||||||
|
因为在`var window`的时候这个变量已经在函数作用域中被声明了,所以当你`window = window`时就是`window = undefined`。
|
||||||
|
|
||||||
|
在那个相关的Issue中,有人把`self`转换类型传入一个单独的函数。这样做也不错,可以保留自己的`self`,不过还是要多一层栈,而且要调用函数,跟类型强转也没有太大区别。
|
||||||
|
|
||||||
|
如果可以调用函数……我们将会有is语法可以用:
|
||||||
|
|
||||||
|
````ts
|
||||||
|
function isServiceWorker(self: WorkerGlobalScope): self is ServiceWorkerGlobalScope {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
````
|
||||||
|
|
||||||
|
然后将Service Worker专属的操作放进 `if (isServiceWorker(self)) { ... }`,一切正常!而且这个方法可以让我们分别做Service Worker和Worker的兼容。不过我们仍然要调用一个函数,而且会多一个branching,好在Service Worker的顶层代码不常执行,所以这个代价也能接受。
|
||||||
|
|
||||||
|
当然,其实类型强转到另一个变量的做法也可以,而且不需要branching的开销。虽然这样说,但其实影响都不大,主要还是看你比较喜欢哪个。
|
||||||
|
|
||||||
|
如果你对我如何处理Service Worker感兴趣的话,可以看看[图图的相关代码](https://code.lightstands.xyz/Rubicon/tutu/src/commit/46e7f1aaea91f742dd816369fbed0e8dc85944cb/src/serviceworker)。图图是一个在Web技术上构建的Mastodon客户端,如果你能试用并给我一些反馈的话就更好啦!
|
Loading…
Reference in a new issue