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 🙏

© 2026 – Pkg Stats / Ryan Hefner

babel-plugin-transform-import-source

v2.1.0

Published

Babel plugin for transforming import sources.

Readme

babel-plugin-transform-import-source

Babel plugin for transforming import sources.

Table of Contents

Overview

This plugin allows transforming import sources in the require, import, and export statements either based on a set of custom rules or by applying a custom function. It can be useful for modifying import sources to match the output file extensions, or manipulating query parameters based on certain conditions at compile-time, etc.

Features

  • Transform import sources in require/import expressions and import/export statements
  • Transform import sources outside of the statements with compile-time macros
  • Resolve index file when the import source refers to a directory
  • Exclude certain import sources to be transformed, with magic comments
  • Handle multiple rules to match and replace import sources with different patterns
  • Force absolute paths to bypass test conditions (easy cross-platform support)
  • Take advantage of the low-level API to implement completely custom behavior

Installation

Install the plugin using npm or another package manager:

npm install -D babel-plugin-transform-import-source

Usage

Add the plugin to the Babel configuration and specify the transformation rules in the options object.

module.exports = {
  plugins: [
    [
      require.resolve("babel-plugin-transform-import-source"),
      {
        transform: {
          rules: [
            {
              // Apply this rule to explicit relative paths only.
              test: /^[.\\/]+.*$/,
              // Bypass the test condition to match absolute paths without
              // any restrictions.
              includeAbsolute: true,
              // Find either the extension part or the end of the string.
              //
              // `$` character matches the end of the string.
              // `?` character makes the pattern optional.
              //
              // As a result, this will cause the transformation to
              // either replace the extension part with `.mjs` or, append
              // `.mjs` to the end of the string if there is no extension.
              //
              // For example, the import source `./file.ts` will be
              // transformed to `./file.mjs`, and the import source
              // `./file` will be transformed to `./file.mjs` as well.
              find: /(?:\.[cm]?[jt]s[x]?)?$/iu,
              replace: ".mjs",
              // If the import source refers to a directory,
              // first append `index` suffix to the import source,
              // then apply the transformation.
              //
              // This will cause the transformation to replace an import
              // source like `./dir` with `./dir/index.mjs`, assuming
              // `./dir` refers to a directory in this example.
              resolveIndex: true,
            },
          ],
        },
      },
    ],
  ],
};

JSON Configuration

In case of using JSON configuration, the RegExp patterns can be provided as objects containing the regexp and optional flags properties.

{
  "plugins": [
    [
      "babel-plugin-transform-import-source",
      {
        "transform": {
          "rules": [
            {
              "test": {
                "regexp": "^[.\\\\/]+.*$"
              },
              "includeAbsolute": true,
              "find": {
                "regexp": "(?:\\.[cm]?[jt]s[x]?)?$",
                "flags": "iu"
              },
              "replace": ".mjs",
              "resolveIndex": true
            }
          ]
        }
      }
    ]
  ]
}

Low-Level API

The plugin can also be used with a custom function to transform import sources.

const { createDefaultTransformer } = require("babel-plugin-transform-import-source");

/**
 * @import { type TransformerContext } from "babel-plugin-transform-import-source";
 */

// The default transformer function can be re-used in a custom one.
const superTransform = createDefaultTransformer();

module.exports = {
  plugins: [
    [
      require.resolve("babel-plugin-transform-import-source"),
      {
        transformer: (
          /**
           * @type {TransformerContext}
           */
          context
        ) =>
        {
          // For example, Babel API or the default transformer function
          // can be used depending on certain conditions.
          if (condition)
          {
            context.nodePath.replaceWith(context.api.types.nullLiteral());
            return undefined;
          }
          else if (otherCondition)
          {
            return `${context.importSource}?enterprise=true&oss=false`;
          }
          return superTransform(context);
        },
      },
    ],
  ],
};

Technical Details

The plugin handles the following types of AST nodes:

  • CallExpression
  • ExportAllDeclaration
  • ExportNamedDeclaration
  • ImportDeclaration
  • ImportExpression

The transformer function is called for each import source in the AST nodes mentioned above. The function receives a context object containing the information about the current import source and the AST node. See the transformer context definition below for more information.

Transformer Context

The transformer context object contains the following properties:

  • dirname: The directory path of the current file.
  • filename: The file path of the current file.
  • importSource: The import source.
  • importSourceNode: AST node of the import source.
  • nodePath: Current AST node path.
  • options: Plugin options.
  • state: Plugin state.

Module Methods

The plugin also provides compile-time .transform module-methods to transform import sources outside of the statements.

When the feature is enabled, the .transform macros can be accessed via the import.meta object or the require function.

// Transforms `./file.ts` and returns the result.
import.meta.transform("./file.ts");

// Same as above.
require.transform("./file.ts");

Type Definitions for Module Methods

The type definitions for the module methods with the default property names are provided in the env.d.ts file. They can be imported with a reference directive.

For custom property names, TypeScript's interface merging feature can be used to extend the default type definitions.

declare module "module"
{
  declare global
  {
    interface ImportMeta
    {
      transform(source: string): string;
    }
  }
}

declare namespace NodeJS
{
  interface Require
  {
    transform(source: string): string;
  }
}

