ReactとSvelteでマイクロフロントエンドを行う二つの方法を試してみる

初めまして、2021年度新入社員の関と申します。

普段の開発ではAWSリソースをTerraform環境で管理できる様にする業務やチームで使用するSlackボットの作成などを行っています。

今回は、React内にSvelteを埋め込んでマイクロフロントエンドもどきなことをやってみたため、紹介します。

この記事は、Reactの実装方法やSvelteについて基礎的な知識があり、webpackなどもある程度理解していることを前提としています。

また、環境としてnodeやnpmコマンドなどが動かせる環境があることを前提とします。

なぜやろうとしたのか

自分はフロントエンドに興味があり、数年前から聞くようになったマイクロフロントエンドについて興味がありました。

しかし、実際にどのように行えば良いのかなどは試したことがありませんでした。

そのため、このブログを良い機会と考え、試してみようと思いました。

マイクロフロントエンドとは何か

ブログ冒頭からマイクロフロントエンドと何度も繰り返していますが、そもそもマイクロフロントエンドとは何かについて少し話します。

ここの部分の話では以下のような簡単なニュースサイトを例に話します

マイクロフロントエンド説明のための例。検索できる部分、ログイン部分、ニュース部分の三つの機能があるページが表示されている
マイクロフロントエンドの説明のためのシステム

 

バックエンド開発のマイクロサービス化

この話をするにはまずバックエンド開発について話す必要があります。

昔のバックエンドは一つのサーバーのみで動かしてすべてのAPIをそこで管理する様にしていました。しかし、この方法では処理が集中してしまうなどの問題が生まれました。(そもそもフロントとバックを分けないモノリシックな設計については割愛します。)

フロントエンドとバックエンドモデル。フロントエンドとバックエンドが分離している画像
フロントエンドとバックエンドモデル

この問題を解決するために、APIの種類ごとに分けてサーバーを立てるマイクロサービスという考えができました。そして、バックエンドでは機能ごとに別のサーバーを立ててアクセスする方法が考えられました。

たとえば冒頭のニュースサイトならばニュース情報取ってくるのはこっちのサーバー(API)、検索はこっちのサーバー(API)、ログイン部分はこっちのサーバー(API)などで分割していることは多くの会社や開発で行っていると思います。

バックエンドのマイクロサービス化の説明。バックエンドがニュース、検索、ログインで別れている
バックエンドのマイクロサービス化の説明。

フロントエンドの昔と今

昔はフロントエンドでそこまで膨大な処理を書く必要はありませんでした。せいぜいhtmlを書いて、cssでデザイン、JavaScriptで動きをつけるくらいしかやらなかったためです。

しかし、Ajaxの登場などにより、フロントエンド側でもいろいろな処理が行われるようになり、さまざまなことがフロントエンドでできるようになりました。

そして、レスポンスの速さなどの理由で、多くのことをフロントエンドで行うことが増え、フロントエンド処理を担当する専門部隊が散見されるようになりました。

そんなこんなでフロントエンドで処理をいろいろと行っていると、フロントエンド層の肥大化が起こり管理が難しくなることが多くなってきました。

それにより、以下のような問題が生まれてしまいました。

  • フロントエンドの技術が偏ってしまうため、フロントエンド領域の変化への追従が困難である。
  • フロントエンドがサービスの成長のボトルネックとなる。
  • すべてのページを一気通貫で作成しているため、ある特定の技術に精通する人材を採用する必要がある。
  • 機能追加の際に全体のバランスを考えた設計が必要になる。
  • テストや調査が困難である。
  • 同じような機能のコンポーネント(処理)が重複する。

このことから、バックエンドで行われるマイクロシステムの考えを、フロントエンドにも適用しようではないか、ということで作られたのがこの概念なわけです。

マイクロフロントエンドとその利点

マイクロフロントエンドでは、バックエンドで行っていたように機能を複数に分けます。例のニュースサイトで言えば、ニュース情報、検索、ログイン部分という感じで分けます。

以下の色違いで囲った部分ごとで別の開発をしていき、最後に合体させるようにするわけです。

マイクロフロントエンドの分け方。検索、ニュース、ログインで分ける
マイクロフロントエンドの分け方

