Have you ever tried to share a freshly designed UI-component as a library? Recently, I was creating some ripple-effect animations in Svelte and decided to publish them in a library. It is neatly compiled with all the runtime, so it can be used without Svelte. To target the large audience of React developers, I also wrote a convenient React wrapper component for it. At first, I thought this would be an easy task, but the build process turned out to be harder than I thought. So, I want to share my experience. I will show a simpler example here, so let' go:
The project
Let's assume, we want to share a simple component, which wraps Html content into a
The rocket is just a div, which has the borders styled in an unusual way with simple css. It is just a toy example, but I want to do this with React and Svelte, so let's start with the basic steps in Svelte:
Svelte
To start, create a folder
and in it run
svelte-div-rocket
npm init
and answer the questions to generate a package.json file. As
choose "main"
and then go ahead and create this file. For this example, I do not bother creating subfolders, for larger projects this would not be wise. Here is the content I put in the "Rocket.svelte"
:
"Rocket.svelte"
<!-- @component ### Div Rocket Wrap anything into a marvellous rocket! - @param direction string: The rocket can fly "left", "right", "up" or "down". --> <script> export let direction = "left"; </script> <style> .uplookingrocket { border-width: 50px 6px 30px 6px; border-style: solid solid groove solid; border-color: grey grey red grey; border-radius: 100% 100% 0 0; } .leftlookingrocket { border-width: 6px 30px 6px 50px; border-style: solid groove solid solid; border-color: grey red grey grey; border-radius: 100% 0 0 100%; } .downlookingrocket { border-width: 30px 6px 50px 6px; border-style: groove solid solid solid; border-color: red grey grey grey; border-radius: 0 0 100% 100%; } .rightlookingrocket { border-width: 6px 50px 6px 30px; border-style: solid solid solid groove; border-color: grey grey grey red; border-radius: 0 100% 100% 0; } </style> <div class={ direction==="right" ? "rightlookingrocket" : direction==="up"?"uplookingrocket" : direction==="down"?"downlookingrocket" : "leftlookingrocket"}> <slot/> </div>
Let me explain: first, we have a comment bracket
where the component is documented. Then there is the script, where we export a single prop <!--... -->
. In the style element, we declare 4 CSS classes, one for each facing direction. And finally, the Html section has a div, where the class is picked based on the direction prop, and we got a slot so that we can add children into the rocket.
direction
What else is to say about the component library? Not much, actually. We could add more information to the package.json, a README.md, and a license file, if we wanted to, or we could already publish this to npm. In a Svelte project, we can (after installation, obviously) use the component with
// Somecomponent.svelte <script> import Rocket from 'svelte-div-rocket'; </script> <Rocket> some text </Rocket>
That's it for Svelte.
React
Let's try the same process. Create a folder
, run react-div-rocket
, answer questions with npm init
, create this file and put this in:
"main":"Rocket.jsx"
// Rocket.jsx import React from 'react'; import './rocket.css' const Rocket = (props) => { const { direction, children } = props; return ( <div className={ (direction === "up") ? "uplookingrocket" : (direction === "down") ? "downlookingrocket" : direction === "right" ? "rightlookingrocket" : "leftlookingrocket"}> {children} </div> ) } export default Rocket;
Also, create a file
where the 4 classes from the Svelte style tag are placed. When I try to use it from a project started with rocket.css
the following error appears:
create-react-app
>../react-div-rocket/rocket.jsx 8:8
Module parse failed: Unexpected token (8:8)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org
It tells me, you can't have .jsx files in a library unless you have an extra webpack loader. The loader would have to be part of the app project, not the library, so it is no option to demand the users of our rocket library to install it. Instead, we have to transpile the jsx into plain js.
In this case, we could do this by hand or with the Babel online transpiler, since we just need to arrive here:
//Rocket.js import React from 'react'; import './rocket.css'; const Rocket = props => { const { direction, children } = props; return /*#__PURE__*/React.createElement("div", { className: direction === "up" ? "uplookingrocket" : direction === "down" ? "downlookingrocket" : direction === "right" ? "rightlookingrocket" : "leftlookingrocket" }, children); }; export default Rocket;
In general, this is a tedious process, so let's start over and try a streamlined version. First, delete the package.json. Then run
npx create-react-library
and answer all the questions. Depending on your computer and internet connection speed, you'll have time to grab some coffee. Small recap, the ready-to-use Svelte library has 2 files and uses ~2 kB disk space, and has zero dependencies. To be fair, it does not have a test environment, an example app, or TypeScript linting and transpiling. The React folder has all that now. It takes about 397 MB (420 MB with TypeScript template) of disk space and the node_modules folder has 1087 (1096 with TypeScript) subfolders. It has no "normal" dependencies, but 19 dev-dependencies (29 with TypeScript) and 1 peer-dependency (react).
It should be easy enough to adjust the template with our rocket component. Or is it not? Copy over the
code to src/index.js, copy Rocket.jsx
into the src folder and build the library with the rocket.css
command. Then import the component into some app. And et voilĂ , nothing happens. Well, at least if the React version of the library and your project matches. If not, you get an error.
npm run build
>Quick fix: update both React versions or simply delete the React folder in the node_modules folder in our rocket library.
Assuming that there are no more errors, why are we not seeing any rockets? First, note that our CSS classes end up in
but they are renamed during the build process. If we want to style something with our class 'uplookingrocket', we would need to write something like this:
dist/index.css
import styles from './rocket.css'; <p className={styles["uplookingrocket"]} />
Adjust our code accordingly aaaand..... still nothing. You can find the reason by looking into the
. There is the following line:
Example/App.js
import 'react-div-rocket/dist/index.css'
That means, in the app, we have to import the component and the CSS file. Maybe that makes sense from a technical perspective since React/Webpack wants to bundle CSS into a single file, but from a user perspective, this is rather inconvenient. For our little project, it even questions, whether it makes sense at all. The "magic" happens in CSS, so we could only publish the CSS file in a library and let the user apply it to her Html elements.
Comparison and reasons
Both libraries work, at last. When you only look at the final results which are delivered to the consumer,
and dist/index.js
for React and dist/index.css
for Svelte, there is no big difference in size and content. Why is the process to get there so different?
Rocket.svelte
React is much older than Svelte. It has a long history and along the way, it redefined itself and with it the way we look at web apps. The most popular way today is probably to create a bundled app with
. It was not always clear that this would be the preferred operation mode by developers. It is also possible to run React completely from CDNs. This has the advantage that the React library is downloaded only once for all pages. This option explains why our library cannot contain jsx files: we can't know if the library is processed further or if it is directly called in the browser, and the browser does not understand jsx.
create-react-app
The Svelte developers on the other side could observe how the landscape of web development changed over time. Developers today transpile and bundle their apps anyway, so why not try to put the heavy lifting into the compile step? So, Svelte is a compiler. It does not have a heavy legacy backpack to carry around (yet?). It gathers all information when the final code is generated. Thus, we don't need to pre-process our library. Svelte condenses a lot that is good in React, but of course, it is much more. It has its own syntax and file type, it gets rid of the
and instead virtual DOM
, and adds a lot of little helper gimmicks that make the life of a developer much easier.
redefines reactivity
Perhaps React shows the way, that Svelte might also go one day: Commit after commit new Ideas are added and more and more use-cases are covered. At the same time, the codebase grows, and complexity increases. Cutting back in features or supported use-cases is hard. In this case, the CDN workflow makes it complicated, But also, I showed above that only little code changes are necessary, to make the React code work, basically just changing the angle brackets syntax
into <.. />
. Yet, create-react-library employs the powerful Babel toolchain. Babel's most magical trick is to transform modern code into something the "beloved" Internet Explorer understands. Identifying the code part whose sole purpose is this would be a tricky endeavor. Perhaps no unpaid volunteer or Facebook employee will ever tackle this problem. New features are sexier than cleanup.
React.createElement(...)
Conclusion
Finally, the simple rocket component is shareable with both React and Svelte. The final bundle size for both packages is small and there are only peer dependencies to React and Svelte. The Svelte component is more convenient to use since we need two imports in React.
I wanted to share my experience, because I was really surprised, how many hoops I had to jump through to get a React component library up and running. I am not saying, that there are no pain points with Svelte (especially when you need to work on the rollup process) but the contrast in this situation is quite amazing. Many developers noticed before, that the dependency tree with React projects grows to an uncontrollable level. I'm certainly not the first or the last one. Having nearly half a gigabyte of disk space blocked for the development of a simple library feels absurd. Memes about the node_modules folder are spreading. There is always a trade-off between flexibility and streamlining. React may have reached the point, where it is so widely used, that you cannot cut down the supported use-cases to the most popular ones, eg. employing a 'create-react-app`-like build process, without upsetting your userbase. It might be easier to start from scratch with something new.
This is where a framework like Svelte steps in. Developers look at these not only because they are shiny and new, but they provide value. In this case, it reduces the complexity. I did not think that people should be able to use my little React library via CDN, or that it would be better to export the css file separately. From my usual use-case of React, the additional steps I had to take to make this library work don't make much sense. Since Svelte has only one way you can use it, I need to understand less.
Update January 26. 2021: I added some morecontent to the "Comparison and reasons" section and the "Conclusion"
Bonus: Add TypeScript to the Svelte Rocket
Maybe you already read my blog post about Svelte and TypeScript. It is easier than ever to create TypeScript declaration files for our Svelte library with svelte-types-writer. Just run:
npx svelte-types-writer ./*.svelte
This generates a nice declaration file
:
Rocket.d.ts
import { SvelteComponentTyped } from 'svelte'; /** * ### Div Rocket * Wrap anything into a marvellous rocket! * - @param direction string: The rocket can fly "left", "right", "up" or "down". */ export default class Rocket extends SvelteComponentTyped<RocketProps, RocketEvents, RocketSlots> { } declare const _RocketProps: { direction?: string; }; declare const _RocketEvents: { [evt: string]: CustomEvent<any>; }; declare const _RocketSlots: { default: {}; }; export declare type RocketProps = typeof _RocketProps; export declare type RocketEvents = typeof _RocketEvents; export declare type RocketSlots = typeof _RocketSlots; export {};
and a simple forwarding file
. Update Rocket.js
, in the package.json and maybe make "main":"Rocket.js"
in the d.ts. file more user-friendly:
_RocketProps
... declare const _RocketProps: { /** The facing direction of the rocket. Default: "left".*/ direction?: "left"|"right"|"up"|"down"; }; ...
Enjoy!
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?
- Porting React components to Svelte - It might be easier than you think
- 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