092 — Modules (export Invoice, import it back, mind the .js extension)
Full companion narrative for TypeScript Tutorial #14 — splitting the Finance Logger into ES modules: src/classes/Invoice.ts, the ES2015 tsconfig setup, the .js import specifier, and why webpack comes next
Video: 092 · TypeScript Tutorial #14 - Modules · watch · ~06:37 · Net Ninja TypeScript tutorial series, lesson 14 (Shaun)
Source: this is a written companion to the video. Part of the Favorite TypeScript Videos curated path, module 05 (objects and interfaces). Until now everything lived in one file — this lesson splits it.
one file has stopped scaling
The motivation is organizational: “Now the best way we can do that in TypeScript is by using the ES6 module system where we can import and export things from different files when we need to.” The Invoice class moves into its own module while instantiation stays in app.ts.
{
"compilerOptions": {
"module": "ES2015",
"target": "ES6"
}
}
Two tsconfig.json settings make it work: "module": "ES2015" keeps import/export in the compiled output, and "target": "ES6" matches modern browsers that understand them.


export the class from its own file
// src/classes/Invoice.ts
export class Invoice {
constructor(
readonly client: string,
private details: string,
public amount: number,
) {}
format() {
return `${this.client} owes $${this.amount} for ${this.details}`;
}
}
“Now what we need to do if we want to use the invoice inside this file is import that invoice from this file. But first of all we have to export it from this file. Now that’s simple enough, all we need to do is put export in front of this and now we’re exporting this invoice class.”

import it back — with the .js extension
// app.ts
import { Invoice } from "./classes/Invoice.js";
const invOne = new Invoice("mario", "work on the mario website", 250);
const invTwo = new Invoice("luigi", "work on the luigi website", 300);
let invoices: Invoice[] = [];
invoices.push(invOne);
invoices.push(invTwo);
invoices.forEach((inv) => {
console.log(inv.client, inv.amount, inv.format());
});
“Now if we want to import that class to another file all we have to do is go to the top right here and then I’m gonna say import curly braces from and we’ll come back to this curly braces in a minute and we need to say what file we want to import it from.” The gotcha that bites everyone: write ./classes/Invoice.js even though the source is .ts — the browser loads the compiled JavaScript, and tsc preserves the specifier verbatim. Plus index.html marks the entry script with <script type="module" src="app.js">.


identical behavior, two honest drawbacks
Verify the refactor changed organization, not behavior: the Finance Logger list (mario £250, luigi £300) renders identically. Then the caveats — “But there are two major drawbacks here. First of all the browser support only modern browsers support this module system so if a user is using an outdated browser or an older browser then you might get an error and it might not work. And secondly it doesn’t bundle our code into a single file. The browser is still using the module system to load separate files and make multiple requests.”
The previewed fix: “Now to combat both of these downfalls you could throw webpack into the mix. That is gonna bundle our code into a single file when it’s completed and that way we only have one network request and all browsers are supported too.” Next lesson: interfaces.


Prev: 091 — Public, Private & Readonly · Next: 093 — Interfaces
The auto-transcript is clean on the module mechanics but loose on paths; this narrative uses the corrected forms: src/classes/Invoice.ts for the module path, ./classes/Invoice.js with the .js extension for the import specifier, and ES2015 for the module setting. Console previews render client names as logged.