つまり、システムとしては以下のように分割されています。

マイクロフロンドで分けた場合の設計。フロントエンドもニュース、検索、ログインで別れている
マイクロフロンドで分けた場合の設計

このように機能ごとに分けることで、以下のことを解決することができます。

  • 技術スタックが偏ってしまう問題の解決。
    • コンポーネント単位で機能を作成することで、コンポーネント単位で新しい技術スタックを用いた開発が可能になる。
  • 依存関係が複雑になる問題の解決。
    • ある機能がほかの機能に影響しないため、依存関係が複雑になるために、設計やテストが楽になる。
  • 新たな機能追加が大変な問題の解決。
    • 技術スタックの選択が容易や依存性が低いことにより、新たな機能追加が比較的容易に可能である。
  • 機能を別のページで共有できない問題の解決。
    • 検索機能などをある一つのサイトだけでなく別のサイトで共有して用いることが可能になり、機能共有が可能。工数の削減につながる。

まとめると、膨大になりつつあるフロントエンド開発を細分化して管理しやすくするための考えがこのマイクロフロントエンドという訳です。

詳しくは、以下のページなどを読むと非常に理解が深まると思います。

[翻訳記事]マイクロフロントエンド

マイクロフロントエンド入門書

今回は、マイクロフロントエンドを行うためのさわりもさわり、別のライブラリのコンポーネントを別のライブラリで読み込んでみるということを行っていきます。

共通コンポーネントとしてのSvelte

なぜSvelteを用いるのかを説明します。

Svelteはいわゆるコンポーネントで開発を行うwebフレームワークです。

記述方法などがReactやVueなどと似ており、それらと比較されることが多々あります。

しかし、SvelteはReactやVueとは似て非なると言えます。というのも、Svelteは仮想DOMを使っていないためです。

Svelteは仮想DOMを使わず、ビルド時に状態変化後のすべての可能性を網羅する形でpureJSの生成を行うことでリアクティブな処理を実現しています(参考)。

build svelte to javascript
build svelte to javascript

今回重要なのはこのPureJSに変換され仮想DOMを用いることがないという点です。

pureJSになるということはJavaScriptが動く環境であれば動かすことが可能であるということになります。

つまり、ReactやVueの環境でも動かすことが可能ということです。

SvelteはPureJSに変換され、ReactとVueで使えることを示した図
SvelteはPureJSに変換される

この性質からさまざまなアプリで共通で動かせるコンポーネントを作成する上では選択肢としてピッタリということができる訳です。

この件は以下のサイトなどで詳しく述べられています。

Svelteコンポーネント、なぜReact / Vue 上で動いてるの?

実際に共通コンポーネントとして読み込んでみる

今回はSvelteのコンポーネントを共通コンポーネントとして用意して、Reactに埋め込むということを行います。

SvelteをReactのアプリケーション上で動かす方法は以下の二つあります。

  • SvelteのインスタンスをReactのコンポーネントに変換して行う方法。
  • webpack5のModule Federationを使用する方法。

このブログではこの二つの方法での実装を説明しつつ、最後にこの二つの方法を比較します。

SvelteのインスタンスをReactのコンポーネントに変換して行う方法

reactの環境を作成する

まず、簡単なReactの実行環境を作成します。

任意の場所に以下のような構造のディレクトリを作成してください。(例ではreact-svelteディレクトリを作成しています)

ファイル内はとりあえず空で大丈夫です

このディレクトリ内で以下のコマンドを実行してください

package.jsonが生成されます。

次にwebpackとbabelをインストールしていきます。

Reactをインストールします

ここまでできたら、webpack.config.jsを記述します。以下のように記述してください

index.htmlとApp.jsxの作成

次にindex.htmlとApp.jsxを作成します。

以下の様に記述してください

index.html

App.jsx

最後に、以下のコマンドを入力します

localhost:8001にアクセスするとHello Worldと表示されるはずです。

Svelte-loaderとSvelte-adapterのインストールと設定

次にSvelte-loaderとSvelte-adapterをインストールしていきます。

Svelte-loaderはwebpackを使ってSvelteを動かす際に必要なもので、svelte-adapterはReactの中にSvelteを埋め込むクラスを提供してくれるライブラリです。

