Package Exports
- rollup-plugin-import-manager
Readme
rollup-plugin-import-manager
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
- How it works
- Usage
- Options
- Examples
- Creating an Import Statement
- Basic ES6 Statement via createModule
- Basic CJS Statement via createModule
- Basic Dynamic Import Statement via createModule
- Manual Statement creation via addCode
- Creating an Import Statement, appended after another statement
- Creating an Import Statement, prepended before another statement
- Creating an Import Statement by replacing another statement
- Removing an Import Statement
- Changing the module
- Addressing the (default) members
- Creating an Import Statement
- General Hints
- Debugging
- License
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.
(The actual work is done by the outsourced program ImportManager which can by used independently from this rollup-plugin.)
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
Copyright (c) 2022, UmamiAppearance