Search code examples
javascripttypescriptasync-await

TypeScript keeps transpiling away async/await even with target: es2017


Consider this test script.

#!/bin/bash -ex

rm -rf ts-async-await
mkdir ts-async-await
cd ts-async-await
npm init -y &> /dev/null
npm i --save-dev [email protected] &> /dev/null
echo "async function x() { return 1; }" > test.ts
echo '{ "compilerOptions": { "target": "es2017" } }' > tsconfig.json
npx tsc --version
npx tsc --showConfig
npx tsc test.ts
cat tsconfig.json
cat test.js

I'm creating an empty directory with a single one-liner TypeScript file, containing async/await.

async function x() { return 1; }

Then, I'm creating a tsconfig.json where the compilerOptions target is set to es2017.

{ "compilerOptions": { "target": "es2017" } }

tsc --showConfig shows:

{
    "compilerOptions": {
        "target": "es2017"
    },
    "files": [
        "./test.ts"
    ]
}

Then, I'm transpiling my test.ts file to test.js.

Expected: This one-liner should transpile cleanly to a one-liner JS file, just like it does in the TypeScript playground https://www.typescriptlang.org/play?#code/IYZwngdgxgBAZgV2gFwJYHsIwB4AoCUMA3jAE4CmyCpWAjANwwC+QA

Actual: TypeScript generates 40 lines of code, downgrading my async/await code into non-async/await code with the __awaiter and __generator helpers.

How do I make my sample TypeScript project do what the playground does? Why isn't target doing the right thing for me here?

Here's the full log of the test output:

+ rm -rf ts-async-await
+ mkdir ts-async-await
+ cd ts-async-await
+ npm init -y
+ npm i --save-dev [email protected]
+ echo 'async function x() { return 1; }'
+ echo '{ "compilerOptions": { "target": "es2017" } }'
+ npx tsc --version
Version 4.7.4
+ npx tsc --showConfig
{
    "compilerOptions": {
        "target": "es2017"
    },
    "files": [
        "./test.ts"
    ]
}
+ npx tsc test.ts
+ cat tsconfig.json
{ "compilerOptions": { "target": "es2017" } }
+ cat test.js
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
function x() {
    return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) {
        return [2 /*return*/, 1];
    }); });
}

Solution

  • Typescript either runs as a CLI invocation on a file, or on a config that specifies the entry point, but not both (try running npx tsc test.js --p tsconfig.js and look at the output you get).

    As such, this will work just fine:

    > npx tsc test.ts --target esnext
    

    and this will also work just fine:

    > npx tsc
    

    or with explicit reference to the config file:

    > npx tsc --p tsconfig.json
    

    But because you're calling npx tsc test.ts in your script, typescript is going to completely ignore your config file and just run what you told it to do purely in your CLI statement.