
사내 프로젝트에서 혼용해서 사용하다가 yarn start가 안됐던 문제를 이해하자!
import logger from '../logger/logger'로 사용하고 있던 내부 코드는 아래와 같았습니다. CommonJS환경과 ESM환경을 혼용해서 사용하고 있었습니다.const winston = require('winston');
//code..
const logger = createLogger({
format: format.combine(
format.label({ label: 'INET_DASHBOARD' }),
format.timestamp({ format: localTimestamp }),
logFormat,
),
transports: [
fileTransportInfo,
fileTransportError,
fileTransportSilly,
// 개발환경에서는 콘솔 로그도 활성화
...(process.env.NODE_ENV !== 'production'
? [
new transports.Console({
format: format.combine(format.colorize(), format.simple()),
logFormat,
}),
]
: []),
],
});
export default logger;
사용하던 빌드 도구에서 타입 체크와 코드 변환은 수행하는 데, 모듈 로딩 방식을 강제하지는 않아서 그냥 통과했던 것 같다고 생각합니다.
yarn start를 사용하여 노드 환경에서 런타임으로 동작시키면 CommonJS와 ESM을 구분하기 때문에 빌드 시 사용하는 로거가 undefined로 넘어와 문제가 발생했습니다.
이를 해결하기 위해서 모듈 로딩 방식을 일치시켜야 했는 데, 기존 프로젝트에 CommonJS환경으로 사용하고 있었기 때문에 일관성을 위해 CommonJS환경으로 변경시켰습니다.
모듈 로딩 방식에 대해 관성적으로만 사용하고 있었는 데, 이런 이슈를 겪고 어떤 이유 때문에 발생했는 지 학습하고자 했습니다.
CJS의 경우, require() / module.exports 문법을 사용하고 있습니다.
노드 초창기에 사용되던 모듈로, named exports와 default exports등의 방식으로 사용되었습니다.
// named exports - 여러 개 내보내기
exports.add = (a,b) => a+b;
exports.subtract = (a,b) => a-b;
// 가져오는 경우
const { add } = require('./파일경로')
//default export - 하나 내보내기
const logger = {
info : (message) => console.log('info : ', message);
error : (message) => console.error('error : ', message);
}
module.exports = logger
// 가져오는 경우
const logger = require('/파일경로')
logger.info('서버 시작')
const log = require('/파일경로') 함께 사용하는 방식도 있으나, 그 경우 module.exports가 더 우선적입니다.
exports.multiply = (a, b) => a * b;
exports.divide = (a, b) => a / b;
module.exports = {
multiply: exports.multiply,
divide: exports.divide,
default: '이건 기본값!',
};
// 가져오는 경우
const math = require('./math');
console.log(math.multiply(2, 3)); // 6
console.log(math.default); // '이건 기본값!'
동기로 이루어져 있습니다. -> promise나 콜백 호출을 리턴하지 않습니다.
또한 사용하지 않는 코드를 제거하여 번들 크기를 줄이는 방식의 트리 셰이킹을 적용하기 어렵습니다.
ESM은 import / export 문법을 사용합니다. import module from 'module'
ES6부터 도입된 모듈 시스템으로서, 브라우저와 노드환경에서 모두 사용 가능하고, 정적인 구조를 가지고 있습니다. 따라서 CJS에 비해 트리 셰이킹이 용이하다는 장점을 가지고 있습니다.
또한 모듈화가 가능하다는 점은 코드를 독립적으로 동작할 수 있는 단위로 나누기가 용이하여 재사용성과 유지보수성이 높다는 장점이 있습니다.
export function add(a,b) {
return a + b;
}
export const PI = 3.14
import {add, PI} from './utils.js';
console.log(add(2,3)) // 5
console.log(PI) // 3.14
import / export를 하는 경우에 renaming을 적용할 수도 있습니다.
export { function1 as newFunction, function2 as newFunction2 }
import {newFunction, newFunction2 } from './ .. ';
export { test }
import { test as newTest }
ESM을 사용하기 위해서는 package.json에서 간단한 설정이 필요합니다.
// package.json
{
"type" : "module",
}
마무리
ESM에 익숙해진 상황에서 관성적으로 사용하다보니 프로젝트에서도 체크없이 사용하다 yarn start가 되지 않는 문제가 있었는데, 해결하는 과정에서 CJS/ESM에 대해 학습하고 왜 혼용해서 사용하면 안되는 지에 대해 생각할 수 있는 경험이었습니다.