DEV Community

Cover image for Creating Standalone Widgets with Svelte: My Journey and Solutions
Breno Lira
Breno Lira

Posted on

Creating Standalone Widgets with Svelte: My Journey and Solutions

On the past months In the past few months, I was tasked with determining how to write and manage standalone widgets for my full-time job. While it was relatively straightforward to ensure they were functional, I quickly realized that maintaining them was a different challenge altogether

With this in mind, I began a side project during my spare time and made it open-source. This allowed me to share my insights and the strategies that helped me ensure the quality of my widgets.

How I did them originally?

Since my widgets required a high level of reactivity, I relied heavily on the Svelte component API and used Rollup for bundling. "It was simple and direct until I got the following problems:

  • My unused CSS increasing overtime and I was also unsure if only the CSS of the desired component was being bundled on.

  • Hard time handling JavaScript through widgets without strict typing. It rapidly became a mess since I had to share some utils like jwt decoding and authentication.

How I changed it?

I began to consider how I could establish some defaults and, more importantly, integrate a type system. This led to the creation of my side project, svelte-standalone.

The goal of svelte-standalone was:

  • Ensure a well minified CSS and remove unused CSS when bundling.
  • Ensure a type system of choice well supported and reused on all my app.

Note: the type system of choice was TypeScript.

  • Ensure unit and integration testing.
  • Ensure that I could check my widgets visually before and after rollup parsing them.

How I achieved all of that?

After ensuring TypeScript compatibility with Rollup plugins and the Svelte preprocessor, I took a step back and broke down my project into key steps. Basically I had:

  1. A <component>.svelte.
  2. A embed.js file responsible for starting the instance of <component>.svelte file and adding it to body.

From that I noticed that my embed file so was basically a default replicated on all my widgets and started generating them. So I was able to use codegen tools to generate 3 files based on my svelte files and my desire of handling the types throughout the app:

  1. declaration.d.ts - enabled that I could directly import my svelte component and wrap it using SvelteComponent type so I turned my svelte components strong typed by default.
  2. types.ts - enabled that I could write a defaultConfig based on the props declared from declaration.d.ts.
  3. embed.ts - enabled start/stop of my component in a standard way for all my widgets!

And voilà! This approach resolved my issues with the type system and improved the maintainability of my widgets.

How I Addressed CSS Challenges:

The main CSS-related challenges I faced were: How can I purge and minify my CSS without the hassle? How can I write CSS that is both easy to collaborate on and integrate into different environments?

The solution was pretty straightforward: just use Tailwind CSS.
bell curve meme: just use tailwind

With this approach, I identified the following benefits:

  • No More Conflicting Styles: Using Tailwind allowed me to stop worrying about conflicting styles. For example, when dealing with a legacy app heavily reliant on Bootstrap, I simply applied a prefix and an important flag to my widget, and the conflicts were resolved.

  • Seamless Integration: When importing my widget into another Tailwind app, I could easily omit certain Tailwind directives to reduce my bundle size.

  • Effortless Purging and Minification: Minifying became straightforward, and with Tailwind’s built-in PurgeCSS, I just needed to configure the content flag properly for each widget. This ensured that only the necessary styles were included in the final bundle.

How I Addressed The Testing Issues?

I faced a challenge in ensuring comprehensive testing for my widgets, covering unit testing, integration testing, and visual testing.

My primary goal was to visualize my components both before and after processing them with Rollup. To achieve this, I took the following steps:

  • Strictly-Typed Storybook: I implemented a strictly-typed Storybook based on my declaration.d.ts and types.ts files. This made it convenient to generate a default story for each of my widgets automatically.

  • Vite Integration: I used Vite to load the bundled component on a Svelte route. It was also convenient to generate a default route component based on my TypeScript files.

That was all! I would wholeheartedly appreciate some feedback! Also, check out svelte-standalone.

Whether you have questions, suggestions, or concerns, feel free to contact me!

Top comments (0)