JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 154
  • Score
    100M100P100Q90025F
  • License MIT

A rollup plugin to add, modify, and remove imports (cjs/es6/dynamic)

Package Exports

  • rollup-plugin-import-manager

Readme

rollup-plugin-import-manager

License npm

A Rollup plugin which makes it possible to manipulate import statements. Features are deleting, adding, changing the members and modules and much more. Supports ES6 Import Statements, CommonJS and Dynamic Imports.

Table of Contents

Install

Using npm:

npm install rollup-plugin-import-manager --save-dev

How it works

rollup-plugin-import-manager analyzes each file (which is used for the rollup building process) for import statements. Those are collected as so called unit objects, on which the user can interact with. Also the creation of new units → import statements is possible.

Usage

Create a rollup.config.js configuration file and import the plugin.

import { importManager } from "rollup-plugin-import-manager";

export default {
    input: "src/index.js",
    output: {   
        format: "es",
        name: "myBuild",
        file: "./dist/build.js",
    },
    plugins: [
        importManager({
            units: [
                {
                    file: "**/my-file.js",
                    module: "my-module",
                    actions: [
                        // ...
                    ]
                }
            ]
        })
    ]
}

Then call rollup either via the CLI or the API.

Options

include

Type: String | Array[...String]
Default: null

A minimatch pattern, or array of patterns, which specifies the files in the build the plugin should operate on. By default all files are targeted. On top of that each unit has the possibility to target a specific file.

exclude

Type: String | Array[...String]
Default: null

A minimatch pattern, or array of patterns, which specifies the files in the build the plugin should ignore. By default no files are ignored.

showDiff

Type: String
Default: null

A debugging method. If set to anything other than the string "file" a console output of diff is shown. It is modified a little and looks much like the default output of diff from the GNU diffutils, with colors on top. If set to "file" the whole file with insertions and deletions is shown. Either way it only gets logged if there are any changes at all. If this is not the case, there is another (now following) global debugging method available.

debug

Type: String
Default: null

