Blocks with noSlot Mode

Blocks with noSlot Mode

MOHAMMAD BAGHER ABIYAT (opens in a new tab) FEB 15 2024

If you've used Million.js for a while, you've probably noticed there are <slot /> HTML elements wrapping your Million.js components. The reasoning behind that was slightly mentioned in the Behind The Block blog post but we're going to touch on that and discuss the ways we're going to try avoiding it.

History

The key point of Million.js is that it takes over the rendering step of a specifc component or a chunk of your React app and then handles it itself and mounts it to the DOM directly rather than letting React do anything on that end.

React to Million mount

The Loader component here is the bridge between Million and React! Or in other words, it's the <slot /> element. Having <slot /> would stop us from worrying about non-Million siblings or siblings that come from other blocks. In other words, it makes rendering much easier for Million.

Issues

This approach works for rendering, but there are a few issues being reported which are completely fair.

Broken Styles

In the process of adding the Million compiler, people often face this issue where the styles they used to apply do not work anymore since they did not expect there are few slots have been added here and there.

function Lion() {
  return <img src="https://million.dev/lion.svg" />;
}
 
// before Million
<img src="https://million.dev/lion.svg" />
 
// after Million
<slot>
  <img src="https://million.dev/lion.svg" />
</slot>

Unpredictable Structure

The users would find it hard to predict where <slot /> elements are going to be appended, especially when they adopt the auto mode.

<slot /> are also wrapped around some of the props in auto mode because the compiler might decide that the rendering of those props might be handled by React rather than Million. We call those "React render scope"s which are just React portals (opens in a new tab).

Solution

After 30 days of failed trial (opens in a new tab) for removing the need for <slot />, I found out this is not an easy thing to do. It was hard to predict React's behaviour, having siblings caused bugs, portals were not being rendered in right position and many complicated issues like that.

So the decision was to take this step by step as an experimentation and tackling each API (block, For, ...) separately so we see how far can we go with this.

Now million offers an experimental flag to enable the noSlot mode which avoids wrapping million blocks with <slot /> elements.

import { experimental_options } from 'million/experimental';
 
experimental_options.noSlot = true
// now `noSlot` mode is activated across your application  

block

For now, only blocks support this feature, For and React Render Scopes are yet to be supported because of the challenging behaviours faced. We'll try our best to deliver this flag for the mentioned APIs so all users would benefit from the new behaviour.

experimental_options.noSlot = true
 
function Lion() {
  return <img src="https://million.dev/lion.svg" />;
}
 
// without experimental_options.noSlot=false
<slot>
  <img src="https://million.dev/lion.svg" />
</slot>
 
// with experimental_options.noSlot=true
<img src="https://million.dev/lion.svg" />

For more information around the limitations, refer to the docs.

What's next

  • noSlot in <For />
  • noSlot in React Render Scopes

As we experimented with different implementations for the next steps, it's not apparent which implementation is the ideal and the best implementation Million.js can make. So we need more time to experiment with them so the best results are delivered to the users.