# Legacy 모듈 관리

## 웹 브라우저 환경에서의 모듈 <a href="#module-management-in-a-web-browser-environment" id="module-management-in-a-web-browser-environment"></a>

웹 애플리케이션 개발에서 모듈 관리는 매우 중요함에도, 관리가 쉽지 않습니다. 관리가 쉽지 않음을 체험하기 위해 간단한 실습을 진행해봅니다. 실습 파일 트리 구조는 다음과 같습니다.

{% tabs %}
{% tab title="디렉토리 구조" %}

```bash
.
├── src/
│   ├── modules/
│   │   ├── Euid/
│   │   │   ├── utils.js
│   │   │   ├── logger.js
│   │   │   └── tester.js
│   │   └── DOM.js
│   └── index.js
└── index.html
```

{% endtab %}
{% endtabs %}

{% file src="/files/-MU2Bdj7vePbjJVd2lwb" %}
모듈 관리 실습 zip (bit.ly/3qGFKVp)
{% endfile %}

실습할 모듈 코드는 다음과 같습니다. 각 탭의 코드를 복사하여 "**디렉토리 구조**" 대로 구성 해봅니다.

{% tabs %}
{% tab title="index.js" %}

```javascript
// 모듈 주입(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);

```

{% endtab %}

{% tab title="src/modules/Euid/logger.js" %}

```javascript
(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 || {});

```

{% endtab %}

{% tab title="src/modules/Euid/utils.js" %}

```javascript
(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 || {});

```

{% endtab %}

{% tab title="src/modules/Euid/tester.js" %}

```javascript
(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 || {}));
```

{% endtab %}

{% tab title="src/modules/DOM.js" %}

```javascript
(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);
```

{% endtab %}
{% endtabs %}

## 참고 <a href="#reference" id="reference"></a>

각 모듈 파일에 ES5 코드로 작성되었습니다. ([IE 9+](https://kangax.github.io/compat-table/es5/#ie9))

![](/files/-MTiSJMnTl7kjX5zewNI)

{% embed url="<https://kangax.github.io/compat-table/es5/#ie9>" %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://yamoo9.gitbook.io/webpack/web-modules/manage-legacy-modules.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
