-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Spike: Research reconversion scope #7729
Comments
Check slot conversion alongside. |
Rendering performanceThe first aspect that I've wanted to tackle is performance. The way the current renderer works is to make minimal changes to the DOM to have good editing experience:
This plays nice with atomic converters and a minimal set of changes. Which is OK in most of the aspects of the editing features. However, we already know that some (possibly small) subset of features is almost impossible to have some features written this way. (ekhm tables). Test dataTo test how costly is this re-rendering stuff I've investigated "big" table of 30 rows x 40 columns. In this table 10 first rows are headings. Both heading and body sections has two rows with every cell merged. The action is to merge those two cells from adjacent rows (in one section):
Note: From the user perspective, both actions look as similarly complex - just two cells merged. Analyzed data:
Impact on performanceMerge in body
Merge in thead:
The command action is perceived as sloooow. Noticeable lag, but no freeze:
Below are the flame chart of actions. As you can see there are many style recalculations (after One of many Every change triggers recalculate style: Comparison to
|
I had some random thoughts on how we can approach this whole re-rendering stuff:
Ad. 2.:
Ad. 3.: I've updated the slot conversion POC and AFAICS it works like most of the "complex" view structures. But at least in tables we only have a one slot there because of |
I've tried to compare how CKEditor inspector renders view (using declarative React API) and it looks like we're on par with it (however I might mis-read what's happening there since the code is obfuscated).
|
After team talks around this: So the renderer is not the only thing that might need refreshment. The other problem is that we do not have a mechanism for re-using view tree structure. The thing is that as defined by a slot conversion marking some part of view tree as a slot might help with re-rendering complex tree structures. The initial idea behind this is to research possible API that could ease marking part of a tree as "not-changed" or outside the change. In other words
|
That ❤️ I'd research it first from this angle. Let's see what we can design in terms of API and paradigms involved in the conversion. And if we'll be happy with that we can think what we can change on the internal/low level in the engine to make such changes possible. Right now, most of the conversion API was designed bottom up – from the lowest level first to the sugar being added later. It worked in this sense that the conversion mechanism is not completely oriented around a couple end-developer cases that would came to our mind back when the whole thing was born. However, the end result is not very friendly and thus we need to reiterate now by looking from the other perspective to shape the sugar layer. We may need to change the internals too, but I think that we don't need too big changes. |
So for a POC / spike research I've came with a complex "box" element which:
The model is quite elegant this way: <box meta="{foo:'foo',bar:{baz: 'bar-baz'}}">
<boxField>
<p>Foo bar</p>
<ul>
<li>baz</li>
</ul>
</boxField>
...
<boxField></boxField>
</box> But in order to properly render Now, an elegant way would be to create an |
In order to start working on this, I've started with implementing upcast & downcast converters to have a real-world scenario of new API usage. At first, I've tried to use
0b1e50e: So I had to write full event-based converter for upcast. The downcast doesn't require me to do a similar thing as downcast conversion has model-to-view mappings. In order to re-render Additionally, I've added a downcast converter decorator that adds Next steps:
|
7d85e32: After some battle with
I had to fix some Table post fixer as it has wrongly change type detection. At this moment the Next steps:
|
7be4b06: I think that this is going in a good direction - a working example of a slot conversion with a minimal API changes:
In the GIF below on UI action I'm changing Notice that although main The funny thing is that my decorator doesn't catch updated on inner meta & filed elements, but maybe it is OK. The second interesting thing is that fist attribute change updates the DOM structure (all |
Idea: how slot should be defined rendered? I'm thinking where the slot re-rendering should happen? Assuming that slot-holder needs to be re-rendered but the slot itself remained the same (ie was only moved around) maybe we can also re-use that? Re-creating big view structures (tables) will be time-consuming so I still see a place for atomic converters. Also, table conversion might be simpler if it's logic would be split to separate components. So table converter would only define the layout of the view and where each "slot" (table cell) should be rendered. And after writing this it seems that I want JSX here as well. But let me write it in code. Instead of writing downcast like this: ckeditor5/tests/manual/article.js Lines 144 to 147 in 9bd831f
I'd image writing this as: for ( const field of modelElement.getChildren() ) {
const insertPosition = writer.createPositionAt( contentWrap, field.index )
const slot = writer.createSlotFor( field );
writer.insert( insertPosition, slot );
} or even: writer.insertSlot( field, contentWrap, field.index );
// model element - for which model element this slot is
// view parent - for insert position (so positionOrElement pattern here)
// offset - for insert position Having this would require me to implement two converters though, one for layout and second for slot element: editor.conversion.for( 'downcast' )
.elementToElement( { model: 'box', view: boxLayoutConverter() } )
.elementToElement( { model: 'boxField: view: { name: 'div', class: 'box-content-field' } ); Now, having this the downcast conversion may just re-use whole view element for a "slot" based on some heuristic (ie no attribute changes or rendered element is the same as current). On second though - heuristic based on model would be better since "slot" view element might also contains "slots" (but I don't know if I want to go deeper for now) or it's view structure might be a multi-level |
Doh.. I've just realised that table cell might be |
Another thing to consider: binding model and view elements need to be updated as well. In most cases we have 1:1 mapping since most features have "flat" view representation. However for multi-level views, we do: conversionApi.mapper.bindElements( model, viewOuter ); // 1
conversionApi.mapper.bindElements( model, viewInner ); // 2
// A mapper.bindElements( modelElement, viewElement ):
this._modelToViewMapping.set( modelElement, viewElement );
this._viewToModelMapping.set( viewElement, modelElement ); Which basically will do:
Again, this looks like a missing API and I wonder how this even work now :D From my understanding we need those mapping, among other things, to map positions. I think that for multi-level structures we need new kind of binding, right now I'm thinking about: conversionApi.mapper.bindElements( model, outer, [ inner ] ); This basically would allow us to properly support multi-level elements (tables, images, anything that goes into a |
I'm investigating |
aa55061: The trigger by was fairly easy to implement, probably would need internals though. The converter definition looks like that: ckeditor5/tests/manual/article.js Lines 201 to 209 in aa55061
And the "slot" binding is done here: ckeditor5/tests/manual/article.js Lines 120 to 128 in aa55061
Ps.: Add this stage the conversion does not support children add/remove but I don't see potential problems there. |
I've compiled an initial proposal for slot conversion: #6510 (comment). I'm almost sure that I've missed something. I might update it after re-reading the whole story. |
Consider also horizontal changes (right now viertical one are considered). Ie change around list item should trigger re-converting a @Reinmar idea on this is
This is inline with the POC - I need to get list of events to implement Potential problem in current code: the events are generated in loop from differ list. The problem is that single
|
Checkout Babel's JSX convertion (how to re-use). |
TypeScript 4.0 supports custom JSX factories. |
AFAIK, this is moving to the development phase, so I'm closing this spike. |
Provide a description of the task
We need to check what to actually do and which parts might cause problems, like:
The text was updated successfully, but these errors were encountered: