개발자
류준열
Webpack Module Federation과 React Refresh
회사에서 webpack module federation 기반의 마이크로 프론트엔드 어플리케이션을 개발하는데, 개발환경에서 코드를 수정하면 새로고침되어 state가 초기화 되는 불편함이 있었다. 이를 개선하는 과정에서 예상보다 큰 어려움을 겪었다.
React Refresh
webpack.config.js에 분명이 HMR 세팅이 있었는데도 코드 변경시 새로고침되고 있었다.
// webpack.config.js plugins: [ ... new HtmlWebPackPlugin({ template: "./src/index.html", favicon: "./public/favicon.ico", }), ],
구글링을 하다가 @pmmmwh/react-refresh-webpack-plugin
와 react-refresh
를 알게 되어 해당 패키지들을 이용하였다.
React Refresh는 개발 환경에서 코드 변경 시 페이지 전체를 새로고침하지 않고, 해당 컴포넌트만 업데이트 하여 현재 state를 유지할 수 있도록 하는 도구이다.
Webpack과 Babel에서의 설정
처음에는 개발환경, 운영환경 신경쓰지 않고 react-refresh가 적용되도록 webpack.config.js와 .babelrc를 설정했다. (webpack, babel 둘 중 하나라도 설정 빼먹으면 에러 남)
// webpack.config.js
const RefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin"); // 추가
module.exports = (_, argv) => {
return {
// ... output, resolve 등 생략
module: {
rules: [
// 다른 로더 설정 생략
{
test: /\.(ts|tsx|js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
plugins: [require.resolve("react-refresh/babel")], // 추가
},
},
},
],
},
plugins: [
new RefreshWebpackPlugin(), // 추가
// Module Federation, HtmlWebPackPlugin, Dotenv 등 기타 플러그인들
],
};
};
// .babelrc { "presets": [ "@babel/preset-typescript", [ "@babel/preset-react", { "runtime": "automatic" } ], "@babel/preset-env" ], "plugins": [ ["@babel/transform-runtime"], "react-refresh/babel" // 추가 ], }
이렇게 추가하니 개발 중 코드변경시에 더이상 새로고침이 되지 않았다.
development 빌드와 production 빌드 차이
development 빌드(webpack --mode develoment
)에서는 React Refresh가 정상 작동하고, webpack dev server를 통해 HMR 환경을 제공하지만, production 빌드(webpack --mode production
)에서는 불필요한 개발용 코드가 포함되어 $RefreshReg$ is not defined
오류가 발생했다. (아래 package.json 참고)
"scripts": { "build": "webpack --mode production", "build:dev": "webpack --mode development", "build:start": "cd dist && PORT=3000 npx serve -s", ... },
이를 해결하기 위해 Babel과 Webpack 모두에서 개발 모드에 한해서만 React Refresh 관련 코드가 포함되도록 조건을 달았다.
// webpack.config.js
const RefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin"); // 추가
module.exports = (_, argv) => {
const isDev = argv.mode === "development"; // 개발환경인지 아닌지
return {
// ... output, resolve 등 생략
module: {
rules: [
// 다른 로더 설정 생략
{
test: /\.(ts|tsx|js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
plugins: isDev ? [require.resolve("react-refresh/babel")] : [], // isDev 조건 추가
},
},
},
],
},
plugins: [
isDev && new RefreshWebpackPlugin(), // isDev 조건 추가
// Module Federation, HtmlWebPackPlugin, Dotenv 등 기타 플러그인들
].filter(Boolean),
// devServer 설정 등 생략
};
};
// .babelrc { "presets": [ "@babel/preset-typescript", [ "@babel/preset-react", { "runtime": "automatic" } ], "@babel/preset-env" ], "plugins": [["@babel/transform-runtime"]], "env": { "development": { "plugins": ["react-refresh/babel"] // 개발환경에서만 react-refresh 이용 } } }
환경변수 확인과 cross-env 사용
"scripts": { "build": "webpack --mode production", }
개발 모드(isDev)에만 React Refresh 가 포함되도록 했고, 빌드명령어가 production으로 지정되어 있는데도 여전히 해결되지 않았다. babel이 의심되어 .babelrc를 babel.config.js로 바꿔서 console.log를 찍어보니 환경변수가 development로 잡히고 있었다.
cross-env를 이용하여 환경변수를 강제하여, Babel과 Webpack 모두 production 환경으로 인식하도록 하였다.
"scripts": { "build": "cross-env NODE_ENV=production webpack --mode production" }
이렇게 하니 $RefreshReg$ is not defined
에러가 사라졌다.
결론
pnpm create mf-app
으로 세팅된 코드는 개발환경에서 코드 변경시 새로고침된다.- React Refresh 설정을 수동으로 넣어주어야 한다.
- 개발 환경과 production 환경의 명확한 분리
- Babel과 Webpack 설정에서 개발 전용 플러그인이 production 번들에 포함되지 않도록 주의해야 한다.
- Webpack의 --mode와 Node 환경변수(
NODE_ENV
)는 서로 다르게 설정 될 수 있기 때문에cross-env
등을 활용하여 강제하는 것이 좋다.