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"
}
}

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.