Type Extraction from CommonJSWhen using a CommonJS export where there are named exports and a default class exportWhen using a CommonJS factory methodImporting the type without the actaul codeDefining reusable types within a CommonJS filesDefining dependencies type when using a constructor based dependency injection in CommonJSExporting the Type of a CommonJS class with the class itselfUsing Typescripts ability to declare a property inside the constructor Why is this useful?
Type Extraction from CommonJS
When using a CommonJS export where there are named exports and a default class export
To access the type of the class instance and not the constructor, you must use the
prototype
named exportclass Example {
public integrationConfigurationRepository: IntegrationConfigurationRepository;
constructor(
private companyService: typeof CompanyService,
private projectService: typeof ProjectService,
private tokenService: InstanceType<typeof TokenService>,
private urlGenerator: InstanceType<typeof UrlGenerator>,
) {
this.integrationConfigurationRepository = new IntegrationConfigurationRepository();
}
{
When using a CommonJS factory method
To access the type of the instance the factory creates, you must use
ReturnType<typeof Logger>
where Logger is the factory method. class Example {
public integrationConfigurationRepository: IntegrationConfigurationRepository;
constructor(
private logger: ReturnType<typeof Logger>,
private notificationService: ReturnType<typeof NotificationService>,
) {
this.integrationConfigurationRepository = new IntegrationConfigurationRepository();
}
{
Importing the type without the actaul code
When using the
import
syntax you can import the type ONLY by using the type
keyword after import
import type { UserRef } from '../../../generic_company/domain/common';
When the typescript gets compiled to JS, it will NOT import the code from that import, and will instead ignore the import completely since JS cannot use types. It WILL still include the JSDoc typings, allowing you to have code completion without increasing the load time of the file
Defining reusable types within a CommonJS files
To defined a reusable type definition within a CommonJS file, you must use
@typedef
and define the type as if it's a normal JSDoc comment. When importing from that CommonJS file in Typescript, it will let you import the type definition explicitly.
/**
* @typedef {import('generic_company/domain/company/Company') & import('lib/entities/Company.entity').Company} CompanyEntity Company Schema & Company Entity TypeDef
*/
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* @typedef {DEFINE_TYPE_HERE} EXPORTED_TYPE_NAME
*/
Defining dependencies type when using a constructor based dependency injection in CommonJS
/**
* @this
* @param {import('generic_company/infrastructure/repositories/company/CompanyRepository.js')} companyRepository
*/
constructor(companyRepository) {
this.companyRepository = companyRepository;
}
Exporting the Type of a CommonJS class with the class itself
This is a nice pattern to use, because it allows you to more easily import the type without the class in TS. You don't need to use typeof all over your codebase, and the
tsc
has an easier time with it. /////////////////////////////////////////////////////////////////////
// lib/CompanyRepository.js /////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
/**
* @typedef {typeof CompanyRepository} CompanyRepositoryI
*/
module.exports = CompanyRepository;
/////////////////////////////////////////////////////////////////////
// index.ts /////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
import { CompanyRepository, CompanyRepositoryI } from './lib/CompanyRepository'
Using Typescripts ability to declare a property inside the constructor
Why is this useful?
This allows you to eaily dependency inject any of the required dependencies of the class while keeping the type information for the newly defined property. While that might seem like a decent trade-off already the other aspect is the ability to inject mock dependencies during Unit Tests to have full control of the dependencies when testing this classes functionality.
CommonJS
private assignmentService: typeof AssignmentService;
private userService: import('lib/services/user/UserService');
private legacyUserService: import('generic_company/app/user/UserService');
private legacyCompanyService: import('generic_company/app/company/CompanyService');
private projectService: import('generic_company/app/project/ProjectService');
private integrationAttach: AttachDocuments;
private tokenService: import('generic_company/app/project/TokenService');
private notificationService: import('lib/services/NotificationService');
private claimService: typeof ClaimService;
constructor(
{
userService = UserService(),
legacyUserService = UserFactory.userService(),
legacyCompanyService = CompanyFactory.companyService(),
projectService = ProjectFactory.projectService(),
integrationAttach = new AttachDocuments(),
tokenService = TokenService(),
notificationService = LegacyFactory.notificationService(),
claimService = ClaimService,
assignmentService = AssignmentService,
} = {
userService: UserService(),
legacyUserService: UserFactory.userService(),
legacyCompanyService: CompanyFactory.companyService(),
projectService: ProjectFactory.projectService(),
integrationAttach: new AttachDocuments(),
tokenService: TokenService(),
notificationService: LegacyFactory.notificationService(),
claimService: ClaimService,
assignmentService: AssignmentService,
}
) {
this.assignmentService = assignmentService;
this.userService = userService;
this.legacyUserService = legacyUserService;
this.legacyCompanyService = legacyCompanyService;
this.projectService = projectService;
this.integrationAttach = integrationAttach;
this.tokenService = tokenService;
this.notificationService = notificationService;
this.claimService = claimService;
}
VS
Typescript
constructor(
private userService = UserService(),
private legacyUserService = UserFactory.userService(),
private legacyCompanyService = CompanyFactory.companyService(),
private projectService = ProjectFactory.projectService(),
private integrationAttach = new AttachDocuments(),
private tokenService = TokenService(),
private notificationService = LegacyFactory.notificationService(),
private claimService = ClaimService,
private assignmentService = AssignmentService
) {}