SVGファイルをDOMにレンダリングする仕組みで実装

mimo氏:このような技術選択を経て、Web Componentsとして自分たちで実装を進めることになりました。ここからは、Web Componentsを自前で実装することにした理由と方法を話します。

Web Componentsとして実装が決まった@charcoal-ui/iconsは、内部でSVGアイコンを文字列として取得して、HTMLにそのタグをレンダリングするという仕組みになりました。

使うアイコンだけをimportすることができ、特定のフレームワークに依存することなく実装ができたので、無理のない実装になりました。

ちなみに、「Ionic」というライブラリの中にある「ion-icon」というアイコンライブラリが、これに近い実装になっています。そのくらい良い手段だったのかなと思っています。

実際、作るにあたっては、Vue 3の実装をお手本にしていました。Vue 3でWeb Components対応が来ると当時から言われていて、SSR対応のCustom Elementsの参考になるのではないかと思い、Vue 3の実装を見てみました。

しかし、HTMLElementsクラスというものを継承する箇所で、HTMLElementsクラスがnode環境で実行した時にundefinedになるのをどうしても回避できず、そこだけglobalThisに頼ってなんとかするという実装になって、1箇所だけglobalThisがあるのですが、それ以外は全部いい感じのコードで書くことができました。

「@charcoal-ui/icons」で良かったポイント

こうして出来上がったのが、@charcoal-ui/iconsです。そして、この@charcoal-ui/iconsが、現場での動きはどうだったのか、良かったポイントを紹介します。

まず、dependenciesがほとんどないパッケージになりました。スライドに書いていますが、「DOMPurify」と「warnings」という2つだけが残っています。もちろんpeer dependenciesもないので、ほかの大きなフレームワークには、なんの依存もありません。

そのおかげで、レガシーなプロダクトでもCSRのプロダクトでもSSRのプロダクトでも使えるライブラリになりました。依存がなかったおかげで、さまざまなプロダクトにスムーズに導入が行えました。

(スライドを示して)これが、現場から上がってきた喜びの声なのですが、ちょっと長いので「ChatGPT」というやつに要約してもらいました(笑)。

(会場笑)

「属人化していて更新しづらくて不幸になっていました。ツールでサポートされたので属人性も手間もなくなり、工数削減だけでなく参入もしやすくなりました」だそうです。

全社のプロダクトに対してアイコンを増やしたい時、@charcoal-ui/iconsでは、Figmaの所定のレイヤーにアイコンを作ってもらって、週1回のActionsでプルリクエストが作られて、それをエンジニアがレビューしてマージしてリリースすれば、アイコンの更新が完了という感じになっているので、大幅な改善になったのかなと思っています。

「@charcoal-ui/icons」における課題

一方、残っている課題がいくつかあるので、それも併せて紹介しておきます。まず、独自アイコンを使う場合は、バンドラの設定が必要という仕様になっていること。

どうしてバンドラの設定が必要になってしまったのかについては、この後詳しく解説しますのでもう少々お待ちください。

また、アクセシビリティが現段階だとおそらく不十分であることが課題になるかなと思います。

今、アクセシビリティの強化をチーム全体、Charcoal全体で行っていて、その中でいろいろ考えているのですが、Web Componentsを使っていて対応できないことが出てきた場合、これはWeb Componentsを採用したことがデメリットになるということになるかなと思います。

また、これはSVGをレンダリングする時にありがちですが、レイアウトシフトが起こりやすいという問題があります。ドキュメントサイトにレイアウトシフトが起こらないように、リセットCSSを載せているのですが、それだけなので、パッケージの中にこのCSSを含めるなど利便性を高めていきたいなと考えています。

「webpack以外でも動いてほしい」という社内の要望を受け改善

続いて、先ほどからちょこちょこ話題に挙げているSVGのimportについてより詳しく話していきます。ここまでは、どうしてWeb Componentsとして実装するに至ったかというちょっと外側気味の話でしたが、ここからはWeb Componentsの内側でどういう処理が行われているのかについて少しだけ紹介します。

まず、@charcoal-ui/iconsのパッケージ構造を今一度おさらいしておきましょう。FigmaからSVGを書き出してくるicons-cli、SVG文字列をexportするためのicon-files、Web Componentsを作るiconsの3つからできています。

SVG文字列をexportするためのicon-filesというパッケージは、少し前のアップデートの時に追加したパッケージです。以前は、SVGファイルを直接iconsパッケージ内で持っていて、SVGファイルを直接JavaScriptのファイルにimportしていたのですが、時代の流れによって、これはやめたほうがいいとなってしまいました。

