Search code examples
javascriptnode.jstypescriptsystemjs

TypeScript for NodeJS via SystemJS


I would like to use TypeScript in my NodeJS app. In order to do that I've thought it would be nice to use SystemJS to make it transpile my source files on the fly to JavaScript. I've managed to bypass most of the issues related to SystemJS configuration except the following:

How to make SystemJS to prevent processing certain imports and make it use original require?

SystemJS via it's own require implementation wraps everything in it's own module representation disrespecting the original type of the module. It makes such NodeJS module as express an object rather than the function (the problem is in the code below)

'use strict';

console.log('In server.ts');

if ('production' === process.env.NODE_ENV)
    require('newrelic');

let PORT = process.env.PORT || 3333;

import * as fs from 'fs';
import * as os from 'os';
import * as https from 'https';
import * as express from 'express';  // <------------- SystemJS makes express an object    
import * as socketio from 'socket.io';

import { Routes } from './routes/index';
import { DBConfig } from './config/db.conf';
import { RoutesConfig } from './config/routes.conf';
import { SocketServer } from "./reflex/SocketServer";

const app = express(); // <------------ This is object, so no way to initialize

RoutesConfig.init(app);
DBConfig.init();
Routes.init(app, express.Router());

const opts = {
  key: fs.readFileSync(__dirname + '/cert/server.key'),
  cert: fs.readFileSync(__dirname + '/cert/server.crt')
};

let server = https.createServer(opts, <any>app);
let socket = socketio(server, { transports: ['websocket'] });

let socketServer = new SocketServer(100, socket);

server.listen(PORT, () => {
  console.log(`Application is up and running @: ${os.hostname()} on port: ${PORT}`);
  console.log(`enviroment: ${process.env.NODE_ENV}`);
});

Another problem is that by replacing original require, not only every module should be covered with SystemJS configuration but also every submodule which is an overkill.

Here's my SystemJS config:

SystemJS.config({
    transpiler: 'typescript',
    defaultJSExtensions: false,
    map: {
        // ------ system modules ------

        "console": "@node/console",
        "buffer": "@node/buffer",
        "querystring": "@node/querystring",
        "events": "@node/events",
        "http": "@node/http",
        "cluster": "@node/cluster",
        "zlib": "@node/zlib",
        "os": "@node/os",
        "https": "@node/https",
        "punycode": "@node/punycode",
        "repl": "@node/repl",
        "readline": "@node/readline",
        "vm": "@node/vm",
        "child_process": "@node/child_process",
        "url": "@node/url",
        "dns": "@node/dns",
        "net": "@node/net",
        "dgram": "@node/dgram",
        "fs": "@node/fs",
        "path": "@node/path",
        "string_decoder": "@node/string_decoder",
        "tls": "@node/tls",
        "crypto": "@node/crypto",
        "stream": "@node/stream",
        "util": "@node/util",
        "assert": "@node/assert",
        "tty": "@node/tty",
        "domain": "@node/domain",
        "constants": "@node/constants",

        // ------ common modules ------
        'helmet': '@node/helmet',
        'morgan': '@node/morgan',
        'express': '@node/express',
        'mongoose': '@node/mongoose',

        'socket.io': '@node/socket.io',
        'socket.io-client': '@node/socket.io-client', // <----- this module is being referenced by 'socket.io'
        'body-parser': '@node/body-parser',

        // ------ SystemJS configuration ------
        json: './node_modules/systemjs-plugin-json/json.js'

    },
    meta: {
        '*.json': {
            loader: 'json'
        }
    },
    paths: {
        'typescript': './node_modules/typescript',
    },
    packages: {
        'typescript': {
            main: 'lib/typescript'
        },
        'server': {
            defaultExtension: 'ts'
        }
    },
    typescriptOptions: tsconfig
});

So the question is the following: how to make SystemJS ignore certain imports (via configuration) and bypass control to native NodeJS require implementation?

Thank you in advance!


Solution

  • If you take a look at how express.js exports things, you will see

    exports = module.exports = createApplication;
    

    and then

    exports.application = proto;
    exports.request = req;
    exports.response = res;
    

    When SystemJS converts this to a module, the function that you need, createApplication, becomes a default export.

    There is a shorthand syntax for importing only the default value

    import express from 'express';
    
    console.log(typeof express); // function
    

    Of course all the properties assigned to it - application, request etc. - are still available.

    Or, if you prefer your original form for import, you can access it as default property

    import * as express from 'express';
    
    console.log(typeof express.default); // function
    

    Now to your question

    how to make SystemJS ignore certain imports (via configuration) and bypass control to native NodeJS require implementation?

    There is no such option in the configuration AFAIK, but SystemJS provides original node require as System._nodeRequire, so you can use this as fallback in the code:

    var express = System._nodeRequire('express');
    

    NOTE: as Lu4 noted in the comment, 'express': '@node/express' mapping in SystemJS config is very important. Besides loading from node_modules, @node special notation tells SystemJS that all the packages required inside the module and its dependencies are to be loaded with node require and not to be converted to modules. This is sometimes necesary because SystemJS module resolution algorithm is different from nodejs, as mentioned in CommonJS module format section in SystemJS docs.