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

What are the capabilities / limitations of the plugins? #2489

Closed
MangelMaxime opened this issue Jul 21, 2021 · 6 comments
Closed

What are the capabilities / limitations of the plugins? #2489

MangelMaxime opened this issue Jul 21, 2021 · 6 comments

Comments

@MangelMaxime
Copy link
Member

MangelMaxime commented Jul 21, 2021

Description

Hello,
Fable 3 re-introduced a plugin system but I think it still doesn't have documentation on it.

In current form what are the capabilities and limitation of the plugins?

Here are some questions I have in mind but if you have others things to add to the list please do :)

Can they allow to rewrite a type definition? For example, if I attach a [<Pojo>]attribute to a record can I control how it is defined or used?

Can it allow modification of the import paths? For example, could it intercept all the ./.fable/fable-library.3.2.9/XXX calls and rewrite them to use a fable-library which could be an npm package?

Can it prevent generation of some of the code like removing reflection informations?

Can it only rewrite "function call" like Feliz does?

@MangelMaxime
Copy link
Member Author

Can the plugins rewrite the module generated code?

For example, for this code

module TestB =

    let getName (v : TestB) =
        if v.Type = 1 then
            "Class"
        else
            "Function"

can it generates something like

export let Config = {}

Config.getName = function (v) {
    if (v.Type === 1) {
        return "Class";
    }
    else {
        return "Function";
    }
}

// or

export const Config = {
    getName: function (v) {
        if (v.Type === 1) {
            return "Class";
        }
        else {
            return "Function";
        }
    }
}

instead of

export function TestBModule_getName(v) {
    if (v.Type === 1) {
        return "Class";
    }
    else {
        return "Function";
    }
}

@alfonsogarciacaro
Copy link
Member

I should write some documentation about plugins, shouldn't I? 😅 Only thing is plugins have access to the internal AST and this is the only thing that's going to change for JS in Fable 4 so maybe it's better to keep them "a secret" a bit longer.

Anyways, I'll try to quickly summarize the mechanism of plugins: Fable 3 allows for libraries to tell Fable it should load and scan the .dll for "hooks" to different steps of the compilation. This is similar to Type Providers in the sense the user doesn't need to pass explicitly the plugins to the compiler (as it happened with Fable 1). At the moment there're only two such hooks (intercepting function declarations and function calls), we can add more but as said above maybe we should wait until Fable 4 for expanding plugin capabilities. And also like TPs, plugins introduce complexity to the compilation process so it's better to explore alternatives before implementing one.

Can they allow to rewrite a type definition?

Not at the moment, but as well as we have a plugin to transform function declaration we probably should add one to alter type declarations. The problem is the "control how it's used" part, because unlike functions that can only be called, types can be used in multiple ways so we would have to intercept too many places for this to work correctly.

Can it allow modification of the import paths?

For this we would likely need a plugin that goes through all the expressions. Although I wouldn't recommend it for this particular case: it's hard to maintain an alternative fable-library in sync with all changes, we may change the way code from fable-library is imported (this may happen in Fable 4) and if you really need something like this you can just make a search & replace in the generated files. In any case, we should probably bring back the Replacements plugin from Fable 1 that was used to provide alternative implementations to FSharp.Core and BCL apis.

Can it prevent generation of some of the code like removing reflection informations?

Not at the moment. We could add a plugin for that, or just a compiler option. Although reflection information is designed to be tree shaken if not used.

Can the plugins rewrite the module generated code?

For that specific example, we could use a feature of Emit that I removed in Fable 3 because I thought nobody used it but it's very easy to bring back: add a ! after an argument placeholder to mean a literal string argument must be emitted as is. For example:

let private getName x = if x > 5 then "foo" else "bar"

[<Emit("export const $0! = { $1!: $2 }")>]
let exportAsObject (name: string) (property: string) (value: obj): unit = jsNative

exportAsObject "Config" (nameof(getName)) getName

This would generate the following code:

function getName(x) {
    if (x > 5) { return "foo"; }
    else { return "bar"; }
}

export const Config = { getName: ((x) => getName(x)) };

@MangelMaxime
Copy link
Member Author

Thank you for all your answers.

I should write some documentation about plugins, shouldn't I? 😅

It would be nice yes ^^ as if it is not documented then it is harder to explore :)

Can they allow to rewrite a type definition?

Not at the moment, but as well as we have a plugin to transform function declaration we probably should add one to alter type declarations. The problem is the "control how it's used" part, because unlike functions that can only be called, types can be used in multiple ways so we would have to intercept too many places for this to work correctly.

This is also my though on it.

Can it prevent generation of some of the code like removing reflection informations?

Not at the moment. We could add a plugin for that, or just a compiler option. Although reflection information is designed to be tree shaken if not used.

The thing is that if you want to serve the F# file as they are generated without post-processing or bundler then there are a "lot" of noise in the module. Because, you see all the Fable specific codes in it like Reflection etc. That's why I asked and also kind of because I really don't like Reflection ^^

Can it allow modification of the import paths?

For this we would likely need a plugin that goes through all the expressions. Although I wouldn't recommend it for this particular case: it's hard to maintain an alternative fable-library in sync with all changes, we may change the way code from fable-library is imported (this may happen in Fable 4) and if you really need something like this you can just make a search & replace in the generated files. In any case, we should probably bring back the Replacements plugin from Fable 1 that was used to provide alternative implementations to FSharp.Core and BCL apis.

The search and replace should indeed not be too hard to do if I needed. That's was a solution I had in mind, as looking up for certain pattern is not too hard to do.

Can the plugins rewrite the module generated code?

For that specific example, we could use a feature of Emit that I removed in Fable 3 because I thought nobody used it but it's very easy to bring back: add a ! after an argument placeholder to mean a literal string argument must be emitted as is. For example:

I didn't know of this feature at the time. I thought for a second we could use emitJsExpr or emitJsStatement to avoid re-adding the ! feature but it does need it too otherwise it generate "string".

let inline exportAsObject (name: string) (property: string) (value: obj): unit = 
    emitJsExpr (name, property, value)
        """
export const $0 = { $1: $2 };
        """

exportAsObject "Config" (nameof(getName)) getName

generates

export const "Config" = { "getName": ((x) => getName(x)) };

instead of

export const Config = { getName: ((x) => getName(x)) };

@alfonsogarciacaro
Copy link
Member

Closing for now, please reopen if further details are needed. If you want to add some of the discussed features (e.g. the compiler flag to skip reflection), can you please open a separate issue for that?

BTW, the Emit trick to emit literal string arguments with $0! is working at least in latest Fable.

@MangelMaxime
Copy link
Member Author

@alfonsogarciacaro I will do.

I kind of put on the same my work on Glutinum but plan on going back to it in the future. I am first trying to clean my backlog of tasks :)

@alfonsogarciacaro
Copy link
Member

I gave it a go :) #2710

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants