# ES 모듈 관리

ES Modules을 활용하면 웹 브라우저 환경에서도 모듈 관리가 수월해집니다. (ES 6+ 코드로 변경)

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

```javascript
import * as Euid from './modules/Euid/index.js'
import { getNode, createElement, render } from './modules/DOM.js'

// Euid 멤버에서 하위 모듈 추출
const {
  logger: { success, error },
  tester: { test, expect },
  utils: { isFunction },
} = Euid

/* -------------------------------------------------------------------------- */

// 타이머 설정
window.setTimeout(() => {
  console.group('MODULE → 모듈 관리 상태')

  isFunction(getNode)
    ? success('의존성 모듈 관리에 문제가 없어 앱이 정상 작동합니다.')
    : error('의존성 모듈 관리에 문제가 있어 앱이 정상 작동하지 않습니다.')
})

/* -------------------------------------------------------------------------- */
// 테스트

test('createElement() 전달 속성', () => {
  const vNode = createElement('h3', { className: 'heading-3' }, 'TDD')

  expect(vNode.type).toBe('h3')
  expect(vNode.props.children).toBe('tdd')
})

/* -------------------------------------------------------------------------- */

// vNode 생성

const moduleLink = createElement(
  'a',
  {
    href: 'https://bit.ly/3brDMBS',
    rel: 'noopener noreferrer',
    target: '_blank',
    className: 'externalLink',
  },
  '모듈'
)

const cube = createElement('img', {
  className: 'cube',
  alt: '',
  src: './src/assets/cube.gif',
  height: 32,
})

const headline = createElement(
  'h1',
  { className: 'headline' },
  moduleLink,
  ' 관리',
  cube
)

const slogan = createElement(
  'p',
  { className: 'slogan' },
  '웹 브라우저 환경에서의 모듈 관리는 까다롭습니다.'
)

const container = createElement(
  'div',
  { className: 'container' },
  headline,
  slogan
)

/* -------------------------------------------------------------------------- */
// 렌더링

render(container, getNode('#root'))
```

{% endtab %}

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

```javascript
export * as logger from './logger.js'
export * as tester from './tester.js'
export * as utils from './utils.js'
```

{% endtab %}

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

```javascript
/* -------------------------------------------------------------------------- */
// 메시지 스타일

const 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;
    `,
}

/* -------------------------------------------------------------------------- */
// 메시지 유틸리티

export function log(message, messageStyle = MESSAGE_STYLES.log) {
  console.log(`%c${message}`, messageStyle)
}

export function info(message) {
  return log(`🔵 ${message}`, MESSAGE_STYLES.info)
}

export function success(message) {
  return log(`🟢 ${message}`, MESSAGE_STYLES.success)
}

export function warn(message) {
  return log(`🟠 ${message}`, MESSAGE_STYLES.warn)
}

export function error(message) {
  return log(`🔴 ${message}`, MESSAGE_STYLES.error)
}
```

{% endtab %}

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

```javascript
import { error } from './logger.js'

// JSON 메서드 추출
const { stringify: _serialize, parse: _deserialize } = window.JSON

// 타입 검사 유틸리티

export function typeIs(data) {
  return Object.prototype.toString.call(data).slice(8, -1).toLowerCase()
}

export function isNumber(data) {
  return typeIs(data) === 'number'
}

export function isString(data) {
  return typeIs(data) === 'string'
}

export function isBoolean(data) {
  return typeIs(data) === 'boolean'
}

export function isFunction(data) {
  return typeIs(data) === 'function'
}

export function isArray(data) {
  return typeIs(data) === 'array'
}

export function isObject(data) {
  return typeIs(data) === 'object'
}

/* -------------------------------------------------------------------------- */
// 배열 유틸리티

export function makeArray(likeArray) {
  return Array.from
    ? Array.from(likeArray)
    : Array.prototype.slice.call(likeArray)
}

/* -------------------------------------------------------------------------- */
// 시리얼라이즈 유틸리티

export const serialize = (data, prettiy) =>
  !prettiy ? _serialize(data) : _serialize(data, null, 2)

export const deserialize = (json) => _deserialize(json)

/* -------------------------------------------------------------------------- */
// 믹스인 유틸리티

