Open edX TypeScript Best Practices

Open edX TypeScript Best Practices

Overview

In the past couple years, we have made a push to add TypeScript support to open edX environments, seeing the benefits of augmenting our front-end code with TypeScript’s strong typing. However, the process of migrating Javascript to TypeScript is an ongoing one, and many of us are still adapting to judiciously using it in the process of doing our normal maintenance and feature development. This guide aims to compile our best practices around migrating and using TypeScript within the open edX ecosystem.

 

Adding TypeScript Support

As it currently stands, not all front-end repositories have TypeScript support. To add support, it is recommended to leverage some existing libraries within the open edX ecosystem.

Using typescript-config

Repository:https://github.com/openedx/typescript-config

A tsconfig.json is required at the root of TypeScript projects, and this library provides a template with recommended compiler options and flags for using TypeScript the open edX ecosystem.

Steps:

  • Add @edx/typescript-config to project’s package.json devDependencies

  • Only if not using frontend-build: Add typescript library and any variants of libraries with TypeScript support

  • Create tsconfig.json file at the root of the project

    • Include clause "extends": "@edx/typescript-config" to pull in template

    • Add references to the root directory, output directory, and directories to include/exclude in TypeScript compilation

Using frontend-build

Repository:https://github.com/openedx/frontend-build

The frontend-build package provides an out-of-the-box setup for frontend projects testing, linting, building, and development server. As of v14.0.0 it comes with TypeScript libraries.

Steps:

Examples

[frontend-app-enterprise-public-catalog] feat: add typescript support #491

Migrating Existing code to TypeScript

Basic Migration Process

Once TypeScript support is added to a project (see above), migrating a Javascript file to TypeScript is simply a matter of changing its filename extension to its TypeScript counterpart (.js → .ts, .jsx → .tsx).

However, a freshly converted Javascript file will often have TypeScript errors that will need to be addressed in order for the project to compile. See Common Issues below for examples of common failures.

Deciding what to migrate

Beyond the decision of which Javascript files to migrate to TypeScript, there is also the matter of which types, interfaces, function and variable types should be explicitly laid out. Generally it doesn’t make sense to convert and type everything all at once, and so we’ve established some recommendations here:

https://github.com/openedx/frontend-app-admin-portal/blob/dfe6569e05386f0254983b9494d65b325b1aad69/docs/decisions/0011-typescript-migration-over-time.rst

TypeScript Resources

Learn Typescript: Free Tutorial

Common Issues

error TS2686: 'React' refers to a UMD global, but the current file is a module.

Make sure to import React, even if it isn't referenced in the file. This might be mostly related to test files.

error ts(2339): Property ‘x' does not exist on type xType | undefined

This can happen when trying to do a destructuring initialization off of a nested nullable member of a type/interface.

Example where this error would be seen:

type xPayload = { value: string }; type xBundle = { payload?: xPayload }; // Payload member is nullable const bundle = { payload: { value: 'stuff' } } as xBundle; // Error scenario const { payload: { value } } = bundle; // 'value' would have error message: "Property 'value' does not exist on type 'xPayload | undefined'" // Working scenario #1 const workingValue = bundle?.payload?.value; // Working scenario #2 const innerPayload = bundle.payload as xPayload; // Specifying that bundle.payload is guaranteed to have values here const { value: workingValue2 } = innerPayload;