13

Looks like optional chaining has landed. Here's an example

What I can't figure out is how to get TS to compile it properly. I'm not getting any syntax errors in my project, but this:

let imageFileId = (await db.query(sql`select id from image_files where sha256=${sha256}`))[0]?.id;

Is being output as:

let imageFileId = (await db.query(mysql3_1.sql `select id from image_files where sha256=${sha256}`))[0]?.id;

Which won't run until we get native support in Node.

Here's my tsconfig:

{
    "compilerOptions": {
        "strict": true,
        "importHelpers": false,
        "inlineSources": true,
        "noEmitOnError": true,
        "pretty": true,
        "module": "commonjs",
        "noImplicitAny": true,
        "suppressImplicitAnyIndexErrors": false,
        "removeComments": false,
        "preserveConstEnums": false,
        "sourceMap": true,
        "lib": ["es2018"],
        "skipLibCheck": false,
        "outDir": "dist",
        "target": "esnext",
        "declaration": false,
        "resolveJsonModule": true,
        "esModuleInterop": false,
        "moduleResolution": "node",
        "allowSyntheticDefaultImports": true,
        "baseUrl": ".",
        "paths": {
            "*": ["src/*"]
        },
        "noEmit": false
    },
    "files": [
        "src/index"
    ],
    "include": [
        "src/**/*.d.ts"
    ]
}

Is there some other option I need to enable to compile the ?. operator?

Please note I'm not using Babel and I don't want to bring it into the picture.

mpen
  • 237,624
  • 230
  • 766
  • 1,119

2 Answers2

19

The problem is you are targeting esnext this will tell the compiler to output all language features as is without any transpilation. Set the language to es2020 (or below) and ?. and ?? will get transpiled to compatible code:

(async function () {
    let imageFileId = (await db.query(sql`select id from image_files where sha256=${sha256}`))[0]?.id;
})()

Playground Link

There is no fine-grained control over which language features get transpiled and which don't do you have to pick a version as a whole unfortunately,

Titian Cernicova-Dragomir
  • 157,784
  • 15
  • 245
  • 242
  • 6
    Problem with that is that I'm also using BigInts which have been supported by node for awhile now :-( "TS2737: BigInt literals are not available when targeting lower than ESNext" – mpen Nov 06 '19 at 08:21
  • @mpen I was just editing my answer to address that. There unfortunately is no way to control which specific language features get compiled and which don't – Titian Cernicova-Dragomir Nov 06 '19 at 08:23
  • @mpen Babel is better at supporting pick and choose with regard to language features.. – Titian Cernicova-Dragomir Nov 06 '19 at 08:24
  • 6
    I'm having the same problem again after upgrading to **TypeScript 3.8**. I think you should actually target `es2019` in order to "polyfill" optional chaining. – icl7126 Mar 02 '20 at 12:15
  • 3
    @icl7126 is right. For **TS 3.8.3** you have to set `ES2019` as a target – Nikita Cherednichenko Mar 16 '20 at 20:09
  • 1
    setting `target: ES2019` is still true for **TS 3.9.5** – I'll Eat My Hat Jun 13 '20 at 13:03
  • @I'llEatMyHat changed the answer to reflect that any version below es2020 will work, it's just esnext that preserves the original operators. – Titian Cernicova-Dragomir Jun 13 '20 at 13:31
  • I mean that ES2020 doesn't work for me though. Only ES2019 and below does. ES2020 results in the error `You may need an additional loader to handle the result of these loaders.` – I'll Eat My Hat Jun 13 '20 at 21:23
  • It seems to be related to [this question](https://stackoverflow.com/questions/59972341/how-to-make-webpack-accept-optional-chaining-without-babel) which refers to a [webpack dependency's PR](https://github.com/acornjs/acorn/pull/891) – I'll Eat My Hat Jun 13 '20 at 22:05
4

Well, I didn't want to use Babel because then I'd have to figure out how to replace ts-node. There's a bunch of outdated docs out there referring to old Babel packages, but these instructions should work as of Nov 2019:

Add a .babelrc file:

{
    "presets": [
        ["@babel/preset-env",{"targets": {"node": "current"}}],
        "@babel/preset-typescript"
    ],
    "plugins": [
        "@babel/plugin-syntax-bigint"
    ]
}

Add these deps:

  "devDependencies": {
    "@babel/cli": "^7.7.0",
    "@babel/core": "^7.7.0",
    "@babel/node": "^7.7.0",
    "@babel/plugin-syntax-bigint": "^7.4.4",
    "@babel/preset-env": "^7.7.1",
    "@babel/preset-typescript": "^7.7.0",
    "@types/node": "^12.7.5",
    "typescript": "^3.7.2"
  }

Execute your code with:

node_modules/.bin/babel-node --extensions ".ts" src/index.ts

The --extensions ".ts" is very important, even though you're explicitly trying to execute a .ts file, it won't transpile it w/out that.

I like to use GNU Make instead of package.json scripts:

MAKEFLAGS += --no-builtin-rules
.SUFFIXES:
NM := node_modules/.bin
.PHONY: build start dev clean test publish

## commands
########################################

__default:
    $(error Please specify a target)

build: build-types build-js dist/package.json

build-types: node_modules/.yarn-integrity
    $(NM)/tsc --emitDeclarationOnly

build-js: node_modules/.yarn-integrity
    $(NM)/babel src --out-dir dist --extensions ".ts" --source-maps inline

run: node_modules/.yarn-integrity
    $(NM)/babel-node --extensions ".ts" src/index.ts

check: node_modules/.yarn-integrity
    $(NM)/tsc --noEmit

dist:
    mkdir -p $@

clean:
    rm -rf node_modules dist yarn-error.log

dist/package.json: package.json | dist
    jq 'del(.private, .devDependencies, .scripts, .eslintConfig, .babel)' $< > $@

## files
########################################

node_modules/.yarn-integrity: yarn.lock
    @yarn install --frozen-lockfile --production=false --check-files
    @touch -mr $@ $<

yarn.lock: package.json
    @yarn check --integrity
    @touch -mr $@ $<

Or just copy from Microsoft's TypeScript Babel Starter.

mpen
  • 237,624
  • 230
  • 766
  • 1,119