Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/suchipi/transform-imports
Tools that make it easy to codemod imports/requires in your JS
https://github.com/suchipi/transform-imports
Last synced: about 1 month ago
JSON representation
Tools that make it easy to codemod imports/requires in your JS
- Host: GitHub
- URL: https://github.com/suchipi/transform-imports
- Owner: suchipi
- Created: 2018-02-21T07:12:53.000Z (almost 7 years ago)
- Default Branch: master
- Last Pushed: 2024-05-07T14:25:47.000Z (9 months ago)
- Last Synced: 2024-12-22T02:02:32.811Z (about 2 months ago)
- Language: JavaScript
- Size: 1.28 MB
- Stars: 55
- Watchers: 4
- Forks: 5
- Open Issues: 9
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
- awesome-codemods - transform-imports - Tools that make it easy to codemod imports/requires in your JS. (JavaScript)
README
# transform-imports
An API that makes it easy to transform imports and require calls.
## Usage
```js
import transformImports from "transform-imports";const code = `
import Foo from "foo";
import Bar from "bar";
`;const newCode = transformImports(code, (importDefs) => {
importDefs.forEach((importDef) => {
if (importDef.source === "bar") {
importDef.source = "something-new";
}
});
});console.log(newCode);
// Logs:
// import Foo from "foo";
// import Foo from "something-new";
````transformImports` calls its callback with an Array of `ImportDefinition` objects, which have this shape:
```ts
class ImportDefinition {
variableName: ?string;
source: ?string;
importedExport: {
name: string,
isImportedAsCJS: boolean,
};
kind: "value" | "type" | "typeof",
isDynamicImport: boolean,
path: NodePath;remove(): void;
fork(?{ insert: "before" | "after" }): void;
}
```You can change the values of the properties on the `ImportDefinition` or call the methods on it to change the underlying import/require statement.
Each `ImportDefinition` is associated with a single import; that is, `import { red, blue } from "colors"` contains two `ImportDefinitions` (one for `red` and one for `blue`).
Here's an example of what some different imports/requires parse into:
```js
import * as React from "react";
// Becomes...
ImportDefinition {
variableName: "React",
source: "react",
importedExport: {
name: "*",
isImportedAsCJS: false,
},
kind: "value",
isDynamicImport: false,
};import traverse from "babel-traverse";
// Becomes...
ImportDefinition {
variableName: "traverse",
source: "babel-traverse",
importedExport: {
name: "default",
isImportedAsCJS: false,
},
kind: "value",
isDynamicImport: false,
};import MyClass, { SOME_CONSTANT } from "my-library";
// Becomes...
ImportDefinition {
variableName: "MyClass",
source: "my-library",
importedExport: {
name: "default",
isImportedAsCJS: false,
},
kind: "value",
isDynamicImport: false,
};
// and
ImportDefinition {
variableName: "SOME_CONSTANT",
source: "my-library",
importedExport: {
name: "SOME_CONSTANT",
isImportedAsCJS: false,
},
kind: "value",
isDynamicImport: false,
};const PropTypes = require("prop-types");
// Becomes...
ImportDefinition {
variableName: "PropTypes",
source: "prop-types",
importedExport: {
name: "*",
isImportedAsCJS: true,
},
kind: "value",
isDynamicImport: false,
};const { darken, lighten } = require("polished");
// Becomes...
ImportDefinition {
variableName: "darken",
source: "polished",
importedExport: {
name: "darken",
isImportedAsCJS: true,
},
kind: "value",
isDynamicImport: false,
};
// and
ImportDefinition {
variableName: "lighten",
source: "polished",
importedExport: {
name: "lighten",
isImportedAsCJS: true,
},
kind: "value",
isDynamicImport: false,
};import type {Node} from "./node";
// Becomes...
ImportDefinition {
variableName: "Node",
source: "./node",
importedExport: {
name: "Node",
isImportedAsCJS: false,
},
kind: "type",
isDynamicImport: false,
};import "something";
// Becomes...
ImportDefinition {
variableName: null,
source: "something",
importedExport: {
name: "*",
isImportedAsCJS: false,
},
kind: "value",
isDynamicImport: false,
};import("something").then((somethingModule) => somethingModule.default());
// Becomes...
ImportDefinition {
variableName: null,
source: "something",
importedExport: {
name: "*",
isImportedAsCJS: false,
},
kind: "value",
isDynamicImport: true,
};
```Here's some more in-depth documentation explaining what each property means/does. In each example, `importDef` refers to an `ImportDefinition` object as passed to the `transformImports` callback.
### `variableName`
This refers to the name of the variable that was created by the import. For example, in `import MyThing from "./overThere";`, it would be `MyThing`.
If you change this string, then the variable name in the code will change. So doing this:
```js
importDef.variableName = "MyOtherThing";
```would change the code into:
```js
import MyOtherThing from "./overThere";
```When dealing with named imports, this only refers to the name of the local variable created. To also change the name of the variable that is being imported, change `importedExport.name`:
```js
// Given this code:
import { red } from "./colors";// Doing this:
importDef.variableName = "blue";// Would only change the code to this:
import { red as blue } from "./colors";// But doing this:
importDef.variableName = "blue";
importDef.importedExport.name = "blue";// Would change the code to this:
import { blue } from "blue";
```Changing `variableName` does not rename references to the variable:
```js
// Given this code:
import MyThing from "./overThere";MyThing.isCool();
// Doing this:
importDef.variableName = "MyOtherThing";// Would change the code to this:
import MyOtherThing from "./overThere";MyThing.isCool();
```If you want to also change references to the variable, you can use the babel Scope object found at `importDef.path.scope`.
> NOTE: An `ImportDefinition` referring to a dynamic import (`import("foo")`) or bare import (`import "foo";`) has no `variableName`, and attempting to set the `variableName` will throw an Error.
### `source`
This refers to the string indicating which file or package this import was obtained from. So for `const Theme = require("../theme")`, it would be `"../theme"`.
If you change this string, then the import's source will change.
```js
// Given this code:
import Something from "../somewhere";// Doing this:
importDef.source = "../somewhereElse";// Would change the code to this:
import Something from "../somewhereElse";
```Note that the source string always reflects the source string found in the source code. If you would like to find the absolute path to a given imported file, you will need to use another package, like `eslint-import-resolver-node` or `eslint-import-resolver-webpack`:
```js
const fileLocation = "/Users/suchipi/Code/my-project/something.js"; // Get this from babel, jscodeshift, etcconst { resolve } = require("eslint-import-resolver-webpack");
const { found, path } = resolve(importDef.source, fileLocation, {
/* import resolver config goes here */
});
```For more info, see [the eslint-plugin-import Resolver spec](https://github.com/benmosher/eslint-plugin-import/blob/master/resolvers/README.md).
### `importedExport.name`
This refers to the name of the export that is being imported into the file where the code associated with this `ImportDefinition` is.
For example, given two files `one.js` and `two.js`:
```js
// one.jsconst Chunky = "chunky";
const Bacon = "bacon";export { Chunky, Bacon };
``````js
// two.jsimport { Chunky as BaconStyle } from "./one";
console.log(BaconStyle);
```The value of `importedExport.name` for the import in `two.js` would be `Chunky`.
If you change this string, then which export you pull in from `two.js` would change:
```js
// If you did this:
importDef.importedExport.name = "default";// Then it would change into a default import:
import BaconStyle from "./one";// If you did this:
importDef.importedExport.name = "*";// Then it would change into a namespace import:
import * as BaconStyle from "./one";// If you did anything else:
importDef.importedExport.name = "Bacon";// Then it would change which named export is imported:
import { Bacon as BaconStyle } from "./one";
```> NOTE: Attempting to change `importedExport.name` on an `ImportDefinition` referring to a dynamic import (`import("foo")`) or bare import (`import "foo";`) will throw an Error.
### `importedExport.isImportedAsCJS`
This refers to the whether the import is using CommonJS or not. Changing this to `true` will change an import statement into a require call, and changing this to `false` will change a require call into an import statement.
```js
// Given this code:
import Default from "somewhere";
import * as Star from "somewhere-else";
import { Named } from "somewhere-else-else";// If you did this to each ImportDefinition:
importDef.importedExport.isImportedAsCJS = true;// Then the code would change into this:
const { default: Default } = require("somewhere");
const Star = require("somewhere-else");
const { Named } = require("somewhere-else-else");// Likewise, given this code:
const All = require("all");
const { Some, Members } = require("members");// If you did this to each ImportDefinition:
importDef.importedExport.isImportedAsCJS = false;// Then the code would change into this:
import * as All from "all";
import { Some, Members } from "members";
```Note that in `const Foo = require("foo")`, `importedExport.name` is `"*"`, not `"default"` like might be expected. This is because `"*"` is the most accurate representation of the way CommonJS imports work in most compilation pipelines.
> NOTE: Attempting to change `importedExport.isImportedAsCJS` on an `ImportDefinition` referring to a dynamic import (`import("foo")`) or bare import (`import "foo";`) will throw an Error.
### `kind`
This value indicates whether the import is a flow type/typeof import, or if it is a normal import (a value import). Possible values are `"type"`, `"typeof"`, and `"value"`. Changing this value will change the import into a type, typeof, or value import.
```js
// Given this code:
import Default from "somewhere";
import * as Star from "somewhere-else";
import { Named } from "somewhere-else-else";// If you did this to each ImportDefinition:
importDef.kind = "type";// Then the code would change into this:
import type Default from "somewhere";
import type * as Star from "somewhere-else";
import type { Named } from "somewhere-else-else";// Likewise, if you did this to each ImportDefinition:
importDef.kind = "typeof";// Then the code would change into this:
import typeof Default from "somewhere";
import typeof * as Star from "somewhere-else";
import typeof { Named } from "somewhere-else-else";
```If you change a require call's kind into type or typeof, then it will turn into an import statement according to the rules established when changing `importedExport.isImportedAsCJS`:
```js
// Given this code:
const All = require("all");
const { Some, Members } = require("members");// If you did this to each ImportDefinition:
importDef.kind = "type";// Then the code would change into this:
import type * as All from "all";
import type { Some, Members } from "members";
```> NOTE: Attempting to change `kind` on an `ImportDefinition` referring to a dynamic import (`import("foo")`) or bare import (`import "foo";`) will throw an Error.
### `isDynamicImport`
Whether the import is a dynamic import (`import("something")`).
This property is not writable.
### `remove()`
Calling this method removes the import specifier associated with this `ImportDefinition` from the source code.
```js
// Given this code:
import * as Everything from "everything";Everything.isAwesome();
// Doing this:
importDef.remove();// Would change the code to this:
Everything.isAwesome();
```If the `ImportDefinition` refers to an import that is not alone in its import/require statement, then only it will be removed:
```js
// Given this code:
import { One, Two } from "every-number";console.log(One < Two);
// Doing this to the ImportDefinition referring to `One`:
importDef.remove();// Would change the code to this:
import { Two } from "every-number";console.log(One < Two);
```> NOTE: Attempting to call `remove()` on an `ImportDefinition` referring to a dynamic import (`import()`) will throw an Error.
### `fork()`
Calling this method will split an import specifier away from its related specifiers into its own statement:
```js
// Given this code:
import { One, Two } from "every-number";// Doing this to the ImportDefinition referring to `One`:
importDef.fork();// Would change the code to this:
import { Two } from "every-number";
import { One } from "every-number";
```Note that the new import/require declaration is inserted _after_ the existing one by default. To change this behavior, call `fork` with `{ insert: "before" }`:
```js
// Given this code:
import { One, Two } from "every-number";// Doing this to the ImportDefinition referring to `One`:
importDef.fork({ insert: "before" });// Would change the code to this:
import { One } from "every-number";
import { Two } from "every-number";
````fork()` is automatically called when you change properties of an `ImportDefinition` such that it cannot remain with its sibling specifiers in the same statement anymore:
```js
// Given this code:
import { One, Two } from "every-number";// Doing this to the ImportDefinition referring to `One`:
importDef.source = "most-numbers";// Would change the code to this:
import { Two } from "every-number";
import { One } from "most-numbers";
```## Usage with jscodeshift
`transform-imports` uses [recast](https://github.com/benjamn/recast), so untouched source styling is preserved. This means that it's suitable for usage in jscodeshift:
```js
// An example jscodeshift transform module that turns
// import React, { PropTypes } from "react";
// into
// import * as React from "react";
// import PropTypes from "prop-types";
const transformImports = require("transform-imports");module.exports = function (fileInfo, api, options) {
return transformImports(fileInfo.source, (importDefs) => {
importDefs.forEach((importDef) => {
if (importDef.source !== "react") {
return;
}if (
importDef.importedExport.name === "default" &&
importDef.importedExport.isImportedAsCJS === false
) {
importDef.fork({ insert: "before" });
importDef.importedExport.name = "*";
}if (
importDef.variableName === "PropTypes" &&
importDef.importedExport.name === "PropTypes"
) {
importDef.importedExport.name = "default";
importDef.source = "prop-types";
}
});
});
};
```