以下のコマンドでインストールします。

その後webpack.config.jsの中にSvelteのコードがロードされるようにrulesを追加します

test.svelteファイルの中身は以下の様に記述してください

App.jsの書き換え

次にApp.jsの内容を書き換えます

コードの説明をするとtoReactというものがsvelte-adapterでSvelteをReactで使う様にするために必要になるものです。

そして、このアダプターは以下の様に引数を指定します。

今回はtest.svelteのtestコンポーネントを使い、cssはなく、囲う要素はdivなので以下の様に記述しています

そして作成したコンポーネントをreactコンポーネントとしてJSX上に記述します。また、stateに「ここはSvelte部分よ」というテキストを渡しています。

サーバーを立ち上げてみる

さて、ここまできたら、SvelteがReact内で動く様になるはずです。

以下のコマンドでサーバーを立ち上げます

サーバーを立ち上げ、localhost:8001にアクセスするとhello worldの下にテキストボックスとテキストが表示されるはずです。

さらに、テキストボックスの中の値を変更すると下のテキストも変更されるはずです。

このテキストボックスと変更されているテキストはSvelteのコードを元に作られたものになっています。

React内にSvelteを埋め込むことができました。

ReactコンポーネントにSvelteのInput要素が埋め込まれている画像
ReactコンポーネントにSvelteのInput要素が埋め込まれている

(余談)どうやって表示しているのか?

これに関しては以下のYoutubeや先述したブログで説明しています。

How to use Svelte component inside a React app?

Svelteコンポーネント、なぜReact / Vue 上で動いてるの?

また、svelte-adapterのソースコードも参考になると思います。

https://github.com/pngwn/svelte-adapter/blob/master/react.js

Svelteは前述したようにコンパイル後はpureJSになるのでそのクラス要素をインスタンス化して、その後、propsや値の変化を受け取るためのリスナーを登録しています。

webpack5のModule Federationを使用する方法

次に、Module Federationというwebpack5で導入された別サーバーにあるwebコンポーネントを読み込んで仕様できる仕組みを使用した方法を紹介します。

今回はsvelteで行いますが、この方法を使うとreactでもvueでもweb componentに変換することで別のライブラリで読み込める様になります。

さて、早速やっていきたいと思います。

今回はReactとSvelte別々にサーバーを立てていきます。

Reactの環境を作る

こちらは前述したReactの環境を作成すると全く同じ方法で構築してください。

なお、最後の方で少し設定は変更します。

Svelteの環境を作る – ファイル構造と初期化

次にSvelteの環境を作っていきます。svelte-cliを使うとバンドルツールがrollupで環境が作られますが、今回はwebpack5で環境の構築を行っていきます。

任意の場所に以下の様な構造のディレクトリを作成してください。

ファイル内は今は空で問題ないです

まずはpackage.jsonファイルを作成します。

Svelteの環境を作る – パッケージのインストール

次にパッケージをインストールします。

まずwebpackをインストールしましょう

次にbabelのインストールをします。

最後にSvelteとsvelte-loaderをインストールします。

Svelteの環境を作る – index.htmlとtest.svelteファイルの作成

次にindex.htmlとtest.svelteファイルを作成していきます

index.htmlは以下の様に記述してください

次にtest.svelteファイルを作成します。

次にindex.jsファイルを作成します。こちらもsrcファイル内で作成してください

さて、Svelteファイルを記述したところで記述内容が違うことに気がついた人もいると思います。

具体的には以下の点が違っています

  • <svelte:options tag={null} />の追加
  • let nameにexportが付いている

これはSvelteをweb コンポーネントに変換するために追加している処理になっています。

今回のModule Federationではwebコンポーネントに変更して処理を行う必要があるためです。

また、index.js内のcustomElements.defineもtestというコンポーネントをtest-1という名前でwebコンポーネントとして出すことを指定しています。

ここまできたらwebpack.config.jsを記述していきます。

以下の様に記述してください

さて、注目するべきなのは二点あります。

一点目はsvelte loaderのoption部分でcompilerOptionsの指定をしていることです。

