At least one of my experiments has the following code:
import path from "path";
import {
ModuleKind,
ModuleResolutionKind,
Project,
type ProjectOptions,
ScriptTarget,
SourceFile,
TypeNode,
StructureKind,
} from "ts-morph";
ImportDeclarationImpl works to support this, but it can be messy to organize several of these. This is especially true with the namedImports property, which uses ImportSpecifierImpl instances.
To make this much easier to deal with, I provide an ImportManager class. There’s also an equivalent for export declarations in the ExportManager class.
At its heart, the ImportManager class has three key parts:
.ts or .mts..js and .mjs if someone asks for it.addImports(context: AddImportContext) method, which allows you to define what you’re importing.getDeclarations() method, which returns ImportDeclarationImpl[] for you to add as statements to a SourceFileImpl for your source file.The AddImportContext interface is very simple:
pathToImportedModule, an absolute path to the module you’re importing from.
isPackageImport set to true..ts or .mts..js and .mjs if someone asks for it.isPackageImport
pathToImportedModule is a package like "path".importNames, the actual names you want to import from the module. (If you want import *, pass in an empty array.)isDefaultImport
importNames represents a default import alias, in which case it must be an array of one stringisTypeOnly
importNames represents only typesimport path from "path";
import {
type AddImportContext,
ImportManager,
SourceFileImpl,
} from "./source/exports.js";
import projectDir from "./constants.js";
const imports = new ImportManager(
path.join(projectDir, "./dist/myFile.ts")
);
imports.addImports({
pathToImportedModule: "path",
isPackageImport: true,
importNames: ["path"],
isDefaultImport: true,
isTypeOnly: false
});
imports.addImports({
pathToImportedModule: path.join(projectDir, "./source/exports.ts"),
isPackageImport: false,
importNames: [
"ImportManager",
"SourceFileImpl",
],
isDefaultImport: false,
isTypeOnly: false
});
imports.addImports({
pathToImportedModule: path.join(projectDir, "./source/exports.ts"),
isPackageImport: false,
importNames: [
"AddImportContext",
],
isDefaultImport: false,
isTypeOnly: true
});
imports.addImports({
pathToImportedModule: path.join(projectDir, "./constants.ts"),
isPackageImport: false,
importNames: [
"projectDir",
],
isDefaultImport: true,
isTypeOnly: true
});
const sourceFile = new SourceFileImpl();
sourceFile.statements.push(...imports.getDeclarations());
The ExportManager class has a very similar design, and is for creating an exports file. Its three main components are:
addExports(context: AddExportContext) method, which allows you to define what you’re importing.getDeclarations() method, which returns ExportDeclarationImpl[] for you to add as statements to a SourceFileImpl for your source file.I use ExportManager heavily to create an exports.ts file, which then Rollup can use to create a bundle of the ts-morph-structures code. This also allows me to declare what is publicly available, versus what is internal code.
The AddExportContext interface is also simple:
pathToExportedModule, an absolute path to the module you’re importing from.exportNames, the actual names you want to import from the module. (If you want export *, pass in an empty array.)isDefaultImport
importNames represents a default import alias, in which case it must be an array of one stringisTypeOnly
importNames represents only typesThe API is similar enough to ImportManager that I shouldn’t need to go into any more detail.
One useful aspect during development, I found, was passing in publicExports.absolutePathToExportFile in as the pathToImportedModule of the AddImportContext. In other words, once I have an ExportManager instance, I can use the absolute path as a starting point for imports.
Similarly, you could pass an import manager’s absolute path in to an export manager’s AddExportContext to define a value you export.