ptommasi
ptommasi

Reputation: 1252

Access an enum in a declared (and not exported) module in TypeScript once transpiled

TL;DR

Without exporting a namespace, but only declaring it, I want to access an enum within it. Once transpiled, the namespace is not accessible (it's an undefined object), therefore the enum fail to be accessed (with an error on the namespace).

Explanation

I'm setting up a project in TypesScript, and I'd like to avoid importing all the definitions specific to this project. In this snippet, I created a namespace where I'm putting the local definitions for my redux logic.

An extract of the redux declarations (redux.d.ts):

declare namespace Redux {

    namespace Actions {

        enum TypeKeys {
            REDUX_INIT          = '@@redux/INIT',
            BOOTSTRAP           = 'BOOTSTRAP',
            CORPUS_LOADED       = 'CORPUS_LOADED',
            ...
        }
        ...
        interface Definition {
            doSomething(): void;
            ...
        }
        ...
    }

    namespace Store {
        interface Definition { ... }
        ...
    }


}

Which allows me to avoid importing the definitions in all my code (e.g. in const mapStateToProps = (state: Redux.Store.Definition) => state; in my top level component, I don't have to import any Redux namespace).

Moving to the problem, I wrote this reducer (corpusReducer.ts extract):

// Note: no need to import Redux!
export default (state: Redux.Store.CorpusManagement, action: Redux.Actions.ActionTypes): Redux.Store.CorpusManagement => {

    switch (action.type) {

        case Redux.Actions.TypeKeys.CORPUS_LOADED:
            return { corpus: action.payload };

        default:
            return state === undefined ? { corpus: { documents: [] } } : state;

    }

};

Once transpiled, I'm getting the error ReferenceError: Redux is not defined for the line case Redux.Actions.TypeKeys.CORPUS_LOADED.

My suspect is that Redux is not available at runtime because, since a namespace, belongs to the TypeScript world. TypeKeys, on the other hand, it's an enum, which should have a mapping in JavaScript, therefore my expected behaviour would be that the enum would still be accessible once transpiled.

If I add an export (changing the line into declare namespace Redux), the file is "promoted" to be a module, but then I need to import it everywhere in my code.

Question

How can I access the enum avoiding the export clause (and therefore to explicitly import the module everywhere)? Is there any good practice I'm not following?

Upvotes: 2

Views: 2733

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250016

Enums use the enum object to hold the values at runtime. This means that if this object is not accessible the code that depends upon it's existence will fail as you discovered.

Your best solution is to make sure that object is exported. Add an extra module that holds the enum and import that.

If this is not possible you could use a const enum in the declarations. Const enums do not depend on the existence of the object as whenever the compiler encounters a reference to an enum value the actual value will be hardcoded in the generated JS.

const enum TypeKeys { // No code for const enums is generated.
    REDUX_INIT = '@@redux/INIT',
    BOOTSTRAP = 'BOOTSTRAP',
    CORPUS_LOADED = 'CORPUS_LOADED',
}


let d = TypeKeys.BOOTSTRAP; // All enum references are replaced with values, so this becomes var d = "BOOTSTRAP"

You should take care with this approach as this means all values are hardcoded in your JS and any change to the enum will require you to rebuild any code taht references the enum.

Edit

Full code that works for me:

// definitions.d.ts
declare namespace Redux {

    namespace Actions {

        const enum TypeKeys {
            REDUX_INIT          = '@@redux/INIT',
            BOOTSTRAP           = 'BOOTSTRAP',
            CORPUS_LOADED       = 'CORPUS_LOADED',

        }
        interface Definition {
            doSomething(): void;
        }
        interface ActionTypes {
            type: TypeKeys
            payload: any;
        }
    }

    namespace Store {
        class CorpusManagement {}
    }
}

// usage.ts
/// <reference path="./definitions.d.ts" />
export default (state: Redux.Store.CorpusManagement, action: Redux.Actions.ActionTypes): Redux.Store.CorpusManagement => {

    switch (action.type) {

        case Redux.Actions.TypeKeys.CORPUS_LOADED:
            return { corpus: action.payload };

        default:
            return state === undefined ? { corpus: { documents: [] } } : state;

    }

};

The output:

(...)
(function (require, exports) {
    "use strict";
    Object.defineProperty(exports, "__esModule", { value: true });
    /// <reference path="./definitions.d.ts" />
    exports.default = (state, action) => {
        switch (action.type) {
            case "CORPUS_LOADED" /* CORPUS_LOADED */:
                return { corpus: action.payload };
            default:
                return state === undefined ? { corpus: { documents: [] } } : state;
        }
    };
});

Upvotes: 1

Related Questions