Rspack에서 React와 함께 vanilla-extract 사용 시 발생하는 react-refresh 이슈 해결 하기

Jusung Hwang
6 min readMay 9, 2024

--

최근에 동료의 Rspack 번들러 도입 후기를 들었는데 긍정적인 반응이라 문득 호기심이 들었다. 그래서 이걸 계기로 이번에 진행하게 된 간단한 성능 측정 프로젝트에 Rspack을 사용해 보고자 했다.

Rspack CLI를 통해 간단하게 React + Typescript 프로젝트를 만들었고, 스타일링 라이브러리인 vanilla-extract를 사용하기 위해 rspack.config.js 설정 파일에 다음과 같이 추가해 주었다.

const rspack = require("@rspack/core");
const refreshPlugin = require("@rspack/plugin-react-refresh");
+const { VanillaExtractPlugin } = require("@vanilla-extract/webpack-plugin");
const isDev = process.env.NODE_ENV === "development";
/**
* @type {import('@rspack/cli').Configuration}
*/
module.exports = {
context: __dirname,
entry: {
main: "./src/main.tsx",
},
resolve: {
extensions: ["...", ".ts", ".tsx", ".jsx"],
},
module: {
rules: [
{
test: /\.(jsx?|tsx?)$/,
use: [
{
loader: "builtin:swc-loader",
options: {
jsc: {
parser: {
syntax: "typescript",
tsx: true,
},
transform: {
react: {
runtime: "automatic",
development: isDev,
refresh: isDev,
},
},
},
env: {
targets: [
"chrome %3E= 87",
"edge >= 88",
"firefox >= 78",
"safari >= 14",
],
},
},
},
],
},
],
},
plugins: [
+ new VanillaExtractPlugin(),
new rspack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
}),
new rspack.ProgressPlugin({}),
new rspack.HtmlRspackPlugin({
template: "./index.html",
}),
isDev ? new refreshPlugin() : null,
].filter(Boolean),
};

설정을 마치고 프로젝트를 실행시켰는데…. 이런.. 다음과 같은 에러로 실행되지 않았다.

$ReactRefreshRuntime$ is not defined 에러가 발생했다.

문제 파악

작년에 디자인 시스템 프로젝트를 진행하면서 vanilla-extract를 검토할 때 이와 비슷한 문제가 있었다.

동일하게 @vanilla-extract/webpack-plugin 플러그인과 react-refresh를 사용했는데 그때는 Cannot read properties of undefined (reading 'signature') 에러가 발생했었다.

당시에 발생한 이슈를 추적하기 위해 vanilla-extract 저장소를 클론 후, yarn link로 로컬 프로젝트에 붙여 하나씩 테스트해 봤을 때는 다음과 같은 부분이 원인이었다.

.css.ts 파일 안에 특정 파일을 import 하는데 해당 파일 안에 Pascal Case로 된 컴포넌트가 들어가면 웹팩 react-refresh 플러그인에서 $Refresh$ 코드를 주입하고, vanilla-extract 파서는 해당 코드를 처리하지 못해서 발생했던 것.

그래서 작년에는 .css.ts 파일 안에 Pascal Case 컴포넌트를 불러오지 않게 별도 엔트리 포인트를 분리해서 처리했었는데 이번 문제에서는 Pascal Case 이슈는 아니었다.

조금 더 찾아본 결과 이번 문제의 원인은 module.rules에서 모든 .ts 파일에 refresh를 적용해서 발생했던 것을 알 수 있었다.

해결 방법

나의 경우 해결 방법을 찾기 위해 vanilla-extract GitHub에서만 단서들을(코드, 이슈, PR) 찾고 있었는데, 우연히 “그러면 .css.ts 파일만 react-refresh에서 예외 처리시키면 되지 않을까?” 생각이 들어서 react-refresh-webpack-plugin GitHub을 방문하게 되었고, API 문서에 있는 exclude 옵션을 사용하여 다음과 같은 코드로 정말 간단하게 이슈를 해결할 수 있었다. (…)

new refreshPlugin({
exclude: /.css.ts/,
})

추가로 위의 방법의 경우 .css.ts 파일에 $Refresh$ 코드를 주입하지 않지만, 만약 .css.ts 파일 내에 다른 ts 파일(module.rules에 refresh가 적용된 .tsx 혹은 .ts)을 불러오게 되면 해당 파일에 $Refresh$ 코드가 주입되어 에러가 발생한다.

마치며

우선 에러가 발생하지 않는 건 확인했는데, react-refresh가 .css.ts를 처리하지 않아도 핫 리로드에 이슈가 없을지는 써보면서 더 지켜봐야 할 것 같다. (아마 없을 것 같지만….)

그리고 Rspack에 놀랐던 게 기존에 vanilla-extract에서 별도 rspack-plugin은 제공하고 있지 않아서 Rspack 버전으로 플러그인을 만들어볼까, 생각도 들긴 했는데 webpack-plugin을 그대로 가져다 써도 동작이 된다는 건 호환성 측면에서 많이 놀랐던 것 같다;

이런 부분에서 확실히 Rspack은 기존 webpack 프로젝트를 Rspack으로 전환할 때 많은 이점이 있는 것 같다. (물론 공식 문서에도 나와 있지만 아직 구현이 되어있지 않거나, 동일한 구현이 어려워서 호환되지 않는 플러그인들도 몇 가지 존재한다.)

--

--

Jusung Hwang
Jusung Hwang

Written by Jusung Hwang

Web Frontend Developer. 디자인과 개발의 영역을 조화롭게 표현할 수 있는 프론트엔드 개발의 매력에 빠진 사람, 황주성입니다 :)