# Migrating from @moreyears/icons v1 to v2

## Why this release exists

v1 inlined every icon into a single module and resolved icons by looking up a
name string at runtime:

```js
// v1, simplified
import { svgRegistry } from './svg-registry';   // all 7,543 SVGs
return svgRegistry[weightEntry.path];           // computed key
```

Because the key is computed, no bundler can prove which icons a project actually
uses, so all of them were retained. Importing `Icon` or `getIconSvg` cost
**2.95 MB gzipped** regardless of how many icons you rendered.

v2 ships one module per icon per weight. Import what you need and the rest is
dropped by your bundler.

| | v1.1.0 | v2.0.0 |
|---|---|---|
| Import `Icon` only | 2.95 MB gzipped | 0.5 KB gzipped |
| 1 icon | 2.95 MB gzipped | 1.2 KB gzipped |
| 30 icons | 2.95 MB gzipped | 18.7 KB gzipped |
| Package unpacked | 128 MB | 71.9 MB |

Verified under both webpack 5 and Vite 5. Every icon renders byte-for-byte
identically to v1: 1,680 render cases are asserted against golden output
captured from a real v1.1.0 build.

## Run the codemod first

```bash
node scripts/codemod-v1-to-v2.js ./src            # report only
node scripts/codemod-v1-to-v2.js ./src --write    # apply
```

It rewrites static call sites, removes v1 imports that become unused, and lists
everything needing a human. It is a regex transform, so review the diff.

## What changed

### Icons are imported directly

```jsx
// v1
import { Icon } from '@moreyears/icons';
<Icon name="mailbox" weight="bold" size={24} />

// v2 - barrel import, tree-shakeable
import { Mailbox } from '@moreyears/icons/bold';
<Mailbox size={24} />

// v2 - deep import, identical result
import Mailbox from '@moreyears/icons/bold/mailbox';
<Mailbox size={24} />
```

Names are PascalCase versions of the slug: `chat-round-dots` becomes
`ChatRoundDots`. `size`, `color`, `strokeWidth`, and `className` work exactly as
they did in v1.

### Dynamic icons take a glyph, not a name

This is the pattern that made v1 untree-shakeable, so it needs the most thought.
Have the component that *knows* which icon it wants do the importing, and pass
the icon itself down:

```jsx
// v1
function Toast({ iconName }) {
  return <Icon name={iconName} weight="bold" />;
}
<Toast iconName="check-circle" />

// v2
import { Icon } from '@moreyears/icons';

function Toast({ glyph }) {
  return <Icon glyph={glyph} />;
}

import CheckCircle from '@moreyears/icons/bold/check-circle';
<Toast glyph={CheckCircle} />
```

If you genuinely need to pick from a set at runtime, import that set explicitly
and map over it. The bundle then contains those icons and nothing else:

```jsx
import CheckCircle from '@moreyears/icons/bold/check-circle';
import DangerCircle from '@moreyears/icons/bold/danger-circle';

const TONE_ICONS = { success: CheckCircle, error: DangerCircle };
<Icon glyph={TONE_ICONS[tone]} />;
```

### getIconSvg is removed

It cannot exist in a tree-shakeable form. Each icon carries its own source:

```js
// v1
import { getIconSvg } from '@moreyears/icons';
const svg = getIconSvg('mailbox', 'bold');

// v2
import Mailbox from '@moreyears/icons/bold/mailbox';
const svg = Mailbox.svg;
```

Components also expose `iconName` and `iconWeight`.

### Metadata moved to a subpath

`getIconInfo`, `getAllIcons`, `getIconsByCategory`, `getCategories`, and
`manifest` now live at `@moreyears/icons/manifest`. It carries metadata only
(0.09 MB gzipped) and pulls in no artwork, which is what an icon browser wants.

```js
// v1
import { getAllIcons, manifest } from '@moreyears/icons';

// v2
import { getAllIcons } from '@moreyears/icons/manifest';
import manifest from '@moreyears/icons/manifest';   // default import still works
```

### Colliding names

Seventeen slugs exist in two categories. Each now has an explicit
category-qualified name, and one of them also holds the bare name:

```js
import { Star } from '@moreyears/icons/bold';            // like/star
import { StarLike } from '@moreyears/icons/bold';        // same icon, explicit
import { StarAstronomy } from '@moreyears/icons/bold';   // the other one
```

**Eight bare names now resolve differently than v1 did.** v1 returned whichever
variant appeared first in the manifest, which was incidental rather than chosen.
The codemod preserves v1's behaviour by importing the qualified module, so your
existing screens do not change. This table matters only when you write new code:

| Bare name | v1 rendered | v2 `import { X }` gives |
|---|---|---|
| `Star` | astronomy/star | like/star |
| `Stars` | weather/stars | astronomy/stars |
| `Cup` | essentional-ui/cup | food-kitchen/cup |
| `Reorder` | essentional-ui/reorder | arrows-action/reorder |
| `Mirror` | essentional-ui/mirror | home-furniture/mirror |
| `Forward` | messages-conversation/forward | arrows-action/forward |
| `Notebook` | school/notebook | notes/notebook |
| `Document` | school/document | notes/document |

The other nine (`ArrowUp`, `ArrowDown`, `ArrowLeft`, `ArrowRight`, `Lightning`,
`Bacteria`, `Bill`, `Home`, `RecordCircle`) resolve exactly as before.

One group is spelled category-first: `money/bill` and `list/bill` export as
`MoneyBill` and `ListBill`, because `BillList` already belongs to the real icon
`money/bill-list`.

`4k` exports as `Icon4k`, since an identifier cannot start with a digit.

### Icons with partial weight coverage

48 icons do not exist in all six weights. They are absent from the weights they
lack, so a bad import is a build error rather than a silently empty element:

```js
import PawFlex from '@moreyears/icons/broken/paw-flex';  // build error
import PawFlex from '@moreyears/icons/bold/paw-flex';    // fine
```

### Web component type augmentation moved

The global JSX types for `<moreyears-icon>` moved from the root entry to
`@moreyears/icons/web-component`, which is what they describe. If you use the
custom element in TSX, import the subpath once:

```ts
import '@moreyears/icons/web-component';
```

### Unchanged

- The web component and its CDN script tag behave exactly as in v1.
- Raw SVG access via `@moreyears/icons/svgs/{weight}/{category}/{name}.svg`.
- `@moreyears/icons/manifest.json` for the raw manifest.
- All rendered output.

## Known gap

The CDN bundle (`dist/web-component.js`) is still 13.4 MB, because a script tag
genuinely does resolve arbitrary names at runtime. Reducing it is tracked
separately; the options and their measured costs are in the project plan.
