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
