😇

npm workspace, Create React Appでmonorepo作成してたら詰まった話

2024/10/29に公開

はじめに

Create React Appで作成したアプリケーションでmonorepo構成を扱いたかったため、npm workspaceでmonorepoの作成をしていましたが、それプラスtranspileにCRACOを使用する必要があったのでその備忘録となります。

ちなみにですが、Viteで同様にmonorepo作成を行った場合は本章のclient_aからcommon_packageを呼び出してみるでエラーにならずコンポーネントが表示されるので、さっさとViteに移行しましょう

構成

今回は以下のように独立して動くアプリケーションclient_a, client_bと単体では動かず共通のライブラリとなるcommon_packageの構成で作成していきます。
client_a, client_bCreate React Appで作成されたアプリケーションです。

.
├── package.json
├── client_a
│   └── package.json
├── client_b
│   └── package.json
└── common_package
    └── package.json

workspaceの作成

既にディレクトリの中身は作成済みだったためnpm init -yでルートのpackage.jsonを作成し、以下の設定を追加。

packgage.json
"workspaces": [
    "client_a",
    "client_b",
    "common_package"
]

その後ルートでnpm installを実行するとルートのnode_modulesにワークスペースのシンボリックリンクができます。

.
├── node_modules
│   ├── client_a -> ../client_a
│   ├── client_b -> ../client_b
│   └── common_package -> ../common_package
├── package-lock.json
├── package.json
├── client_a
│   └── package.json
├── client_a
│   └── package.json
└── common_package
    └── package.json

ルートにてclient_acommpn_packageをinstall

npm install common_package -w client_a

これにて準備は完了です。

client_aからcommon_packageを呼び出してみる

client_a/src/App.js
import { HelloWorld } from "common_package/src/HelloWorld"

function App() {
  return  <HelloWorld />;
}

export default App
common_package/src/HelloWorld.js
export function HelloWorld() {
  return <div>Hello World</div>;
}

client_aでnpm run startを実行すると、次のようなエラーが。

setPrototypeOf.js:5 Uncaught Error: Module build failed (from ../node_modules/babel-loader/lib/index.js):
SyntaxError: /common_package/src/HelloWorld.js: Support for the experimental syntax 'jsx' isn't currently enabled (2:10): (at setPrototypeOf.js:5:1)

[0m [90m 1 |[39m [36mexport[39m [36mfunction[39m [33mHelloWorld[39m() {
[31m[1m>[22m[39m[90m 2 |[39m   [36mreturn[39m [33m<[39m[33mbutton[39m[33m>[39m[33mHello[39m [33mWorld[39m[33m<[39m[33m/[39m[33mbutton[39m[33m>[39m[33m;[39m
 [90m   |[39m          [31m[1m^[22m[39m
 [90m 3 |[39m }
 [90m 4 |[39m[0m

Add @babel/preset-react (https://github.com/babel/babel/tree/main/packages/babel-preset-react) to the 'presets' section of your Babel config to enable transformation.
If you want to leave it as-is, add @babel/plugin-syntax-jsx (https://github.com/babel/babel/tree/main/packages/babel-plugin-syntax-jsx) to the 'plugins' section to enable parsing.

If you already added the plugin for this syntax to your config, it's possible that your config isn't being loaded.
You can re-run Babel with the BABEL_SHOW_CONFIG_FOR environment variable to show the loaded configuration:
	npx cross-env BABEL_SHOW_CONFIG_FOR=common_package/src/HelloWorld.js <your build command>

どうやらJSXのトランスパイルをしてあげる必要があるっぽい。

CRACOでtranspile

トランスパイル(transpile)はあるプログラミング言語を別の言語に変換することで、今回の場合は、JSXをJSに変換してあげる必要がある。

そこでCRACO(Create React App Configuration Override) の出番です。
CRACOは、Create React App (CRA) の設定をejectせずにカスタマイズするためのツールです。
ESLint、Babel、PostCSS などの構成をカスタマイズに加え、他のパッケージのトランスパイル設定やエイリアス設定などを行うことができます。

client_aにCRACOをインストール

npm i @craco/craco craco-babel-loader --save-dev

package.jsonの設定を変更

client_a/package.json
"scripts": {
-  "start": "react-scripts start"
+  "start": "craco start"
-  "build": "react-scripts build"
+  "build": "craco build"
}

craco.config.jsを作成。craco-babel-loaderを用いてトランスパイルします。

client_a/craco.config.js
const fs = require("fs");
const path = require("path");

const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath);

module.exports = {
  plugins: [
    {
      plugin: require("craco-babel-loader"),
      options: {
        // 相対パスを指定
        includes: [resolveApp("../common_package/")],
      },
    },
  ],
};

さて、再度npm run startで実行してみましょう。
ちゃんとHello Worldが表示されましたね。
これにて終了です。

Discussion