Codectionary / Developer documentation / TypeScript

TypeScript in the Browser

Build a small browser page with TypeScript, compile src/main.ts to dist/main.js, and use the emitted file to update text after a click.

Browsers normally run JavaScript

A normal browser page executes JavaScript, not full TypeScript source with annotations. TypeScript checks source before the page runs and emits JavaScript that the browser can load. The emitted script has the same runtime responsibilities as handwritten JavaScript: it must find elements, handle events and cope with missing data.

This walkthrough is a small classic script with no imports or framework. If you later use ES modules, a bundler or a framework, the script element and module settings need to match that workflow.

Create the three files

Install Node.js and npm first using the relevant setup guide. In a new folder, run the commands below, make a src directory, and save the configuration from the tsconfig.json guide at the project root. The files are index.html, tsconfig.json and src/main.ts. npm adds package.json, package-lock.json and node_modules.

This example sets module: None because main.ts has no imports or exports. target: ES2020 emits JavaScript appropriate for browsers supporting the APIs used here. The DOM library supplies browser type information to the checker; it does not create document at runtime.

Create and prepare the project

mkdir browser-typescript
cd browser-typescript
npm init -y
npm install --save-dev typescript
mkdir src

Save as tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "None",
    "lib": ["ES2020", "DOM"],
    "strict": true,
    "rootDir": "src",
    "outDir": "dist",
    "noEmitOnError": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["dist", "node_modules"]
}

Write the TypeScript interaction

Save this source as src/main.ts. querySelector searches the page and can return null. The instanceof checks narrow the elements to the expected browser types before adding a click listener or changing textContent. If the HTML IDs are wrong, the handler is not installed; the page does not crash from a null access.

The : string annotation describes nextText at check time. It is removed from dist/main.js. The event listener and assignment remain and run when the user clicks the button.

Save as src/main.ts

const button = document.querySelector("#change");
const message = document.querySelector("#message");

if (button instanceof HTMLButtonElement && message instanceof HTMLElement) {
  button.addEventListener("click", () => {
    const nextText: string = "Button clicked.";
    message.textContent = nextText;
  });
}

Load the emitted JavaScript from HTML

Save the HTML below as index.html beside tsconfig.json. The script tag points to dist/main.js, not src/main.ts. defer waits until HTML parsing is complete before running this classic script, so the button and paragraph are available when querySelector runs.

The button has type="button" to avoid accidental form submission if this pattern is moved into a form. The IDs match the selectors in the TypeScript source.

Save as index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>TypeScript button example</title>
  <script defer src="dist/main.js"></script>
</head>
<body>
  <button id="change" type="button">Change message</button>
  <p id="message">Waiting for a click.</p>
</body>
</html>

Check, compile and try the page

Run the checker with --noEmit first; it should report no errors and create no dist folder. Run the second command to write dist/main.js. Open index.html in a browser and click Change message: the paragraph becomes Button clicked. For this no-import example, a local file can work; a module-based or networked app should be served using its project development server.

If the text does not change, check that dist/main.js exists, the script path is correct, the IDs match, and the browser console has no error. A common mistake is editing main.ts but forgetting to recompile: the browser keeps running the old dist/main.js. Another is loading .ts directly. Type checking can catch an invalid use of a known DOM type, but it cannot guarantee that the page contains a particular element or that the interaction behaves as intended.

Check and produce dist/main.js

npx tsc --project tsconfig.json --noEmit
npx tsc --project tsconfig.json

Syntax

<script defer src="dist/main.js"></script>

Examples

Create and update a button in the preview

A self-contained variation that also works in the documentation preview, which supplies a minimal HTML page.

const button = document.createElement("button");
button.textContent = "Change message";
const message = document.createElement("p");
message.textContent = "Waiting for a click.";
document.body.append(button, message);

button.addEventListener("click", () => {
  const nextText: string = "Button clicked.";
  message.textContent = nextText;
});
  1. The example creates its own button and paragraph because the documentation preview supplies only a minimal HTML wrapper. The local project above uses existing elements in index.html instead.
  2. append places both elements on the page. The click listener runs only when the user activates the button and changes the paragraph text.
  3. The TypeScript string annotation is erased before preview execution. Running the preview demonstrates runtime behaviour, not a full project type check.

Best practices

  • Load the generated .js file, not the .ts source, in ordinary HTML.
  • Guard DOM lookups that may return null.
  • Recompile after edits and test the resulting JavaScript in the browser.
  • Match module settings, script tags and hosting to the actual project.

Runtime & versions

Compiled to JavaScript. Feature availability depends on the language and runtime version used by your project; consult the official reference below.

TypeScript Handbook

Specifications & further reading