Search code examples
typescriptdomdeno

How to use types from lib.dom.d.ts when compiling with Deno?


I'm using Deno to compile some TypeScript and then serve it as part of a web page, so that it is run on the browser side. I'm trying to use a canvas element on the client side, and for that I need types like CanvasRenderingContext2D or CanvasGradient, which are defined in lib.dom.d.ts, but they are not available: Deno compilation gives errors like TS2304 [ERROR]: Cannot find name 'CanvasRenderingContext2D'.. (On the other hand, type Path2D (defined in the same file) does not cause problems.)

Note: I know the types will exist in runtime when the code runs in the browser, but I want Deno to know about them in compile time.

I've tried including the .d.ts file somehow. Things I tried:

  • specifying "libs": ["deno.window", "esnext"] etc. in the compiler options (in deno.json).
  • importing the type like this:
/// <reference types="https://raw.githubusercontent.com/microsoft/TypeScript/main/lib/lib.dom.d.ts" />
  • or this:
// @deno-types="https://raw.githubusercontent.com/microsoft/TypeScript/main/lib/lib.dom.d.ts"

Some of these attempts didn't work at all, and some weren't even parsed apparently. Looks like I don't understand how Deno loads the type definitions, e.g. where does it load the Path2D type declarations from. How to fix this?


Solution

  • You need to configure Deno to use only DOM and ES types when type-checking your program. You can do this using the supported TypeScript compiler options in a Deno config file:

    ./deno.json:

    {
      "compilerOptions": {
        "lib": [
          "esnext",
          "dom",
          "dom.iterable"
        ]
      }
    }
    
    

    This instructs the compiler that the program won't be running in Deno, but in a browser-like environment with those ambient global types.

    Here's an example source file:

    ./main.ts

    import {assertExists} from 'https://deno.land/std@0.126.0/testing/asserts.ts';
    
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    assertExists(ctx);
    
    

    When you import from other TypeScript modules into a module like this that's

    • going to be compiled by Deno, and
    • is destined to be executed in a browser-like environment

    then you'll have to bundle the result, because browsers can't process TypeScript source modules as imports:

    deno bundle --config deno.json main.ts main.js
    

    The resulting JavaScript looks like this:

    // deno-fmt-ignore-file
    // deno-lint-ignore-file
    // This code was bundled using `deno bundle` and it's not recommended to edit it manually
    
    const { Deno  } = globalThis;
    typeof Deno?.noColor === "boolean" ? Deno.noColor : true;
    new RegExp([
        "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
        "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))", 
    ].join("|"), "g");
    var DiffType;
    (function(DiffType1) {
        DiffType1["removed"] = "removed";
        DiffType1["common"] = "common";
        DiffType1["added"] = "added";
    })(DiffType || (DiffType = {}));
    class AssertionError extends Error {
        name = "AssertionError";
        constructor(message){
            super(message);
        }
    }
    function assertExists(actual, msg) {
        if (actual === undefined || actual === null) {
            if (!msg) {
                msg = `actual: "${actual}" expected to not be null or undefined`;
            }
            throw new AssertionError(msg);
        }
    }
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    assertExists(ctx);
    
    

    It's safe to use the module at https://deno.land/std@0.126.0/testing/asserts.ts in the compilation of your program because it does runtime feature-detection of the Deno namespace before trying to use any of its APIs. Any modules which don't do that will result in a runtime error.