microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
98.98k stars 12.28k forks source link

Service Worker typings #11781

Open jgw96 opened 7 years ago

jgw96 commented 7 years ago

TypeScript Version: 2.0.3

Types for Service Workers should be built into the default definitions for TypeScript. There are some good examples out there of Service Worker typings from the community.

mhegazy commented 7 years ago

Since service workers run in their own context, i suppose we need --lib serviceworker (similar to --lib webworker).

@zhengbli can we update our declaration files to pull in serviceworker spec idl as well

idiotWu commented 7 years ago

I think it's necessary to add service worker typings into built-in definitions, though it's still a work-in-progress API.

When I tried to add /// <reference no-default-lib="true"/> directive into service_worker_api to exclude the default lib (like that in lib.webworker.d.ts), I got tons of type missing errors like:

 error TS2304: Cannot find name 'Function'.

So instead I have to use

interface Window extends ServiceWorkerGlobalScope {}

to attach interfaces onto self.

This brings some other problems, eg. I must change the incompatible properties like onmessage into what they are on Window interface:

// expected
interface ServiceWorkerGlobalScope {
    onmessage: (messageevent: ExtendableMessageEvent) => void;
}

// now
interface ServiceWorkerGlobalScope {
    onmessage: (messageevent: MessageEvent) => void;
}

Therefore, I think it would be much better to include service worker typings in default definitions, or, at least there should be a way to make no-default-lib directive act like the built-in ones.

code-tree commented 7 years ago

interface Window extends ServiceWorkerGlobalScope {} isn't even working for me.

Anyone know how I can force TS to change the type of self??

mhegazy commented 7 years ago

Anyone know how I can force TS to change the type of self??

first you need --lib webworker to include the webworker library definition. self is defined as declare var self: WorkerGlobalScope;. so update the declaration of WorkerGlobalScope to get what you want.

rektide commented 7 years ago

I can't vouch for it, but scouting about it looks like this gist is updating pretty regularly. May be a good candidate for TS to adopt?

michaeljota commented 6 years ago

I really don't like this type of comments, but... Any updates here? I mean, this is just a definition file with a proper compiler flag, isn't it? Maybe we won't be writing service workers in Typescript, but Typescript definitions are really important now days in Javascript development, for me at least. :P.

mhegazy commented 6 years ago

PRs welcomed. the files are generated https://github.com/Microsoft/TSJS-lib-generator. I would be happy to help if someone wants to pick this issue up.

tiernan commented 6 years ago

I wrote an updated gist from the one linked above by @rektide here: Service Worker Typings to Supplement lib.webworker.d.ts.

It's intended to be included by a triple slash directive in the service worker, and registration scripts. It works well for me using the webworker lib along with es5+. Hope this helps

caseyhoward commented 5 years ago

I feel like I'm completely missing something. I tried a couple of the type definition files and they're pretty much missing everything I need. addEventListener isn't declared to take a callback that takes a ServiceWorkerMessageEvent. And ServiceWorkerMessageEvent doesn't have the properties I'm using (request, waitUntil, respondWith). self is missing everything since it's still defined as a WorkerGlobalScope.

And to further add to my confusion this says that the dom lib now has service worker stuff: https://www.npmjs.com/package/@types/service_worker_api Why would that be? I didn't think there was a DOM inside service workers. Also, I tried it and it didn't work.

Is there something else I need to configure or include somewhere?

jhpratt commented 5 years ago

Pending an update on this, I've just switched my service worker code into vanilla JS. Though types would be great for development, this is a basic failing of TypeScript currently.

shqld commented 5 years ago

I'm addressing the issue like this. Currently works well.

https://gist.github.com/shqld/32df51a4a4ed429f2c76e4e2cfdf6f96#gistcomment-2793376

shqld commented 5 years ago

And created a package https://github.com/shqld/types-serviceworker

(If this violates licenses, please tell me)

13rac1 commented 5 years ago

@caseyhoward The PR removing Service Worker API is https://github.com/DefinitelyTyped/DefinitelyTyped/pull/14786 Agreed, the "dom lib" message is incorrect. I got it seemingly working with --lib es6,webworker as explained in https://github.com/Microsoft/TypeScript/issues/14877#issuecomment-340279293

@shqld Thanks for this! Edit: I've tried both methods. Many hacks are required to use the webworker library and types-serviceworker just works.

hsource commented 5 years ago

