I am trying to refactor an unwieldy config interface/object by separating its various sections into separate files under a namespace I've cleverly named Config.
The documentation talks about namespaces that span multiple files and declaration merging of interfaces, but I can't seem to get them to work together.
src/config/index.ts
/// <reference path="./server.ts" />
import fs from 'fs';
import json5 from 'json5';
const _config = readConfig();
namespace Config {
export const config = _config;
export interface IConfig {
someGeneralProperty: {
// ...
}
}
}
function readConfig(): Config.IConfig {
return json5.parse(fs.readFileSync('./path/to/config.json', 'utf-8'));
}
function doSomeOtherStuff() {
// fails: Property 'server' does not exist on type 'IConfig'.
console.log(_config.server.host);
}
src/config/server.ts
/// <reference path="./index.ts" />
namespace Config {
export interface IConfig {
server: {
host: string;
port: number;
}
}
}
src/index.ts
// fails: Module '"./config"' has no exported member 'config'.
import { config } from './config';
// fails: Cannot use namespace 'Config' as a value.
// fails: Namespace 'Config' has no exported member 'config'.
import config = Config.config;
I've tried several variations of exporting things, such as export default Config;, export namespace Config {...} in each of the src/config/... files, changing export const config to export var config. In src/config/index.ts I tried export * from './server'. Nothing seems to help.
I have a feeling I'm just going about this all wrong.
Oddly, the interfaces within the namespace in every file are exported from the namespace, so in src/index.ts, I can do:
import IConfig = Config.IConfig;
let c: IConfig;
console.log(c.server.host);
but I cannot do that in either src/config/index.ts nor src/config/server.ts.
At first you should decide yourself, if you want to assign the
configobject to a module scope (i.e.import/export) or in the global scope (i.e.windowin browsers,globalin node).The main purpose of namespaces is to define properties/values on the global scope. As you pointed out correctly with the links, equally named namespaces are merged - that includes contained inner members like the
IConfiginterface.Here is the deal: Merging only happens when the file containing the
namespaceis a script (a non-module file withoutimport/exportat top-level).In
src/config/index.ts, you've gotimportstatements, so the file becomes a module andnamespace Configdoes not get merged. Instead it is rather a module internal namespace, which is not evenexported (see Needless Namespacing, Do not use namespaces in modules in the docs). TheConfignamespace insrc/config/server.tsforms its own global namespace (non-module file), that is why you can still use the containedIConfigtype.In summary, if you want to have the config (value and type) globally, make sure, every part of the multi file part namespace is declare in a non-module file. If the config is to be exported from a module (preferred way if feasible!; better encapsulation, no global scope pollution, the "modern" way), read on.
Alternative: export config in a module
src/config/server.ts:
src/config/index.ts:
src/index.ts:
Feel free to adjust the parts, you need.