Búsqueda de sitios web

Obtener la ruta de importación de `import()` dinámico como tipo de cadena en TypeScript


¿Es lo siguiente posible con TypeScript?

const mod = await import('./path/to/module')
// Is it possible to implement a utility type "ImportPath" that extracts
// the import path? Here the type of modImportPath would be './path/to/module'.
type modImportPath = ImportPath<typeof mod>

Contexto

Soy el autor de vite-plugin-ssr y me gustaría poder inferir el ID de la página a partir de lo siguiente.

// The vite-plugin-ssr user provides the app's routes
const routes = [
  {
    // How to infer the page's ID `LandingPage` from this dynamic import?
    Page: () => import('./pages/LandingPage'),
    route: '/'
  },
  {
    // How to infer the page's ID `AboutPage` from this dynamic import?
    Page: () => import('./pages/AboutPage'),
    route: '/about'
  }
]

Respuesta #1

Hay al menos 3 formas diferentes de implementar esta función, pero ninguna de ellas es perfecta

declare const RawImportPath: unique symbol;
declare const PageId: unique symbol;

type SplitOffStart<T, V extends string> = T extends `${string}${V}${infer E}` ? E : T;
type SplitOffEnd<T, V extends string> = T extends `${infer E}${V}${string}` ? E : T;
type SplitGetLast<T, V extends string> = T extends `${string}${V}${infer E}` ? SplitGetLast<E, V> : T;
type GetName<Path extends string> = SplitOffEnd<SplitGetLast<Path, '/' | '\\'>, '.vue'>


function myImport<T extends string>(path: T) {
    let v = import(path)
    return v as typeof v & { [RawImportPath]: T, [PageId]: GetName<T> }
    // or `return v as Promise<{ [RawImportPath]: T, [PageId]: GetName<T>}>`
    // (`any` should be excluded as it would void types)
}
function addType<T extends string>() {
    // double-function is somewhat dirty but I don't know a better way
    return <V>(v: V) => v as V & { [RawImportPath]: T, [PageId]: GetName<T> }
}
type Imported<T extends string> = { [RawImportPath]: T, [PageId]: GetName<T> } & Promise<any>

const routes = [
    {
        /**
         * The import safety(file exists) typecheck is lost
         * The import suggestions are lost
         * Module type is lost
         * (property) Page: () => Promise<any> & {
         *     [RawImportPath]: "./pages/LandingPage.vue";
         *     [PageId]: "LandingPage";
         * }
         */
        Page: () => myImport('./pages/LandingPage.vue'),
        route: '/'
    },
    {
        /**
         * Looks dirty
         * No string sameness typecheck
         * (property) Page: () => Promise<typeof import('./pages/AboutPage')> & {
         *     [RawImportPath]: "./pages/AboutPage";
         *     [PageId]: "AboutPage";
         * }
         */
        Page: () => addType<'./pages/AboutPage'>()(import('./pages/AboutPage')),
        route: '/about'
    },
    {
        /**
         * No string sameness typecheck
         * Module type is lost
         * (property) Page: () => Promise<any> & {
         *     [RawImportPath]: "./pages/AboutPage";
         *     [PageId]: "AboutPage";
         * }
         */
        Page: () => import('./pages/ThirdPage') as Imported<'./pages/ThirdPage'>,
        route: '/about'
    }
]

Mejores formas están bloqueadas por https://github.com/microsoft/TypeScript/issues/32705

Artículos relacionados