TypeScript: Unable to access ServiceWorkerGlobalScope via `self.`

Since ServiceWorker related types sit in lib.webworker.d.ts, webworker lib need to be added to tsconfig.json.

{
  "compilerOptions": {
    "lib": [
      "es2017",
      "webworker"
    ]
  }
}

But in current lib.webworker.d.ts, it declare var self: WorkerGlobalScope;, so we can’t access ServiceWorkerGlobalScope via self.


TypeScript Version: 2.2.1

Expected behavior:

Can access ServiceWorkerGlobalScope via self.(like self.clients)

Actual behavior:

Access with error.

image

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Reactions: 53
  • Comments: 35 (5 by maintainers)

Commits related to this issue

Most upvoted comments

Here is what worked for me:

  • in tsconfig.json:
{
  "compilerOptions": {
    "lib": [
      "es6",
      "webworker",
      "dom"
    ]
  }
}
  • in the service worker:
import { } from ".";
declare var self: ServiceWorkerGlobalScope;

Not sure why the empty import is required 🤷‍♂️

It feels like TypeScript needs to break the various worker types apart in its definitions. This isn’t just a service worker issue, it’s an issue for other contexts that have a different global, like shared workers & the various worklets.

Either that, or there should be a way to declare which interface is the global.

The various self hacks in this thread only work for references to self. You’ve still got incorrect types for the global.

Adding declare var self: ServiceWorkerGlobalScope; on top of my service worker implementation fixes that

Piecing together several comments in here, this is all I ended up needing:

  • Include webworker lib in tsconfig:
{
    "compilerOptions": {
        "lib": ["dom", "webworker"]
    }
}
  • Redeclare self and export null in service worker file:
export default null;
declare var self: ServiceWorkerGlobalScope;

I found multiple config files and installing npm packages not necessary (or even helpful).

Here is a good workaround, with typescript 4.5.5

sw.ts

/// <reference lib="webworker" />

declare const self: ServiceWorkerGlobalScope

@Tetr4 using an empty import caused an error for me:

Cannot find module ‘.’.

Adding export default null instead of import { } from "." in the service worker silenced the error.

As a workaround I am currently doing this

(function (self: ServiceWorkerGlobalScope) {
   //Service worker contents...
})(<ServiceWorkerGlobalScope>self);

Anyone have a better solution?

With TypeScript 4.3, I now have the following:

/// <reference no-default-lib="true"/>
/// <reference lib="es2020" />
/// <reference lib="WebWorker" />

const sw = self as ServiceWorkerGlobalScope & typeof globalThis

The no-default-lib is needed to workaround the fact that self is also a Window and not castable. Of course, now, you must use sw and not self 😞

@soryy708 You’re mistaken. Typescript supports either TS files or JSDocs syntax. In fact, it’s part of the official proposal.

https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html

https://github.com/tc39/proposal-type-annotations

Any progress about this? It’s kinda sad that we cannot use Service Workers alongside Type Script.

Using the lib Triple-Slash Directive /// <reference lib="WebWorker" /> makes ServiceWorkerGlobalScope available

and no-default-lib Triple-Slash Directive /// <reference no-default-lib="true"/> disables the DOM library as Glandos points out:

The no-defaul-lib is needed to workaround the fact that self is also a Window and not castable.

However, if I use these directives in my webworker’s source file, other files in my project (and inside node_modules) choke because they can no longer find the DOM stuff (Window, HTMLCollection, document, etc…)

The docs say:

The compiler performs a preprocessing pass on input files to resolve all triple-slash reference directives The process starts with a set of root files; these are the file names specified on the command-line or in the files list in the tsconfig.json file. These root files are preprocessed in the same order they are specified. Before a file is added to the list, all triple-slash references in it are processed, and their targets included.

Looks like using these directives is global, therefore pretty much equivalent to setting lib in tsconfig.json, which is unexpected. I expected the Triple-Slash directive to be local to the file in which it is written.

Related: https://github.com/Microsoft/TypeScript/issues/20595

Possible solution: https://joshuatz.com/posts/2021/strongly-typed-service-workers/#solution-b-tsconfig-libs

@clshortfuse JSDoc isn’t related to TypeScript and isn’t Microsoft’s responsibility. Regarding your issue with JS only (JSDoc), try asking JSDoc instead. https://github.com/jsdoc/jsdoc

@abarisain I came up with your solution as well but it fails with following error

Variable 'self' must be of type 'WorkerGlobalScope', but here has type 'ServiceWorkerGlobalScope'.

