JavaScript/Syntax

[JavaScript] Modules

Withlaw 2022. 8. 27. 17:34

-모던 자바스크립트 프로그래밍에서는 모든 코드를 하나의 스크립트에서 작성하지 않는다.

-프로젝트를 여러 파일로 분할하여 관리하는데, 이 분리된 파일을 모듈이라 부른다.

-모듈은 직접 만들기도하고 외부에서 불러오기도 한다(외부 오픈 소스 모듈: Third-party module).

-모듈은 보통 클래스 하나 혹은 특정한 목적을 가진 복수의 함수로 구성된 라이브러리 하나로 구성된다.

 

-Third-part package는 내가 직접 코드를 처음부터 개발하는데에 힘쓰지 않고 핵심 비즈니스 논리에만 집중하도록 해준다.

-외부 모듈 파일을 설치하는 방법은 직접 스크립트를 다운로드하거나 CDN url 링크를 삽입하는 방법이 있다.

  *CDN 방식은 외부 서버에서 파일을 로드하기 때문에 직접 관리할 필요도 없고, 무엇보다 다운로드 속도가 빠르다.

 

-단순히 파일을 분할하여 HTML에서 로드하는 방식은 다음과 같은 단점이 있다.

    1. 필요 없는 코드까지 모두 다운로드 되어 오버헤드 현상이 발생한다. 이는 성능 저하로 이어진다.

    2. 의존성을 고려하여 스크립트를 로드하는 순서를 하나하나 수동으로 지정해야한다. 이는 손이 많이가고 오류를 유발할 수 있다.

 

-모던 자바스크립트의 모듈 시스템은 이러한 문제점을 해결하였다.

-js 파일 내에서 'export' 키워드를 사용하는 순간 해당 파일은 잠기게 된다. 

    1. 모듈 파일로 전환되어 html에서 script에 type="module" 속성을 추가해준다.

    2. 모든 모듈은 각각 고유의 스콥을 형성한다. 모듈화의 핵심은 캡슐화이다.

    3. 모듈 내에서 정의된 모든 변수, 함수, 객체 등의 데이터는 private 상태가 되어 외부에서 직접 접근할 수 없게 된다. export로 public api를 제공해야 한다.

    4. 기본적으로 strict mode로 실행 된다. this 키워드는 window 전역객체가 아닌 undefined를 출력한다. (하지만 window 객체에 직접 접근할 수 있다. window 혹은 globalThis를 사용)

  

-키워드

  -> import: 외부 모듈에서 데이터를 가져옴 

  -> export: 외부 모듈에서 해당 데이터에 접근 가능하도록 설정함

 

-작동 방식

    1. 브라우저는 호스팅 서버로부터 html -> css -> js의 순서로 파일을 다운로드 한다.

    2. root 스크립트 파일을 파싱하고 실행하면, 최상단의 import 문이 가장 먼저 실행된다.

    3. import statement는 정적이고 동기적으로 실행된다. (항상 import 코드부터 실행됨. 코드가 맨 아래에 적혀있어도 호이스팅됨)

    4. 번들링을 하지 않는다면 매번 import 문을 만날때 마다 해당 모듈 로드 요청을 서버에 보낸다. 

    5. 의존성 트리를 따라 import 구문이 없는 최종 모듈 파일의 로드가 완료되면 파싱과 실행을 역순으로 진행한다. (root 모듈이 가장 마지막에 실행된다.)

    6. 각 모듈 파일은 최초 1회만 평가 된다.

 

*처음 페이지 로딩 속도를 향상시킬 수 있는 최적화 방법: 기본적으로 import는 정적으로 실행되기 때문에 클라이언트가 url에 접근하면 모든 모듈 파일이 다운로드 된다. 이는 페이지 로딩 속도를 늦춘다. import를 프로미스로 처리하면 동적 로딩(코드 분할) 기능을 구현할 수 있다. 당장 필요한 것만 서버에 요청하여 다운로드 및 파싱 시간을 단축할 수 있다. 동적 로딩이란 코드가 필요한 시점에 조건부로 다운로드 한다는 말이다. 

    => function (...) { ... import('./helper.js').then(module=>{ ... module.helper() ....  }) ... };

 

-구문

1. named export 

    -내보내고자 하는 변수나 함수 선언문과 함께 작성함. 해당 선언은 모듈의 최상단 스콥에서 이루어져야함.

    -'named' 내보내기임. 즉 선언된 변수, 함수, 클래스 등에 사용함

    -이때, export 모듈의 코드는 객체에 담겨서 내보내짐

// exporting module (shoppingCart.js)
const shippingCost = 10;
const cart = [];
export const addToCart = function (product, quantity) {
  cart.push({ product, quantity });
  console.log(`${quantity} ${product} added to cart`);
}


// importing module (index.js)
import './shoppingCart.js';
import { addToCart } from './shoppingCart.js'

 

    -여러 코드를 보낼 땐 위 방식을 계속 사용하면됨. 한 번에 보낼 땐 마지막에 {}로 묶어서 보냄. 함수에 인자 전달하듯이

// exporting module (shoppingCart.js)
const totalPrice = 200;
const totalQuantity = 15;
export { totalPrice, totalQuantity };

// importing module (index.js)
import { totalPrice, totalQuantity } from './shoppingCart.js'

    -값의 이름을 변경할 땐 as를 사용한다.

export { totalPrice as total, totalQuantity };

//or 

import { totalPrice as total, totalQuantity } from './shoppingCart.js'

    -모듈 파일 내 모든 코드들을 불러올 땐 * 과 as를 사용한다.

import * from './shoppingCart.js'

// *는 객체이므로 다음과 같이 사용할 수 있다.
import * as shoppingCart from './shoppingCart.js'
shoppingCart.addToCart('bread' 5);

 

2. default export

    -해당 모듈에서 export하는 개체는 이것이 유일하다 라는 의미를 담고 있다.

    -컴포넌트와 같이 핵심 요소는 기본 내보내기로 설정한다.

    -expression만 내보낼 수 있다.

    -funtion, class 선언문과 함께 쓰일 수 있으나, 변수할당은 할당연산자가 포함되기 때문에 못쓰인다.

    -객체에 담지 않고 바로 내보낸다. 따라서 import할 때 중괄호가 필요 없다.

// exporting module (shoppingCart.js)
export default function (product, quantity) {
  cart.push({ product, quantity});
  console.log(`${quantity} ${product} added to cart`);
}

// importing module (index.js)
import add from './shoppingCart.js';

 

*** imports are not copies of the export, they are instead like a live-connection. (point to the same place in memory)

 

 

*자바스크립트 모듈 import/export 문법 심화

https://jakearchibald.com/2021/export-default-thing-vs-thing-as-default

 

 

*es6가 등장하기 이전에는 IIFE 함수와 클로저를 사용하여 다음과 같이 모듈 패턴을 사용하였다.

const ShoppingCart = (function () {
  const cart = [];
  const shippingCost = 10;
  const totalPrice = 237;
  const totalQuantity = 23;

  const addToCart = function (product, quantity) {
    cart.push({ product, quantity });
    console.log(
      `${quantity} ${product} added to cart (sipping cost is ${shippingCost})`
    );
  };

  const orderStock = function (product, quantity) {
    console.log(`${quantity} ${product} ordered from supplier`);
  };

  return {
    addToCart,
    cart,
    totalPrice,
    totalQuantity,
  };
})();

ShoppingCart.addToCart('apple', 4);
ShoppingCart.addToCart('pizza', 2);
console.log(ShoppingCart);
console.log(ShoppingCart.shippingCost);