
기존 코드에서 tinymce/tinymce-react 를 CDN방식으로 이용하고 있었다.
이유는,
- 프로젝트에 파일을 포함하지 않아도 됨
- 캐싱 활용 가능 (다른 사이트에서도 같은 CDN 사용 시)
- 초기 설정이 간단
... 가장 큰 이유는 회사에서 빨리 HTML <-> 에디터 읽기쓰기 호환 기능을 가진 에디터를 도입해달라고 했기 때문.
하지만 React에서 CDN으로 불러오면 아래와 같은 단점이 있어서 npm방식으로 바꾸려 한다.
- CDN 서버 장애 시 사용 불가
- 버전 관리가 명확하지 않음 (URL에 버전이 있어도 관리가 어려움)
- 보안 이슈 가능성 (외부 스크립트 실행)
- 번들러 최적화 불가
아지만 이렇게 하다보니
예전에 써서 문제가 없던 Quill에디터는
모노리식 번들(단일번들) 이라 css만 import하면 동작하고, 모든것을 webpack이 처리해서 문제가 없었다고 한다.
TinyMCE 라는 라이브러리는 모듈러 아키텍처라 필요한 리소스를 런타임에 로드하기 때문에 React 에서 사용시 문제가 생겼다. 이 라이브러리의 유연성과 확장성 때문에 리소스 경로설정 이라는 것이 필요했다.
추가로 번들 크기가 증가할것이다 라는 문제점이 또 보였고..
결국 CRACO/webpack 설정을 하기로 했다.
루트/
├── craco.base.js # 공통 webpack 설정
├── .nvmrc # Node.js 버전 고정
└── apps/
└── seller-admin/
└── craco.config.js # 공통 설정 import + 오버라이드
Jenkins 배포 시 Node.js 24.6.0 이상 필요
빌드 메모리 사용량 증가 가능 (webpack 최적화)
seller-admin 테스트 완료 후 system-admin에도 동일 적용
script 에서
'start':'react-script start' 와 같은 것들을 아래처럼 바꾸고
devDependencies에서 아래와 같은 것들을 설치
const baseConfig = require('../../craco.base.js');
module.exports = {
...baseConfig,
// system-admin 특화 설정이 필요한 경우 여기에 추가
// 예: 특정 플러그인, 로더 설정 등
};
const fs = require('fs');
const path = require('path');
// 루트 디렉토리 찾기 (craco.base.js와 동일한 기준)
const rootDir = path.resolve(__dirname, '../../..');
// 여러 가능한 경로 시도
const possibleSources = [
// 1. 루트의 node_modules (craco.base.js와 동일한 방식)
path.resolve(rootDir, 'node_modules/tinymce'),
// 2. pnpm의 .pnpm 디렉토리에서 찾기
(() => {
try {
const pnpmStore = path.resolve(rootDir, 'node_modules/.pnpm');
if (fs.existsSync(pnpmStore)) {
// tinymce@로 시작하는 디렉토리 찾기
const entries = fs.readdirSync(pnpmStore);
for (const entry of entries) {
if (entry.startsWith('tinymce@')) {
const entryPath = path.join(pnpmStore, entry);
if (fs.statSync(entryPath).isDirectory()) {
const tinymcePath = path.join(entryPath, 'node_modules/tinymce');
if (fs.existsSync(tinymcePath)) {
return tinymcePath;
}
}
}
}
}
return null;
} catch (e) {
return null;
}
})(),
// 3. ui-components의 node_modules
path.resolve(rootDir, 'packages/ui-components/node_modules/tinymce'),
// 4. system-admin의 node_modules
path.resolve(__dirname, '../node_modules/tinymce'),
];
// 존재하는 소스 경로 찾기
let source = null;
for (const possibleSource of possibleSources) {
if (possibleSource && fs.existsSync(possibleSource)) {
source = possibleSource;
break;
}
}
if (!source) {
console.error('❌ TinyMCE not found in node_modules');
console.error(' Tried paths:');
possibleSources.forEach((p, i) => {
if (p) {
const exists = fs.existsSync(p) ? '✓' : '✗';
console.error(` ${i + 1}. ${exists} ${p}`);
} else {
console.error(` ${i + 1}. (skipped)`);
}
});
console.error('\n Please ensure TinyMCE is installed:');
console.error(' pnpm install');
console.error('\n Or check if TinyMCE is in packages/ui-components:');
console.error(' ls packages/ui-components/node_modules/tinymce');
process.exit(1);
}
const dest = path.resolve(__dirname, '../public/tinymce');
const checkFile = path.resolve(dest, 'plugins/help/js/i18n/keynav/en.js');
// 파일이 없을 때만 복사 (빠름)
if (!fs.existsSync(checkFile)) {
console.log('📦 Copying TinyMCE files (first time only)...');
console.log(` From: ${source}`);
console.log(` To: ${dest}`);
const { execSync } = require('child_process');
if (fs.existsSync(dest)) {
fs.rmSync(dest, { recursive: true, force: true });
}
const isWindows = process.platform === 'win32';
if (isWindows) {
execSync(`xcopy /E /I /Y "${source}" "${dest}"`, { stdio: 'inherit' });
} else {
execSync(`cp -R "${source}" "${dest}"`, { stdio: 'inherit' });
}
console.log('✅ Done');
} else {
console.log('⚡ TinyMCE files already exist');
}