How did you configure your tsconfig.json to have it working? Thanks!

The workaround I found was:

const _self = self as ServiceWorkerGlobalScope;

…but you then have to use _self everywhere 😦

I’m wondering whether we need to separate typings of web worker and service worker for the following reasons:

  1. If we put all worker typings into one lib, How can we prevent web worker user using service worker methods and properties like clients.
  2. close is not accessible in service worker, how can we omit close in ServiceWorderGlobalScope?
  3. We can access service worker methods and properties like clients registration via self in Chrome 57.(Just like the self in Window)

image

Does anybody have a JS only solution (jsdoc syntax)?

I got as far as:

/// <reference lib="WebWorker" />
/** @type {ServiceWorkerGlobalScope} */
const context = /** @type {unknown} */ (self);

But I’d have to change all references to self. I can’t manage to enforce it on self.

/// <reference no-default-lib="true"/>
/// <reference lib="WebWorker" />
/** @type {ServiceWorkerGlobalScope} */
const self = globalThis.self;

Would yield Cannot redeclare block-scoped variable 'self'.ts(2451).

If I try just importing the the lib, It can’t find it:

/** @type {import('typescript/lib/lib.webworker').ServiceWorkerGlobalScope} */
const self = globalThis.self;

File '/home/user/project/node_modules/typescript/lib/lib.webworker.d.ts' is not a module.ts(2306)

self is used in multiple works, so TypeScript may need to specify in the configuration which files refer to which worker types .

Using the lib Triple-Slash Directive /// <reference lib="WebWorker" /> makes ServiceWorkerGlobalScope available

after adding bun-types to qwik project, ServiceWorkerGlobalScope type lost it’s reference and result in TS compile error.

this hint helped me.

My current favorite workaround:

(function (this: ServiceWorkerGlobalScope) {
    this.addEventListener('install', async (event) => {
        await this.skipWaiting();
        // ...
    });
}).call(self);

@clshortfuse

We need to cast to unknown first, to remove the error about incompatible types. It is not very pretty, but at least just two lines overall. This seems to work today without any errors and full Intellisense:

/* eslint-env serviceworker */
/// <reference lib="webworker" />
const self = /** @type {ServiceWorkerGlobalScope} */(/** @type {unknown} */(globalThis.self))

The workaround described in this issue doesn’t appear to work anymore in TypeScript 4.1 because it actually puts the export statement into the build output now, which fails evaluation in the browser.

@Tetr4 I got errors using your approach as I then got duplicated references between dom and webworker.

I ended up having to build a referenced tsconfig.json with two library configs (kind of like this), which seemed to work ok, but then with export default null at the top, that was copied into my JavaScript output as exports.__esModule and caused errors during registration.

So, I still have some configuration issue, but I’m pretty close.

I have not found references in the sepec that self is of type ServiceWorkerGlobalScope.

I think clients should be available as a global variable though. i.e.:

declare var clients: Clients;

In TypeScript 4.1+ it seems you can still get it working if you something like this

/// <reference lib="webworker" />
//@ts-ignore
sw=self;
declare var sw: ServiceWorkerGlobalScope;

at the top of your file, and then use sw whenever you need to use self. But it’s a very ugly workaround.

Are you using the latest typescript version?

Turns out since I commented, my needs evolved: I needed to define new variables on the global scope. Since my project mixes a lot of environments, I also had to have many sub .tsconfig files.

Here is what works for me:

/// <reference path='../../../node_modules/typescript/lib/lib.es6.d.ts' />
/// <reference path='../../../node_modules/typescript/lib/lib.webworker.d.ts' />
/// <reference path="../../types/contextual/serviceworker-patches.d.ts" />

import { foo } from './bar'
declare var self: MyWorkerGlobalScope;
interface MyWorkerGlobalScope extends ServiceWorkerGlobalScope {
  handleForwardedEvent?: (eventName: string, event: Event) => Promise<void>;
}

serviceworker-patches.d.ts has some other patches to fix the incomplete definitions

here is my tsconfig

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "lib": ["es6"]
  }
}

(the extend part is irrelevant, as all options defined in my base tsconfig are for other stuff)

I did this:

const { clients, addEventListener, skipWaiting, registration } = self as ServiceWorkerGlobalScope;

Note you need “webworker” lib with 2.4

self property will return browsing context object. And according to MDN, sometimes return ServiceWorkerGlobalScope object. See following docs.