Search code examples
typescripttscdeno

TSC Compile Error (TS2307) when using the Deno API


I have a little ts project that should be compiled into js. Since I don't know with which runtime (Deno, Bun or Node) will run, I really need to compile it to js and can not use the Deno built-in compiler.

When I try to compile now the compiler throws an error:

 error TS2304: Cannot find name 'Deno'.

Line 34                 version: Deno.version.deno

This is my tsconfig file:

{
  "compilerOptions": {
    "sourceMap": true,
    "target": "es6",
    "strict": true,
    "esModuleInterop": true,
    "moduleResolution": "node",
    "baseUrl": "./src",
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "types": ["bun-types"]
  },
  "include": [
    "src/**/*.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}

Do you know how I can fix this issue?

I know I could add // @ts-ignore right before but I would prefer to find a proper configuration.

Thank you in advance!


Solution

  • When writing isomorphic TypeScript (targeted at multiple runtimes/environments), you won't be able to reliably depend on any values which aren't common to every environment without using feature detection. And because of this, you also can't safely include any ambient library types during compilation — for example: types like Node.js (@types/node), Bun (bun-types), Deno (deno.ns, deno.window, etc.), browser/DOM (@types/web, dom, dom.iterable, etc.), or others — which makes it even more complicated.

    TypeScript offers a helpful feature called type predicates that can be used with type guard functions to perform these kinds of validations at runtime — and they'll also inform the compiler that values are of the expected types and are safe to use as such.

    You can reference the Deno namespace type declarations at the project's GitHub repository — for example, here's a direct link to the line for Deno.version.deno for the current CLI version as I write this answer (v1.32.4): https://github.com/denoland/deno/blob/v1.32.4/cli/tsc/dts/lib.deno.ns.d.ts#L4708

    In your code, you can feature-detect for the Deno version value like this:

    ./src/example.mts:

    // The following triple-slash directives aren't necessary when using
    // the TSConfig in this answer, but I'm including them here so that the module
    // can be type-checked in isolation, apart from the config file (e.g. by Deno):
    /// <reference no-default-lib="true" />
    /// <reference lib="esnext" />
    /// <reference path="./types/global.d.ts" />
    
    function isObject<T>(value: T): value is T & object {
      return typeof value === "object" && value != null;
    }
    
    // There's a global Deno variable
    if ("Deno" in globalThis) {
      // But we don't yet know what type it is
      const { Deno } = globalThis as Record<string, unknown>;
      // It's an object
      if (isObject(Deno)) {
        // And it has a "version" key that's also an object
        if ("version" in Deno && isObject(Deno.version)) {
          // And that object has a "deno" key that's a string
          if ("deno" in Deno.version && typeof Deno.version.deno === "string") {
            // Only in this scope can we safely access it with confidence
            console.log("Deno version:", Deno.version.deno);
          } else {
            console.log(`There's no "deno" string key in Deno.version`);
          }
        } else {
          console.log(`There's no "version" object key in Deno`);
        }
      } else {
        console.log("Deno is not an object");
      }
    } else {
      console.log("There's no global Deno");
    }
    
    

    For completeness, here are the other files in my reproduction directory for your question:

    ./package.json:

    {
      "name": "so-76034796",
      "version": "0.1.0",
      "type": "module",
      "scripts": {
        "compile": "tsc"
      },
      "devDependencies": {
        "typescript": "^5.0.4"
      }
    }
    
    

    ./tsconfig.json:

    {
      "compilerOptions": {
        "strict": true,
        "exactOptionalPropertyTypes": true,
        "noImplicitOverride": true,
        "noImplicitReturns": true,
        "noUncheckedIndexedAccess": true,
    
        "removeComments": true,
        // "inlineSourceMap": true,
        // "inlineSources": true,
    
        "forceConsistentCasingInFileNames": true,
    
        "module": "esnext",
        "moduleDetection": "force",
        "moduleResolution": "nodenext",
        // "moduleResolution": "bundler",
        // "allowImportingTsExtensions": true,
        "isolatedModules": true,
        "esModuleInterop": true,
    
        "target": "esnext",
        "lib": ["esnext"],
    
        // "jsx": "react-jsx",
    
        "outDir": "dist",
        // "noEmit": true,
    
        "typeRoots": ["./src/types"]
      },
      "include": ["src/**/*"]
    }
    
    

    I've included these types from the Console API because it's available in all of the environments mentioned to be targeted. Otherwise, you'd need to feature-detect for it as well:

    ./src/types/global.d.ts:

    /**
     * @see https://github.com/microsoft/TypeScript/blob/v5.0.4/lib/lib.dom.d.ts#L17498
     * 
     * For Node.js, refer to:
     * - [global](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/04c9ac7555c9fd1562aea37475af4e176db0019a/types/node/globals.d.ts#L28)
     * - [console](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/04c9ac7555c9fd1562aea37475af4e176db0019a/types/node/console.d.ts#L66)
     */
    interface Console {
      assert(condition?: boolean, ...data: any[]): void;
      clear(): void;
      count(label?: string): void;
      countReset(label?: string): void;
      debug(...data: any[]): void;
      dir(item?: any, options?: any): void;
      dirxml(...data: any[]): void;
      error(...data: any[]): void;
      group(...data: any[]): void;
      groupCollapsed(...data: any[]): void;
      groupEnd(): void;
      info(...data: any[]): void;
      log(...data: any[]): void;
      table(tabularData?: any, properties?: string[]): void;
      time(label?: string): void;
      timeEnd(label?: string): void;
      timeLog(label?: string, ...data: any[]): void;
      timeStamp(label?: string): void;
      trace(...data: any[]): void;
      warn(...data: any[]): void;
    }
    
    declare var console: Console;
    
    

    Compiling with vanilla tsc and running in Node and Deno:

    so-76034796 % npm install
    
    added 1 package, and audited 2 packages in 445ms
    
    found 0 vulnerabilities
    
    so-76034796 % npm run compile
    
    > so-76034796@0.1.0 compile
    > tsc
    
    so-76034796 % deno check src/example.mts
    
    so-76034796 % deno run src/example.mts
    Deno version: 1.32.4
    
    so-76034796 % deno run dist/example.mjs
    Deno version: 1.32.4
    
    so-76034796 % node dist/example.mjs
    There's no global Deno
    
    so-76034796 % node --version
    v18.16.0
    
    so-76034796 % npm --version
    9.5.1
    
    so-76034796 % deno --version
    deno 1.32.4 (release, aarch64-apple-darwin)
    v8 11.2.214.9
    typescript 5.0.3