npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

importx

v0.5.0

Published

Unified tool for importing TypeScript modules at runtime

Downloads

576,730

Readme

importx

npm version npm downloads bundle JSDocs License

Unified tool for importing TypeScript modules at runtime.

Motivation

It's a common need for tools to support importing TypeScript modules at runtime. For example, to support configure files written in TypeScript.

There are so many ways to do that, each with its own trade-offs and limitations. This library aims to provide a simple, unified API for importing TypeScript modules, providing an easy-to-use API, and making it easy to switch between different loaders.

By default, it also provides a smart "auto" mode that decides the best loader based on the environment, trying to ease out their limitations and provide the best experience.

The goal is for this library to swallow the complexity of the underlying implementations, where you can just focus on the feature set you need. This library will keep up-to-date with the latest loaders and the runtime environment.

Usage

npm i importx
const mod = await import('importx').then(x => x.import('./path/to/module.ts', import.meta.url))

Options

You can turn the second argument of import into an object to provide options:

const mod = await import('importx').then(x => x.import('./path/to/module.ts', {
  parentURL: import.meta.url, // *required

  // The following options are their default values
  cache: null, // false, if you want to always get a new module
  listDependencies: false, // true, if you need to get the list of dependencies
  loader: 'auto', // most of the time, you don't need to change this as they will be chosen automatically
}))

When loader is not provided in the options explicitly, it will read from IMPORTX_LOADER environment variable, and then fall back to the auto.

Loaders

auto

Automatically choose the best loader based on the environment (if the below graph doesn't render, click here to view it on GitHub).

graph TD
  A((Auto)) --> Cache

  Cache --> |false| RuntimeTsx
  Cache --> |true / null| IsTS

  IsTS --> |Yes| SupportTs
  IsTS --> |No| Native1

  SupportTs --> |No| RuntimeTsx
  SupportTs --> |Yes| Native2

  RuntimeTsx --> |Yes| Tsx
  RuntimeTsx --> |No| ListDeps

  ListDeps --> |Yes| BundleRequire
  ListDeps --> |No| Jiti

  IsTS{{"Is importing a TypeScript file?"}}
  SupportTs{{"Supports native TypeScript?"}}
  Cache[["Cache enabled?"]]
  Native1(["native import()"])
  Native2(["native import()"])
  RuntimeTsx{{"Is current runtime supports tsx?"}}
  ListDeps[["Need to list dependencies?"]]
  Tsx([tsx loader])
  Jiti([jiti loader])
  BundleRequire([bundle-require loader])

  classDef auto fill:#0f82,stroke:#0f83,stroke-width:2px;
  classDef question fill:#f9f2,stroke:#f9f3,stroke-width:2px;
  classDef cache fill:#eb527120,stroke:#eb527133,stroke-width:2px;
  classDef native fill:#8882,stroke:#8883,stroke-width:2px;
  classDef ts fill:#09f2,stroke:#09f3,stroke-width:2px;
  classDef tsx fill:#0fe2,stroke:#0fe3,stroke-width:2px;
  classDef jiti fill:#ffde2220,stroke:#ffde2230,stroke-width:2px;
  classDef bundle fill:#5f32,stroke:#5f33,stroke-width:2px;
  class A auto;
  class RuntimeTsx question;
  class Cache,ListDeps cache;
  class Native1,Native2,Native3 native;
  class IsTS,SupportTs ts;
  class Tsx tsx;
  class Jiti jiti;
  class BundleRequire bundle;
  linkStyle default stroke:#8888

native

Use the native import() to import the module. According to the ESM spec, importing the same module multiple times will return the same module instance.

tsx

Use tsx's tsImport API to import the module. Under the hood, it registers Node.js loader API and uses esbuild to transpile TypeScript to JavaScript.

Pros

  • Native Node.js loader API, consistent and future-proof.
  • Get the file list of module dependencies. Helpful for hot-reloading or manifest generation.
  • Supports scoped registration, does not affect the global environment.

Limitations

  • Requires Node.js ^18.18.0, ^20.6.0 or above. Does not work on other runtime yet.

jiti

Use jiti to import the module. It uses a bundled Babel parser to transpile modules. It runs in CJS mode and has its own cache and module runner.

Pros

  • Self-contained, does not depend on esbuild.
  • Own cache and module runner, better and flexible cache control. Works on the majority of Node-compatible runtimes.

Limitations

bundle-require

Use bundle-require to import the module. It uses esbuild to bundle the entry module, saves it to a temporary file, and then imports it.

Pros

  • Get the file list of module dependencies. Helpful for hot-reloading or manifest generation.

Limitations

  • It creates a temporary bundle file when importing (will external node_modules).
  • Can be inefficient where there are many TypeScript modules in the import tree.
  • Imports are using esbuild's resolution, which might have potential misalignment with Node.js.
  • Always import a new module, does not support module cache.

Cache

By definition, ESM modules are always cached by the runtime, which means you will get the same module instance when importing the same module multiple times. In some scenarios, like a dev server watching for config file changes, the cache may not be desired as you want to get the new module with the latest code on your disk.

importx allows you to specify if you want to have the module cache or not, by providing the cache option:)

