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.