2

So I have three Typescript files:

config/env/development.ts

import { Config } from '../config';

class DevConfig {
    public config : Config = {
        HOST: 'localhost',
        PORT: 8080
    };

    constructor() {
        this.config.HOST = 'localhost';
        this.config.PORT = (process.env.PORT as unknown as number) || 8080;
    }
}

export default new DevConfig().config;

config/index.ts

import { Config } from './config';

process.env.NODE_ENV = process.env.NODE_ENV || 'development';

class AppConfig {
    public allConfig : Config;
    public envConfig : Config;

    constructor() {
        this.envConfig = require(`${__dirname}/env/${process.env.NODE_ENV}.js`);
        this.allConfig = require(`${__dirname}/all.js`);
    }
}

export default new AppConfig().envConfig;

and the other file is: ./app.ts

import * as express from 'express';
import { hi } from './controllers/status';
import AppConfig from './config';

class App {
    public express: any;
    public config: object;

    constructor() {
        this.express = express();
        this.config = AppConfig;
        this.mountRoutes();

        console.log(`Output test: ${JSON.stringify(this.config, null, 4)}`);
    }

    private mountRoutes() {
        const router = express.Router();

        router.get('/', hi);
        this.express.use('/', router);
    }
}

export default new App();

The output I see when I run this is the following:

Output test: { "default": { "HOST": "localhost", "PORT": 8080 } }

Where is the "default" coming from? If I try to do console.log(this.config.PORT);, I get an undefined. Could someone please clarify?

codehitman
  • 1,148
  • 11
  • 33
  • Well, where is `{ "HOST": "localhost", "PORT": 8080 }` coming from? – Bergi Jul 22 '19 at 20:15
  • That first file is the `./config` file, and it imports itself? – Bergi Jul 22 '19 at 20:16
  • That comes from here `this.envConfig = require(`${__dirname}/env/${process.env.NODE_ENV}.js`);` I haven't included that file here. But it's similar to the first file. – codehitman Jul 22 '19 at 20:17
  • 1
    You should not [export a class instance](https://stackoverflow.com/q/38739499/1048572). Use [multiple named exports instead](https://stackoverflow.com/q/29893591/1048572). – Bergi Jul 22 '19 at 20:17
  • 1
    So does the file that you `require` have a default export? Please post its contents, it seems very relevant (much more than the second file you posted). – Bergi Jul 22 '19 at 20:18
  • @Bergi Yes, the `require` files have an `export default`. Just edited the question. – codehitman Jul 22 '19 at 20:20
  • 2
    There you are, that's where the `default` is coming from. Have a look at the code that the Typescript compiler is producing. – Bergi Jul 22 '19 at 20:27
  • Having same module names (`./config` and `../config`) can be dangerous. Not sure if that is relevant to the problem though. – Jonas Wilms Jul 22 '19 at 20:28
  • @Bergi Thanks, for those two links! I will remove the class instance to simply exporting the class (not default). – codehitman Jul 22 '19 at 20:54
  • @JonasWilms Could you please elaborate on why my module structure is dangerous? – codehitman Jul 22 '19 at 20:55
  • You might forget a dot somewhere, and the IDE won't spot it because you accidentally imported the wrong file ... – Jonas Wilms Jul 22 '19 at 20:56
  • @JonasWilms The other Config is an `interface`, but point taken. – codehitman Jul 22 '19 at 21:03
  • @codehitman In your case it seems much more appropriate to export an object literal, and not create a class at all. You already got the `Config` interface for type safety anyway. – Bergi Jul 22 '19 at 21:04
  • @Bergi I'm going to be adding some methods to that `class` which will be called from within the `constructor.` Do you think it's better if I get rid of the `class` and just call the `function`s when the object is `import`ed? `export config = { host: 'localhost', firebase: () => {}, ... }` something like that? – codehitman Jul 22 '19 at 21:10
  • 2
    @codehitman since you still want to export an object only(?), I doubt that you need *methods* that do anything with a class *instance*. Just write plain `function` declarations. If you want to export an object with methods, you can write method definitions in object literals too :-) – Bergi Jul 22 '19 at 21:13
  • @Bergi Ok. Cheers! – codehitman Jul 22 '19 at 21:32

1 Answers1

1

A module can have a default export and regular exports, yet require() can only return one value. Therefore the transpiler you are using has to turn multiple exports into one value. It does so by turning all the exports into an object.

  // this
  export default 1;
  export const named = 2;

  // turns into:
  module.exports = { 
    default: 1,
    named: 2 
  };

As default is a reserved keyword and thus cannot be used as a named export, having a default key for the export default makes perfectly sense.

You can easily get the default export with require("...").default.

Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • 1
    …or alternatively just write `module.exports = {HOST: "localhost", PORT: process.env.PORT || 8080};` instead of that overengineered typescript stuff :-) – Bergi Jul 22 '19 at 20:43
  • 1
    "*having a `default` key for the export default makes perfectly sense.*" - that's not just because `default` is a reserved keyword, rather the default export really is just a normal export with a special identifier that saves some typing. You could export and import is with "named" syntax as well. – Bergi Jul 22 '19 at 20:45
  • I was about to mention json config, but using `process.env` requires a script. – Bergi Jul 22 '19 at 20:47