const mod = await import('importx')
  .then(x => x.import('./path/to/module.ts', {
    cache: false, // <-- this
    parentURL: import.meta.url,
  }))

Setting cache: null (default) means you don't care about the cache (if you only import the module once).

Note that some loaders always have a cache, and some loaders always have no cache. With the auto loader, we will choose the best loader based on your need. Otherwise, an unsupported combination will throw an error. For example:

// This will throw an error because `bundle-require` does not support cache.
const mod = await import('importx')
  .then(x => x.import('./path/to/module.ts', {
    cache: true,
    loader: 'bundle-require',
    parentURL: import.meta.url,
    // ignoreImportxWarning: true // unless you have this
  }))

Get Module Info

You can get the extra module information by passing the module instance to getModuleInfo:

await import('importx')
  .then(async (x) => {
    const mod = await x.import('./path/to/module.ts', import.meta.url)
    const info = x.getModuleInfo(mod)
    console.log(
      info.loader, // the final loader used
      info.timestampInit, // timestamp when the module is initialized
      info.timestampLoad, // timestamp when the module is imported
      info.dependencies, // list of dependencies (available only in `tsx` and `bundle-require` loader),
      (info.timestampLoad - info.timestampInit) // time taken to load the module (in ms)
    )
  })

List Module Dependencies

In cases like loading a config file for a dev server, where you need to watch for changes in the config file and reload the server, you may want to know the module's dependencies to watch for changes in them as well.

tsx and bundle-require loaders support listing the dependencies of the module. You can get the list of dependencies by getting the module info. To ensure you use always have the dependencies list in auto mode, you can set the listDependencies option to true:

const mod = await import('importx')
  .then(x => x.import('./path/to/module.ts', {
    listDependencies: true,
    parentURL: import.meta.url,
  }))

Fallback Loaders

Since v0.4, importx supports fallback loaders when previous loaders fail to load the module. By default ['jiti'] will be used as it's the most compatible loader. You can customize the fallback loaders by setting the fallbackLoaders option:

const mod = await import('importx')
  .then(x => x.import('./path/to/module.ts', {
    fallbackLoaders: ['jiti', 'tsx'],
    parentURL: import.meta.url,
  }))

You can also disable fallback loaders by setting it to false:

const mod = await import('importx')
  .then(x => x.import('./path/to/module.ts', {
    fallbackLoaders: false,
    parentURL: import.meta.url,
  }))

Runtime-Loader Compatibility Table

Importing a TypeScript module with importx:

Generated with version v0.4.4 at 2024-09-27T00:27:46.691Z

