Skip to content

Instantly share code, notes, and snippets.

@scmx
Last active May 12, 2025 07:08
Show Gist options
  • Save scmx/cfb23ca501662354db402d5cbf824536 to your computer and use it in GitHub Desktop.
Save scmx/cfb23ca501662354db402d5cbf824536 to your computer and use it in GitHub Desktop.
Vanilla JavaScript setup with JSDoc TypeScript Compiler Linting using jsconfig.json + Node.js builtin Test Runner

Vanilla JavaScript setup with JSDoc TypeScript Compiler Linting + Node.js builtin Test Runner

Here's how I usually set up vanilla javascript projects these days to enable typescript compiler jsdoc linting via jsconfig.json with checkJs: true.

JSDoc TypeScript Reference

First you need a jsconfig.json with at least "checkJs": true. TypeScript as a dev dependency npm i -D typescript. Not needed for build since it's vanilla javascript.

{
  "compilerOptions": {
    "target": "es2021", // Latest major browser support
    "module": "Node16", // Use Node16 module syntax
    "moduleResolution": "node16",
    "checkJs": true, // Enable type-checking for JavaScript files
    "strict": true, // Enforce strict type-checking
    "noImplicitAny": false, // Disallow implicit 'any' types
    "noUnusedLocals": true, // Error on unused local variables
    "noUnusedParameters": true, // Error on unused function parameters
    "noImplicitReturns": true, // Ensure all code paths in a function return a value
    "noFallthroughCasesInSwitch": true, // Prevent fallthrough errors in switch statements
    "allowSyntheticDefaultImports": true, // For compatibility with some module systems
    "noEmit": true, // Prevents any output files
    "lib": ["es2021", "dom", "dom.iterable"], // Include DOM typings for browser environments
  },
}

You might want to disable many of these if you are adding this to an existing codebase.

Then you'll start seeing TypeScript warnings in your codebase. Here are some strategies for handling those warnings in vanilla javascript.

Defining types in a .d.ts separate typescript declaration file

types.d.ts

type State = "foo" | "bar" | "baz";

Types defined there are automatically available in all js files in the project, no need to import any types.

thing.js

/** @type {State} */
let state = "foo";

Casting to a type, similar to as any in TypeScript

const element =
  /** @type {HTMLCanvasElement} */ document.querySelector("#canvas");

Here's where the syntax might get a little ugly. But in the end it's not that different from some workarounds you would sometimes need in regular TypeScript.

JSDoc TypeScript Reference - Casting

Bonus: Leveraging Node.js modules support and built in test runner

Having a unit test suite for Node.js can be useful even if you are just targeting the browser and loading normal js-files from html-files. Here's how you can unit-test javascript-files without adding another dependency.

Node.js Test Runner Documentation

  1. Do npm init to create a package.json

    • Would recommend having private true unless you are going to publish to npm
    • Set "type": "module" to enable normal .js files to be ecmascript modules with import/export
    {
      "private": true,
      "type": "module"
    }
  2. Extract what you want to test into a separate file thing.js if possible

  3. Create for example a thing.test.js test file

    import test, { describe } from 'node:test'
    import assert from 'node:assert'
    import thing from './thing.js`
    describe('thing', () => {
      test('works', () => {
        assert.deepEqual(thing(), { foo: 'bar' })
      })
    })
  4. Run node --test

  5. Try watch-mode node --test --watch

  6. Try running an individual file node --test thing.test.js

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment