googleapis/google-cloud-node

TypeScript import @google-cloud/tasks error

Closed this issue ยท 5 comments

Since this change, import is broken from TypeScript with NodeNext.

https://github.com/googleapis/google-cloud-node/blame/6773a05ce96d64e4a1034e8b63ef6ec0d6688fd2/packages/google-cloud-tasks/package.json#L14


Code generating the error:

import { CloudTasksClient } from "@google-cloud/tasks";

Error message:

The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("@google-cloud/tasks")' call instead.


tsconfig.json:

{
    "compilerOptions": {
        "module": "NodeNext",
        "noImplicitReturns": true,
        "noUnusedLocals": true,
        "outDir": "lib",
        "skipLibCheck": true,
        "sourceMap": true,
        "strict": true,
        "target": "ESNext"
    },
    "compileOnSave": true,
    "include": [
        "src"
    ]
}

Workaround 1: Downgrade to 4.0.1.
Workaround 2: Use dynamic import.

The Workaround 1 is posing another challenge. Fix for #5000 requires upgrading the @google-cloud/tasks to 5.2.1+, but you can't build it above 4.0.1. ๐Ÿ˜ 

The reason for this is that @google-cloud/tasks is now published as a dual-format ESM and CJS module as of 5.0. Using NodeNext tells the compiler to import as ESM if you're using imports. One option is to use dynamic imports, like you said, or you can just require tasks, it will use CJS.

@danielbankhead as FYI for future notes to better explain this feature.

I understand what is causing it, but the problem is that it's happening. None of the other @Google-Cloud packages have this problem (that I know of). @google-cloud/tasks didn't have this problem until recently either. A different design or way to export multiple format should be considered. I have started using dynamic imports as a workaround but regular import at the top of the file would be a much better option. Thank you.

The reason for this is that @google-cloud/tasks is now published as a dual-format ESM and CJS module as of 5.0. Using NodeNext tells the compiler to import as ESM if you're using imports. One option is to use dynamic imports, like you said, or you can just require tasks, it will use CJS.

This is not correct. Typescript resolves the import for @google-cloud/tasks to the cjs bundle because it is imported from a CommonJS context, so require will be emitted in the compiled Javascript code if the error does not exist. Typescript is warning that we are requireing the package because the cjs bundle looks like an ESM module on the Typescript side.

Reason that typescript misunderstands the module type is:

  • Typescript considers build/cjs/src/index.d.ts as the entrypoint (it doesn't care that the corresponding Javascript file (build/cjs/src/index.cjs) has cjs extension)
  • Typescript infers the NodeFormat of index.d.ts to be ESM because the nearest package.json has "type":"module" in it.

So this can be resolved if we correctly tell the NodeFormat of build/cjs/src/index.d.ts to Typescript. This can be done by one of the following:

  • rename index.d.ts to index.d.cts
    • you have to rewrite the corresponding references in the types field and the exports field of the package.json
  • place package.json with {"type":"commonjs"} in build/cjs/src
    • This might change the behavior of Node.js if some .js files exist under build/cjs/src since it will no longer be treated as ESM