Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Computed import types #44636

Open
5 tasks done
reverofevil opened this issue Jun 17, 2021 · 9 comments
Open
5 tasks done

Computed import types #44636

reverofevil opened this issue Jun 17, 2021 · 9 comments
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript

Comments

@reverofevil
Copy link

reverofevil commented Jun 17, 2021

Suggestion

πŸ” Search Terms

import label:feature-request

βœ… Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

⭐ Suggestion

Add ability to typeof import() a string literal type

πŸ“ƒ Motivating Example

declare module 'test' {
    export const test: number;
}

type GetExport<T extends string> = typeof import(T)

type Test = GetExport<'test'> // typeof import('test') β‰ˆ {test: number}

πŸ’» Use Cases

babel, eslint and webpack use package names in their API, dynamically require them and proxy data into their interface. Generally, API that looks like

doSomething({
    package: 'package-name',
    options: {
        optionForThisPackage: 'value',
    },
});

could greatly improve on typing with this.

Also it's often the case with dynamic imports that resulting Promise gets passed through several other methods (like React.lazy or loadable from @loadable/component). Currently the only way to get good types there is to have these duplicated at every import() site. It would be possible to assign some types to wrapper function in this case too.

type LazyRegistry<Packages extends Record<string, string>> = {
    [Name in keyof Packages]: Promise<(typeof import(Name))[Packages[Name]]>
}
const lazyRegistry = <Packages extends Record<string, string>>(packages: Packages): LazyRegistry<Packages> => {
    return Object.fromEntries(
        Object.entries(packages).map(([name, importName]) => {
            const lazy = React.lazy(async () => {
                return (await import(name))[importName];
            });
            return [key, lazy] as const;
        }),
    ) as LazyRegistry<Packages>;
};

type LoadableRegistry<Packages extends Record<string, string>> = {
    [Name in keyof Packages]: AsLoadableComponent<typeof import(Packages[Name])>
}
const loadableRegistry = <Packages extends Record<string, string>>(packages: Packages) => {
    return Object.fromEntries(
        Object.entries(packages).map(([name, importName]) => {
            return [key, loadable(() => import(importName))] as const;
        }),
    ) as LoadableRegistry<Packages>;
};

const DynamicComponentRegistry = someRegistry({
    Foo: 'Foo',
    Bar: 'Bar',
});
@RyanCavanaugh RyanCavanaugh added Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript labels Jun 17, 2021
@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Jun 17, 2021

This is a pretty heavy lift from the architecture side since, during the typechecking phase, we generally don't go do new expensive I/O. We could potentially allow it for modules that are already in scope (probably pretty common) but if you already know the completely list of modules you might want to look up this way, it's pretty straightforward to write a helper type somewhere with that list instead of using typeof import at the use site

@reverofevil
Copy link
Author

reverofevil commented Jun 17, 2021

@RyanCavanaugh Doesn't typeof import() already do the same thing during typechecking phase?

The criteria "module has to be in scope" doesn't apply to babel, webpack and eslint, because neither of them imports modules from callee side at all.

If you let me add my subjective 2 cents, this feature is required to correctly type check at least 5 core frontend libraries, so it seems this is quite an important heavy lift to do.

@jespertheend
Copy link
Contributor

My use case is that my library has support for mocking dynamic imports. Essentially the api looks like this:

const {mockedImport} = await importer.import("path/to/import.js");

The api closely mimics dynamic import() syntax. So it would be nice if the returned type from this function would return the same type as you would get when actually dynamically importing it. If this were supported the signature could look like:

async importWrapper<T extends string>(url: T) : Promise<typeof import(T)>

Right now I'm resorting to using the module itself as a generic parameter. But using it like this is kind of ugly:

const {mockedImport} = await importer.import<typeof import("path/to/import.js")>("path/to/import.js")

@yarinsa
Copy link

yarinsa commented Apr 3, 2023

Having the same issue with vitest, this could be a nice feature!

import { vi } from "vitest";

export function importActual<T extends Parameters<typeof vi.importActual>>([path, ...args]: T): ReturnType<typeof import(`${T[0]}`)> {
    return vi.importActual(path, ...args);
}

@robbiespeed
Copy link

robbiespeed commented Apr 4, 2023

@RyanCavanaugh would it help if there was a new module primitive, such that any generic of type module is part of a first pass (before general type checking) which does all I/O. Then during type checking phase is performing a lookup on a table of all modules that were loaded during I/O phase.

It seems like something like this might be necessary if module expressions land. I imagine module expressions would be type module & Module (Module being the type for the class instance).

Usage could look like this:

function lazyImport <M extends module>(pathOrModule: M): () => typeof import(M) {
  return () => import(pathOrModule);
}

@AliakseiMartsinkevich
Copy link

To add one more use case, we use jest in out React application. And jest have ability to mock modules. To keep it typesafe we use the following pattern:

jest.mock(
  'path/to/module',
  (): typeof import('path/to/module') => ({  }),
);

If we could use generic import types it would make it less verbose and would remove duplication of path.

@DerGernTod
Copy link

same issue with jest and mocked functions. if you go the road and import the globals (instead of using ambient types), jest.SpiedFunction<T> expects a generic. if you combine this with isolateModules where you always want dynamic imports, you get types like this:

const myFnSpy = jest.SpiedFunction<typeof import("my-imported-module")["myImportedFunction"]>;

you can imagine this getting a bit verbose if you need a lot of spies in a test

@testersen
Copy link

When can we expect this to land?

@imtaotao
Copy link

imtaotao commented Apr 9, 2024

What's the current status of this feature? Is there an expected support time?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Awaiting More Feedback This means we'd like to hear from more people who would be helped by this feature Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

9 participants