Downleveling is TypeScript's term for transpiling to an older version of JavaScript. This flag is to enable support for a more accurate implementation of how modern JavaScript iterates through new concepts in older JavaScript runtimes.
ECMAScript 6 added several new iteration primitives: the for / of loop (for (el of arr)), Array spread ([a, ...b]), argument spread (fn(...args)), and Symbol.iterator. downlevelIteration allows for these iteration primitives to be used more accurately in ES5 environments if a Symbol.iterator implementation is present.
Example: Effects on for / of
With this TypeScript code:
const str = "Hello!";
for (const s of str) {
console.log(s);
}const str = "Hello!";
for (const s of str) {
console.log(s);
}Without downlevelIteration enabled, a for / of loop on any object is downleveled to a traditional for loop:
// @target: ES5
// @showEmit
const str = "Hello!";
for (const s of str) {
console.log(s);
}// @target: ES5
// @showEmit
const str = "Hello!";
for (const s of str) {
console.log(s);
}This is often what people expect, but it's not 100% compliant with ECMAScript iteration protocol. Certain strings, such as emoji (😜), have a .length of 2 (or even more!), but should iterate as 1 unit in a for-of loop. See this blog post by Jonathan New for a longer explanation.
When downlevelIteration is enabled, TypeScript will use a helper function that checks for a Symbol.iterator implementation (either native or polyfill). If this implementation is missing, you'll fall back to index-based iteration.
// @target: ES5
// @downlevelIteration
// @showEmit
const str = "Hello!";
for (const s of str) {
console.log(s);
}// @target: ES5
// @downlevelIteration
// @showEmit
const str = "Hello!";
for (const s of str) {
console.log(s);
}You can use tslib via importHelpers to reduce the amount of inline JavaScript too:
// @target: ES5
// @downlevelIteration
// @importHelpers
// @showEmit
const str = "Hello!";
for (const s of str) {
console.log(s);
}// @target: ES5
// @downlevelIteration
// @importHelpers
// @showEmit
const str = "Hello!";
for (const s of str) {
console.log(s);
}Note: enabling downlevelIteration does not improve compliance if Symbol.iterator is not present in the runtime.
Example: Effects on Array Spreads
This is an array spread:
// Make a new array who elements are 1 followed by the elements of arr2
const arr = [1, ...arr2];// Make a new array who elements are 1 followed by the elements of arr2
const arr = [1, ...arr2];Based on the description, it sounds easy to downlevel to ES5:
// The same, right?
const arr = [1].concat(arr2);// The same, right?
const arr = [1].concat(arr2);However, this is observably different in certain rare cases. For example, if an array has a "hole" in it, the missing index will create an own property if spreaded, but will not if built using concat:
// Make an array where the '1' element is missing
let missing = [0, , 1];
let spreaded = [...missing];
let concated = [].concat(missing);
// true
"1" in spreaded;
// false
"1" in concated;// Make an array where the '1' element is missing
let missing = [0, , 1];
let spreaded = [...missing];
let concated = [].concat(missing);
// true
"1" in spreaded;
// false
"1" in concated;Just as with for / of, downlevelIteration will use Symbol.iterator (if present) to more accurately emulate ES 6 behavior.