Svelte lures developers with the promise of better performance and smaller bundle size than competitors like React. But, since it is new, its eco-system is small. Let's change that!
There are many differences between Svelte and React. Svelte is a compiler, while React lives in the browser. Svelte components are written into '.svelte' files, while React uses
/jsx
. React has the virtual DOM while, Svelte doesn't. So, are these frameworks so different, that we have to write every library from scratch?
tsx
No.
I took a React library, which I like, and translated it to Svelte. It is a good exercise to see what the real differences between React and Svelte are, and to see which concepts are related. The library I chose is
. It has unimpressive 295 stars as a standalone library at GitHub, but it belongs to the react-virtualized-auto-sizer
library, which is currently at 21 thousand stars. For comparison, the highest-rated Svelte-specific library besides Svelte and Sapper is react-virtualized
with 1.5k stars. The svelte-material-ui
allows your component to grow or shrink to the available space on your page.
auto-sizer
Porting Auto-Sizer
>Here is a link to the finished library: svelte-virtualized-auto-sizer
The library
has only one exported component, so it is quite a low-hanging fruit to port. It is older, so it is implemented as a React class component. React components have more lifecycle methods than in Svelte, so it may be hard or impossible to reproduce the same behavior. In this case, it will be no problem.
react-virtualized-auto-sizer
AutoSizer component
There are only two files of interest in the library. The first file is
and it contains only code, that is independent of React. So, I could simply copy it into my project. The other file is src/vendor/detectElementResize.js
. The author used Flow for static type checking, so it starts with a lot of type definitions. Other than that, the file just holds the component source code.
src/index.js
In the root of my new project
I create a file svelte-virtualized-auto-sizer
.
AutoSizer.svelte
Props
First, in the scripts part I've put all props of the React component, except children and style. Both of these are handled a bit differently in Svelte. Some of the props have a default, so I copied that over:
/// react-virtualized-auto-sizer/src/index.js export default class AutoSizer extends React.PureComponent<Props, State> { static defaultProps = { onResize: () => {}, disableHeight: false, disableWidth: false, style: {}, }; ...
This becomes the following:
/// svelte-virtualized-auto-sizer/index.js <script> export let onResize = () => {}; export let disableHeight = false; export let disableWidth = false; export let className = ""; export let defaultHeight = undefined; export let defaultWidth = undefined; export let nonce = undefined; ... </script> ...
State
The React component defines a state variable and a few class member:
/// react-virtualized-auto-sizer/src/index.js ... state = { height: this.props.defaultHeight || 0, width: this.props.defaultWidth || 0, }; _parentNode: ?HTMLElement; _autoSizer: ?HTMLElement; _detectElementResize: DetectElementResize; ...
which both become simple state variables in Svelte:
/// svelte-virtualized-auto-sizer/index.js ... let height = defaultHeight || 0, width = defaultWidth || 0; let _parentNode, _autoSizer, _detectElementResize; ...
componentDidMount and componentWillUnmount
The code from the
and componentDidMount
member functions is put into Sveltes componentWillUnmount
and onMount
callbacks respecitvely.
onDestroy
/// react-virtualized-auto-sizer/src/index.js ... componentDidMount() { const {nonce} = this.props; if ( this._autoSizer && ...
The variable unloading (
) is deleted and also all const {nonce}...
-s are also removed (thank you vs-code find-and-replace function :) ):
this
/// svelte-virtualized-auto-sizer/index.js ... onMount(() => { if ( _autoSizer && ...
_onResize
The "
" member becomes a const function "_onResize = () => {...
". Again, props unloading is deleted. The React component uses const _onResize = () => {...
and width
again, so I had to rename the new consts to height
and width_
. And the height_
part is changed:
setState
/// react-virtualized-auto-sizer/src/index.js ... if ( (!disableHeight && this.state.height !== newHeight) || (!disableWidth && this.state.width !== newWidth) ) { this.setState({ height: height - paddingTop - paddingBottom, width: width - paddingLeft - paddingRight, }); ...
to simple assignment:
/// svelte-virtualized-auto-sizer/index.js ... if ( (!disableHeight && height !== newHeight) || (!disableWidth && width !== newWidth) ) { height = height_ - paddingTop - paddingBottom; width = width_ - paddingLeft - paddingRight; ...
render
So far, there have been no significant changes. The syntax is a little bit different, but not much. For the
function we got a little more to do. Let's break this down into four steps.
render
Step 1 - Styling: The React code defines a
object
outerStyle
/// svelte-virtualized-auto-sizer/index.js ... render() { ... const outerStyle: Object = {overflow: 'visible'}; ...
and in the code, the height and the width of this style object can be set to 0. In Svelte, inline styling is not supplied as an object, but as a string. An equivalent implementation is straightforward, but I take a different route here since Svelte makes it easier to avoid inline styling. I create two boolean variables
, which are set to true when the let outerstylewidth = false, outerstyleheight = false
or width
style would have been set to 0 by the React component. Then I add the following style element to the height
file and make the two classes conditional with the .svelte
directive:
class:
... <style> div { overflow: visible; } .outerstylewidth { width:0; } .outerstyleheight{ height:0; } </style> <div class={className} class:outerstylewidth class:outerstyleheight bind:this={_autoSizer}> ...
Step 2 - ref -> bind: I use the The
directive as an exchange for the bind:this
-logic. Note that this makes the ref
member function unnecessary.
_setRef
Step 3 - make render code reactive: The logic part of the
function is marked as reactive, since it is called each time something changes:
render
/// svelte-virtualized-auto-sizer/index.js const childParams: Object = {}; // Avoid rendering children before the initial measurements have been collected. // At best this would just be wasting cycles. let bailoutOnChildren = false; if (!disableHeight) { if (height === 0) { bailoutOnChildren = true; } outerStyle.height = 0; childParams.height = height; } if (!disableWidth) { if (width === 0) { bailoutOnChildren = true; } outerStyle.width = 0; childParams.width = width; } ...
To:
/// svelte-virtualized-auto-sizer/index.js ... const childParams = {}; // Avoid rendering children before the initial measurements have been collected. // At best this would just be wasting cycles. let bailoutOnChildren = false, outerstylewidth = false, outerstyleheight = false; $: { bailoutOnChildren = false; if (!disableHeight) { if (height === 0) { bailoutOnChildren = true; } outerstyleheight = true; childParams.height = height; } if (!disableWidth) { if (width === 0) { bailoutOnChildren = true; } outerstylewidth = true; childParams.width = width; } } </script> ...
Step 4 return -> html: Finally, the return statement
/// svelte-virtualized-auto-sizer/index.js ... return ( <div className={className} ref={this._setRef} style={{ ...outerStyle, ...style, }}> {!bailoutOnChildren && children(childParams)} </div> );
is straightforward translated to this code. The
statement replaces the {#if}
part and children translate to slot:
{...&&...}
/// svelte-virtualized-auto-sizer/index.js ... ... <div class={className} class:outerstylewidth class:outerstyleheight bind:this={_autoSizer}> {#if !bailoutOnChildren} <slot width={childParams.width} height={childParams.height} /> {/if} </div>
Types
Typescript declarations make the life of the consumer of the library easier. The types of the React library are stored within the definitelyTyped project
.
@types/react-virtualized-auto-sizer
The React component is changed into a Svelte component:
/// @types/react-virtualized-auto-sizer/index.d.ts import * as React from "react"; ... export default class extends React.Component<AutoSizerProps> {}
To:
/// svelte-virtualized-auto-sizer/index.d.ts import {SvelteComponentTyped} from 'svelte'; ... export default class extends SvelteComponentTyped<AutoSizerProps,{},{default:{width?:number,height?:number}}> {}
How to use it
That's it, the library is ported. You can load the library with
npm install --save-dev svelte-virtualized-auto-sizer
This is how you would use the original in React:
<AutoSizer> {(height,width)=>( <div style={{!width!}}> Test </div> )} </AutoSizer>
and here is the equivalent in Svelte, using the
directive:
let:
<AutoSizer let:width={childWidth} let:height={childHeight}> <div style={"width:"+childWidth+"px;height:"+childHeight+"px;"}> Test </div> </AutoSizer>
Poll: Which React library would you love to see in Svelte?
You can select more than one, or add a new entry
Conclusion
I picked a small library to see, how easy it would be to translate it to Svelte. To me, porting
library to Svelte felt like it was straightforward. This is my first post about porting of React libraries, so I was showing all details here. Further posts are planned, I am currently half-way through porting react-virtualized-auto-sizer
, and I promise to focus on the interesting changes only.
react-window
Share this article on twitter - Vote on next topics - Support me
This might also be interesting for you:
- How to Implement Custom React Hooks in Svelte
- React Hooks in Svelte
- tree-shake-css - project page
- Sveltes tick is my new best friend - porting react-textfit to Svelte
- How to merge cells with svelte-window
- Porting React to Svelte - From react-window 1:1 to svelte-window
- Poll: Which React library would you love to see in Svelte?
- Svelte and Typescript
- How this blog is made - Basic Structure, Elder and Markdown
- How this blog is made part 2 - Make your website sketchy with Tailwind and Roughjs
- Showcase: The Descent Ripple Effect or The React Descent Ripple Effect
- Why are component libraries so complicated with React compared to Svelte?