Skip to main content

Modules

txiki.js uses standard ES modules. Every .js file is treated as a module, and you can import from local files, HTTP URLs, the standard library, and more.

Local imports​

Relative and absolute paths work as expected:

import { helper } from './lib/utils.js';
import config from '../config.js';

File extensions are always required — there is no automatic .js resolution or index.js lookup.

Standard library (tjs:)​

Built-in modules are imported with the tjs: prefix:

import assert from 'tjs:assert';
import path from 'tjs:path';
import { Database } from 'tjs:sqlite';

See the Standard Library for the full list.

HTTP imports​

You can import modules directly from HTTP and HTTPS URLs:

import { render } from 'https://esm.sh/preact';
import data from 'https://example.com/api/config.js';

The module is fetched synchronously during module resolution. The URL is used as the module identifier for caching — the same URL won't be fetched twice.

Import attributes​

Import attributes let you import non-JavaScript files as modules. The type attribute controls how the file is interpreted.

JSON​

import data from './data.json' with { type: 'json' };
// data is the parsed JSON object

import pkg from './package.json' with { type: 'json' };
console.log(pkg.version);

Files ending in .json are automatically treated as JSON even without the attribute.

Text​

import template from './template.html' with { type: 'text' };
// template is a string containing the file contents

import query from './query.sql' with { type: 'text' };

Bytes​

import wasm from './module.wasm' with { type: 'bytes' };
// wasm is a Uint8Array containing the raw file bytes

import cert from './cert.pem' with { type: 'bytes' };

All three types expose their value as the default export.

Import maps​

Import maps let you map bare specifiers (like "lodash") to file paths or URLs. Without an import map, bare specifiers fail because they don't resolve to a file path.

CLI flag​

tjs run --import-map import-map.json app.js

TPK app packages​

For TPK app packages, add imports and scopes directly to app.json:

{
"version": 0,
"build": {},
"main": "src/main.js",
"imports": {
"utils": "./src/lib/utils.js"
}
}

Programmatic​

Use tjs.setImportMap(map, baseDir) to install a map from JavaScript — handy when the map is computed at runtime or shipped in a non-JSON form. Relative targets in map are resolved against baseDir:

tjs.setImportMap({
imports: {
'lodash': './vendor/lodash/index.js',
'lodash/': './vendor/lodash/',
},
scopes: {
'./legacy/': {
'pkg': './vendor/pkg-v1/index.js',
},
},
}, import.meta.dirname);

const _ = await import('lodash');

tjs.setImportMap() must be called before the affected import / import() runs — already-resolved modules are not retroactively remapped. Calling it again replaces the previous map.

Format​

An import map is a JSON object with two optional fields: imports and scopes. Paths are resolved relative to the import map file (or app.json for TPK apps).

Basic mappings​

{
"imports": {
"lodash": "./vendor/lodash/index.js",
"api": "https://cdn.example.com/api.js"
}
}
import _ from 'lodash'; // → ./vendor/lodash/index.js
import api from 'api'; // → https://cdn.example.com/api.js

Prefix mappings​

Keys ending with / act as path prefixes:

{
"imports": {
"lodash/": "./vendor/lodash/"
}
}
import { merge } from 'lodash/merge.js'; // → ./vendor/lodash/merge.js

Scoped mappings​

Override mappings for specific directories:

{
"imports": {
"pkg": "./vendor/pkg-v2/index.js"
},
"scopes": {
"./legacy/": {
"pkg": "./vendor/pkg-v1/index.js"
}
}
}

Files inside ./legacy/ resolve pkg to v1, while everything else gets v2. The most specific scope (longest prefix match) wins.

Notes​

  • Only one import map can be active at a time.
  • The import map must be set before any modules are loaded.
  • tjs:* built-in modules are not affected by import maps unless explicitly mapped.
  • Unmatched specifiers fall through to default resolution.