export function mixins() {
  return makeArray(arguments).reduce((o1, o2) => {
    for (let key in o2) {
      if (o2.hasOwnProperty(key)) {
        const o1Value = o1[key]
        const 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 ?? []), ...o2Value]
        } else {
          o1[key] = o2Value
        }
      }
    }
    return o1
  }, {})
}

const _checkValueType = (method, value, key) => {
  if (!method(value)) {
    const message = `혼합할 각 객체 ${key} 속성 유형이 다릅니다.`
    error(message)
  }
}
```

{% endtab %}

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

```javascript
import { log, success, error } from './logger.js'
import { serialize } from './utils.js'

// 테스트 유틸리티
export const test = (title, callback) => {
  console.group(`TEST → ${title}`)
  try {
    log('테스트 결과:')
    callback()
  } catch (error) {
    error('테스트 실패: ' + error.message)
  }
  console.groupEnd()
}

// 익스펙트 유틸리티
export const expect = (actual /* 실제 값 */) => ({
  toBe: (expected /* 기대 값 */) => {
    expected !== actual
      ? error(
          `결과 값(${serialize(actual)})과 기대 값(${expected})이 다릅니다.`
        )
      : success(
          `결과 값(${serialize(actual)})과 기대 값(${expected})이 같습니다.`
        )
  },
  notToBe: (expected) => {
    // ...
  },
  toBeGreaterThan: (expected) => {
    // ...
  },
  toBeLessThan: (expected) => {
    // ...
  },
})
```

{% endtab %}

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

```javascript
import { isString, isFunction } from './Euid/utils.js'

/* -------------------------------------------------------------------------- */
// 유틸리티 함수

export function getById(idName) {
  return document.getElementById(idName)
}

export function getNode(selector, context = document) {
  return context.querySelector(selector)
}

export function getNodeList(selector, context = document) {
  return context.querySelectorAll(selector)
}

/* -------------------------------------------------------------------------- */

// vNode 생성 유틸리티
export function createElement(type, props, ...children) {
  props = { ...props, children }

  // type이 함수 컴포넌트인 경우
  if (isFunction(type)) {
    // 함수 호출 (props 전달)
    return type.call(null, props)
  }

  return { type, props }
}

// [비공개] 속성 바인딩 유틸리티
const _bindProps = (element, props) => {
  // props 복제
  props = { ...props }

  // children 속성 제거
  delete props.children

  Object.entries(props).forEach(([prop, value]) => {
    // 클래스 속성 설정
    if (prop === 'className') {
      element.classList.add(value)
    }

    // 이벤트 속성
    const isEventProp = /^on/.test(prop)
    const propIsClassName = prop !== 'className'

    if (isEventProp && propIsClassName) {
      element.addEventListener(prop.replace(/on/, '').toLowerCase(), value)
    }

    // 나머지 속성
    if (!isEventProp && propIsClassName) {
      element.setAttribute(prop, value)
    }
  })
}

// [비공개] vNode 렌더링 유틸리티
const _renderElement = (vNode) => {
  // vNode가 텍스트인 경우
  if (isString(vNode)) {
    return document.createTextNode(vNode)
  }

  // vNode = {type, props}
  // 요소 생성
  const element = document.createElement(vNode.type)

  // 속성 바인딩
  _bindProps(element, vNode.props)

  // 자식(들) 순환
  vNode.props.children
    // 재귀 호출
    .map(_renderElement)
    // 자식 노드 마운트
    .forEach((childNode) => element.appendChild(childNode))

  // 요소 반환
  return element
}

/* -------------------------------------------------------------------------- */

// vNode → DOM 노드 마운트(mount)
export const render = (vNode, domNode) =>
  domNode.appendChild(_renderElement(vNode))
```

{% endtab %}
{% endtabs %}

{% hint style="danger" %}
IE 웹 브라우저는 ES Modules를 지원하지 않습니다.
{% endhint %}

![\<script type="module"> 브라우저 지원율](/files/-MTgn6L3FLayXqFGIF5I)

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

{% embed url="<https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Modules>" %}
JavaScript 모듈 - Mozilla Developer Network
{% endembed %}

{% embed url="<https://caniuse.com/es6-module>" %}
ES6 모듈 브라우저 지원율
{% endembed %}


---

# 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-es-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.
