Skip to main content

Modules

API BackendTooling

Load and resolve custom file types at runtime with a Node.js require replacement or next-generation hooks. Currently supports TypeScript for .ts and .tsx files.

Installation

yarn add @boost/module

CommonJS requires

Node.js's native require() has historically only supported .js and .json files (and now .cjs too). But what if we were able to require non-JavaScript files at runtime also? Like TypeScript? This package does just that through a new function called requireModule().

This function operates by patching the list of allowed file types/extensions in Node.js's module resolution. Begin by importing the function and importing a supported file type!

import { requireModule } from '@boost/module';

const result = requireModule('./some/non-js/file');

This function should only be used to import module-like files, like JavaScript and TypeScript. It should not be used for other file types, like JSON or YAML.

If you'd prefer to not use requireModule() and still use native require(), but also support custom file types, you may do so by calling registerExtensions() at the top of your script or application entry point.

Module interoperability

Unlike require() which returns imported values as-is, requireModule() changes the shape of the import to align with ECMAScript modules. We do this for interoperability and consistency sake, so that the developer experience is the same for both systems.

So what does this mean exactly? The biggest change is that CommonJS default exports (module.exports) are now returned under a default property, like so.

example.js
module.exports = 123;
const value = require('./example'); // 123
const { default: value } = requireModule('./example'); // 123

Another change is that CommonJS named exports (exports.<name>) are returned as properties in the imported object, as well as properties in an object on the default property. This pattern exists to match Node.js >= v12.20 functionality.

example.js
exports.foo = 'abc';
exports.bar = 123;
const value = require('./example');
value.foo; // abc
value.bar; // 123

const value = requireModule('./example');
value.foo; // abc
value.bar; // 123
value.foo === value.default.foo; // true

Generic types

In TypeScript, the native require() is typed to return any, which isn't that ideal. However, requireModule() can type both the default and named exports of a module via generics.

The 1st generic slot types the default export (module.exports for CJS and export default for ESM) under the returned default property.

const result = requireModule<string>('./example');
result.default; // string

While the 2nd generic slot types named exports (exports.<name> for CJS and export <name> for ESM) under their respective property names.

const result = requireModule<string, { foo: string; bar: number }>('./example');
result.foo; // string
result.bar; // number
result.default; // string

For backwards compatibility with CommonJS (can't mix module.exports and exports.<name>), named exports are also encapsulated under the default property. To type this correctly, pass void for the default generic, which passes the type through accordingly.

const result = requireModule<void, { foo: string; bar: number }>('./example');
result.foo; // string
result.bar; // number
result.default.foo; // string
result.default.bar; // number

Node.js hooks

Node.js supports an experimental feature called hooks, where non-JavaScript files can be loaded, parsed, and evaluated at runtime through Node.js's module system when using import/export.

To make use of hooks, the following requirements must be met.

  • Node.js >= v16.20
  • Must use import() or import/export syntax (no require)
  • Source files must be modules (.mjs, .ts, etc)
  • Imported files must have trailing file extensions

If you meet all of these requirements, then you may run your Node.js script with one of the following patterns.

# v20+
node --import @boost/module/register ./path/to/entry-point.mjs

# v18+
node --loader @boost/module/hook-typescript ./path/to/entry-point.mjs

# v16+
node --experimental-loader @boost/module/hook-typescript ./path/to/entry-point.mjs

For example, with the above you can now import TypeScript files as if they were standard JavaScript!

// entry-point.mjs
import defaultValue, { namedValue } from './some/file/written/in.ts';

Supported file types

TypeScript

TypeScript files are supported for .ts, .tsx, .cts, and .mts file extensions. The TypeScript source code will be down-leveled according to the currently running Node.js version and its capabilities.

The TypeScript only hook/loader can be referenced with @boost/module/hook-typescript.