phosphor-icons / webcomponents

A flexible icon family for the web
Other
12 stars 2 forks source link

Tree Shaking #3

Open ksweetie opened 1 year ago

ksweetie commented 1 year ago

Hello! I was wondering if you had any thoughts on reducing bundle size when using this library. We're using a relatively small number (50~) of icons, so I'd like to leverage tree shaking to only include the icons we're using. As is, the icons load in noticeably after the rest of the page, causing a flicker when they render in. I think this could be fixed if we weren't making the browser download and parse JS for thousands of icons.

I would expect this to only load a single icon when used with, for example, Vite tree shaking:

import { PhArrowBendUpRight } from '@phosphor-icons/webcomponents'

But it looks like every icon gets bundled.

Curious if you have any thoughts, ideas, workarounds, etc. Thank you!

rektdeckard commented 5 months ago

Hey @ksweetie, sorry for taking so long to respond 😅.

I recommend importing each icon separately from their fully-qualified path, rather than using the barrel file. Since the import side-effect is what registers the custom elements, you don't need to use the values and can just stick all of these in a file somewhere, then source that file:

icons.ts

import "@phosphor-icons/webcomponents/PhAvocado";
import "@phosphor-icons/webcomponents/PhCheese";
import "@phosphor-icons/webcomponents/PhChefHat";

main.tsx

import "./icons";

export default function App() {
  return (
    <main>
      <ph-avocado size={32} weight="duotone" color="darkolivegreen" />
      <ph-cheese size={32} weight="duotone" color="goldenrod" />
      <ph-chef-hat size={32} weight="duotone" color="white" />
    </main>
  );
}

This is sorta pseudo-code, because I don't know what framework (if any) you're working in, but as long as the icons are imported for effect, you'll be able to use them. Some JSX implementations will need to be taught about the custom elements to have correct type inference in your editor — for example in SolidJS I would include the following in my icons file:

icons.ts

import type { IconAttrs } from "@phosphor-icons/webcomponents";
import "@phosphor-icons/webcomponents/PhAvocado";
import "@phosphor-icons/webcomponents/PhCheese";
import "@phosphor-icons/webcomponents/PhChefHat";

declare module "solid-js" {
  namespace JSX {
    interface IntrinsicElements {
      "ph-avocado": IconAttrs;
      "ph-cheese": IconAttrs;
      "ph-chef-hat": IconAttrs;
    }
  }
}

I believe Typescript should pick up the JSX extension automatically when using them in HTML or React files.

rektdeckard commented 5 months ago

Just to put a finer point on it, as far as I know, most bundlers will not attempt to tree-shake if modules are marked as having side-effects. When importing something directly from the barrel file as you showed in your example, the bundler has no way of knowing that it is OK not to load the other imports in that file besides PhArrowBendUpRight, because important things might happen when a module is loaded.

We rely on those side-effects to register the custom element type on the window, so we can't really do the barrel-file package structure. We could, but we'd have to then force you to call some method manually to register the custom elements you use:

import { register, PhArrowBendUpRight } from "@phosphor-icons/webcomponents";

register(PhArrowBendUpRight);