A debugging method. If more than one source file is involved, this really only is useful in combination with include. It stops the building process by throwing an intentional error and lists all units of the first file, that is getting processed. Even more verbose information about all unit objects can be made accessible by passing the strings verbose, object(s) or import(s) (which one to use doesn't matter).

warnings

Type: Boolean
Default: true

Set to false to prevent displaying warning messages.

units

Type: Object | Array[...Object]
Default: null

This is where the plugin comes to life. Here is the place where units are getting selected, created or removed. It has several options by itself. Units are objects, for multiple units pass an array of objects:


module [option for units]

Type: String
Default: null

Selects a unit by its module name. Each import has a name object. This is constructed from the module. Path information are getting removed. This may look like this:

import foo from "./path/bar.js";

The internal name will be bar.js. And can be matched with: module: "bar.js"
(The matching method is actually a little more generous. You can skip the extension or even bigger parts if you like and if this doesn't lead to multiple matches).

Absolute imports are directly taken as the name attribute. Eg:

import foo from "bar";

The internal name will be bar and can be matched by that name: module: "bar"

hash [option for units]

Type: String
Default: null

Selects a unit by its hash. This is more like an emergency solution. If for any reason it is not possible to match via the module name, this is an alternative. If multiple matches are found the hashes are logged to the console. Also by running a global debugging, the hash can be found.

The hash is generated by the module name and its members and also the filename. If the filename or any of the other properties are changing so is the hash. The build will fail in this case, so no need to worry to overlook it. The matching via module name should nevertheless be preferred.

If the hash option is set, the module option will get ignored.

id [option for units]

Type: Number
Default: null

Internally every unit gets an Id. There are different scopes for the generation:

type scope
es6 1000
dynamic 2000
cjs 3000

The first ES6 Import statement of a file will have the Id 1000, the second 1001 and so forth. For a quick test you can select via Id (if the filename is specified). But actually this is only an internal method to locate the statements. Testing is the only other reason to use it. If one statement is added before the one to match, the Id will change, and there is a good change to not even realize that. You have been warned (and you will get warned again by the plugin if you decide to use it).

If the id option is set, hash and module will get ignored.

file [option for units]

Type: String
Default: null

A minimatch pattern, which specifies the file where the unit is located.

It is always a good idea to set it, even if the files are already limited by include or exclude. The reason for this is, that a the unit is expected to be in the specified file if the value is set and an error is thrown if it doesn't match. Otherwise it will simply be ignored, if a match is not there.

Also for unit creation this is almost always critical. If there are multiple source files, and no file is specified, the fresh import statement will get created in any file, that is processed (and this probably not what you want and also will most likely lead to errors).

However, it is not mandatory.

type [option for units]

Type: String
Default: null

A possibility to specify the unit type. Valid parameters are:

  • es6
  • cjs
  • dynamic

This argument is mainly necessary when creating new units. Without members or default members the type cannot be guessed and needs to be specified. But the argument can also be helpful for selecting modules, if there are overlapping matches across the types. For example if es6 and dynamic import share the same module name. Which is admittedly a very unusual scenario.

createModule [option for units]

Type: String
Default: null

Creates a new module. Every selection method (id, hash, module) will get ignored if this key is passed to a unit. For the value set the module (path).
Eg: createModule: "./path/to/my-module.js"

addCode [option for units]

Type: String
Default: null

This is the manual version of createModule. The value is a string which gets inserted, appended or prepended to the code.
Example

insert [option for units]

Type: String
Default: "bottom"

Additional parameter for createModule/addCode. If set to bottom, the file is analyzed and the import statement is appended after the last found es6 import statement (which is the default behavior if not set). Setting it top top will append the statement on top of the file, directly after the the description if present (this is the default if no other es import statement was found).
Example

append [option for units]

Type: Object
Default: null

Additional parameter for createModule/addCode. Instead of inserting a fresh statement at the top or bottom of the other statements, it is also possible to append it after another import statement. This works by passing a unit as a value.
Example.

prepend [option for units]

Type: Object
Default: null

Additional parameter for createModule/addCode. Instead of inserting a fresh statement at the top or bottom of the other statements, it is also possible to prepend it before another import statement. This works by passing a unit as a value.
Example.

replace [option for units]

Type: Object
Default: null

Additional parameter for createModule/addCode. Instead of somehow adding it around another unit, this keyword replaces the according import statement, which is also passed as a unit object.
Example.

const [option for units]

Type: String
Default: null

Additional parameter for createModule. Only has an effect if cjs or dynamic modules are getting created. const is the declarator type, the value is the variable name for the import.

let [option for units]

Type: String
Default: null

Additional parameter for createModule. Only has an effect if cjs or dynamic modules are getting created. let is the declarator type, the value is the variable name for the import.

var [option for units]

Type: String
Default: null

Additional parameter for createModule. Only has an effect if cjs or dynamic modules are getting created. var is the declarator type, the value is the variable name for the import.

global [option for units]

Type: String
Default: null

Additional parameter for createModule. Only has an effect if cjs or dynamic modules are getting created. If global is set, there is no declarator type and the variable should be declared before this statement. The value is the variable name for the import.

actions [option for units]

Type: Object | Array[...Object]
Default: null

This is the place where the actual manipulation of a unit (and ultimately a statement) is taking place. Several actions/options can be passed, for a singular option, use an object for multiple an array of objects:


debug [option for actions]

Type: Any
Default: null

A debugging method for a specific unit. This also throws an intentional debugging error, which stops the building process. Verbose information about the specific unit are logged to the console. The value is irrelevant. If this is the only action it can be passed as a string: actions: "debug"

select [option for actions]

Type: String
Default: null

Select the part you like to modify. This can be specific part (which also needs the option name to be passed):

  • defaultMember
  • member
  • module

Or the groups:

  • defaultMembers
  • members

Common JS and dynamic imports only have the module available to select.

name [option for actions]

Type: String
Default: null

For the selection of a specific part (defaultMember or member) the name needs to be specified. The name is directly related to the name of a member or default member (without its alias if present).
A member part of { foobar as foo, baz } can be selected with name: "foobar" and name: "baz".

alias [option for actions]

Type: String
Default: null

An option to target an alias of a selected defaultMember or member. If a value is set, this will change or initially set the alias to the this value. Aliases for members can also be removed, in this case the value for alias will be ignored.

rename [option for actions]

Type: String
Default: null

This option is used to rename a selected specific part (defaultMember, member, module). The value is the new name of the selected part.

modType [option for actions]

Type: String
Default: "string"|"raw"

If renaming is done with modType string there are quotation marks set around the input by default, mode raw is not doing that. This can be useful for replacing the module by anything other than a string (which is only valid for cjs and dynamic imports). By default the modType is defined by the existing statement. If it is not a string, type raw is assumed (those are rare occasions).

keepAlias [option for actions]

Type: Boolean
Default: false

This is an extra argument to rename a (default) member. If true, the alias will kept untouched, otherwise it gets overwritten in the renaming process, wether a new alias is set or not.

remove [option for actions]

Type: Any
Default: null

When no part was selected, this removes the entire unit → import statement. The value is irrelevant. If this is the only action it can be passed as a string: actions: "remove". If a part is selected (defaultMembers, members, module or alias) only the according (most specific) part is getting removed.

add [option for actions]

Type: String | Array[...String] Default: null

An additional parameter for defaultMembers or members. It adds one or multiple (default) members to the existing ones. The group has to be selected for the add keyword to have an effect.
Example

Examples

Creating an Import Statement

There are a few options on how to create new import statements. The createModule is working a lot like the the methods for selecting existing statements.

Basic ES6 Statement via createModule

plugins: [
    importManager({
        units: {
            file: "**/my-file.js",
            createModule: "./path/to/foo.js", 
            actions: [
                {
                    "select": "defaultMembers",
                    "add": "bar"
                },
                {
                    "select": "members",
                    "add": "baz as qux"
                }
            ]
        }
    })
]

Without specifying insert or append/prepend the following import statement is getting inserted after the last import statement:

import bar, { baz as qux } from "./path/to/foo.js";

Basic CJS Statement via createModule

CJS Imports are also supported. But this time the type needs to be specified. Also a variable name has to be set. In this example the const foo. (Other declaration types are: let, var and global)

plugins: [
    importManager({
        units: {
            file: "**/my-file.js",
            createModule: "./path/to/foo.js", 
            type: "cjs",
            const: "foo"
        }
    })
]

Result:

const foo = require("./path/to/foo.js");

Basic Dynamic Import Statement via createModule

Almost exactly the same (only the type differs) goes for dynamic imports:

plugins: [
    importManager({
        units: {
            file: "**/my-file.js",
            createModule: "./path/to/foo.js", 
            type: "dynamic",
            let: "foo"
        }
    })
]

Result:

let foo = await import("./path/to/foo.js");

Manual Statement creation via addCode

If this is all to much predetermination, the addCode method is a very handy feature. It allows to inject a string containing the code snippet (most likely an import statement). Which is very different but behaves exactly the same in other regards (inserting, appending/prepending, replacing).

Example:

const customImport = `
let foobar;
import("fs").then(fs => fs.readFileSync("./path/to/foobar.txt"));
`;

plugins: [
    importManager({
        units: {
            file: "**/my-file.js",
            addCode: customImport,
        }
    })
]

Result:

let foobar;
import("fs").then(fs => foobar = fs.readFileSync("./path/to/foobar.txt"));

The addCode value can contain any code you like. You probably should not get too creative. It is designed to add import statements and it gets appended to existing statements.

Creating an Import Statement, appended after another statement:

Example Target module:

import { foo } from "bar";
plugins: [
    importManager({
        units: {
            file: "**/my-file.js",
            createModule: "./path/to/baz.js", 
            actions: {
                "select": "defaultMembers",
                "add": "* as qux"
            },
            append: {
                module: "bar"
            }
        }
    })
]

Result:

import { foo } from "bar";
import * as qux from "./path/to/baz.js";

Creating an Import Statement, prepended before another statement:

Example:

import { foo } from "bar";
plugins: [
    importManager({
        units: {
            file: "**/my-file.js",
            createModule: "./path/to/baz.js", 
            actions: {
                "select": "defaultMembers",
                "add": "* as qux"
            },
            prepend: {
                module: "bar"
            }
        }
    })
]

Creating an Import Statement by replacing another statement:

Example:

import { foo } from "bar";
plugins: [
    importManager({
        units: {
            file: "**/my-file.js",
            createModule: "./path/to/baz.js", 
            actions: {
                "select": "defaultMembers",
                "add": "* as qux"
            },
            replace: {
                module: "bar"
            }
        }
    })
]

Result:

import * as qux from "./path/to/baz.js";

Removing an Import Statement

If we take the example from before:

import { foo } from "bar";
import * as qux from "./path/to/baz.js";

Module bar can be removed like this:

plugins: [
    importManager({
        units: {
            file: "**/my-file.js",
            module: "bar",
            actions: [
                {
                    remove: null,
                }
            ]
        }
    })
]

Shorthand Method

The above can be shortened by a lot as the removal is the only action and the value is not relevant:

plugins: [
    importManager({
        units: {
            file: "**/my-file.js",
            module: "bar",
            actions: "remove"
        }
    })
]

Changing the module

In this example there is a relative path that should be changed to a non relative module.

import foo from "./path/to/bar.js";

This can be achieved like this:

plugins: [
    importManager({
        units: {
            file: "**/my-file.js",
            module: "bar.js",
            actions: {
                select: "module",
                rename: "bar"
            }
        }
    })
]

Result:

import foo from "bar";

Addressing the (default) members

defaultMembers and members are using the exact same methods. It is only important to keep in mind to address default members with select: "defaultMembers" or for a specific one select: "defaultMember"; for members select: "members" and select: "member".

Adding a defaultMember

Example:

import foo from "bar";

A default Member can be added like this:

plugins: [
    importManager({
        units: {
            file: "**/my-file.js",
            module: "bar",
            actions: {
                select: "defaultMembers",
                add: "* as baz"
            }
        }
    })
]

Result:

import foo, * as baz from "bar";

Adding multiple members, again for the same example:

import foo from "bar";
plugins: [
    importManager({
        units: {
            file: "**/my-file.js",
            module: "bar",
            actions: {
                select: "members",
                add: [
                    "baz",
                    "qux"
                ]
            }
        }
    })
]

Result:

import foo, { baz, qux } from "bar";

Removing a member

import { foo, bar, baz } from "qux";
plugins: [
    importManager({
        units: {
            file: "**/my-file.js",
            module: "qux",
            actions: {
                select: "member",
                name: "bar",
                remove: null
            }
        }
    })
]

Result:

import { foo, baz } from "qux";

Removing a group of members

import foo, { bar, baz } from "qux";
plugins: [
    importManager({
        units: {
            file: "**/my-file.js",
            module: "qux",
            actions: {
                select: "members",
                remove: null
            }
        }
    })
]

Result:

import foo from "qux";

Changing a defaultMember name

Example:

import foo from "bar";
plugins: [
    importManager({
        units: {
            file: "**/my-file.js",
            module: "bar",
            actions: {
                select: "defaultMember",
                name: "foo",
                rename: "baz"
            }
        }
    })
]
Renaming but keeping the alias

Example:

import { foo as bar } from "baz";

By default the alias gets overwritten, but this can be prevented.

plugins: [
    importManager({
        units: {
            file: "**/my-file.js",
            module: "bar",
            actions: {
                select: "member",
                name: "foo",
                rename: "qux"
                keepAlias: true
            }
        }
    })
]

Result:

import { qux as bar } from "baz";
Addressing an alias

Aliases can also be addressed (set, renamed and removed). All possibilities demonstrated at once via chaining.

Example:

import { foo as bar, baz as qux, quux } from "quuz";
plugins: [
    importManager({
        units: {
            file: "**/my-file.js",
            module: "bar",
            actions: [
                {
                    select: "member",
                    name: "foo",
                    alias: null,
                    remove: null // redundant **
                },
                {
                    select: "member",
                    name: "baz",
                    alias: "corge"
                },
                {
                    select: "member",
                    name: "quux",
                    alias: "grault"
                },
            ]
        }
    })
]

// ** remove can be set, but if the alias
//    is null, this is redundant
//    (the option is only there to keep the
//    method syntactically consistent)

Result:

import { foo, baz as corge, quux as grault } from "quuz";

General Hints

Chaining

It is possible to address every part of a statement in one go. The order usually doesn't matter. But one part should not be selected twice, which might produce unwanted results. To address every part of a unit with its actions can be as complex as follows.

Example Statement:

import foo, { bar } from "baz";
plugins: [
    importManager({
        units: {
            file: "**/my-file.js",
            module: "baz", 
            actions: [
                {
                    select: "defaultMember",
                    name: "foo",
                    remove: null
                },
                {
                    select: "defaultMembers",
                    add: "qux"
                },
                {
                    select: "member",
                    name: "bar",
                    alias: "quux"
                },
                {
                    select: "members",
                    add: [
                        "quuz",
                        "corge"
                    ] 
                },
                {
                    select: "module",
                    rename: "grault"
                }
            ]
        }
    })
]

Result:

import qux, { bar as quux, quuz, corge } from "grault";

This is in no way an efficient, but an example to show the complexity modifications are allowed to have.

Array and Object shortening

As a general rule, all arrays can be unpacked if only one member is inside. Objects with meaningless values, can be passed as a string, if syntactically allowed. An example is shown here.

Debugging

Show Diff

A general hint while creating a rollup.config.js configuration file: it is useful to enable diff logging to see how the source file is actually getting manipulated.

plugins: [
    importManager({
        showDiff: null,
        units: {
            //...
        }
    })
]

This will log the performed changes to the console.

Debugging Files

To visualize the properties of a specific file, it can help to stop the building process and throw a DebuggingError.

plugins: [
    importManager({
        include: "**/my-file.js"
        debug: null,
        units: {
            //...
        }
    })
]

Or more verbose:

plugins: [
    importManager({
        include: "**/my-file.js"
        debug: "verbose",
        units: {
            //...
        }
    })
]

In both cases the include keyword is also passed. Otherwise the debug key would make the build process stop at the very first file it touches (if there is only one file involved at all, it is not necessary to pass it).

Debugging Units

Also a single unit can be debugged. The keyword can be added to the existing list in an actions object.

plugins: [
    importManager({
        units: {
            file: "**/my-file.js",
            module: "foo",
            actions: {
                select: "defaultMember",
                name: "foo",
                rename: "baz"
                debug: null
            }
        }
    })
]

Or as a shorthand, if it is the only option:

plugins: [
    importManager({
        units: {
            file: "**/my-file.js",
            module: "foo",
            actions: "debug"
        }
    })
]

License

MIT

Copyright (c) 2022, UmamiAppearance