When using keyValueList the rule CaseRules.LowerFirst is applied after [<CompiledName>] attribute.
I am not sure if this is a bug. But if not, how do I provide a specific compile name?
open Fable.Core
open Fable.Core.JsInterop
type MyType =
| [<CompiledName("PascalCase")>] PascalCase of string
let myList = [ PascalCase "value" ]
let myObj = keyValueList CaseRules.LowerFirst myList
printfn "%O" myObj
_Expected:_ {"PascalCase":"value"}
_Actual:_ {"pascalCase":"value"}
dotnet fable --version): 1.3.8 (in REPL2 the behavior is the same)You can use CaseRules.None instead. We can open this to discussion, it may be possible to change the behaviour at compile time but it will be difficult at runtime because currently it's not possible to know from JS if the name comes from the case name or the CompiledName attribute.
@mvsmal Until you find the workaround, you can always just do the conversion manually:
[<Emit("$2[$0] = $1")>]
let setProp (key: string) (value: obj) (objectLiteral: obj) : unit = jsNative
type Options =
| PascalCase of string
| CamelCase of string
let createLiteral (opts: Options list) =
let emptyLiteral = obj()
let combine state elem =
match elem with
| PascalCase value ->
setProp "PascalCase" value state
state
| CamelCase value ->
setProp "camelCase" value state
state
List.fold combine emptyLiteral opts
let custom = createLiteral [ PascalCase "first"; CamelCase "second" ]
Fable.Import.Browser.console.log(custom)
// { PascalCase: "first", camelCase: "second" }
Thanks @Zaid-Ajaj, but it is not an option, I have too many cases. I actually found a workaround I think.
type Options =
| [<Erase>] Custom of string * obj
let myList = [ Custom ("PascalCase", "value") ]
Erased cases keep their letter casing.
However I would encourage you to rethink the approach, it is quite confusing that [<CompiledName>] is not accurate 馃槙
Well, it's accurate in a sense :) The compiled name in JS is whatever you have in the attribute (which btw I'm not sure it happens in .NET F#) but then you call keyValueList with the meaning "build a JS object using the following list of union cases representing key and value pairs applying this case rules to the key names". The way to compile case names and the keyValueList helper are two different things, albeit related.
I use keyValueList for Reacts props. I have DUs implementing IHTMLProp with hundreds of cases in total, also HTMLAttr could be passed in the same list since it implements IHTMLProp as well.
The problem is that Material-UI, which I have bindings for, sometimes needs props exactly in PascalCase. So in this case I don't have an option except HTMLAttr.Custom but then I cannot provide a DU case for such prop as e.g. ModalProps and users must use Custom too.
With [<CompiledName>] that would absolutely straightforward.
Hope you get my point.
The problem is that keyValueList isn't working only at compile time but at runtime time. And we currently have no information that the property was generated using CompiledName. Adding this info would increase the bundle size and need an extract check per properties convertion and could reduce performance.
If you are designing an API which is prefixed. It's easy to hide the Custom under the hood for the end user.
module Component =
type Props =
| [<Erase>] Custom of string * obj
| Width of float
let SpecialPropA (h : float) = Custom ("SpecialPropA", h)
// By adding a generic arg you can inline the call in the user coe
let inline SpecialPropB<'a> (h : float) = Custom ("SpecialPropB", h)
let x =
[ Component.Width 3.
Component.SpecialPropA 5.
Component.SpecialPropB 5. ]
And like that the API is consistent and easy to explore for the user
Thank you @MangelMaxime, I didn't think about this solution. So let's close this issue then.
Thanks a lot for the suggestion @MangelMaxime! I just edited your code to add the [<Erase>] attribute to Custom.
BTW,
keyValueListis resolved at compile time when possible but this is not always the case so as you say we do need to take always runtime resolution in mind.
@mvsmal Sorry, I forgot. There was actually a similar problem with React Native and the solution was to use a different union type for the rules that must be in Pascal Case, then in the helper to build the options object, you can split the lists and use different CaseRules for each.
@alfonsogarciacaro Ah yes good catch :)
Most helpful comment
The problem is that
keyValueListisn't working only at compile time but at runtime time. And we currently have no information that the property was generated usingCompiledName. Adding this info would increase the bundle size and need an extract check per properties convertion and could reduce performance.If you are designing an API which is prefixed. It's easy to hide the
Customunder the hood for the end user.And like that the API is consistent and easy to explore for the user