Legacy 모듈 관리
Last updated
Last updated
웹 애플리케이션 개발에서 모듈 관리는 매우 중요함에도, 관리가 쉽지 않습니다. 관리가 쉽지 않음을 체험하기 위해 간단한 실습을 진행해봅니다. 실습 파일 트리 구조는 다음과 같습니다.
.
├── src/
│ ├── modules/
│ │ ├── Euid/
│ │ │ ├── utils.js
│ │ │ ├── logger.js
│ │ │ └── tester.js
│ │ └── DOM.js
│ └── index.js
└── index.html
실습할 모듈 코드는 다음과 같습니다. 각 탭의 코드를 복사하여 "디렉토리 구조" 대로 구성 해봅니다.
// 모듈 주입(injection)
(function main(global, Euid, DOM) {
'use strict';
/* -------------------------------------------------------------------------- */
// Euid 모듈 멤버 추출
var logger = Euid.logger;
var tester = Euid.tester;
var utils = Euid.utils;
// logger 모듈 멤버 추출
var success = logger.success;
var error = logger.error;
// tester 모듈 멤버 추출
var test = tester.test;
var expect = tester.expect;
// utils 모듈 멤버 추출
var isFunction = utils.isFunction;
/* -------------------------------------------------------------------------- */
// DOM 모듈 추출
var getNode = DOM.getNode;
var createElement = DOM.createElement;
var render = DOM.render;
/* -------------------------------------------------------------------------- */
// 유효성 검사 조건 변수
var isValid = isFunction(getNode);
// 타이머 설정
global.setTimeout(function () {
console.group('MODULE → 모듈 관리 상태');
isValid
? success('의존성 모듈 관리에 문제가 없어 앱이 정상 작동합니다.')
: error('의존성 모듈 관리에 문제가 있어 앱이 정상 작동하지 않습니다.');
});
/* -------------------------------------------------------------------------- */
// 테스트
test('createElement() 전달 속성', function () {
const vNode = createElement('h3', { className: 'heading-3' }, 'TDD');
expect(vNode.type).toBe('h3');
expect(vNode.props.children).toBe('tdd');
});
/* -------------------------------------------------------------------------- */
// vNode 생성
var moduleLink = createElement(
'a',
{
href: 'https://bit.ly/3brDMBS',
rel: 'noopener noreferrer',
target: '_blank',
className: 'externalLink',
},
'모듈'
);
var cube = createElement('img', {
className: 'cube',
alt: '',
src: './src/assets/cube.gif',
height: 32,
});
var headline = createElement(
'h1',
{ className: 'headline' },
moduleLink,
' 관리',
cube
);
var slogan = createElement(
'p',
{ className: 'slogan' },
'웹 브라우저 환경에서의 모듈 관리는 까다롭습니다.'
);
var container = createElement(
'div',
{ className: 'container' },
headline,
slogan
);
/* -------------------------------------------------------------------------- */
// 렌더링
render(container, getNode('#root'));
})(window, window.Euid, window.DOM);
(function logger(Euid) {
'use strict';
/* -------------------------------------------------------------------------- */
// 메시지 스타일
var MESSAGE_STYLES = {
log: '\
color: #1c1c1d;\
font-weight: bold;\
',
success: '\
color: #00c712;\
font-weight: bold;\
',
info: '\
color: #006afc;\
font-weight: bold;\
',
warn: '\
color: #ff9500;\
font-weight: bold;\
',
error: '\
color: #ee3327;\
font-weight: bold;\
',
};
/* -------------------------------------------------------------------------- */
// 메시지 유틸리티
function log(message, messageStyle) {
console.log('%c' + message, messageStyle || MESSAGE_STYLES.log);
}
function info(message) {
return log('🔵 ' + message, MESSAGE_STYLES.info);
}
function success(message) {
return log('🟢 ' + message, MESSAGE_STYLES.success);
}
function warn(message) {
return log('🟠 ' + message, MESSAGE_STYLES.warn);
}
function error(message) {
return log('🔴 ' + message, MESSAGE_STYLES.error);
}
/* -------------------------------------------------------------------------- */
// 모듈 내보내기
Euid.logger = {
log,
warn,
error,
success,
};
})(window.Euid = window.Euid || {});
(function utils(Euid) {
'use strict';
/* -------------------------------------------------------------------------- */
// 타입 검사 유틸리티
var typeIs = function (data) {
return Object.prototype.toString.call(data).slice(8, -1).toLowerCase();
};
var isNumber = function (data) {
return typeIs(data) === 'number';
};
var isString = function (data) {
return typeIs(data) === 'string';
};
var isBoolean = function (data) {
return typeIs(data) === 'boolean';
};
var isFunction = function (data) {
return typeIs(data) === 'function';
};
var isArray = function (data) {
return typeIs(data) === 'array';
};
var isObject = function (data) {
return typeIs(data) === 'object';
};
/* -------------------------------------------------------------------------- */
// 배열 유틸리티
var makeArray = function (likeArray) {
return Array.prototype.slice.call(likeArray);
};
/* -------------------------------------------------------------------------- */
// 시리얼라이즈 유틸리티
var serialize = function(data, prettiy) {
return !prettiy ? JSON.stringify(data) : JSON.stringify(data, null, 2)
}
var deserialize = function(json) {
return JSON.parse(json)
}
/* -------------------------------------------------------------------------- */
// 믹스인 유틸리티
var mixins = function () {
return makeArray(arguments).reduce(function (o1, o2) {
for (var key in o2) {
if (o2.hasOwnProperty(key)) {
var o1Value = o1[key];
var o2Value = o2[key];
if (isObject(o2Value)) {
o1Value && _checkValueType(isObject, o1Value, key)
o1[key] = mixins(o1Value || {}, o2Value);
}
else if (isArray(o2Value)) {
o1Value && _checkValueType(isArray, o1Value, key)
o1[key] = (o1Value || []).concat(o2Value);
}
else {
o1[key] = o2Value;
}
}
}
return o1;
}, {});
};
var _checkValueType = function(method, value, key) {
if (!method(value)) {
var message = '혼합할 각 객체 ' + key + ' 속성 유형이 다릅니다.';
if (Euid.logger) {
Euid.logger.error(message)
} else {
throw new Error(message);
}
}
}
/* -------------------------------------------------------------------------- */
// 모듈 내보내기
Euid.utils = {
typeIs: typeIs,
isNumber: isNumber,
isString: isString,
isBoolean: isBoolean,
isFunction: isFunction,
isArray: isArray,
isObject: isObject,
makeArray: makeArray,
serialize: serialize,
deserialize: deserialize,
mixins: mixins,
};
})(window.Euid = window.Euid || {});
(function tester(Euid) {
'use strict';
// Euid 모듈 멤버 추출
var logger = Euid.logger;
var utils = Euid.utils;
/* -------------------------------------------------------------------------- */
// 테스트 유틸리티
var test = function (title, callback) {
console.group('TEST → ' + title);
try {
logger.log('테스트 결과:');
callback();
} catch (error) {
logger.error('테스트 실패: ' + error.message);
}
console.groupEnd();
};
// 익스펙트 유틸리티
var expect = function (actual /* 결과 값 */) {
return {
toBe: function (expected /* 기대 값 */) {
if (expected !== actual) {
logger.error(
'결과 값(' + utils.serialize(actual) +
')과 기대 값("' + expected + '")이 다릅니다.');
} else {
logger.success(
'결과 값(' + utils.serialize(actual) +
')과 기대 값("' + expected + '")이 같습니다.');
}
},
notToBe: function (expected) {
// ...
},
toBeGreaterThan: function (expected) {
// ...
},
toBeLessThan: function (expected) {
// ...
},
};
};
Euid.tester = {
test,
expect,
};
})((window.Euid = window.Euid || {}));
(function DOM(global, Euid) {
'use strict';
/* -------------------------------------------------------------------------- */
// 의존 모듈 검사
if (!Euid) {
throw new Error('DOM 모듈이 정상 작동하려면 Euid 모듈이 필요합니다.');
}
/* -------------------------------------------------------------------------- */
// Euid 모듈 멤버 추출
var utils = Euid.utils;
// utils 멤버 추출
var isString = utils.isString;
var isFunction = utils.isFunction;
var makeArray = utils.makeArray;
var mixins = utils.mixins;
/* -------------------------------------------------------------------------- */
// 폴리필(Polyfill)
if (!Object.entries) {
Object.entries = function (obj) {
// Object.keys() IE 9+
var ownProps = Object.keys(obj);
var i = ownProps.length;
var resArray = new Array(i);
while (i--) {
resArray[i] = [ownProps[i], obj[ownProps[i]]]
}
return resArray;
};
}
/* -------------------------------------------------------------------------- */
// 유틸리티 함수
var getById = function (idName) {
return document.getElementById(idName);
};
var getNode = function (selector, context) {
return (context || document).querySelector(selector);
};
var getNodeList = function (selector, context) {
return (context || document).querySelectorAll(selector);
};
/* -------------------------------------------------------------------------- */
// vNode 생성 유틸리티
var createElement = function() {
// arguments → args 배열 변경
var args = makeArray(arguments)
var type = args[0]
var props = args[1] || {}
var children = args.slice(2) // 나머지 인자 집합(배열)
props.children = children
// type이 함수 컴포넌트인 경우
if (isFunction(type)) {
// 함수 호출 (props 전달)
return type.call(null, props)
}
return {
type: type,
props: props
}
}
// [비공개] 속성 바인딩 유틸리티
var _bindProps = function(element, props) {
// props 복제
var props = mixins(props)
// children 속성 제거
delete props.children
var propValues = Object.entries(props)
propValues.forEach(function(propValue) {
var prop = propValue[0]
var value = propValue[1]
// 클래스 속성 설정
if (prop === 'className') {
element.classList.add(value)
}
// 이벤트 속성
var isEventProp = /^on/.test(prop)
var propIsClassName = prop !== 'className'
if (isEventProp && propIsClassName) {
element.addEventListener(prop.replace(/on/, '').toLowerCase(), value)
}
// 나머지 속성
if (!isEventProp && propIsClassName) {
element.setAttribute(prop, value)
}
})
}
// [비공개] vNode 렌더링 유틸리티
var _renderElement = function(vNode) {
// vNode가 텍스트인 경우
if (isString(vNode)) {
return document.createTextNode(vNode)
}
// vNode = {type, props}
// 요소 생성
var element = document.createElement(vNode.type)
// 속성 바인딩
_bindProps(element, vNode.props)
// 자식(들) 순환
vNode.props.children
// 재귀 호출
.map(_renderElement)
// 자식 노드 마운트
.forEach(function(childNode) {
element.appendChild(childNode)
})
// 요소 반환
return element
}
/* -------------------------------------------------------------------------- */
// vNode → DOM 노드 마운트(mount)
var render = function(vNode, domNode) {
domNode.appendChild(_renderElement(vNode))
}
/* -------------------------------------------------------------------------- */
// 모듈 내보내기
global.DOM = {
getById,
getNode,
getNodeList,
createElement,
render,
};
})(window, window.Euid);
각 모듈 파일에 ES5 코드로 작성되었습니다. (IE 9+)