I am creating a project in which I want to easily define new plugins and I want to use TypeScript in my IDE to help me use the names of these plugins. I have a folder structure like this:
src
│ ...
└── plugins
└── pluginA
| index.ts
└── pluginB
| index.ts
└── ...
index.ts <-- "here I want to combine all plugins in one object"
Each plugin has a default export in index.ts
with interface like this:
interface Plugin {
name: string
...
}
src/plugins/pluginA/index.ts
export default {
name: "pluginA",
...
}
src/plugins/index.ts
import pluginA from "./pluginA"
import pluginB from "./pluginB"
...
export default {
[pluginA.name]: pluginA
[pluginB.name]: pluginB
...
}
And I want TypeScript to understand what the names of the existing plugins (to infer the Literal type of keys of the plugins).
In other words, I want to use defined plugins in my code like this:
import plugins from "src/plugins"
...
const plugin = plugins["nonExistingPlugin"] // I want the typescript to show error here, since there is no plugin with this name
// ~~~~~~~~~~~~~~~~~~~
const anotherPlugin = plugins["plug_"] // I want the IDE to autocomplete the name of the plugin from the existing ones
// "pluginA"
// "pluginB"
// "..."
All my attempts led me to the fact that TypeScript understood that the name
property of the plugin is a string
, but didn't infer Literal type.
Can TypeScript do that? And if not, is there any other way to achieve this?
CodePudding user response:
You can use as const
assertions here:
const pluginA = {
name: 'pluginA',
//
} as const
const pluginB = {
name: 'pluginB',
//
} as const
const pluginC = {
name: 'pluginC',
//
} as const
const plugins = {
[pluginA.name]: '',
[pluginB.name]: '',
[pluginC.name]: '',
}
const plugin = plugins["nonExistingPlugin"]
// Gives me a type error as expected
// Element implicitly has an 'any' type because expression of type '"nonExistingPlugin"' can't be used to index type '{ pluginA: string; pluginB: string; pluginC: string; }'.
// Property 'nonExistingPlugin' does not exist on type '{ pluginA: string; pluginB: string; pluginC: string; }'.ts(7053)
const plugin = plugins["plugin"] // Editor shows autocompletes for `pluginA`, `pluginB`, `pluginC`
CodePudding user response:
I would suggest using enum
for that:
types.d.ts
export enum PluginNames {
PluginA = 'pluginA',
PluginB = 'pluginB',
PluginC = 'pluginC',
}
interface Plugin {
name: PluginNames
}
src/plugins/pluginA/index.ts
const pluginA: Plugin = {
name: PluginNames.PluginA
}
export default pluginA
src/plugins/index.ts
import pluginA from "./pluginA"
import pluginB from "./pluginB"
...
export default {
[PluginNames.PluginA]: pluginA
[PluginNames.PluginB]: pluginB
...
}
You can also do the same using type unions:
interface Plugin {
name: 'pluginA' | 'pluginB' | 'pluginC'
}