Typescript: TS compiler thinks express "errorhandler" module isn't a module

Created on 24 Jun 2015  ·  6Comments  ·  Source: microsoft/TypeScript

I'm using tsc Version 1.5.0-beta

I tried to switch from standard node require syntax to import for several common express packages. TS allows this conversion for some, but not all packages ... and I cannot tell what accounts for the difference.

Here are four packages w/ the original require syntax

import express = require("express")
import bodyParser = require("body-parser");
import methodOverride = require("method-override");
import errorHandler = require("errorhandler");

Here they are again w/ import

import * as express from 'express';                 // OK
import * as bodyParser from 'body-parser';          // OK
import * as methodOverride from 'method-override';  // fails
import * as errorHandler from 'errorhandler';       // fails

of course the code actually runs because the imports transpile to the requires form that I traditionally write in JS

var express = require("express");
var bodyParser = require("body-parser");
var methodOverride = require("method-override");
var errorHandler = require("errorhandler");

Inspecting the code within these packages does not reveal differences that suggest to me an obvious explanation.

For convenience, the npm _package.json_ for these packages is:

    "express": "4.11.1",
    "body-parser": "1.10.2",
    "errorhandler": "1.3.2",
    "method-override": "2.3.1"

FWIW "body-parser" and "method-override" are both quite short.

Question

Most helpful comment

ES6 modules are not fully compatible with commonjs (nodejs) modules. This is valid ES6:

export default function() {}
export var apply = {};

But you cannot compile it with an export assignment, as that would cause side-effects:

module.exports = function() {}
module.exports.apply = {};

Instead, the default function is emitted as a property of the exports object. When you use TypeScript for the whole project, you'll get ES6 behavior for default exports. Creating a behavior that's consistent in all cases, compatible with ES6 and commonjs, is (as far as I know) not possible as commonjs isn't fully compatible with ES6.

In your case you have to use the old import syntax:

import methodOverride = require('method-override');

All 6 comments

What packages does it not work for? I read your issue several times and I can't figure out what specifically are you expecting to work.

You realise that what you are trying to do is convert from old TypeScript module syntax to ES6 module syntax but then you are targeting ES5 for your code which then is transpiling to CommonJS format.

Well it half works.

I've listed the four npm node/express packages at the bottom of my issue; they are extremely well known to anyone who writes a node/express application. Add them to any _package.json_ and call npm install to acquire them.

The node server app works flawlessly whether I use the "old" require syntax or the "new" import syntax because BOTH transpile to the standard ES5 common.js require statements.

What I'm call out is that the TS language service is happy with some and unhappy with others and I can't understand why.

TS is perfectly content with the FIRST two import statements:

import * as express from 'express'; 
import * as bodyParser from 'body-parser'; 

BUT it complains that the next two are "non-modules"

import * as methodOverride from 'method-override'; 
import * as errorHandler from 'errorhandler'; 

For example, for one of these it reports: error TS2497: External module '"errorhandler"' resolves to a non-module entity and cannot be imported using this construct.

My question: _why is TS happy with the first two imports but not the second two when they are in exactly the same form._

I see nothing about the implementation of "body-parser" that is materially different from "method-override". TS should recognize both as modules ... or neither.

I'm not the author of these express modules, btw. But I'm not just throwing something obscure your way. These two express modules appear in just about every node/express application you will ever see. There must be hundreds of thousands of applications with these modules.

Linking to the DefinitelyTyped .d.ts files would be useful here:

https://github.com/borisyankov/DefinitelyTyped/blob/master/errorhandler/errorhandler.d.ts
https://github.com/borisyankov/DefinitelyTyped/blob/master/method-override/method-override.d.ts

ES6 has the concept of a "default export". It also has the notion of importing named exported values from some 'root' object. It does _not_ have the concept of importing a single non-default non-named entity (e.g. a function), even though CommmonJS does.

You can think of a CommonJS (node) module as having exactly one exported value: some object. That object might be a function, or an object with many keys, or a combination of both.

Distinctively different, an ES6 module consists of some number of named exports, plus an optional default export that can only be accessed through the default syntax. There is no "I am giving you this entire object, good luck" mode like in CommonJS.

In the case of method-override and errorhandler, you can't use the ES6 module import syntax to import these things because their definition files (and probably their implementation, though I can't say for certain) don't match the ES6 module spec.

INTERESTING.

Thanks to your prompting, I re-discovered that my project has a supplementary, handcrafted _express-middleware.d.ts_ that describes the three of the middleware packages I mentioned. That's where I can see how things go wrong.

I'm sure you are right about ES6 not importing a function. Something must have changed. Permit me to quote Axel Rauschmayer's well-known post from 2014 on what one _ought_ to be able to do. He says

Default exports (one per module)
Modules that only export single values are very popular in the Node.js community. But they are also common in frontend development where you often have constructors/classes for models, with one model per module. An ECMAScript 6 module can pick a default export, the most important exported value. Default exports are especially easy to import.

The following ECMAScript 6 module “is” a single function:

    //------ myFunc.js ------
    export default function () { ... };

   //------ main1.js ------
    import myFunc from 'myFunc';
    myFunc();

Both "bad" express packages export a single function. Following Axel, it would seem I should be able to revise their d.ts declarations to this:

declare module "method-override" {
    import express = require("express");
    export default function methodOverride(): express.RequestHandler;
}

declare module "errorhandler" {
    import express = require("express");
    export default function errorHandler(opts?: any): express.ErrorRequestHandler;
}

Now their import statements are like this:

import methodOverride from 'method-override';
import errorHandler from 'errorhandler';

The TS transpiler no longer objects ... Hooray!

But not so fast. The code fails to execute. Unfortunately, TS no longer generates "the right code". It generates this instead:

var method_override_1 = require('method-override');
...
// consume it:
var x = method_override_1.default();

Oops. Per Axel, I should have seen:

var method_override = require('method-override');
...
// consume it:
var x = method_override();

This is painful!

A lot of node/express modules export one thing ... a function ... just as Axel says.

We can't go changing everybody's modules.

Am I to conclude that the only way I can import a single-function export module is with the "old" commonjs require syntax?

May we assume that option will always remain with us?

ES6 modules are not fully compatible with commonjs (nodejs) modules. This is valid ES6:

export default function() {}
export var apply = {};

But you cannot compile it with an export assignment, as that would cause side-effects:

module.exports = function() {}
module.exports.apply = {};

Instead, the default function is emitted as a property of the exports object. When you use TypeScript for the whole project, you'll get ES6 behavior for default exports. Creating a behavior that's consistent in all cases, compatible with ES6 and commonjs, is (as far as I know) not possible as commonjs isn't fully compatible with ES6.

In your case you have to use the old import syntax:

import methodOverride = require('method-override');

OK. We just have to make sure this is well documented.

The key is that I will always be able to fall back to the common.js syntax when necessary.

Was this page helpful?
0 / 5 - 0 ratings