For anyone else who comes upon this thread, there seems to be 2 main solutions:

  1. Use the https://github.com/shqld/types-serviceworker package by @azarashd, with the triple-slash directives to include the typings. Note that you can only use these if you don't have { "lib": ["dom"] } declared

  2. Follow this StackOverflow reply by Jake Archibald (one of the main Chrome engineers who worked on Service Workers) to break your project into multiple tsconfig files. One of the tsconfig files will only include the { "lib": ["webworker"] } for the Service Worker and one will only include { "lib": ["dom"] } for the normal site. The file also needs to declare var self: ServiceWorkerGlobalScope; export {}; to fix the scope as a hack

A few additional notes:

I had a setup where the broader project had many typings installed via @types/*. By default, all of these typings are included. However, many typings assume that your tsconfig includes { "lib": ["dom"] } already and return type errors without them.

To ensure that this works, make sure you include { "types": [] } to not include the typings by default. Instead, only include typings you use in the tsconfig project with the Service Worker.

simon-robertson commented 5 years ago

It would make things much easier if we could declare a script's environment with triple-slash directives. These could override any configuration that is being used.

/// env node /// env worker /// env serviceworker

ESLint provides a similar solution.

/* eslint-env node */ /* eslint-env worker */ /* eslint-env serviceworker */

Most of the development teams that I know tend to push TS scripts through Babel and Webpack, and need a simple way to target multiple environments in one TS code base so VSCode (and friends) can provide the correct APIs (code hints etc).

simon-robertson commented 5 years ago

The way the service worker issue was resolved here was by adding "webworker" to the tsconfig lib array, and then wrapping the service worker code in a function.

const initialize = (service: ServiceWorkerGlobalScope): void => {
    service.addEventListener("install", ... )
}

initialize(self as any)

Not ideal, because the service worker APIs are now picked up by all of the scripts in the project, but at least this way we have access to the service worker type definitions.

zbjornson commented 4 years ago

It would make things much easier if we could declare a script's environment with triple-slash directives. These could override any configuration that is being used.

@simon-robertson you can, see https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-lib-

/// <reference lib="webworker" /> 
msvinth commented 4 years ago

It would make things much easier if we could declare a script's environment with triple-slash directives. These could override any configuration that is being used.

@simon-robertson you can, see https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-lib-

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

I did the following and it worked for my needs e.g.:

/// <reference no-default-lib="true"/>
/// <reference lib="es2015" />
/// <reference lib="webworker" />

interface Window extends ServiceWorkerGlobalScope {}

self.addEventListener("fetch", (event: FetchEvent) =>  {
    event.respondWith(caches.match(event.request));
});
simon-robertson commented 4 years ago

I did the following and it worked for my needs e.g.:

/// <reference no-default-lib="true"/>
/// <reference lib="es2015" />
/// <reference lib="webworker" />

interface Window extends ServiceWorkerGlobalScope {}

self.addEventListener("fetch", (event: FetchEvent) =>  {
    event.respondWith(caches.match(event.request));
});

Short of breaking up a project into multiple workspace projects, I guess that would be the best solution for now. I was thinking of something more along the lines of ...

/// <environment type="dedicatedworker" /> or /// <environment type="serviceworker" /> or /// <environment type="audioworklet" /> and so on

Essentially overriding the libraries specified in the tsconfig.json file for isolated use cases.

iiic commented 4 years ago

Which lib I need for InstallEvent ?

…videlicet…

I added "webworker" into local config json and it perfectly worked with ServiceWorkerGlobalScope ExtendableEvent PushEvent SyncEvent , … but still have error (2304) in InstallEvent

InstallEvent code looks like:

sw.addEventListener( 'install', function ( /** @type {InstallEvent} */ event )
{
    //…
});

where sw is instance of ServiceWorkerGlobalScope

Someone understand it? Do I missing some lib for InstallEvent?

grundmanise commented 4 years ago

@iiic https://github.com/microsoft/TypeScript/issues/14877#issuecomment-326751509

simon04 commented 3 years ago

For JavaScript, the following snippet helped to bring IntelliSense to Visual Studio Code by making use of JSDoc:

// service-worker.js

/// <reference no-default-lib="true"/>
/// <reference lib="es2015" />
/// <reference lib="webworker" />

/**
 * @type {ServiceWorkerGlobalScope}
 */
const sw = self;

sw.addEventListener("install", event => {
  console.log(event);
  console.log(sw.caches);
  console.log(sw.registration);
});
michaeljota commented 3 years ago

@simon04 Just it, and you don't even have to alias self. You can see it here:

TS Play

