Quantcast
Channel: CSSタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8728

マイクロフロントエンド③ CSSとRoutingにおける罠

$
0
0

前回までの記事から引き続き、マイクロフロントエンドの構築でハマった罠についてまとめました。

CSSのコンフリクト

MarketingプロジェクトのPricingページからAuthプロジェクトのSignInページに遷移したとき、SignInのCSSが新しくロードされることで、Pricingに戻ったときにも表示に影響を与えてしまうことがあります。

この問題の対策として、CSSのスコーピングという方法があります。
スコーピングとはCSSの影響範囲を絞ることをいい、CSS-in-JSやBootstrapとかMaterial-UIのようなライブラリを利用することで実現することができます。

しかし、スコーピングを行っても本番環境で表示が崩れる場合があります。

例えば、Material-UIではCSSスタイルシートをコンポーネントに提供するためにmakeStyles()という関数を使います。

constuseStyles=makeStyles((theme)=>({heroContent:{backgroundColor:theme.palette.background.paper,padding:theme.spacing(8,0,6),},}

ビルドする際に、makeStyles-heroContent-2のようなセレクタが作られ、.makeStyles-heroContent-2: { backgroundColor: ~ }というCSSと{ heroContent: 'makeStyles-heroContent-2' }というJSにわけられるのですが、本番環境でのビルドだと、ファイルをできるだけ小さくするために、jss1jss2といった短いセレクタ名に変換されてしまいます。その結果、プロジェクト間でセレクタ名がコンフリクトしてしまい、ルーティングした際にページの表示崩れが発生します。

この問題を解消するためには、プロジェクトごとにセレクタにprefix(ma-co-など)をつけてあげる必要があります。
Material-UIであれば、createGenerateClassNameを使用することでprefixを指定することができます。

App.js
constgenerateClassName=createGenerateClassName({productionPrefix:'ma',});exportdefault({history})=>{return(<div><StylesProvidergenerateClassName={generateClassName}><Routerhistory={history}><Switch><Routeexactpath="/pricing"component={Pricing}/>
<Routepath="/"component={Landing}/>
</Switch>
</Router>
</StylesProvider>
</div>
);};

Routingの設定

マイクロフロントエンドのRoutingでは以下の点に注意する必要があります。

  • Containerおよび各子プロジェクトにそれぞれRouting用ライブラリが必要
  • ContainerのRoutingではどの子プロジェクトを表示するかを決める
  • 子プロジェクトのRoutingではどのページを表示するかを決める(ルートを追加してもContainerの再デプロイの必要なし)
  • 同じ画面に2つ以上の子プロジェクトが表示される場合も想定する(サイドバー+メインでプロジェクトを分ける場合など)
  • 既存のRoutingライブラリを使用する
  • 子プロジェクトのRoutingは開発時の独立した環境でも使えるようにする
  • Containerと子プロジェクト間でRoutingに関する情報のやりとりが必要(history更新のため)

React Router

ReactはSPAであるため、ブラウザからは1つのページとしてしか認識されません。そのため、SPAの画面状態とURLを紐づけてあげる必要があり、そこで使われるのがReact RouterというRouting用のライブラリです。React Routerを使うとHistory APIを操作できるようになり、URLを指定して直接特定の画面にいけたり、ブラウザバックを利用できるようにすることができます。

History

Historyはカレントパスの取得と設定を行うためのオブジェクトです。

  • Browser History: カレントパスをURLの/以下で追跡する
  • Hash History: カレントパスをURLの#以下で追跡する
  • Memory History: カレントパスをメモリ内で追跡する

Containerと子プロジェクトでBrowser Historyを使うと両方でアドレスバーのURLを書き換える必要がでてきてしまうので、子プロジェクト側ではMemory Historyを使います。そのために、createMemoryHistory()を導入して、Routerコンポーネントにhistoryとして渡してあげます。

bootstrap.js
constmount=(el)=>{consthistory=createMemoryHistory();ReactDOM.render(<Apphistory={history}/>, el);
};
App.js
exportdefault({history})=>{return(<div><StylesProvidergenerateClassName={generateClassName}><Routerhistory={history}><Switch><Routeexactpath="/pricing"component={Pricing}/>
<Routepath="/"component={Landing}/>
</Switch>
</Router>
</StylesProvider>
</div>
);};

MarketingプロジェクトにMemory Historyを導入したところで、ホーム画面からMarketingプロジェクトのページ(pricing)に遷移してみると、URLが/のままです...
スクリーンショット 2021-03-16 22.40.01.png

マイクロフロントエンドにおけるRoutingの罠

このような問題が起こってしまうのは、ページ内のリンクをクリックしたときにそのリンク先を含んでいるプロジェクトのRoutingを参照してしまうためです。例えば、pricingのリンクをクリックしたときは、MarketingプロジェクトのRoutingを参照することになります。ここではMemory Historyが使われているので、リンク先に遷移してもURLは変化しません。

この問題を解消するために、Marketing(子プロジェクト)関連のリンクをクリックしたときにはContainerのBrowser Historyのカレントパスを更新し、Container関連のリンクをクリックしたときはMarketingのMemory Historyのカレントパスを更新する必要があります。

ここでonNavigateonParentNavigateを使います。

onNavigateとonParentNavigate

onNavigateを使うことで、pricingのリンクをクリックしたときに、ContainerのBrowser Historyにカレントパスの変更を指示することができます。

useHistoryでContainerのBrowser Historyのオブジェクトを呼び、useEffect内でカレントパスを更新します。

MarketingApp.js
consthistory=useHistory();// ContainerのhistoryオブジェクトuseEffect(()=>{mount(ref.current,{onNavigate:({pathname:nextPathname})=>{// Marketingのカレントパスconst{pathname}=history.location;// Containerのカレントパスif(pathname!==nextPathname){history.push(nextPathname);// Containerのカレントパスの更新}},});});

Container関連のリンクをクリックしたときにMarketingのMemory Historyのカレントパスを更新できるよう、onParentNavigateを追加します。

MarketingApp.js
consthistory=useHistory();useEffect(()=>{const{onParentNavigate}=mount(ref.current,{onNavigate:({pathname:nextPathname})=>{const{pathname}=history.location;if(pathname!==nextPathname){history.push(nextPathname);}},});history.listen(onParentNavigate);},[]);

onParentNavigate内でMarketingのカレントパスを更新することで、ContainerのBrowser Historyと整合がとれるようになります。

bootstrap.js
constmount=(el,{onNavigate})=>{consthistory=createMemoryHistory();// Marketingのhistoryオブジェクトif(onNavigate){// 本番環境(Containerがある)のときだけ実行history.listen(onNavigate);}ReactDOM.render(<Apphistory={history}/>, el);
return{onParentNavigate({pathname:nextPathname}){// Containerのカレントパスconst{pathname}=history.location;// Marketingのカレントパスif(pathname!==nextPathname){history.push(nextPathname);// Marketingのカレントパスの更新}},};};

Memory HistoryとBrowser Historyの切替

子プロジェクトでも開発環境ではBrowser Historyを使用したい場合があります。
そんなときは、開発環境のときだけ、Browser Historyのオブジェクトをmountに渡してあげるようにすれば大丈夫です。

bootstrap.js
import{createMemoryHistory,createBrowserHistory}from'history';..consthistory=defaultHistory||createMemoryHistory();..if(process.env.NODE_ENV==='development'){constdevRoot=document.querySelector('#_marketing-dev-root');if(devRoot){mount(devRoot,{defaultHistory:createBrowserHistory});}}

おわりに

Historyの概念がうろ覚えだったので勉強になりました。

参考資料


Viewing all articles
Browse latest Browse all 8728

Latest Images

Trending Articles

<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>