6.102
6.102 — Software Construction
Spring 2024

TypeScript in the Web Browser

This page is an introduction to using TypeScript in a web browser.

Compiling and bundling

Here’s one way to run TypeScript inside a web page:

  • compile the TypeScript code into JavaScript
  • bundle all the imported JavaScript files together into a single JavaScript file (often called bundle.js or something similar)
  • use a <script> tag to load the bundled JavaScript file into the web page

In the project setup, the first two steps are done by a tool called esbuild, in combination with the TypeScript compiler. When you run npm run compile, it will produce both:

  • dist/StarbServer.js (which is run by npm run server) as well as compiled .js files for all your other .ts files, and
  • dist/client-bundle.js, which is loaded by starb-client.html.

But because software development is very iterative – editing the TypeScript source, then running it, then editing it again, then running again, etc. – it is easier if the compiling and bundling happens automatically whenever you edit.

To use this setup, start by running a watch script:

npm run watch-example      # if you are editing ExamplePage.ts
npm run watch-client       # if you are editing StarbClient.ts

It will compile and bundle the code to produce a bundle in the dist/ folder (either example-bundle.js or client-bundle.js). If you look in example-page.html and starb-client.html, you will see a corresponding <script> tag at the end, which loads the bundle into the web page. So your next step is:

  • open example-page.html (or starb-client.html) in a web browser

You will see the web user interface in your browser, and the code that you compiled and bundled with npm run watch-... will be running in that web page.

Importantly, the npm run watch-... command keeps running. It watches your TypeScript source files for changes. Whenever you edit and save any .ts file that ExamplePage.ts (or StarbClient.ts) depended on, it will automatically recompile and rebundle.

So once you have started a watch, and loaded the corresponding HTML file into your browser, your normal development cycle will be:

  • edit TypeScript code
  • save it to disk (you will see output from both tsc and eslint saying that it recompiled and rebundled)
  • reload the web page in your browser

The reloaded web page will now be running the updated code.

If the page makes requests to a server, that server must also be running. If you change the server-side code, don’t forget to stop and restart the server (perhaps in addition to reloading the web page to reset its state) to test that new code.

No Node dependencies

The following will not work:

Puzzle.ts

import fs from 'fs';

export class Puzzle { ... }

export async function fromFile(...): Promise<Puzzle> {
  // use fs.promises.readFile to read in the file
  //   and make a Puzzle from its contents
}

export function fromString(...): Puzzle {
  // make a Puzzle from the given string,
  //   without using the filesystem at all
}

StarbServer.ts

import { fromFile } from './Puzzle.js';

// call fromFile to make a Puzzle
//   by reading in a file


StarbClient.ts

import { fromString } from './Puzzle.js'; // oops!

// call fromString to make a Puzzle
//   from a string sent by the server

When you try to bundle StarbClient.ts for the browser, you will see an error:

✘ [ERROR] Could not resolve "fs"
src/puzzle.ts:1:15:       
  1 │ import fs from 'fs';
    ╵                ~~~~ 
The package "fs" wasn't found on the file system but is built into node. Are you trying to bundle for node? You can use "--platform=node" to do that, which will remove this error.

The suggestion in the error message is not helpful, because you are not trying to bundle for Node: you are trying to bundle for the browser. Even though StarbClient.ts only uses fromString in this example, it cannot import any file that imports fs (or that imports a file that imports fs, or…), so it cannot import Puzzle.ts.

Here is a quick fix. Think about the best way to accomplish the same goal in your group’s design, which likely involves additional modules:

Puzzle.ts

export class Puzzle { ... }

export function fromString(...): Puzzle {             
  // make a Puzzle from the given string
}

StarbServer.ts

import fs from 'fs';
import { fromString } from './Puzzle.js';

async function fromFile(...): Promise<Puzzle> {
  // use fs.promises.readFile to read in the file,
  //   then call fromString to make the Puzzle
}


StarbClient.ts

import { fromString } from './Puzzle.js'; // ok!

// call fromString to make a Puzzle
//   from a string sent by the server

Viewing console output

When your TypeScript code is running in the web browser, debugging output appears in the developer console. Here’s how to open the developer console in common browsers:

  • Chrome: View → Developer → JavaScript Console
  • Firefox: Tools → Browser Tools → Web Developer Tools, then choose the Console tab in the developer pane that opens.
  • Safari: Develop → Show JavaScript Console. (If you don’t see the Develop menu in the menubar, choose Safari → Preferences, click Advanced, then select “Show Develop menu in menu bar.”)

This console view displays:

  • console.log and console.error
  • stack traces from exceptions that were thrown and not caught