Magic Comments

To exclude certain import sources from being transformed, @babel-plugin-transform-import-source-ignore directive can be used in a leading comment line.

// The import source will remain unchanged.
// @babel-plugin-transform-import-source-ignore
import "./file.ts";

// Same as above.
require(
  // @babel-plugin-transform-import-source-ignore
  "./file.ts",
);

// The import source here will remain unchanged as well.
// And the expression will return `./file.ts` as it is.
import.meta.transform(
  // @babel-plugin-transform-import-source-ignore
  "./file.ts",
);

Options

The plugin accepts an options object with the following properties.

moduleMethods

  • Type: object
  • Description: Configuration for the module methods.
  • Properties:
    • transformImportSource: (Optional) Configuration for the compile-time .transform methods.
      • Type: object
      • Properties:
        • importMeta: (Optional) A boolean to enable/disable the import.meta.transform macro. Or an object with propertyName property to specify the custom property name. Default is false.
        • require: (Optional) A boolean to enable/disable the require.transform macro. Or an object with propertyName property to specify the custom property name. Default is false.

transform

  • Type: object
  • Description: Configuration for transforming import sources.
  • Properties:
    • rules: An array of transformation rules.

Rule

A transformation rule object can have the following properties:

  • find: A string or RegExp to match the import source to replace.
  • replace: A string to replace the matched import source.
  • includeAbsolute: (Optional) A boolean to include absolute paths by bypassing the test.
  • resolveIndex: (Optional) A boolean to resolve index file when the import source refers to a directory.
  • test: (Optional) A string or RegExp to test the import source.

transformer

  • Type: function
  • Description: A custom function to transform import sources.
  • Arguments:
    • context: The transformer context object.
  • Returns: The transformed import source. If the function returns null or undefined, the import source remains unchanged.

Development Notes

The plugin is written in TypeScript and compiled to both CommonJS and ECMAScript modules, and the import sources are transformed to match the output file extensions accordingly. The following notes are not specific to the plugin only, but they can be helpful for understanding the motivation behind the plugin.

Targeting both CommonJS and ECMAScript modules in a single package is a common use case to provide compatibility with different environments and tools. Achieving this usually requires workarounds to ensure that the module types are resolved correctly in different environments. These workarounds are like defining type fields in package.json files (e.g., "type": "commonjs" or "type": "module"), or writing wrapper scripts around the main entry points. Since using wrapper scripts can be helpful in avoiding dual-package hazards, they can also reduce the static evaluation of the package and make it less optimizable (assuming that the module type is CommonJS at the beginning).

By using .cjs or .mjs file extensions, the module type becomes explicit and independent of the environment. This allows the module type to be recognized as a CommonJS or ECMAScript module without relying on the environment or configuration files like package.json. Instead, it can be determined by the file extension itself. This can be beneficial in many ways, such as making the package more portable and less error-prone in different environments.

While CommonJS allows import paths to be written without file extensions, ECMAScript spec requires import paths to be fully specified with explicit file extensions. This mismatch can cause issues in certain scenarios, such as when a package is written in TypeScript with extensionless imports, and then it's compiled and consumed by ECMAScript modules, etc.

To make the module types explicit while compiling with Babel, the --out-file-extension flag can be used to set the output file extensions (e.g., --out-file-extension '.cjs' or --out-file-extension '.mjs'). However, -according to my tests- Babel does not use this flag for import sources in statements to be transformed respectively. So, the import sources remain unchanged, while extensions of the output files are modified. This can cause the exact issue mentioned above as well.

The plugin can be used to fill this gap for transforming the import sources in the statements to make them match the output file extensions. This way, the module types are resolved correctly in both CommonJS and ECMAScript modules, and the package becomes more consistent and reliable.

Build Process

The plugin uses itself to transform its own import source strings to target the desired module type explicitly.

To make the plugin able to use itself in its own build process, there is a bootstrap phase that compiles the plugin to CommonJS modules first. Since the format of the import paths is extensionless in the source code, and this is acceptable in CommonJS modules, they don't need to be changed in the output files. But, at this step, the output file names have .js extension. Although the default module type in Node.js is CommonJS, the type field in the package.json file is set to "type": "commonjs" to ensure that the files are recognized as CommonJS modules in any case. Finally, these compiled files can be imported into the Babel configurations successfully.

After the bootstrap phase, the actual build process gets started. At this point, the early version of the plugin that obtained from the bootstrap phase is used as a part of the build process to transform the import sources in the statements to match the output file extensions.

The process can be observed in the following steps:

  1. The bootstrap::cjs script defined in the package.json file gets executed to compile the plugin to CommonJS modules using the babel.config.bootstrap.cjs configuration file.
  2. babel.config.cjs.cjs and babel.config.esm.cjs configuration files require the CommonJS modules compiled in the bootstrap phase. This is where the plugin uses itself to compile its own source code to target the desired module types explicitly.
  3. The build::cjs and build::esm scripts defined in the package.json file get executed to compile the plugin to both CommonJS and ECMAScript modules using the corresponding configuration files mentioned in the previous step.

License

This project is licensed under the MIT License.

See the LICENSE file for more information.