-
-
Notifications
You must be signed in to change notification settings - Fork 110
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
allow plugins to declare an interface for their settings #58
allow plugins to declare an interface for their settings #58
Conversation
This comment has been minimized.
This comment has been minimized.
Options get even harder. 🤷♂️ Will these types support that? There are plugins that support non-objects as settings. And there are plugins that support multiple arguments For example,
|
This shouldn't be problem. interface SomePluginObjectOptions {
...
}
type SomePluginOptions = SomePluginObjectOptions | boolean
This might be difficult though... I guess we have two options.
@ChristianMurphy With this, the type definition of // *.d.ts in remark-parse
declare module 'unified' {
interface Processor {
use(RemarkParseAttacher, RemarkParseOptions): Processor
}
} I'll prepare some examples tonight so we could review it together. |
@ChristianMurphy @wooorm But I think using generics with module-augmentation is NOT a good idea. If However, module-augmentation is not a silver bullet. Because we cannot extend So to consider DX, I think using module augment without generics should be better although we cannot type-check for the usage with tuple. |
@Rokt33r can you help me understand the Pros of using module augmentation in this case? Conceptually I tend to think about unified as a Strategy Pattern in a Builder Pattern. Put another way, at a types level, My caution with module augmentation, is that I don't see a way to check that community modules are implementing the import unified = require('unified')
interface MyCustomPlugin {
random: string;
attributes: boolean;
}
interface MyCustomPluginOptions {
custom?: 'strict_type'
}
declare module 'unified' {
interface Processor {
use(plugin: MyCustomPlugin, options: MyCustomPluginOptions): Processor
}
}
const customPlugin: MyCustomPlugin = { random: 'string', attributes: true }
// NOTE: This is valid in TypeScript, but would error when the code is run
unified().use(customPlugin, {
custom: 'strict_type'
}) Thoughts? 💭 |
I think it will prevent end users from making mistake when adopting plugins because they don't need to provide generics. After they installed And if we go with generics, type checking won't work if end users don't set generics properly.
We still can provide interfaces and types, like
Yeah, it is too exaggerated... I just wanted to show plugin publishers can easily augment own usage of their plugins. |
I'm still confused here. 😕
Isn't this what these tests demonstrate: processor.use(typedPlugin, typedSetting)
processor.use([typedPlugin, typedSetting])
// $ExpectError
processor.use(typedPlugin, wrongTypeSettings)
// $ExpectError
processor.use([typedPlugin, wrongTypeSettings]) That the processor will validate the settings match the expected interface?
It would, for example: const implicitlyTypedPlugin = (settings?: ExamplePluginSettings) => {}
processor.use(implicitlyTypedPlugin, typedSetting)
processor.use([implicitlyTypedPlugin, typedSetting])
// $ExpectError
processor.use(implicitlyTypedPlugin, settings)
// $ExpectError
processor.use([implicitlyTypedPlugin, settings]) works, with no Generic explicitly set.
I don't see this as an either or.
It may be over exaggerated, but it demonstrates a real problem. It is also more verbose import unified = require('unified')
interface MyCustomPlugin {}
interface MyCustomPluginOptions {
custom?: 'strict_type'
}
declare module 'unified' {
interface Processor {
use(plugin: MyCustomPlugin, options: MyCustomPluginOptions): Processor
}
}
const customPlugin: MyCustomPlugin = {}
// ===================
// VS
// ===================
interface MyCustomPluginOptions {
custom?: 'strict_type'
}
const implicitlyTypedPlugin = (settings?: MyCustomPluginOptions) => {} |
@ChristianMurphy Hmm.. I changed my mind while I was writing this comment. Let's go with generics now. Anyway, if we are going to use generics for use<T1, T2>(plugin: Plugin<T1, T2>, options1: T1, options2: T2) Or should we module augment for this kind of special case? |
@wooorm is it possible for the number of parameters to exceed 2?
I like this idea. 👍 |
The changes look good to me. Now the only thing we need to decide is how many argument types should provide. I think, as long as we don't have any plan to limit the number of options, we should provide like 4 or 5 argument types. |
Yes, it is possible that there are third and other parameters. On the API side, unified and the engine support it, but it’s not used by the plugins developed inside the collective. The second parameter is used, such as by remark-rehype, and the engine passes a second parameter, which is only used by remark-validate-links. I’m not very happy with how second parameter of the engine works, and would like to change it (maybe in “uniflow”?) |
We definitely could do like: interface RemarkRehypeOptions extends ToHASTOptions {
destination: Processor
} So if we really want to modify the current behavior in future, we could merge this pr with two option params for now while making it deprecated. |
@wooorm @Rokt33r So I'm hearing a few things:
Is this accurate? |
1 is accurate! 2 and 3 are not what I meant to say above. I meant to say that I don‘t like the engine passing a second argument. Because it interferes with plugins accepting other arguments. I do think this PR is ready to go, and we could talk more about 2 and 3 somewhere else. |
How should this be released? |
Existing plugins should continue to work, they now have the option to declare a settings interface, if they don't the default generic matches the behavior from before (type |
Related to remarkjs/remark#383. Closes GH-58. Reviewed-by: Junyoung Choi <fluke8259@gmail.com> Reviewed-by: Titus Wormer <tituswormer@gmail.com>
Related to unifiedjs/unified#53. Related to unifiedjs/unified#54. Related to unifiedjs/unified#56. Related to unifiedjs/unified#57. Related to unifiedjs/unified#58. Related to unifiedjs/unified#59. Related to unifiedjs/unified#60. Related to unifiedjs/unified#61. Related to unifiedjs/unified#62. Related to unifiedjs/unified#63. Related to unifiedjs/unified#64. Related to #426. Reviewed-by: Titus Wormer <tituswormer@gmail.com> Reviewed-by: Junyoung Choi <fluke8259@gmail.com> Reviewed-by: Christian Murphy <christian.murphy.42@gmail.com> Co-authored-by: Junyoung Choi <fluke8259@gmail.com> Co-authored-by: Christian Murphy <christian.murphy.42@gmail.com>
An initial implementation for remarkjs/remark#383 (comment)
This makes plugins generic, defaulting to unified's
Settings
type.Plugins that do not specify a settings interface should not be unaffected.