How to Solve a Problem like ExpressJs' express() !?

Topics: General
Oct 16, 2012 at 1:14 PM
Edited Oct 16, 2012 at 1:18 PM

In expressjs 3.x the nodejs module express.js exports a function as the module like this:

exports = module.exports = createApplication;

which means in nodejs you do the following:

  var express = require("express");
  var app = express();

How does one:
a) declare this in a TypeScript express.d.ts file and
b) use it in a TypeScript app.ts file.

Please note that from what I can determine the express.d.js used in the MS examples is for an earlier version of express (2.x) since it uses the deprecated createServer function.

Oct 16, 2012 at 2:13 PM

Underscore.js do the same:

declare module "underscore" {
    // OO-style
    export function (arg : any) : UnderscoreOOStatic;

    // Collection Functions (Arrays or Objects)
    export function each (list : any[], iterator : UnderscoreVoidListIterator, context? : any) : void;
.
.
.

Oct 17, 2012 at 7:44 AM
Edited Oct 17, 2012 at 7:51 AM

Thank you @alvivi,

I couldn't get that to work because the express.d.ts is declared as a module.  Exporting function '()' as type ExpressServer caused an error:

declare module "express" {
    export function (): ExpressServer; // Error: The name 'ExpressServer' does not exist in the current scope.
    export function createServer(): ExpressServer; // This works...
    export function static(path: string): any;
    import http = module("http");
    export var listen;

Not sure if this is a bug... so I ended up going like this:

declare module "express" {
    export function (): any;
...

and using it like this:

import express = module('express');
var app = <express.ExpressServer>express();
Oct 17, 2012 at 1:38 PM

Yes, I have the same problem. I think it is a bug, you cannot use a interface declared in a module in the definition of () function of that module. I had to move out the interface declarations from their module to implement this in underscore.js.

Oct 17, 2012 at 1:57 PM

This will work if you qualify the type with the module name:

declare module express {
    export function (): express.ExpressServer;
    export interface ExpressServer {listen():string;}
}

Oct 17, 2012 at 3:34 PM

@markrendle When I try your example, using this files:

express.d.ts

 

declare module express {
    export function (): express.ExpressServer;
    export interface ExpressServer {listen():string;}
}

test.ts

/// <reference path="express.d.ts" />

import express = module express;

 

"tsc test.ts" gives me an "cannot read property 'nodeType' of null" error.

 

Oct 18, 2012 at 6:19 AM

@markrendle, I have the same problem that @alvivi is having.

The difference is in how the module name is specified. i.e. 

declare module "express" { ... } // quoted
// or
declare module express { ... }   // not quoted 

As per chapter 10 of the TypeScript spec, "An AmbientModuleIdentification with a StringLiteral declares an external module."  So the first declaration is the one that should be used.  When using an external (literal) declaration the reference to express.ExpressServer does not work.

I still wonder if the error from an unqualified reference to ExpressServer is a bug or a 'feature'!?

Oct 24, 2012 at 8:47 AM

Ok -- i'm not understanding what the actual fix is

/// reference path='Definitions/node-0.8.d.ts'
/// reference path='Definitions/express-2.d.ts'
import express = module("express");
var app = express();

app.get('/', function(req, res){
  res.send('Hello World');
});
VS highlights the express in module("express":
and says:
Error 1 The name '"express"' does not exist in the current scope X\app.ts 3 25 app.ts
Error 2 A module cannot be aliased to a non-module type Xapp.ts 3 25 app.ts
I don't see what the problem is and the error is pretty non-descriptive.
Oct 25, 2012 at 5:24 AM
Edited Oct 26, 2012 at 6:18 AM

Yes, this is a real pain...

It would be great if the issue was just a once off, but in NodeJs this is a common pattern for initialising modules!  I would be interested in some clarity on, whether this is by intention or 'design', whether this can be changed or whether this is a bug.

Sep 25, 2013 at 6:07 AM
I think I managed to make this work recently.

The pattern is "Callable module", I made this name up myself.

It can be implemented using "Declaration Merging" (spec 7.2) and "Export Assignments" (spec 11.2.4)
//express.d.ts

declare module "express" {
  function e(): e.Application;

  module e {
    interface Application {
      //omitted
    }
    // omitted rest of members
  }

  export = e;
}
I tried to make a pull request https://github.com/borisyankov/DefinitelyTyped/pull/1077 for DefinitelyTyped's express.d.ts file but it hasn't been reject/accepted yet. I also made some other improvements like putting the interfaces inside of the express namespace instead of having them in the global namespace with ExpressServer as a prefix.