The styleguide has the documentation on how to use our Row and Cell modules. So if you are just trying to get going using or understanding the thinking behind Row and Cell, open the frontend in your browser, press Cmd + Shift + period ('.'), and then click "styleguide" and then "grid".
In this document I want to talk about some of the implemtnation details.
We have noticed that rendering the full note list is kind of slow. Going from a blank page to a full 100 note long note list in the frontend takes a perceivably long amount of time. Somewhere around 200-500ms if I remember correctly.
I looked into why this was, and it turns out that the code is taking a long time to calculate how the view should look. This is unusual. Usually performance problems in browsers come down to the browser itself taking a long time to update and style html nodes.
So there is something unusual in our Elm code that is extremely costly. Here is what is going on.
The most computationally expensive things you can do in Elm is:
- update a record
- call a function
We do that everywhere. Like in every step of every view function. Thats just how our pattern of view components works, where we have like a Button msg record and then lots of Button msg -> Button msg functions.
The Cell msg record is huge with ~30 fields. And every function like Cell.shrink is consuming and constructing a new record. The most expensive kind of operation that can happen is happening constantly.
I think we need a V2 for Cell and Row. That, almost the same thing (indeed, maybe V1 can be refactored to using V2 internally), except the instead of using records and functions, it uses a new Attr msg type and lists respectively. So this..
Cell.fromString authorFullName
|> Cell.pad (Padding.right rightPadding)
|> Cell.withFontWeight Font.mediumWeight
|> nameSizing
|> Cell.when (enableUnreadIndicator && unread) Cell.withBoldFont
|> Cell.centerYshould become this..
Cell.fromString authorFullName
[ Cell.pad <| Padding.right rightPadding
, Cell.withFontWeight Font.MediumWeight
, nameSizing
, Cell.when (enableUnreadIndicator && unread) Cell.withBoldFont
, Cell.centerY
]and internally instead of this
type alias Cell msg =
{ width : Width
, renderBehavior : RenderBehavior
, onClick : Maybe msg
, children : List (Html msg)
}
fromString : String -> Cell msg
fromString str =
{ emptyCell | children = [ H.text str ] }
toHtml cell =
let
styles =
[ case cell.width of ..
, case cell.renderBehavior of ..
]
attrs =
[ case cell.onClick of ..
]
we do this
type alias Cell msg =
{ attrs : List (Attr msg)
, children : List (Html msg)
}
type Attr msg
= OnClick msg
| RenderBehavior RenderBehavior
| Width Width
| When Bool (Attr msg)
| None
fromString : String -> List (Attr msg) -> Cell msg
fromString string attrs =
{ attrs = attrs, children = [ H.text string ] }
toHtml cell =
let
styles =
List.map attrToStyles cell.attrs
|> List.concat
|> Css.batch
attrs =
List.map attrsToHtmlAttrs cell.attrs
|> List.concat
I benchmarked this in this ellie app, and the second approach seems to be a speed up of 10x: https://ellie-app.com/cTzZK8j5rRSa1
So, I think whenever we need to fix these performance problems, the steps could be:
- Making a
View.Cell.V2that is a simple translation of the v1 code into this v2 form, where everyCell msg -> Cell msghas anAttr msgalternative. - Refactoring
View.Cellto useView.Cell.V2internally - Either all at once in one mega tedious PR, or piece by piece, replace every instance of
View.Cellin our application with the directView.Cell.V2version. - Delete
View.Cell - Repeat steps 1 through 4 for
Rowas well.