CommonJS →  Typescript Migration Tips

CommonJS → Typescript Migration Tips


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 export
class Example {
	public integrationConfigurationRepository: IntegrationConfigurationRepository;

    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;

    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



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.
	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;

      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;
    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
  ) {}