> For the complete documentation index, see [llms.txt](https://yamoo9.gitbook.io/webpack/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yamoo9.gitbook.io/webpack/web-modules/manage-es-modules.md).

# 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 %}