どうしてそう判断するに至ったのか。それを紹介していきます。

まず、初期バージョンの@charcoal-ui/iconsでは、「webpack」の利用が必須でした。少し前なら、webpackさえ動いていればそれでいいやという感じでしたが、最近になって「Vite」、「SWC」、「Parcel」といった新しいツールが出てきて、それらのほうが動きやパフォーマンスがいいとよく言われるようになりました。

そもそもiconsは、webpackでしか動作確認をしていなかったということもあり、これらのツールでは一切動きませんでした。社内のいくつかのプロダクトでViteを使いたいという声がだんだん上がるようになり、対応してほしいという意見が、直に届くようになり、対応することにしました。

そもそも、なぜwebpackでしか動かなかったのかですが、JavaScriptファイルではないものをimportしていたために、バンドラによって動作が異なると思われたためでした。

実際に、(スライドを示して)こういうコードを使ってimportしていたのですが、これがwebpack以外のバンドラでも動くという保証がありませんでした。

そこで、JavaScriptファイルをimportして、JavaScriptのルールに従って文字列として扱えば、そんな変なことは起こらないんじゃないかと考え、改善に着手しました。

JavaScriptに従って文字列を扱うために、まずはicons-cliに機能追加を行いました。Figmaから取ってきたSVG文字列をSVGファイルとして保存する処理に加えて、SVG文字列をexportする.cjsファイルを保存するようにしました。

そして、それらの保存先をiconsパッケージの中からicon-filesの中へと変更しました。別パッケージに切り出したのは、バンドラ周りにTypeScriptを使ったりすると、import、export周りが変換されて難しくなるのを防ぐためです。

CommonJSに従ったコードを.cjsファイルとしてあらかじめ作り出しておくことで、そういった変換が生まれる必要がなく、また、別パッケージであれば、その中の変換処理は走らないことが予想できるため、このようなことを行っています。そこから先は今までどおりです。git commitしてpushしてプルリクエストを作るというところは変わっていません。

続いて、iconsに行った修正についてです。SVGファイルをimportする処理に加え、SVG文字列をJavaScriptからimportする関数を作成しました。これによって、先ほど作ったicon-filesを読み込めるようになります。

従来のSVGファイルをimportする関数も依然として残しています。独自アイコンのSVGファイルを引き続きimportして使えるようにするためです。そのため、独自アイコンを設定する時は、バンドラの設定が必要という仕様が生まれる原因になっています。

(スライドを示して)これが改善した例ですが、上に書いているコードがバージョン1系のimport部分に関するコードで、下がバージョン2系のimport関連のコードです。

バージョン1系では、SVGをダイナミックインポートしていたのですが、バージョン2系では、icon-filesを普通にJavaScript的にimportして、それを使うという処理に変わっています。

より安定し、よりハイパフォーマンスに動くライブラリを目指す

最後は、Charcoalの今後の話で締めくくろうと思います。このような改善を行ったことから、動作環境や依存ライブラリは、どんどんWebの世界では時流によって変わっていくんだなということを痛感しました。

今回のiconsに限った話で言えば、webpack以外でも使えることは事前に確認しておくべきだったし、チームの外から指摘される前に対応するべきだったのかなと思っています。

あと、これはけっこう今、現在進行形でCharcoalが影響を受けている部分なのですが、依存ライブラリのバージョンアップも、こういった安定動作の影響を及ぼすものになるかなと思います。

今回は、たまたま@charcoal-ui/iconsという依存ライブラリのとても少ないライブラリの紹介でしたが、ほかの「@charcoal-ui/react」、「@charcoal-ui/tailwind-config」、「@charcoal-ui/styled」などのパッケージは、そもそもReactやTailwindやstyledがpeer dependenciesになっているので、これらの影響をもろに受けます。

@charcoal-ui/styledに関しては、この間アップデートがあったstyled-componentsバージョン6で実は動きません。iconsも引き続きアクセシビリティやパフォーマンスの問題を抱えており、Charcoal全体としてもそれらの問題があります。

Charcoalは、開発が始まって2年、3年ぐらい経ち、品質を上げるステップに入ったのかなと考えています。より安定して動くライブラリ、よりハイパフォーマンスに動くライブラリであるべきだと今は考えています。

これらの改善に関しては、Charcoalのチーム全員で、がんばっている最中で、1個1個紹介していくととても20分では足りないので、「Ask the Speaker」というサブステージの質問コーナーで、自分をつかまえて聞いてください。

来てくれるかはちょっとわからないのですが、デザイナーやiOSエンジニアに声掛けはしておいたので、それらの業種の方もぜひ話に来てください。

(会場拍手)