How to add icons to SolidJS apps

The easiest way would be to use the Icon component provided by your UI library of choice. In my case, that would have been to use the Icon component of Hope-UI. Given that Hope-UI doesn’t include any icons by default, they give you a basic Icon wrapper to either import one from a library or create your own.

Being the newbie I am, I thought it would be easier to go with the latter initially: just for the couple of icons I wanted to display the effort to look for an icon library and install it seemed overwhelming.

// IconBell.tsx (HiOutlineBell from HeroIcons library)
import { Icon } from "@hope-ui/solid";

export function IconBell(props) {
  return (
    <Icon viewBox="0 0 200 200" {...props}>
      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9"></path>
    </Icon>
  );
}

// main.ts
import { IconBell } from './IconBell';
//...
<IconBell />

So you basically have to manually transform each icon before using it. But I’m so lazy when it comes to repetitive tasks that I thought to myself:

Copy-pasting SVG inner elements from some icon file to your own icon component seems such a waste of energy. Why don’t I automate it? I only need to (1.1) pass the icon name to my icon component, then (1.2) make it load the icon file, then (1.3) make it display the icon markup, et voilà. Tidy!
How hard can it be?

Quite hard indeed. In fact, so hard to make #1.3 work that after many hours of tinkering I concluded it wasn’t possible in SolidJS. I even explored some esoteric concepts like Tagged Template Literals to no avail.

Back to square one, I looked for icons libraries for SolidJS and discovered that someone already did the SVG to SolidJS component conversion for thousands of Open Source icons from many different libraries, and collected them into the solid-icons library. With that installed, things get much easier.

// main.tsx
import { HiOutlineBell as IconBell } from 'solid-icons/hi';
//...
<IconBell />

And that would be it, but you still have to deal with the facts that (2.1) each icon is its own component, (2.2) not integrated with your UI library. For (2.1), that means to import icons one by one while sticking to a naming convention like Icon<name>, to help you at keeping track of them project-wide. For (2.2), that means to wrap each icon into some basic component of your UI library of choice, to be able to format the icon to your needs using the same design system as any other component.

Hope-UI’s Icon component solves #2.2.

// main.tsx
import { Icon } from "@hope-ui/solid";
import { HiOutlineBell } from 'solid-icons/hi';
// ...
<Icon as={HiOutlineBell} />

My Icon component solves #2.1 too.

// Icon.tsx
import { splitProps } from "solid-js";
import { Dynamic } from "solid-js/web";
import { JSX } from "solid-js/jsx-runtime";
import { Box } from "@hope-ui/solid";
import * as hi from "solid-icons/hi";

export function Icon(props) {
  const [local, rest] = splitProps(props, ["name"]);
  return (
    <Box style={{ display: "inline-block" }} {...rest}>
      <Dynamic component={hi[local.name as string] as JSX.Element} />
    </Box>
  );
}

// main.tsx
import { Icon } from "./Icon";
//...
<Icon name="HiOutlineBell" />

Tree shaking shouldn’t be a concern according to this thread. However, you could just register all needed icons in a single import/export IconsRegistry file and later import * from "./IconsRegistry".


But then I remembered, out of the blue, a piece of SolidJS internal code I had seen in some issue days ago.

export namespace JSX {
  type Element =
    | Node
    | ArrayElement
    | FunctionElement
    | (string & {})
    | number
    | boolean
    | null
    | undefined;

where the JSX.Element type is just a union of many different types, including a real Node element type, and that is exactly what I needed for #1.3 !!

Here is my Icon component to load the SVG from a file and display it. Hurrah!

// BareIcon.tsx
import { Box } from "@hope-ui/solid";
import { splitProps, createResource, createSignal } from "solid-js";

function fragment(html: string) {
  const tpl = document.createElement("template");
  tpl.innerHTML = html;
  return tpl.content;
}

async function importIcon(filename) {
  const module = (await import(`../icons/${filename as string}.svg`)) as {
    default: string;
  };
  return module.default;
}

export function BareIcon(props) {
  const [local, rest] = splitProps(props, ["filename"]);
  const [filename, setFilename] = createSignal("");
  const [svg] = createResource(filename, importIcon);
  // eslint-disable-next-line solid/reactivity
  setFilename(local.filename);
  return (
    <Box style={{ display: "inline-block" }} {...rest}>
      {svg.loading ? "" : fragment(svg())}
    </Box>
  );
}

// main.tsx
import { BareIcon } from "./BareIcon";
//...
<BareIcon filename="bell" color="red" />

To make it work, you’ll need to install the Vite SVG loader plugin, and configure it to always load SVG files in RAW mode.

// vite.config.js
import { defineConfig } from 'vite';
import solidPlugin from 'vite-plugin-solid';
import eslintPlugin from 'vite-plugin-eslint';
import svgLoader from 'vite-svg-loader';

export default defineConfig({
  plugins: [
    solidPlugin({
      dev: true
    }),
    eslintPlugin(),
    svgLoader({
      defaultImport: 'raw'
    })
  ],
  //...
});

About NPM deprecation warnings

It baffles me how many deprecation warnings appear when installing some NPM package. Like if I was the only one in the world seeing them, and worrying about them.

% npm i -g truffle

Some people say: “if it’s important (e.g. a security problem), notify the package maintainers; if not, ignore the deprecation warning.

NPM deprecation warnings are displayed without any classification with respect to the dependency where they originate.

