aws / aws/graph-explorer

Build canvas lucide SVG without react-dom/server

Open
#2,107 0 comments 0 reactions 0 assignees View on GitHub
internal performance tech debt
Dominant language
TypeScript
Stars
481
Forks
108
Avg merge
6d 8h
Merged PRs (30d)
5

Description

Remove `react-dom/server` from the client bundle by building the cytoscape lucide SVG string from Lucide geometry data instead of rendering a React element to markup.

## Context

`utils/lucideIcons.ts` produces the canvas SVG string with:

```ts
const { default: Icon } = await dynamicIconImports[iconName]();
return renderToStaticMarkup(createElement(Icon));
```

`renderToStaticMarkup` is the only reason `react-dom/server` (~238 KB raw, ~60–80 KB gzipped) is in the client bundle — verified as the sole import across `packages/graph-explorer/src`. The DOM surfaces no longer need it at all: `VertexSymbolIcon` renders lucide through `DynamicIcon`, so only the cytoscape `background-image` still needs a string.

Every lucide icon module also exports its geometry as plain data:

```js
// dist/esm/icons/plane.mjs
const __iconNode = [["path", { d: "M17.8 19.2 …", key: "1v9wt8" }]];
export { __iconNode, Plane as default };
```

`DynamicIcon` itself reads `__iconNode`, so this is the same data lucide-react renders from.

## Why this was deferred

PR #1777 had a hand-rolled `buildSvgString` + `escapeXmlAttr` and its review removed it — finding **R7, "`buildSvgString` unvalidated tag interpolation"**, resolved as "no longer constructing SVG manually". Reintroducing string concatenation reopens a closed finding, which is a conversation this branch did not need to have.

**Build via `document.createElementNS` + `XMLSerializer`, not string concatenation.** With no interpolation there is no injection surface to validate, so R7 does not apply by construction, and `XMLSerializer` is already used in `core/icons/iconImageUrl.ts`.

## Scope

- Pin `lucide-react` to exact `1.20.0` (currently `^1.20.0`) — `__iconNode` is exported and typed but underscore-prefixed, so treat it as semi-public.
- Add a shape-assertion test so a future bump that changes `__iconNode` fails CI loudly rather than silently rendering blank icons.
- Build the canvas SVG from `__iconNode` + lucide’s `defaultAttributes` via `createElementNS`.
- Drop `renderToStaticMarkup` and confirm `react-dom/server` leaves the bundle.

## Notes from investigation

Across all 1985 icon modules in 1.20.0:

- Only 7 element types: `circle`, `ellipse`, `line`, `path`, `polygon`, `polyline`, `rect`. No `g`.
- Zero icons use `id`, `defs`, gradients, masks, `clipPath`, or `style`, so there is no collision surface.
- All icon-level attribute keys are already lowercase. React’s `key` must be stripped rather than emitted.
- camelCase conversion is needed for exactly three wrapper attributes, all from the fixed `defaultAttributes`: `strokeWidth`, `strokeLinecap`, `strokeLinejoin`. A camelCase attribute emitted into raw SVG is silently ignored by the browser, so this is the main correctness risk.
- 10 icons set their own `fill="currentColor"` (`vault`, `tag`, `tags`, `chart-scatter`, …), so per-element `fill` must be emitted when present.

## Out of scope

Recoloring custom SVG icons — tracked in #2105.

## Related

Follows the icon pipeline rework in #2102. Background in `docs/adr/20260813-icon-registry-not-react-query.md` (see the final consequence).

---

> [!IMPORTANT]
> Internal only — this issue is maintained by the core team and is not accepting external contributions.

Contributor guide

Open the contributing guide

Research direction

Start in packages/graph-explorer/src/utils/lucideIcons.ts and inspect core/icons/iconImageUrl.ts for the existing XMLSerializer usage. Add the requested __iconNode shape assertion, verify the lucide-react pin and bundle dependency changes, and confirm react-dom/server is no longer included while canvas icons still render.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
build-system, frontend
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.