현재 회사에서 turborepo를 사용하여 모노레포 환경을 구축하여 서비스를 운영 중 입니다.
4가지 정도 되는 각 도메인의 서비스 중 NextJS로 작업 된 서비스가 있는 방면
CRA환경(craco) 환경의 제법 레거시한 환경도 존재 합니다.
이번 내용은 CRA환경(craco) 에 관한 내용을 다룰려고 합니다
#CRA환경(craco) 문제점
제가 처음 회사에 입사 했을 때 가장 기본이 되는 도메인이 CRA환경으로 운영되고 있는 환경
로컬서버를 실행을 시켰을때 2분이 지나서야 실행이 되는 문제가 있었어요.
그래서 이것을 저는 DX문제가 있다고 보고 어떻게 개선을 할지 고민을 했어요.
#Weppack esbuild-loader 사용시도
기존 craco config파일에 esbuild-loader를 사용하여 서버 실행을 해보았어요
몇초 정도 빨라지긴 했지만 여전히 느린 번들링 속도를 보여주었습니다
#로컬작업만을 위한 Esbuild 고민
기존 craco로 로컬 혹은 프로덕션 빌드까지 전부 통일하여 서비스를 운영 중이에요
그래서 로컬환경만 craco서버가 아닌 Esbuild 환경으로 개발 환경을 해도 되는건가?
라는 의문점이 있었어요.
고민 중에 NextJs에서 "dev": "next dev --turbo"가 문득 생각이 들었고
Next에서도 이렇게 로컬 빌드환경과 프로덕션 빌드 환경을 나누어
운영(추후 프로덕션 빌드도 turbopack이 가능하도록 개발 중인걸로 알고 있다)중인데
괜찮을거 같다 라는 결론을 내렸습니다.
#Esbuild 환경 구축
1. EsbuildIndex.js파일 생성(코드 요약)
const root = document.getElementById("wrap");
ReactDOM.render(<Root />, root);
2. esbuild config 파일 작성
import * as esbuild from "esbuild";
let ctx;
try {
ctx = await esbuild.context({
entryPoints: ["src/esbuildIndex.js"],
bundle: true,
minify: true,
allowOverwrite: true,
target: 'esnext',
outfile: "public/static/bundle.js",
});
await ctx.watch();
const { host, port } = await ctx.serve({
port: 3002,
servedir: "public",
fallback: "public/esbuild.html",
});
console.info(`해피 해킹 http://${host}:${port}`);
} catch (error) {
console.error(error);
process.exit(1);
}
문제1. global,ENV파일 not found에러 발생 (define로 초기선언하여 해결)
const root = process.cwd();
const femEnv = fs.readFileSync(path.resolve(root, ".env-cmdrc"), {
encoding: "utf8",
});
const sellEnv = fs.readFileSync(path.resolve(root, "../sell/.env-cmdrc"), {
encoding: "utf8",
});
const defineEnv = () => {
const env = {};
Object.entries(JSON.parse(femEnv).local).forEach(
([key, value]) => (env[`process.env.${key}`] = `"${value}"`)
);
Object.entries(JSON.parse(sellEnv).local).forEach(
([key, value]) => (env[`process.env.${key}`] = `"${value}"`)
);
return env;
};
ctx = await esbuild.context({
entryPoints: ["src/esbuildIndex.js"],
bundle: true,
minify: true,
allowOverwrite: true,
target: 'esnext',
define: { ...defineEnv(), global: "{}" },
loader: {
".svg": "text",
".js": "jsx",
".png": "dataurl",
".module.scss": "local-css",
".jpg": "dataurl",
".gif": "dataurl",
},
outfile: "public/static/bundle.js",
});
문제2.SCSS파일 url path선언 resolve-path 컴파일링 못하는 문제
esbuild resolve alias 관련 플러그인 전부 시도 해봐도 해결이 안되어서 결국 커스텀 플러그인 생성하여 해결
plugins: [
sassPlugin({
async transform(source, _, path) {
const fileName = path.match(/\/([^/]+)\.module\.scss$/)?.[1];
if (fileName) {
const { css } = await postcss([
postcssModules({
generateScopedName: `${fileName}_module_[local]`,
}),
]).process(source);
return css;
}
return source;
},
precompile(source, pathname) {
if (/\.(scss|sass|module\.(scss|sass))$/.test(pathname)) {
return source.replace(
/[/]assets[/]images/g,
`${path.resolve(process.cwd(), "public/assets/images/")}`
);
}
return source;
},
importMapper: (alias) =>
alias.replace("app/assets", path.resolve(root, "src/assets/")),
})
]
문제3. scss적용 안되는 문제
css바인딩을 저희는 classnames/bind를 사용하고 있고 css모듈을 사용하고 있지만
classname이 제대로 모듈화가 되지 않은 문제
마찬가지로 해당 문제 커스텀 플러그인 만들어서 해결
{
name: "esbuild-html-module",
setup: (build) => {
build.onLoad({ filter: /.js/ }, async (args) => {
let content = await fs.promises.readFile(args.path, "utf8");
const fileName = args.path.match(/\/([^/]+)\.js$/)?.[1];
content = content.replace(
/import classNames from 'classnames\/bind'/,
`import classNames from '${path.resolve(process.cwd(), "esbuild-config/classNameBind")}'`
);
content = content.replace(/(cx\()\s*(?=['"])/g, `$1"${fileName}_module_", `)
return { contents: content, loader: "jsx" };
});
},
},
#Esbuild성공 실패?
결론적으로 실패 하였습니다 위에 문제 3가지를 전부 해결하고 드디어 번들링으로써 가장 복잡도가 적은 로그인 화면은 정상적으로 띄웠어요
하지만 조금 복잡한 차량 상세 등등 여러 도메인을 확인하였을때 문제들이 너무 많이 발생되어
제손을 떠난 느낌을 받았습니다
#Vite로 다시시도 해보자
Esbuild를 처음 시도한 이유는 로컬에서만 실행시키는 환경인데 아무래도 esbuild하나의 패키지만 설치하여(가벼움) 시도해보고 싶었어요.
하지만 실패를 하고 익숙한 vite로 다시시도 해보기로 했습니다.
익숙한 이유는 현재 회사내 모노레포 패키지들은 vite환경으로 구성되어 있기 때문이에요
esbuild에서 문제가 되었던 설정들은 config 옵션들로 해결을 할 수 있었어요
1. scss파일 url path선언 reslove-path 컴파일링 못하는 문제
defineConfig({
resolve: {
alias: {
app: path.resolve(root, "./src"),
},
},
})
2. scss적용 안되는 문제
css module같은 경우도 vite는 내장되어 지원되고 있고 여러가지 신경 쓸게 많이 사라졌어요
// vite config 파일
defineConfig({
css: {
modules: {
generateScopedName: '[name]__[hash:base64:5]',
},
},
})
3. global,ENV파일 not found에러 발생 (define로 초기선언하여 해결)
해당 문제는 vite에서도 동일하게 발생하여 같은 방식으로 해결 하였습니다
#최종 Vite config 파일
/* eslint-disable */
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "path";
import fs from "fs";
const root = process.cwd();
const femEnv = fs.readFileSync(path.resolve(root, ".env-cmdrc"), {
encoding: "utf8",
});
const defineEnv = () => {
const env = {};
Object.entries(JSON.parse(femEnv).local).forEach(
([key, value]) => (env[`process.env.${key}`] = `"${value}"`)
);
env["process.env.NODE_ENV"] = '"local"';
return env;
};
export default () => {
return defineConfig({
server: {
port: 3002,
host: "loc.encar.com",
},
css: {
modules: {
generateScopedName: "[name]__[hash:base64:5]",
},
preprocessorOptions: {
scss: {},
},
},
resolve: {
alias: {
app: path.resolve(root, "./src"),
},
},
define: {
...defineEnv(),
},
optimizeDeps: {
esbuildOptions: {
loader: { ".js": "jsx" },
},
},
plugins: [
react({
jsxRuntime: "classic",
babel: {
presets: ["@babel/preset-react"],
plugins: ["@babel/plugin-transform-react-jsx-development"],
},
}),
],
});
};
# 개발환경 vite로 전환 성공
기존 craco설정에서 FEM이라는 서비스 실행 시키면 화면이 나오는 시간 대략 3분정도 걸렸어요
하지만 이번에 Vite로 환경을 바꾸고 화면을 실행 시키면 대략 30초 이내로 화면을 띄워 줬어요
사실 vite config 설정만 본다면 정말 간단하고 쉬워보이지만 개인적으로는 실력이 아직 부족한지 많은 시행착오 끝에
생긴 결과물 이란걸...
/* eslint-disable react/react-in-jsx-scope */
/* eslint-disable max-len */
예시.
['BNKCAPITAL', {
rate: '최저 연 6.9 ~ 최고 연 15.9%(개인신용평점 및 구간별 차등적용)',
date: '2024.02',
deliberation1: '제2024-0014호(2024.02.05)',
deliberation2: <>2024-C1h-01768호 <br /> (2024.02.20~2025.02.19)</>,
}],
#실제도입
간단히 프로토타입을 만든 후에 프론트엔드 기술공유 시간에 공유를 했습니다
결과는 프로덕션 환경과 개발환경이 다른 점에서 반대 의견이 있었고 또 다른 의견은 현재 프론트엔드 팀 내부적으로
Vite에 대해 전문적인 지식을 가지고 있는 사람이 없어 유지보수가 어려울 수도 있다는 의견 이였어요
(이전에 스토리북 vite로 전환 작업을 했다가 실패한 경험을 말씀 주셨습니다)
결국 일단 실제 도입은 보류가 된 상태이고 현재 개인적으로 vscode extention을 만들어 사용하고 싶은 팀원들만 사용 할 수
있도록 만들어 두었어요!
추후 vscode extention에 대해 글을 작성할 예정 입니다
#유지보수
vscode extention을 이용해 개인적으로 사용하고 있던 중, 수 많은 파일들이 만들어지고 삭제되고 있었어요
그 중 새로 만들어진 파일 중 문제가 발생하였습니다
#문제
현재 어떠한 .js파일에서 <></> 프래그먼트 사용과 jsx문법을 사용을 하였지만
React를 import해보지 않아서 문제가 되고 있었어요.
/* eslint-disable react/react-in-jsx-scope */
/* eslint-disable max-len */
예시.
['BNKCAPITAL', {
rate: '최저 연 6.9 ~ 최고 연 15.9%(개인신용평점 및 구간별 차등적용)',
date: '2024.02',
deliberation1: '제2024-0014호(2024.02.05)',
deliberation2: <>2024-C1h-01768호 <br /> (2024.02.20~2025.02.19)</>,
}],
실제코드에요 이미 기존에 jsx파일이 아닌데 jsx문법을 사용해서 eslint오류가 발생 했을 거에요
하지만 저희 서비스 폴더의 컨벤션으로 jsx파일을 만들지 못하고 있는 상황이 였을거라 의심이 들어요.
그래서 급하게 eslint-disable로 오류를 막은거 같습니다.
해결
첫번째로는 jsx로 파일 확장자를 바꾸고 import React from 'react'하면 해결이 됐을 거 에요
하지만 컨벤션상 js파일만을 만들어서 사용 중이죠(tmi: 오래된 react환경이라 그대로 규칙을 따르고 있어요)
한창 vite config파일을 구성할때 babel 플러그인도 많이 보았던 경험이 있어서 아래의 플러그인을 바로 사용해보았어요
@babel/plugin-transform-react-jsx-development
해당 플러그인은 제가 이해하기로는 파일의 확장자 관계없이 React JSX 문법을 JavaScript로 변환해주는 역할을 하는 걸로 알고 있어요
그래서 사용하여 문제를 해결 하였어요.
'개발' 카테고리의 다른 글
AWS S3 웹 호스팅 도메인 연결 & https 적용하기 (0) | 2024.08.05 |
---|---|
Vite(react) production배포시 IE11호환 되지 않는 문제 (0) | 2024.05.28 |
[velog 2023-10-24] 모바일 인덱스 UI개편 swipe core컴포넌트 개발 (0) | 2024.05.28 |
[velog 2023-09-22] turborepo watch모드 build시 종속성 순서 보장하지 않는 문제 (0) | 2024.05.28 |
[velog 2023-06-23] 아이웨딩 스드메 계산기 개편 후기/회고 (0) | 2024.05.28 |