@microsoft/msfs-ts-transformer
v1.0.0
Published
MSFS Typescript Transformer
Readme
MSFS Typescript Transformer
A transformer that optimizes the Javascript code emitted by the Typescript compiler for use in Microsoft Flight Simulator's CoherentGT environment.
Usage
- Install
ts-patch. - Install the transformer package:
npm i @microsoft/msfs-ts-transformer --save-dev - Add the transformer package as a compiler plugin in your project's
tsconfig.json:
{
"compilerOptions": {
"plugins": [
{ "transform": "@microsoft/msfs-ts-transformer" },
],
},
}- Run
ts-patchto inject the transformer as a compiler plugin into Typescript. Refer to thets-patchdocumentation on how to runts-patch.
Optimizations applied
This transformer applies the following optimizations to Javascript code emitted by the Typescript compiler. Note that the transformer has no effect on the type-checking phase, nor does it alter emitted declaration (.d.ts) files.
For-of statements
The transformer converts for-of statements that iterate through array-like objects into classical for loops. For example:
// Typescript:
const array = [0, 1];
for (const element of array) {
// Do stuff.
}
// Emitted JS without transformer:
const array = [0, 1];
for (const element of array) {
// Do stuff.
}
// Emitted JS with transformer:
const array = [0, 1];
for (let _i_1 = 0; _i_1 < array.length; ++_i_1) {
const element = array[_i_1];
// Do stuff.
}
// In cases where the expression of the for-of statement is not an identifier, the value of the expression will be
// memoized into a new const to ensure that the expression is evaluated exactly the same number of times as it would be
// in the un-optimized code:
// Typescript:
for (const element of [0, 1]) {
// Do stuff.
}
// Emitted JS without transformer:
for (const element of [0, 1]) {
// Do stuff.
}
// Emitted JS with transformer:
const _arr_1 = [0, 1];
for (let _i_1 = 0; _i_1 < _arr_1.length; ++_i_1) {
const element = _arr_1[_i_1];
// Do stuff.
}For-of statements iterate through objects using the iterator returned by [Symbol.iterator](). This type of iteration is not well-optimized by CoherentGT's Javascript interpreter or compiler and incurs non-trivial execution time and memory allocation overhead compared to iteration using a classical for loop. As a result, it is always faster to iterate through arrays using classical for loops than for-of statements.
This optimization works for all object types that are considered "array-like", not just the built-in Array type. An "array-like" object is any object that satisfies the following interface:
interface ArrayLike<T> extends Iterable<T> {
readonly [index: number]: T;
readonly length: number;
}This means that the optimization will work for, among other things, the built-in typed arrays (e.g. Float64Array).
In cases where Typescript was unable to resolve the type of the object being iterated by the for-of statement, the optimization will not be applied. This includes cases where the type of the iterated object is the any type:
// Will *not* be optimized.
for (const element of [] as any) {
}Array destructuring
The transformer de-sugars array-destructuring syntax when the object being destructured can be directly indexed instead. For example:
// Typescript:
const array = [0, 1];
const [a, b] = array;
// Emitted JS without transformer:
const array = [0, 1];
const [a, b] = array;
// Emitted JS with transformer:
const array = [0, 1];
const a = array[0], b = array[1];
// Omitted expressions inside the destructuring syntax are supported:
// Typescript:
const array = [0, 1];
const [, b] = array;
// Emitted JS without transformer:
const array = [0, 1];
const [, b] = array;
// Emitted JS with transformer:
const array = [0, 1];
const b = array[1];
// In cases where the right-hand-side (RHS) of the destructuring expression is not an identifier, the value of the
// RHS will be memoized into a new const to ensure that the RHS is evaluated exactly the same number of times as it
// would be in the un-optimized code:
// Typescript:
const [a, b] = [0, 1];
// Emitted JS without transformer:
const [a, b] = [0, 1];
// Emitted JS with transformer:
const _arr_1 = [0, 1];
const a = _arr_1[0], b = _arr_1[1];Behind the scenes, array destructuring relies on iterating through the destructured object using the iterator returned by [Symbol.iterator](). Iterating through objects this way incurs non-trivial execution time and memory allocation overhead. As a result, it is always faster to destructure objects "manually" by directly indexing the object.
This optimization works for all object types that can be indexed by number, not just the built-in Array type. In other words, all objects that satisfy the following interface:
interface Indexable<T> extends Iterable<T> {
readonly [index: number]: T;
}This means that the optimization will work for, among other things, the built-in typed arrays (e.g. Float64Array).
In cases where Typescript was unable to resolve the type of the object being destructured, the optimization will not be applied. This includes cases where the type of the destructured object is the any type:
// Will *not* be optimized.
const [a, b] = [0, 1] as any;Optimization will also not be applied when destructuring uses the rest (...) operator or when any variable on the left-hand side of the destructuring pattern has an initializer:
// Will *not* be optimized.
const array = [0, 1];
const [a, ...b] = array;// Will *not* be optimized.
const array = [0, 1];
const [a = 5, b] = array;In addition to variable declarations, the following patterns are also optimized when using array-destructuring syntax:
Variable assignment:
// Typescript:
const array = [0, 1];
let a: number, b: number;
[a, b] = array;
// Emitted JS without transformer:
const array = [0, 1];
let a, b;
[a, b] = array;
// Emitted JS with transformer:
const array = [0, 1];
let a, b;
a = array[0], b = array[1], array;
// Note that the optimized code always ends with a reference to the destructured object.
// This is so that code that references the result of the original assignment expression still works properly:
// Typescript:
const array = [0, 1];
let a: number, b: number;
const sorted = ([a, b] = array).sort();
// Emitted JS without transformer:
const array = [0, 1];
let a, b;
const sorted = ([a, b] = array).sort();
// Emitted JS with transformer:
const array = [0, 1];
let a, b;
const sorted = (a = array[0], b = array[1], array).sort();Parameter declaration:
// Typescript:
function foo([a, b]: number[]): void {
// Do stuff.
}
// Emitted JS without transformer:
function foo([a, b]) {
// Do stuff.
}
// Emitted JS with transformer:
function foo(_arrArg_1) {
let a = _arrArg_1[0], b = _arrArg_1[1];
// Do stuff.
}
// NOTE: If a parameter references one of the variables bound by the destructuring syntax in its initializer,
// then the destructured parameter will not be optimized:
// Will *not* be optimized.
function foo([a, b]: number[], c = a): void {
// Do stuff.
}For-of statement initializer:
// Typescript:
for (const [key, value] of new Map()) {
// Do stuff.
}
// Emitted JS without transformer:
for (const [key, value] of new Map()) {
// Do stuff.
}
// Emitted JS with transformer:
for (const _arr_1 of new Map()) {
const key = _arr_1[0], value = _arr_1[1];
// Do stuff.
}