You must define configuration on a per-extension basis. Any unknown extensions are ignored by tsm, deferring to Node's native handling of that file type. By default, tsm will always handle the .jsx
, .tsx
, and .ts
extensions, but you may redefine these in your configuration file to override default options.
Every extension is given its own set of esbuild.transform
options. tsm provides moderate defaults per extension, but does not enforce any option values or offer any custom options of its own.
All extensions are loaded with these default esbuild
options:
Important: Any configuration that you define also inherits these defaults. Provide a new value to override the default setting.
let options = {
format: format, // "esm" for CLI or --loader, else "cjs"
charset: 'utf8',
sourcemap: 'inline',
target: 'node' + process.versions.node,
logLevel: isQuiet ? 'silent' : 'warning',
color: enabled, // determined via `process` analysis
}
Additionally, tsm defines a few extensions by default, each of which is assigned an appropriate esbuild loader. The entire default tsm configuration is as follows:
let config = {
'.jsx': { ...options, loader: 'jsx' },
'.tsx': { ...options, loader: 'tsx' },
'.mts': { ...options, loader: 'ts' },
'.cts': { ...options, loader: 'ts' },
'.ts': { ...options, loader: 'ts' },
}
When using tsm through a --require
hook, then tsm also intercepts all .mjs
files loaded via the require()
method.
With CLI and/or --loader
usage, tsm also handles .json
files, which allows you to import
or import()
a JSON file.
When a tsm.js
file exists in the current working directory (process.cwd()
), it's automatically loaded and merged with the tsm default configuration.
The module format of the tsm.js
file is controlled by the root package.json
file, also located in the current working directory. For example, if it contains "type": "module"
then the tsm.js
file may be written in ESM syntax (import
/export
). Otherwise it must be in CommonJS format. (This is true for all .js
files.)
Additionally, when using tsm as a node
replacement, you may provide a path to an alternate configuration file through the --tsmconfig
argument. For example, to load a tsm.config.mjs
file, you should run:
$ tsm server.ts --tsmconfig tsm.config.mjs
Note: Any
--tsmconfig
value is always resolved from theprocess.cwd()
When using tsm through a --require
or --loader
hook, the --tsmconfig
flag is respected and your custom configuration file will be autoloaded, if found.
There multiple ways to define your configuration.
Note: See Examples below for demonstrations.
Conceptually, configuration is broken down by extension, allowing each each extension to take its own esbuild.transform
options. The extensions themselves are used as keys within the configuration object; for example, .ts
not ts
. While verbose, this is the clearest way to visualize and understand what/how each extension is handled.
The extensions' configuration can remain free-standing, but you may also wrap it in a config
key for added clarity. This object may be exported from the file as a named config
export or the default export.
However, as you might imagine, this may become overwhelming and/or repetitive. To alleviate this, tsm allows a common
object to extract and share common options across all extensions. The common
key may coexist with all other configuration formats (see below) and may be exported from the configuration file as a named common
export or as a common
key on the default exported object.
After extracting shared options to a common
key, you may find that all your config
is doing is defining an esbuild loader
option. If this is the case, you may replace the config
object with a loaders
object that maps an extension to its (string) loader name. For example:
--let config = {
-- '.ts': {
-- loader: 'ts'
-- },
-- '.html': {
-- loader: 'text'
-- }
--};
++let loaders = {
++ '.ts': 'ts',
++ '.html': 'text',
++};
However, when using the loaders
approach, you cannot continue to use a config
object. The loaders
key may only coexist with the common
options object. Should you need to add additional, extension-specific configuration, then you cannot use loaders
and must use the config
approach instead.
Finally, the tsm/config
submodule offers a define
method that can be used to typecheck/validate your configuration. All previous approaches and combinations still apply when using the define
helper. For simplicity, you should use this helper as your default export; for example:
// ESM syntax
import { define } from 'tsm/config';
export default define({
common: {
target: 'es2021',
minify: true,
},
'.ts': {
minify: false,
},
'.html': {
loader: 'text',
},
// ...
});
Important: Ignoring the authoring format (CommonJS vs ESM), all snippets produce the identical final configuration.
Define each extension
let config = {
'.ts': {
minifyWhitespace: true,
target: 'es2020',
loader: 'ts',
},
'.tsx': {
jsxFactory: 'preact.h',
jsxFragment: 'preact.Fragment',
banner: 'import * as preact from "preact";',
minifyWhitespace: true,
target: 'es2020',
loader: 'tsx',
},
'.jsx': {
jsxFactory: 'preact.h',
jsxFragment: 'preact.Fragment',
banner: 'import * as preact from "preact";',
minifyWhitespace: true,
target: 'es2020',
loader: 'jsx',
}
};
/**
* PICK ONE
*/
// ESM - default
export default config;
// ESM - named
export { config };
// CommonJS - default
module.exports = config;
// CommonJS - named
exports.config = config;
Hoist common
options
// Merged with default options
// Shared with *all* extensions
let common = {
minifyWhitespace: true,
target: 'es2020',
jsxFactory: 'preact.h',
jsxFragment: 'preact.Fragment',
banner: 'import * as preact from "preact";',
}
// Retain unique per-extension config
let config = {
'.ts': {
loader: 'ts',
},
'.tsx': {
loader: 'tsx',
},
'.jsx': {
loader: 'jsx',
}
};
/**
* PICK ONE
*/
// ESM - named
export { config, common };
// CommonJS - named
exports.config = config;
exports.common = common;
Invoke loaders
shortcut
// Merged with default options
// Shared with *all* extensions
let common = {
minifyWhitespace: true,
target: 'es2020',
jsxFactory: 'preact.h',
jsxFragment: 'preact.Fragment',
banner: 'import * as preact from "preact";',
}
// When *only* need to define a loader
// use `loaders` object as a shortcut
// NOTE:
// You CANNOT define `config` *and* `loaders`.
// If both are present, only `loaders` will apply.
let loaders = {
'.ts': 'ts',
'.tsx': 'tsx',
'.jsx': 'jsx',
};
/**
* PICK ONE
*/
// ESM - named
export { loaders, common };
// CommonJS - named
exports.loaders = loaders;
exports.common = common;
Import define
helper
// Includes TypeScript checks
// NOTE: use `require` for CommonJS
import { define } from 'tsm/config';
export default define({
common: {
minifyWhitespace: true,
target: 'es2020',
jsxFactory: 'preact.h',
jsxFragment: 'preact.Fragment',
banner: 'import * as preact from "preact";',
},
// Here you can define (exclusive):
// - a `config` object;
// - a `loaders` object;
// - inline extension configs; OR
// - nothing else
loaders: {
'.ts': 'ts',
'.tsx': 'tsx',
'.jsx': 'jsx',
}
});