TypeScript type for module name string

1.3k Views Asked by At

Background: TypeScript checks the modules that you try and import, and fails at compile-time if a string literal is used that does not match the name of an actual module or path to a supported file type.

For example import {Component} from 'react' and import item from './item' compile fine, but import {Component} from 'reactt' and import item from './otem' would fail to compile assuming that there is a react module installed and an item.ts file in the project but no reactt module installed and no ./otem file with the extensions .ts, .js, .json, etc in the project.

Problem: I'm not sure how to create a function where one of the arguments is a string literal that must correspond to an actual module or supported file.

function customImportEffect(moduleName: module_string_literal) {
    ...
}

What type should module_string_literal be so that compile-time checking fails when incorrect inputs such as 'reactt' or './otem' are supplied as arguments?

Reason for asking: I am trying to make a webpack file transformer (a.k.a. a loader) which adds code to the top of TypeScript files before they are transpiled, so users can have type-safe hot module reloading code. For example, imagine the following code being inserted above every typescript file before being transpiled to JS:

class HotModule {
    public static accept(moduleName: module_string_literal, onModuleReplace: Function(updatedModule: typeof import(moduleName)):void) {
        if(module.hot) {
            module.accept(moduleName.toString(), function() {
                onUpdate(require(moduleName.toString()))
            }
        }
    }
}

Then hot module reloading could be type-safe and appear more clean. Assuming that the default export from the ./item.ts file is an object with a member named message, one could write

HotModule.accept("./item", ({message: newMessage}) => {
    const oldMessage = message;
    messages[messages.indexOf(oldMessage)] = message = newMessage;
}

to implement hot reloading of the message instead of

if(module.hot) {
    module.hot.accept("./item", function() {
        const oldMessage = message;
        messages[messages.indexOf(oldMessage)] = message = require("./item");
    }
}

I am hoping this may be possible because since TypeScript v2.4 there has been the ability to asynchronously dynamically import modules, and the string literal supplied is checked for correctness, and incorrect string literals cause a compile-time failure.

Currently I'm just including "types": ["webpack-env"] in the compilerOptions object in my tsconfig.json file, but I'd like a hot module API which takes better advantage of TypeScript's checking toolset.

1

There are 1 best solutions below

2
On

make a module-type.d.ts somewhere with the content

import json from "./package.json" // or whereever your package json is
export type ModuleName=keyof typeof json["dependencies"]

you will need to enable jsonmodule in your tsconfig

"resolveJsonModule": true,

then you import it wherever you need it