> makibishi throw|

jQuery製のページをGatsbyの静的ページに移行時、develop環境を正常に動作させる

極力やりたくはないが、どうしても jQuery 製の Web ページを Gatsby の静的ページに載せ替えたいケースに使える方法。

jQuery 製ページを Gatsby 移行した時に発生する問題

例えばWordPressで作成されたホームページを Gatsby に移行したいとする。また、このホームページには全ページで共通で読み込まれるcommon.jsというスクリプトファイルがあり、jQuery で作られているとする。

この時、どのページも大体以下のような構成になっているはずである。

<head></head>
<body>
  <script src="~/jquery.min.js">
  <script src="~/common.js">
</body>

このcommon.jsを static なファイルとしてそのまま利用したい場合、gatsby のhtml.jsは以下のようになる。

import React from "react"
import PropTypes from "prop-types"

export default function HTML(props) {
  return (
    <html>
      <head></head>
      <body><div
          key={`body`}
          id="___gatsby"
          dangerouslySetInnerHTML={{ __html: props.body }}
        />
        <script src="~/jquery.min.js">
        <script src="~/common.js">
      </body>
    </html>
  )
}

これは、リリースビルド(gatsby buildの生成結果)のソースではうまく動作する。 しかし、gatsby developで develop 環境を立ち上げた場合にうまく動作しない。

gatsby developがうまくいかない原因

gatsby develop起動時、開発中の Web ページは React アプリケーションとして動作する。この react レンダリングのためのスクリプトは一番最後に読み込まれる。 一方、common.jsは jQuery ベースであり、body要素内の DOM が全て用意できているという前提の作りになっている。よって、

  1. jquery.min.jsスクリプト実行
  2. common.jsスクリプト実行
  3. gatsby の開発環境用スクリプトが実行され、react によって body 要素内の DOM がレンダリングされる

という順序になり、DOM が用意されている前提のcommon.jsの実行が失敗してしまう。

develop 環境を動作させる方法

develop 環境でも正常に動作させるには、react のレンダリングが終わった後にcommon.jsが読み込まれるようにすればよい。

1. 環境変数の定義

これを実現するためには develop 環境のみの分岐処理が必要なので、環境変数の設定ファイル.env.developmentを作成し、変数を定義する。

GATSBY_ENV=development

GATSBY_ プレフィックスがついている変数のみがブラウザレベルで利用できるため、GATSBY_をつけている。

2. common.jsの読み込みを html.js から行わないように変更

develop 環境では、html.js内でcommon.jsの読み込みを行わないようにする。

import React from "react"
import PropTypes from "prop-types"

export default function HTML(props) {
  return (
    <html>
      <head></head>
      <body><div
          key={`body`}
          id="___gatsby"
          dangerouslySetInnerHTML={{ __html: props.body }}
        />
        <script src="~/jquery.min.js">
        {process.env.GATSBY_ENV === "development" ? (
          <></>
        ) : (
          <script type="text/javascript" src="/js/common.js"></script>
        )}
      </body>
    </html>
  )
}

3. react のレンダリング完了後にcommon.jsを読み込むように変更

適当にLayoutコンポーネントを用意し、全てのページでLayoutコンポーネントが読み込まれるようにしておく。

const Page = ({ anything }) => {
  return <Layout>...</Layout>
}

Layoutコンポーネント内では、DOM のレンダリングが終わった後にcommon.jsを無理矢理読み込む。

const Layout = ({ children }) => {
  const [loadJs, setLoadJs] = useState(false)

  useEffect(() => {
    if (process.env.GATSBY_ENV === "development") {
      if (!loadJs) {
        const head = document.getElementsByTagName("head")[0]
        const scriptUrl = document.createElement("script")
        scriptUrl.type = "text/javascript"
        scriptUrl.src = "/js/common.js"
        head.appendChild(scriptUrl)

        setLoadJs(true)
      }
    }
  }, [])

  return <div>...</div>
}