googol commented 3 years ago

Not aliasing self works if you are writing a web worker, but to use the service worker events like install, you can't just use self out of the box. These events are only defined in the ServiceWorkerGlobalScope interface.

What I ended up using today is:

const sw: ServiceWorkerGlobalScope & typeof globalThis = self as any

alongside separate tsconfigs that specify different libs and includes (and extend a common base).

jhony1104 commented 3 years ago

I don't know if this is a good idea, but one could, instead of aliasing self, change the typing of self with something like:

declare var self: ServiceWorkerGlobalScope & typeof globalThis;
HolgerJeromin commented 3 years ago

I don't know if this is a good idea, but one could, instead of aliasing self, change the typing of self with something like:

At least my TypeScript 4.0 setup is not happy:


1>CacheManagement\ServiceWorker.ts(6,13): error TS2403: Build:Subsequent variable declarations must have the same type.  
Variable 'self' must be of type 'WorkerGlobalScope & typeof globalThis', but here has type 'ServiceWorkerGlobalScope & typeof globalThis'.
Glandos commented 3 years ago

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 :disappointed:

jimmywarting commented 2 years ago

for jsdoc and vanilla js developers:

/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="WebWorker" />
const sw = /** @type {ServiceWorkerGlobalScope & typeof globalThis} */ (globalThis)
Mlocik97 commented 2 years ago

jimmywarting thanks for that solutions, even tho, it gave me another error, now from ESLint:

'globalThis' is not defined.eslint[no-undef](https://eslint.org/docs/rules/no-undef)

as well as TypeScript gave error:

Conversion of type 'Window & typeof globalThis' to type 'ServiceWorkerGlobalScope & typeof globalThis' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
  Type 'Window & typeof globalThis' is missing the following properties from type 'ServiceWorkerGlobalScope': clients, onactivate, onfetch, oninstall, and 5 more.ts(2352)

So I did this:

/// env serviceworker
const globalThis = /** @type {unknown} */ (self);
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="WebWorker" />
const sw = /** @type {ServiceWorkerGlobalScope & typeof globalThis} */ (globalThis)

yeah, that's crazy.

alexbruno commented 1 year ago

The simplest solution that worked for me, is just this on the Service Worker code:

/// <reference lib="WebWorker" />

const sw: ServiceWorkerGlobalScope = self as any
F2 commented 1 year ago

To avoid aliasing self, this seems to work in the latest version of TypeScript:

/// <reference lib="WebWorker" />

declare let self: ServiceWorkerGlobalScope;
ShafSpecs commented 1 year ago

To avoid aliasing self, this seems to work in the latest version of TypeScript:

/// <reference lib="WebWorker" />

declare let self: ServiceWorkerGlobalScope;

It gives an error:

Cannot redeclare block-scoped variable 'self'

Using this instead worked:

/// <reference lib="WebWorker" />

export type {};
declare let self: ServiceWorkerGlobalScope;

Yep, it's getting weirder 😆

icazemier commented 1 year ago

This works for me (for now... 🤷🏻 ):

/// <reference lib="esnext" />
/// <reference lib="webworker" />
/// <reference no-default-lib="true"/>

// service-worker.ts
(() => {

  const initializeSW = (sw: ServiceWorkerGlobalScope) => {
    sw.addEventListener('install', (event: ExtendableEvent) => {
      // eslint-disable-next-line no-console
      console.log(event);
    });

    // see: https://developer.mozilla.org/en-US/docs/Web/API/Clients/claim
    sw.addEventListener('activate', (event: ExtendableEvent) => {
      // eslint-disable-next-line no-console
      console.log(event);
      event.waitUntil(sw.clients.claim());
    });

  };

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  initializeSW(self as any);
})();

tsconfig:

{
  "compilerOptions": {
    "allowJs": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "esModuleInterop": true,
    "strictNullChecks": true,
    "useUnknownInCatchVariables": false,
    "lib": ["WebWorker", "ESNext"],
    "noEmit": true,
    "strict": true,
    "outDir": "./public/sw",
    "module": "esnext",
    "target": "esnext",
    "incremental": false,
    "sourceMap": true,
    "isolatedModules": false,
    "moduleResolution": "node",
    "resolveJsonModule": true
  },
  "include": ["service-worker.ts"],
  "exclude": ["node_modules"]
}
"typescript": "^5.0.2",
noel-schenk commented 3 months ago

@icazemier ty :) adding to tsconfig.json:

"lib": ["WebWorker", ...], did it for me