Trying to create an extern for a Node module

507 views
Skip to first unread message

Michael Bolin

unread,
Jun 2, 2011, 10:43:34 AM6/2/11
to Closure Compiler Discuss
There is a routing package for Node called beeline: https://github.com/xavi-/beeline

Here is the code that I would expect to be able to write (and
typecheck using Advanced mode) with a proper externs file:

var bee = /** @type {NodeBeeline} */ (require('beeline'));
var router = bee.route(...);

However, when I run this with Verbose warnings, I get:

JSC_TYPE_PARSE_ERROR. Bad type annotation. Unknown type NodeBeeline at
js/main.js line 10 : 32

I am compiling using the following externs file -- any idea what I
need to change?

var NodeBeeline = {};

/**
* @param {!Object.<Function>} routes Most values will implement
* http.requestListener, but error handling functions will take a
third
* error parameter.
* @return {http.requestListener} that can be used as an argument to
* http.createServer().
*/
NodeBeeline.route = function(routes) {};

/**
* @param {string} pathToFile
* @param {string} mimeType
* @return {http.requestListener}
*/
NodeBeeline.staticFile = function(pathToFile, mimeType) {};

/**
* @param {string} pathToDir
* @param {Object.<string>} mimeTypes is a map where keys are file
extensions,
* such as ".gif" and values are corresponding MIME types, such as
* "image/gif".
* @return {http.requestListener}
*/
NodeBeeline.staticDir = function(pathToDir, mimeTypes) {};

Michael Bolin

unread,
Jun 2, 2011, 10:46:03 AM6/2/11
to Closure Compiler Discuss
A workaround that I'm using (but am not happy about) is pretending
that NodeBeeline is a constructor rather than a namespace:

/** @constructor */
var NodeBeeline = function() {};

/**
* @param {!Object.<Function>} routes Most values will implement
* http.requestListener, but error handling functions will take a
third
* error parameter.
* @return {http.requestListener} that can be used as an argument to
* http.createServer().
*/
NodeBeeline.prototype.route = function(routes) {};

/**
* @param {string} pathToFile
* @param {string} mimeType
* @return {http.requestListener}
*/
NodeBeeline.prototype.staticFile = function(pathToFile, mimeType) {};

/**
* @param {string} pathToDir
* @param {Object.<string>} mimeTypes is a map where keys are file
extensions,
* such as ".gif" and values are corresponding MIME types, such as
* "image/gif".
* @return {http.requestListener}
*/
NodeBeeline.prototype.staticDir = function(pathToDir, mimeTypes) {};

Ilia Mirkin

unread,
Jun 2, 2011, 10:51:43 AM6/2/11
to closure-comp...@googlegroups.com
Well, I think that a namespace is not a type. It's like trying to do
/** @type {goog.dom} */, no? I wonder if goog.scope can play
positively into what you're doing (that's as far as my thought process
goes... I haven't actually thought of anything concrete).

Hmmm... you could also do like

var NodeBeeline = require('beeline');

and keep the externs as you had them originally... I wonder if that'll work.

-ilia

Michael Bolin

unread,
Jun 2, 2011, 11:00:19 AM6/2/11
to Closure Compiler Discuss
True...If you've been following the related thread on Closure Library
Discuss (http://groups.google.com/group/closure-library-discuss/
browse_thread/thread/7b58d14cb026db04) we've been discussing two
approaches for playing with externs. The other one (which is basically
what you describe) works, but it requires concatenating "var
NodeBeeline = require('beeline')" to the top of the output. If you
include it in the compiled code, then the Compiler complains because
"var NodeBeeline" conflicts with the extern.

I'm guessing that I'll have to modify the Compiler to support this
behavior. The goal is to use some long name for the namespace in the
externs file, but only make it appear once in the code:

var bee = /** @type {NodeBeeline} */ (require('beeline'));

This way, users of the extern file can use whichever local variable
name they want in their code:

var bee = /** @type {NodeBeeline} */ (require('beeline'));
var beeline = /** @type {NodeBeeline} */ (require('beeline'));

The existing method requires everyone to use the same variable name
for the object returned by require(), which is probably too brittle to
be useful.

Ilia Mirkin

unread,
Jun 2, 2011, 11:10:58 AM6/2/11
to closure-comp...@googlegroups.com
Sounds like what you really want to be able to add to the compiler is
an alias annotation, e.g.

var bee = /** @alias {NodeBeeline} */ (require ... )

or even

/** @alias {NodeBeeline} */
var bee = require(...);

Or something like that (@namespace perhaps?). I think using type here
would be a hack, since NodeBeeline is not a type, nor do you really
want or need to treat it like one.

-ilia

Chad Killingsworth

unread,
Jun 2, 2011, 11:13:49 AM6/2/11
to closure-comp...@googlegroups.com
Nick and I have been discussing the problem: http://code.google.com/p/closure-compiler/issues/detail?id=448

I'd like to see it fixed, but I'm not going to have time to work on it.

Chad Killingsworth

Michael Bolin

unread,
Jun 2, 2011, 11:18:12 AM6/2/11
to Closure Compiler Discuss
Agreed, introducing a new annotation is probably the way to go.

Compiler team folks -- is there anything like this in the works?

Currently, on a small Node project on my Mac, it takes 8-10 seconds to
compile in Simple mode with Verbose warnings enabled, so I really want
to figure out a way that I can write code for Node in Closure style
that can be checked by the Compiler, but still works without any sort
of pre-processing by the Compiler. (Basically, the equivalent of RAW
mode in plovr).

Nick Santos

unread,
Jun 2, 2011, 5:38:50 PM6/2/11
to closure-comp...@googlegroups.com
My thinking on this hasn't evolved much since I wrote the comment on
the bug Chad pointed at.

It basically just needs a proposal spec that sorts out the various
tricky cases (e.g., what happens when NodeBeeline is assigned to a
different anonymous object? would the type name point to the new
object? or to the old one? or would using this annotation just imply
@const?)

Reply all
Reply to author
Forward
0 new messages