Offirmo Offirmo - 4 months ago 15
Node.js Question

Getting errors: How can I properly consume my transpiled and typed typescript npm module from typescript?

I'm still giving a shot at typescript. I've written a trivial "hello world" typescript module and published it to npm. Really trivial, just does a default export:

export default function hello(target: string = 'World'): void
{
console.log(`Hello, ${target} :-(`)
}


It is consumed perfectly well by node.js 0.10 -> 6.
The module also has a proper
"typings"
property in package.json, pointing to an existing
.d.ts
file generated by tsc as explained in the official documentation :

export default function hello(target?: string): void;


However, I can't get it consumed by typescript code, neither in typescript 1.8 nor 2 :

import hello = require('hello-world-emo')

hello()
hello('Offirmo')


Transpiling with
tsc
or executing with
ts-node
both give the same error message:

TSError: тип Unable to compile TypeScript
hello.ts (3,1): Cannot invoke an expression whose type lacks a call signature. (2349)


However, the generated .js works. It just seems a typing issue. Using other import formats like
import hello from 'hello-world-emo'
doesn't work either.

What is typescript complaining ? What did I miss ?

In case you want to inspect
tsconfig
, the module is here and I'm consuming it from here

Answer

The problem lies not in typescript but rather in ES6/CommonJS interop : there is no perfect matching between ES6 and CommonJS exports, especially for the ES6 "export default", cf. this article and this open issue on webpack.

Here the module code is written in ES6 using ES6 modules. The build procedure is:

  1. transpile typescript to ES6 with tsc (typescript compiler)
  2. transpile the ES6 code and bundle it with rollup+babel for:
    • ES6@node4 / CommonJS (1st class citizen)
    • ES5@node0.10 / CommonJS (for still widely used legacy)
    • ES5 / umd (for browsers)

Node 4 (stable) being the 1st class citizen, the corresponding bundle (ES6@node4 / CommonJS) is exposed via the npm "main" entry. It has been converted to CommonJS as best as rollup could do, making it consumable by node 4 but no longer by typescript.

Another option would have been to transpile to ES5/CommonJs with tsc, which adds special annotations for linking back CommonJS to ES6 modules. But then it's 1) no longer bundled 2) more roughly transpiled (not using ES6 features included in node 4) and 3) more hardly consumable by node

A compromise solution: avoid the CommonJS / ES6 modules interop problem by not using export default but only named exports. This mitigation technique only downside is for node 4 :

  • ES6: import hello from '...' becomes import { hello } from '...' OK
  • node >=6: const hello = require('...') becomes const { hello } = require('...') OK
  • node <=4: var hello = require('...') becomes var hello = require('...').hello less elegant but acceptable