このオプションはsvelteファイルをwebコンポーネントに変換して使用する際にそれを指定する設定になっています。これがないとwebコンポーネントに変更できないエラーが出ます。

二点目はModuleFederationPluginというものが使われている部分です。

この部分が今回の最も重要な部分でModule Federationを使うために必要なものとなります。

設定項目は以下の様になっています

 

詳しくは公式ドキュメントを参照してください

ここまできたらサーバーを立ち上げてhttp://localhost:8080/remoteEntry.jsにアクセスしてみてください。

以下の様な画面が表示されるはずです

Module Federationでoutputした結果
Module Federationでの出力

Reactのwebpack.config.jsを書き換える

先ほど、出力したwebコンポーネントを使用するためにReact側のwebpack.config.jsも書き換えが必要です。

具体的には以下の様にModuleFederationPluginをplugins内で作成して、先ほど作ったweb componentを読み込みます。

 

React側でコンポーネントを読み込む

さてここまで設定したら、ReactのApp.jsファイルを以下の様に変更してください

また、index.jsとは別にboot.jsファイルをルートディレクトリに作成しindex.jsの内容を全て移動してください

ファイル構造は以下の様になると思います

 

そしてindex.jsは以下の様に修正してください

この状態でSvelteのサーバーを立ち上げ、Reactのサーバーを立ち上げてください。どちらのサーバーも以下のコマンドで立ち上げることができます。

localhost:8001にアクセスすると以下の様にsvelteのコンポーネントが読み込めているはずです。

React内にSvelteのinput要素が埋め込まれている画像
React内にSvelteのinput要素が埋め込まれている

問題点

この手法の問題点としてSvelteをwebコンポーネントに変換したものはSvelteとは少し挙動が違う点が挙げられます。

詳しくは以下のzennにまとまっていますが、Svelteの機能を完全にweb componentsに変換することはできない様です。

Svelte で Web Components を開発するときの Tips (2021年7月時点)

二つの手法の比較

今回はSvelteを共通コンポーネントとして使用するための二つの手法について紹介しました。

最後にて二つの手法のメリットとデメリットを比較します。

useRef使ってReactに埋め込む方法

メリット

  • いちいち、使うコンポーネントの面倒な設定は必要ない
  • コンパイルしてpurejs化したSvelteを使う。そのため、web component化はしないので、Svelteの機能はすべて使える(Reactとvue側で実装すれば)

デメリット

webpack使う方法

メリット

  • React内やvue内で使うためのコードを用意する必要はない
  • 完全に別のサーバーとしてコンポーネントを用意し読み込ませることが可能

デメリット

  • web component化するための問題がある場合がある。詳しくは参考サイトのSvelteでweb componentを参照
  • webpack5の機能なのでrollupやviteなどでSvelteを使いたい場合は使えない

個人的にはuseRefの方法はいろいろと処理を追加する上で大変な気がするのと、共通コンポーネントとして使用するには別のファイルを参照するようにwebpackの内容を変更するというのがあまり良くないと思うため、webpackの方法の方が共通コンポーネントとして作成する場合は良いのかなと思いました。

まとめ

今回はSvelteで共通コンポーネントを作成するために使用できるかもしれない手法を二つ紹介しました。

今回やったことはさわりもさわりなので、今後はこれらの手法を使用してマイクロフロントエンドな設計で何か作成するなどやっていきたいと思います。

参考文献

マイクロフロントエンドについて

[翻訳記事]マイクロフロントエンド

マイクロフロントエンド入門書

Svelteについて

Svelte

Svelteとは

Svelteコンポーネント、なぜReact / Vue 上で動いてるの?

SvelteのインスタンスをReactのコンポーネントに変換して行う方法

How to use Svelte component inside a React app?

svelte-loader

svelte-adapter

NuxtでSvelteコンポーネントを動かしてみる

Next.js + SvelteによるnoteのフロントエンドApp分割

webpack5のModule Federationを使用する方法

Webpack5 Module Federation で始めるマイクロフロントエンド

ModuleFederationPlugin

svelte – Custom element API

SvelteでWeb Componentsを作ってみた

Webpack module federation

Svelteでweb components

webpack@5で入るModule Federationについて

webpack 5 の webpack federation という概念について