3

I'm trying to use an existing js library (validate.js) in both the client and server.

I installed it using npm, and everything compiles for both server and client.
When using it in the server it works great, but when I execute it in the browser it throws an error.

The same file is used in both cases:

import validate = require("validate.js");

export function RequestValidator(data: any): any {
    return (validate as any)(data, constraints, { allowEmpty: true });
}

validate is asserted to any becuase otherwise I get:

TS2349: Cannot invoke an expression whose type lacks a call signature.

The .d.ts I'm using is:

declare module "validate.js" {
    export interface ValidateJS {
        (attributes: any, constraints: any, options?: any): any;
        async(attributes: any, constraints: any, options?: any): Promise<any>;
        single(value: any, constraints: any, options?: any): any;
    }

    export const validate: ValidateJS;
    export default validate;
}

The module only exports a function, and that works well in the server, but in the client when invoking this function I get:

Uncaught TypeError: validate is not a function(…)

The code is compiled using target commonjs for the server:

"use strict";
const validate = require("validate.js");
...

And system for the client:

System.register(["validate.js"], function(exports_1, context_1) {
    "use strict";
    var __moduleName = context_1 && context_1.id;
    var validate;
    ...

    return {
        setters:[
            function (validate_1) {
                validate = validate_1;
            }],
    ...

When debugging it, validate indeed isn't a function it's:

validate: r
    EMPTY_STRING_REGEXP: (...)
    get EMPTY_STRING_REGEXP: function()
    set EMPTY_STRING_REGEXP: function()
    Promise: (...)
    get Promise: function()
    set Promise: function()
    __useDefault: (...)
    get __useDefault: function()
    set __useDefault: function()
    async: (...)
    get async: function()
    set async: function()
    capitalize: (...)
    get capitalize: function()
    set capitalize: function()
    cleanAttributes: (...)
    get cleanAttributes: function()
    set cleanAttributes: function()
    ...

Any idea what's going on and why it behaves this way in the browser?

3
  • 1
    See the last sentence in this paragraph on CommonJS: github.com/systemjs/systemjs/blob/master/docs/…. SystemJS doesn't load CommonJS modules exactly the same way as NodeJS does. There's @node however I have no personal experience with this kind of black magic. Commented Nov 8, 2016 at 10:14
  • @martin well, isn't that dandy. I especially like: "this should only be used when absolutely necessary as it stops code from being universal, and makes it only compatible with NodeJS". So now I'm forced to use jspm as well? Commented Nov 8, 2016 at 10:39
  • I've never used that option myself, I just remember is saw it mentioned there... Commented Nov 8, 2016 at 10:41

1 Answer 1

3

When you compile with "module": "system", node-compatible import

import validate = require("validate.js");

no longer works - the value you are getting for validate is a module, not a function. This may be a bug in typescript, or it could be by design - I don't know. (update: from github comment addressed to JsonFreeman here, it looks like it's by design: you get the module object with a set of properties including one named default).

There are several ways around this.

First, you can do easy conversion yourself - the function that you need is provided as default property of the module, so this line will fix it:

validate = validate.default ? validate.default : validate;

Or, you can compile with "module": "commonjs" even for the browser, so typescript will generate working code, and systemjs will detect format for your modules automatically.

Or finally, you could still compile with "module": "system", but import validate.js as it's intended in its typings:

import validate from 'validate.js';

That way you don't have to do any casts to any, and typescript will generate necessary access for default property, but the drawback is that it will not work at all in node when imported that way.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks! I ended up using the validate.default option. It would have been great to import it like the last option you gave, but unfortunately the same code needs to run in both the browser and node.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.