Merging properties in createObj
I'm trying to figure out if there's a way to merge multiple lists / sequences of key-value pairs into a single createObj call, and have them compile down to a JS object.
For example, I'd like to be able to do something like this:
type Something =
static member inline x = [
"a" ==> "b"
"c" ==> "d"
]
let y = createObj [
"e" ==> "f"
Something.x
]
and have it compile to:
const y = {
"e": "f",
"a": "b",
"c": "d"
};
I can currently do this:
let y = createObj [
"e" ==> "f"
yield! Something.x
]
But that ends up compiling to a bunch of shim calls to create the object, which I'm trying to avoid.
I also tried doing this using the Emit attribute and emitJs functions, but those end up parenthesizing my arguments which breaks the object creation syntax in JS.
Any suggestions or alternatives would be very much appreciated!
I don't think there is a way to do it like that.
I was able to do it for a single element but not for a sequence:
let inline x<'T> : (string * obj) =
"a" ==> "b"
let y = createObj [
"e" ==> "f"
x
]
generates
export const y = {
e: "f",
a: "b",
};
Are you forced to do it in a single pass? Would setting properties with setter works?
open Fable.Core
open Fable.Core.JsInterop
[<AllowNullLiteral>]
[<Global>]
type Options
[<ParamObject; Emit("$0")>]
(
searchTerm: string,
?isCaseSensitive: bool,
?limit: int
) =
member val searchTerm: string = jsNative with get, set
member val isCaseSensitive: bool option = jsNative with get, set
member val limit: int option = jsNative with get, set
let additionalOptions (o : Options) =
o.isCaseSensitive <- Some true
o.limit <- Some 10
let o = Options("a")
additionalOptions o
JS.console.log o
// Output:
// {
// "searchTerm": "a",
// "isCaseSensitive": true,
// "limit": 10
//
If you don't like [<ParamObject>] because it is too verbose, you can achieve the same with jsOption
Unfortunately I can't do it like this, since I'd like for the final object in the generated JS code to have all the fields (instead of them being assigned to it), in order to allow a babel plugin to transform it as a post-processing step. I'll keep experimenting!
Closest I've gotten is this:
[<Erase>]
type Something =
[<Emit("a: \"b\", c: \"d\"")>]
static member inline x = jsNative
[<Emit("e: \"f\"")>]
static member inline y = jsNative
[<Emit("{$0...}")>]
static member inline create ([<ParamArray>] styles: obj list) : obj = jsNative
let jsObj = Something.create [
Something.x
Something.y
]
But unfortunately this compiles to:
export const jsObj = {(a: "b", c: "d"), (e: "f")};
And I can't find any way I can make Fable omit the parentheses.
On a side note, I suspect it should be possible to do some more compiler magic in Fable to detect when user do something like:
let a =
createObj [
"a" ==> 1
yield! [
"b" ==> 2
]
]
So where the yield! but right now I am not familiar enough with that portion of Fable code to confirm it.
Been poking at the Fable internals a bit, and I think this wouldn't be trivial. When using yield!, the whole thing seems to get compiled as a sequence expression instead of a regular tuple value list, so I guess it would need quite a bit of acrobatics to get it to print the desired JS code.
I've instead been playing around with the idea of adding a new flag to EmitAttribute that controls whether the emitted expressions get parenthesized or not here:
https://github.com/fable-compiler/Fable/blob/308aceff8602cfb0f6242c7fa2e85acd29413506/src/Fable.Transforms/BabelPrinter.fs#L532-L536
Essentially adding a check in IsComplex for when expr is an EmitExpression with said flag set to false:
https://github.com/fable-compiler/Fable/blob/308aceff8602cfb0f6242c7fa2e85acd29413506/src/Fable.Transforms/BabelPrinter.fs#L509-L529
Not sure if that's a good approach for this, so if anyone who's more familiar with the internals of Fable can pitch in that'd be great!
TBH I am feeling like this is pushing Fable interop boundaries a bit too much compared to the complexity it can introduce.
Is there a reason why you can create the object in one go or build an anonymous record from scratch?
Yeah I'm not super happy with this either, so I'll probably be taking the hit in regards to the generated code and deal with it in other ways. The main reason I would like this is for the developer experience. Being able to compose objects like this would make life a lot easier while maintaining some performance characteristics that are relevant to my application.
@OrfeasZ Object.assign is your friend when merging objects
@Zaid-Ajaj That's what I've ended up doing currently, but I'd like for objects to be emitted with all their properties without having to compose that at runtime with Object.assign or otherwise.