CommonJS with TypeScript
Large JavaScript codebases can be arranged using the fundamental idea of modules, which enables programmers to divide code into reusable, manageable chunks. TypeScript improves this procedure by offering control over the JavaScript output that is generated and supporting a number of module standards.
Understanding JavaScript Module Standards
Because JavaScript didn’t have a native module system in the past, a number of standards were developed to manage dependencies, chief among them CommonJS (CJS), Asynchronous Module Definition (AMD), and the more recent ES Modules (ESM).
CommonJS (CJS)
The module standard made popular by Node.js is called CommonJS.
- Mechanism: CJS exposes functionality via
module.exports
orexports
and imports dependencies using synchronous loading via therequire()
function. - Nature: CJS is ideal for server-side settings like Node.js, where files may be read instantly from the local disc, because loading is synchronous.
- Usage: It emerged as the de facto standard for server-side JavaScript and module bundling.
ES Modules (ESM)
ECMAScript 2015 (ES6) introduced ES Modules as the official, standard module system.
- Mechanism: ESM has a specific syntax, including
imports
for functionality consumption andexports
for functionality exposure. - Nature: Because ESM is static by nature, imports and exports are defined at build time, enabling advanced tooling features like tree-shaking and static analysis.
- Usage: ESM is the most recent standard for writing TypeScript and JavaScript code that is modular.
AMD (Asynchronous Module Definition)
AMD was widely utilised in browser settings; RequireJS was one such implementation.
- Nature: AMD is asynchronous and required for browser module loading over a network connection.
- Recommendation: The specifically state that AMD is “dead tech” and should not be used today. ES Modules are thought to be superior to SystemJS, another older experiment.
TypeScript’s Support for Modules
TypeScript maintains compatibility with earlier runtime environments, like as CJS, while supporting the native syntax of the ES Module.
ES Import/Export Syntax in TypeScript
Authoring all TypeScript code using the standard ES Module syntax (import
and export
) is the current best practice. An import
or export
statement at the root level of a TypeScript file is handled as a File Module (or external module), which establishes a local scope and prevents the global namespace from becoming contaminated.
The adaptable ES syntax for modules is fully supported by TypeScript:
- Named Exports: Prior to declarations, use the
export
keyword. - Named Imports: To import certain bindings, use destructuring.
- Default Imports/Exports: You can import a single default item without curly brackets and export it.
- Bundled Imports: Importing all exports into a single namespace object is known as “bundled imports.”
Compiling to Different Module Systems via the Option
TypeScript uses the module
compiler option in tsconfig.json
to determine which JavaScript module system should be created in the output (.js
) files when you write your code using the ES Module syntax.
The value chosen for the module
option dictates how TypeScript converts your ES import
/export
statements into runtime code.
module Option Value | Target System | Primary Use Case |
commonjs | CommonJS | Node.js applications |
amd | AMD | Older browser loaders like RequireJS |
es2015 /esnext | ES Modules | Modern bundlers (like Webpack/Rollup) or environments with native ESM support (Deno, newer Node.js) |
umd | UMD | Libraries that need universal compatibility (browser or server) |
The general recommendation, especially when targeting older Node.js environments or common bundling workflows, is often to use:
{
"compilerOptions": {
"module": "commonjs", /* Specify module code generation: 'commonjs' */
"target": "es5" /* Specify ECMAScript target version: 'ES5' */
}
}
Setting module: "NodeNext"
is advised if you are currently using Node.js since it preserves CommonJS compatibility while supporting the newest ES Module capabilities.
ESM to CommonJS
The TypeScript compiler converts the current ES syntax into the CJS structure (require
and exports
) when it comes across ES Module syntax in a file and is set up to produce CommonJS.
Consider two TypeScript files written using standard ES Module syntax, compiled with "module": "commonjs"
and "target": "es5"
.
TypeScript Module ()
// math.ts (Input: ES Module Syntax)
export const PI = 3.14159;
export function add(a: number, b: number): number {
return a + b;
}
JavaScript Output ()
The named exports are converted into properties on the CommonJS exports
object:
// math.js (Output: CommonJS)
"use strict";
// Generated by typescript
exports.PI = 3.14159;
function add(a, b) {
return a + b;
}
exports.add = add;
TypeScript Consumer ()
// index.ts (Input: ES Module Syntax)
import { PI, add } from './math';
let result = add(10, 5);
console.log(`Result: ${result}, PI: ${PI}`);
JavaScript Output ()
The ES import
is converted to a synchronous require
call, capturing the exports into a local variable (math_1
):
// index.js (Output: CommonJS)
"use strict";
var math_1 = require("./math"); // CJS require call
var result = (0, math_1.add)(10, 5);
console.log("Result: " + result + ", PI: " + math_1.PI);
/* Output when executed in Node.js runtime:
Result: 15, PI: 3.14159
*/
While guaranteeing that the produced code functions properly in settings that need CommonJS, this automatic transpilation enables developers to utilise the preferred, statically-friendly ES Module syntax.