| | native | tsx | jiti | bundle-require | | ------- | --- | --- | --- | --- | | node | Import: ❌Cache: ❌No cache: ❌Deps: ❌CTS Import: ❌ESM/CJS Mixed: ❌Const Enum: ❌Import ESM Dep: ❌ | Import: ✅Cache: ✅No cache: ✅Deps: ✅CTS Import: ✅ESM/CJS Mixed: ✅Const Enum: ✅Import ESM Dep: ✅ | Import: ✅Cache: ✅No cache: ✅Deps: ✅CTS Import: ✅ESM/CJS Mixed: ✅Const Enum: ✅Import ESM Dep: ✅ | Import: ✅Cache: ❌No cache: ✅Deps: ✅CTS Import: ❌ESM/CJS Mixed: ✅Const Enum: ✅Import ESM Dep: ✅ | | tsx | Import: ✅Cache: ✅No cache: ❌Deps: ❌CTS Import: ✅ESM/CJS Mixed: ✅Const Enum: ✅Import ESM Dep: ✅ | Import: ✅Cache: ✅No cache: ✅Deps: ✅CTS Import: ✅ESM/CJS Mixed: ✅Const Enum: ✅Import ESM Dep: ✅ | Import: ✅Cache: ✅No cache: ✅Deps: ✅CTS Import: ✅ESM/CJS Mixed: ✅Const Enum: ✅Import ESM Dep: ✅ | Import: ✅Cache: ❌No cache: ✅Deps: ✅CTS Import: ❌ESM/CJS Mixed: ✅Const Enum: ✅Import ESM Dep: ✅ | | deno | Import: ✅Cache: ✅No cache: ❌Deps: ❌CTS Import: ❌ESM/CJS Mixed: ❌Const Enum: ❌Import ESM Dep: ✅ | Import: ❌Cache: ❌No cache: ❌Deps: ❌CTS Import: ❌ESM/CJS Mixed: ❌Const Enum: ❌Import ESM Dep: ❌ | Import: ❌Cache: ❌No cache: ❌Deps: ❌CTS Import: ❌ESM/CJS Mixed: ❌Const Enum: ❌Import ESM Dep: ❌ | Import: ✅Cache: ❌No cache: ✅Deps: ✅CTS Import: ❌ESM/CJS Mixed: ✅Const Enum: ✅Import ESM Dep: ✅ | | bun | Import: ✅Cache: ✅No cache: ❌Deps: ❌CTS Import: ✅ESM/CJS Mixed: ✅Const Enum: ✅Import ESM Dep: ✅ | Import: ❌Cache: ❌No cache: ❌Deps: ❌CTS Import: ❌ESM/CJS Mixed: ❌Const Enum: ❌Import ESM Dep: ❌ | Import: ✅Cache: ❌No cache: ❌Deps: ✅CTS Import: ✅ESM/CJS Mixed: ✅Const Enum: ✅Import ESM Dep: ✅ | Import: ✅Cache: ❌No cache: ✅Deps: ✅CTS Import: ❌ESM/CJS Mixed: ✅Const Enum: ✅Import ESM Dep: ✅ |

Features-Loader Table

| | native | tsx | jiti | bundle-require | | --------------------------- | --- | --- | --- | --- | | Cache: true | ✅ | ✅ | ✅ | ❌ | | Cache: false | ❌ | ✅ | ✅ | ✅ | | List dependencies | ❌ | ✅ | ✅ | ✅ | | Runtimes other than Node.js | ✅ | ❌ | ✅ | ✅ | | Native ESM Import | ✅ | ✅ | ✅ | ✅ | | Top-level await | ✅ | ✅ | ✅ | ✅ | | Runtime module type* | ESM | ESM | CJS | ESM/CJS |

*This indicates what's the module type for each loader to evaluate the modules. For CJS, it means the loader transpiles the module to CJS and executes it in CJS mode, which may have some limitations like top-level await.

Sponsors

License

MIT License © 2024-PRESENT Anthony Fu