  • If the code of the deprecated package was not used at all (fake dependency), then we could safely ignore the deprecation warning.
  • If the code of the deprecated package was only used in development (dev dependency), then we could almost safely ignore the deprecation warning.
  • If the code of the deprecated package was also used in production, then we couldn’t ignore the deprecation warning without a detailed analysis.

Sample analysis

Let’s look at the first warning above:

testrpc@0.0.1: testrpc has been renamed to ganache-cli, please use this package from now on.

The deprecation message is not alarming. What should I do? Let’s research a bit more.

Here is the path from testrpc back to truffle:

% npm –global –all ls testrpc@0.0.1

/Users/andrea/.nvm/versions/node/v16.13.0/lib
└─┬ truffle@5.4.18
  └─┬ @truffle/db@0.5.36
    └─┬ @truffle/resolver@7.0.34
      └─┬ @truffle/contract@4.3.39
        └─┬ @ensdomains/ensjs@2.0.1
          └─┬ @ensdomains/ens@0.4.3
            └── testrpc@0.0.1

And here is the package.json in truffle:

package.json

Looking into package.json, we see that @truffle/db is an optional dependency, i.e. a production dependency which could also be not automatically installed when installing truffle.

Looking into @truffle/db, we see that it allows to configure truffle for using it:

db: {
  enabled: true
}

Given that I went for the default installation (and I don’t have a replacement ready), this is a production dependency for me. What should I do? Let’s research a bit more.

Googling @ensdomains/ens we get to the project page on GitHub:

https://github.com/ensdomains/ens

So this project is pretty popular, used by 7.7k other projects. And yet it uses a deprecated package. It’s still unclear what should I do.

This project was included (in its parent, @ensdomains/ensjs) at version 0.4.3. Such a version is nowhere to be found in the repo: not a release, not a tag, not a branch.

Let’s assume it’s a close ancestor of the current code in the master branch, which is marked as 0.5.0 in package.json.

This is what we get after searching for testrpc in the repo:

We see that this is a code dependency whose code is never used but the package name is cited in a README file. That’s it. it’s a fake dependency.

Now, I needed (wasted) a solid hour to find that out. That’s unreasonable.

Could a tool automate all of the above? Not really, because I had to assume that the code of the lost version 0.4.3, in all places where testrpc would be used, is equivalent to the code of the master version (maybe 0.5.0).

Posterity

There is something else that troubles me. If I wanted to persist the result of my research there is little support in the Open Source world as a whole. (I could be wrong. Please, correct me if you know better.)

A possible, but cumbersome, and incomplete solution could be one based on the following tools:

  1. selective dependency resolutions, implemented in NPM by npm-force-resolutions
  2. a fork of @ensdomains/ens (the troublesome package), where I could fix its deprecated dependencies, like aercolino/ensdomains-ens#fix-deprecations
  3. force a resolution, like
{
  "@ensdomains/ens@0.4.3": "aercolino/ensdomains-ens#fix-deprecations"
}

However, that is totally hypothetical, because:

  • the package whose installation complained about testrpc@0.0.1 is global, which means that I do not have any package.json file where I can add my resolutions
  • npm-force-resolutions is poorly documented
  • the Selective dependency resolutions feature of Yarn, upon which npm-force-resolutions is based, doesn’t seem to support versions on the left, nor full repos on the right.

In the end, my research is going to get lost (in my biological memory) and my confidence in NPM lowers.

Looking for the perfect match

Lately, I passed through many selection processes, and I still will go through some more. Did I meet a company where I really wanted to work at? Surely, for many I could have made a positive impact working there. But none was a perfect match. 1I’m not talking about cultural fit here

Why do we look for the perfect match? We should already know that such a thing doesn’t even exist. Lasting couples aren’t a perfect match. How can colleagues be a perfect match to each other? For n colleagues, that would require n * (n - 1) / 2 perfect matches. Insane.

I don’t look for the perfect match with my colleagues, but I must confess that I’m often relieved to get rejected by colleagues which present to me in the testudo formation.

Almost impossible to be admitted here, by design.

How do you expect to find a new team mate if, as soon as a discussion arises, you close ranks and state you know better. Nonetheless, they keep looking for the perfect match, which boils down to a developer with the biggest experience to write quality code without spoon-feeding her, and the smallest experience to follow orders with humbleness.

That, or likemindedness. Isn’t it a known problem to hire like minded people?

Such practices can 2I stroke this through also lead to groupthink where all go along with decisions for the sake of team harmony. Creativity, innovation and growth can 3I stroke this through suffer in the absence of diversity, vigorous debate and conflict.

I never liked to sheepishly follow others. Decades ago, when I was 10 years old, our school teacher gave us a mathematical problem to solve cooperatively. I immediately found a short solution, and shared it with the others. They dismissed it and decided to elaborate one of their own. How proud I was when our teacher validated my answer and not theirs.

During my job interviews, I genuinely make an effort to understand and value the programming decisions, mostly the architectural ones, that my yet-to-be colleagues made in the projects I’m asked to collaborate in. How could I not show my surprise when they tell me:

  • We can’t use Docker / Kubernetes because our setup is too complex
    • What? They are used at Google, I don’t think they have a simpler setup.
  • We prefer vanilla JavaScript to React, because it wouldn’t scale for us.
    • What? It’s used at Facebook, I don’t think they have a smaller scale.