How to have testable Javascript in a Phoenix LiveView Project?

I was really surprised that no one had an approach to share on how to have testable JavaScript in a Phoenix project.

I will share my approach:

  1. Created a pure TypeScript package at assets/vendor/package-name, with strict set to true and with Vitest for the test runner.
  2. I write all my TypeScript code for the LiveView Hooks with tests.
  3. I include each hook in assets/js/app.js.
  4. I assign the hook as usual with phx-hook and if needed I configure it via data-* attributes in the heex template.

The assets/vendor/canvas/package.json:

{
  "type": "module",
  "devDependencies": {
    "@types/jsdom": "^27.0.0",
    "@types/node": "^24.10.1",
    "jsdom": "^27.2.0",
    "typescript": "^5.9.3",
    "vitest": "^4.0.8"
  }
}

The assets/vendor/canvas/tsconfig.json:

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "allowJs": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "outDir": "./dist",
    "types": ["vitest/globals", "node"],
    "baseUrl": ".",
    "paths": {
      "@src/*": ["src/*"],
      "@test/*": ["test/*"]
    }
  },
  "include": ["src/**/*.ts", "test/**/*.ts"],
  "exclude": ["node_modules", "dist"]
}

The assets/vendor/canvas/vitest.config.ts:

import { defineConfig } from "vitest/config";
import path from "path";

export default defineConfig({
  test: {
    environment: "jsdom",
  },
  resolve: {
    alias: {
      "@src": path.resolve(__dirname, "./src"),
      "@test": path.resolve(__dirname, "./test"),
    },
  },
});