skip to Main Content

I want to create a UI Component Library where consumers can bundle only the styles for the components they are actually using.

Let’s say this question is framework-agnostic since I am more interested to evaluate existing approaches.

The idea would be for a user to be able to

import { Button } from '@mylib/ui`

and not being required to go to their main.ts and add

import '@mylib/ui/styles/main.min.css`

Now, obviously, the first solution is to avoid bundling the CSS together, so a user would be able to import separate stylesheet for each component

import '@mylib/ui/styles/button.min.css';
import '@mylib/ui/styles/accordion.min.css';

Now, this approach works well if you use a handful of components and want to keep the bundle size in check, but it doesn’t scale if a consumer starts using dozen of components.

How can I overcome that challenge?

The only approach I can think of is inline styling, which won’t work because of missing selectors, media queries, etc.

What I would love to achieve, is a way for consumer to declare what they need, and get the styles for that component without any additional configuration/import, while preserving the ability of only bundle the CSS that belongs to the components that are imported

import { Button } from '@mylib/ui`

I am scratching my head to come up with an idea of how this would work…

2

Answers


  1. Chosen as BEST ANSWER

    Thanks to @Maksymilian Tomczyk and his suggestion of using unbuild I've come up with a configuration that serves my need:

    // vite.config.ts
    import { fileURLToPath, URL } from "node:url";
    import { defineConfig } from "vite";
    import vue from "@vitejs/plugin-vue";
    // https://vitejs.dev/config/
    export default defineConfig({
      plugins: [vue()],
      resolve: {
        alias: {
          "@": fileURLToPath(new URL("./src", import.meta.url)),
        },
      },
      /**
       * Build is handled by Unbuild
       */
    });
    
    
    // build.config.ts
    import { defineBuildConfig } from "unbuild";
    
    export default defineBuildConfig({
      entries: [
        { builder: "mkdist", input: "./src/" },
        { builder: "mkdist", input: "./src/", format: "cjs", ext: "js" },
      ],
      declaration: true,
      clean: true,
    });
    
    {
    "name": "my-lib",
      "version": "0.0.4",
      "private": false,
      "files": [
        "dist"
      ],
      "module": "./dist/index.mjs",
      "main": "./dist/index.js",
      "types": "./dist/index.d.ts",
      "exports": {
        ".": {
          "import": "./dist/index.mjs",
          "require": "./dist/index.js"
        }
      },
      "sideEffects":false,
      "scripts": {
        "build: "unbuild"
      }
    }
    

    This is the structure of the project:

    initial src folder

    This is the final output:

    enter image description here

    This creates an untouched output that consumers can import and use their bundler to optimise tree shaking/compilation.

    Only TypeScript and SASS are pre-compiled by unbundled


  2. I think it’s hard to answer this question without considering the framework you are using. If we don’t know the framework, importing CSS sheets for each component is probably the most straightforward answer to this question.

    Knowing the framework for which the library is designed is essential because then you know what bundling tools & technologies are available. Recently I had to resolve such a problem with Vue 3 SFC library. The solution I found was to deliver library code unbundled, (transpiled only .vue files) (you can check unbuild tool).

    I believe other frameworks may offer similar solutions.
    Of course, there are downsides to such a library (for example you can’t use it without a bundler).

    However, this solution directly answers your question, as with such constructed library you are able to write code like this:

    import { Button } from '@mylib/ui`
    

    and bundler will bundle styles only from this component (as in SFC files styles are inside the file).

    PS, if you will play with this solution remember to set sideEffects: false in library package.json, so the bundler in a project which uses your library will know it can treeshake your library.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search