콘텐츠로 이동

2023 12 15

2023-12-15

JS 모듈 시스템 - CJS, AMD, UMD, ESM

참고: https://beomy.github.io/tech/javascript/cjs-amd-umd-esm/ 참고: https://betterprogramming.pub/what-are-cjs-amd-umd-esm-system-and-iife-3633a112db62

  • 개요
    • 현대 JS 프로젝트는 번들러를 통해서 작은 조각의 코드를 하나의 뭉텅이로 뭉쳐서 내보내야 함 (라이브러리/어플리케이션 구축)
      • webpack, Rollup, Parcel, RequireJS, Browserify
    • JS 모듈을 가져오거나 내보내는 방법이 없었음 => 하나의 파일에 모든 기능들이 들어가야 함
    • CJS,AMD,UMD,ESM이 등장 후에 모듈로 개발/배포할 수 있게 됨
  • 번들러
    • 하나의 파일로 뭉텅이 지어서 사용하는것은 간단한 경우에는 괜찮은데...
      • 프로젝트가 확장될 수록, 우리는 모듈화의 니즈가 커짐 (별도의 namespace, 캡슐화, 의존성관리, 재사용 등)
    • 이때 번들러가 필요해짐
      • css, 이미지 등등
    • 번들러가 코드를 어떤 형식으로 내보낼지 정의하게 됨
      • cjs: node 혹은 타 번들러에 적합
      • amd: RequireJS와 같은 모듈 로더와 함께 쓰임
      • umd: amd, cjs, iife 올인원으로 쓰임
      • es/esm: ES 모듈 파일 <script type=module> 태그임
      • system: SystemJS에서 Native Format
      • iife: Self Executing Function
  • CJS (CommonJS)

    • CommonJS: JS를 브라우저뿐만이 아니라 범용언어로 사용하고자 만들어진 그룹
    • [코드 예제]
      var lib = require('package/lib');
      
      function foo() {
          lib.log('hello world');
      }
      
      exports.foobar = foo;
      
      // ---
      var foo = require('foo');
      var bar = require('bar');
      foo.log('It is foo');
      bar.log('It is bar');
      
    • [특징]
      • 동기적으로 동작
      • 서버사이드에서 사용하기 용이함
      • Node.js가 CommonJS를 채택하여 사용 중
  • AMD (Asynchronous Module Definition)

    • CommonJS는 모든 파일이 로컬디스크에 있어서 바로 불러올 수 있어야 해
    • 동기적으로 동작이 가능한 서버사이드 JS 환경을 전제로 함
    • 브라우저에서는 이게 다운 못 받으면 암것도 못하는 상황으로 치닫음
    • AMD는 브라우저 안에서의 동작을 중점을 둔 그룹임
    • [코드 예제]
      define(['package/lib'], function(lib) {
          function foo() {
              lib.log('hello world');
          }
      
          return {
              foobar: foo
          };
      });
      
      require(['package/myModule'], function(myModule) {
          myModule.foobar();
      });
      
    • [특징]
      • 비동기
      • 네트워크를 통해 모듈을 내려받는 브라우저이기에, 비동기로 동작해야 함
      • 비동기적인 특징으로 클라이언트 사이드 개발에 용이
  • UMD (Universal Module Definition)

    • AMD, CommonJS 호환을 위해 해결
      • AMD, CommonJS, window에 추가하는 방식까지 모두 가능한 모듈 작성 방식
    • 두 부분으로 구성됨
      • 모듈 로더를 확인하는 즉시 실행 함수(IIFE): 이 함수는 root(전역 범위)와 factory(모듈을 선언하는 함수) 2개의 파라미터 가짐
      • 모듈을 생성하는 익명 함수: 이 함수가 즉시 실행 함수의 2번째 파라미터로 넘어감
    • [코드 예제]
      (function(root, factory) {
          if (typeof define === 'function' && define.amd) {
              // AMD
              define(['exports', 'b'], factory);
          } else if (typeof exports == 'object' && typeof exports.nodeName !== 'string') {
              // CommonJS
              factory(exports, require('b'));
          } else {
              // Browser globals
              factory((root.commonJsStrict = {}), root.b);
          }
      }(this, function (exports, b) {
          exports.action = function() {};
      }));
      
    • [특징]
      • 여러 모듈 로더에서 사용 가능
      • AMD/CommonJS 모두 사용가능
  • ESM (ECMAScript Module)

    • ES6에 JS 모듈 기능이 추가됨
    • [코드 예제]
      import lib from 'package/lib';
      
      function foo() {
          lib.log('hello world');
      }
      
      export { foobar: foo };
      
    • [특징]
      • ECMAScript에서 지원하는 JS 자체 모듈 시스템
      • 브라우저에서는 import, export를 지원하지 않아 번들러를 함께 사용해야 함
    • [부록]
      • <script type="module" src="index.mjs">
        • type="module" 선언하면 자바스크립트 파일은 모듈로 동작
        • 모듈이라는 것을 명확히 알기 위해 mjs 확장자를 사용하도록 권장
        • 해당 파일에 import, export 사용이 가능
        • foo.mjs, bar.mjs 파일의 window는 서로 공유되지 않음
          <html lang="en">
          <body>
              <script type="module" src="foo.mjs"></script>
              <script type="module" src="bar.mjs"></script>
          </body>
          </html>
          

IIFE (Immediately Invoked Function Expression)

참고: https://developer.mozilla.org/ko/docs/Glossary/IIFE 참고: https://mine-it-record.tistory.com/339

  • 개요
    • 정의되자마자 즉시 실행되는 JS Function
  • 형식
    • 크게 두 부분으로 나뉨
      1. Grouping Operator
        • () 안에 어휘 범위로 둘러싸인 익명함수.
        • 전역 스코프에 불필요한 변수 추가해서 오염을 방지함.
          • 스코프를 딱 요만큼안으로 제한
        • IIFE 안으로 다른 변수 접근 못하게 함.
      2. 즉시 실행함수를 생성하는 괄호
        • JS 엔진은 함수를 즉시 해석해서 실행
          (function () {
              // ...
          })();
          
          (() => {
              // ...
          })();
          
          (async () => {
              // ...
          })();
          
  • 언제 사용하지?
    1. 코드 사이의 충돌을 예방 (스코프 제한)
    2. 전역 변수/전역 함수가 되지 않도록 방지
    3. 변수의 값을 즉시 할당

JS 코드 축소

참고: https://www.cloudflare.com/ko-kr/learning/performance/why-minify-javascript-code

  • 최소화?
    • JS에서 기능을 변경하지 않고 불필요한 문자를 모두 제거하는 프로세스
    • 공백/주석/세미콜론 제거/더 짧은 함수-변수 이름 사용
    • 코드 재사용, 캐시 적용, CDN 사용
    • 코드의 압축
  • 압축(minify)
    • 전체 소스코드 중 아래의 경우를 제거하는 작업
      • 불필요한 줄바꿈, 공백 및 들여쓰기
      • 짧게 쓸 수 있는 긴 구문 축소
      • 주석
      • 스코프 내 미사용 변수
  • 난독화(uglification)
    • 본질적으로 최소화와 동일
    • JS 파일을 축소하기 위한 JS 라이브러리
    • 분석을 어렵게 하기 위해 난독화 뚝딱
  • 압축 도구
    • 주요 JS 라이브러리에서 가장 많이 쓰이는 소스 압축 도구
    • Node.js 기반이라 다른 Node 모듈과의 연계 좋음
    • npm install -g uglify-js
    • uglifyjs hello.js -o hello.min.js

IOT (Index Organized Table) - 인덱스 일체형 테이블

참고: https://blog.naver.com/geartec82/220954857049 참고: http://gurubee.net/lecture/2601

  • 일반 테이블
    • 키값 -> 인덱스 -> ROW_ID -> ROW_ID로 테이블 데이터 읽기
    • 키 칼럼이 인덱스와 테이블 양쪽에 중복해서 저장되기에, 키 값이 큰 경우에는 중복된 인덱스 크기가 할당됨.
    • 테이블 구조 기본하에 인덱스가 참조하는 방식
  • IOT
    • 인덱스 구조 하에 테이블 데이터 저장 구조를 가진 것
    • 키값에 해당되는 레코드를 테이블에서 읽을 필요도, 데이터의 중복 문제도 동시 해결 가능
    • 인덱스를 읽는 것만으로도 데이터 참조가 가능하다네?!
    • Primary Key를 근간으로 한 인덱스 이기에, 전제조건으로 Primary를 반드시 필요로 함
  • IOT 특징
    • IOT는 테이블 데이터를 PK에 대한 B-Tree Index에 적재
    • IOT의 index row들은 인덱스 Key 값과 Non-Key 값을 포함
    • IOT의 index에는 ROW_ID 정보가 없음
  • IOT 장점
    • RANGE SEARCH, EXACT MATCH 수행 시 일반적인 TABLE보다 빠른 KEY-BASED ACCESS 가능
    • FULL TABLE SCAN시 PK에 대한 FULL INDEX SCAN이 이루어짐 (자동적인 ORDERING)
    • INDEX KEY COLUMN과 ROW_ID에 대한 스토리지 중복을 피할 수 있음 (스토리지 절약)
  • IOT 제약사항
    • 추가적인 index 생성 X
      • 오로지 PK에 대해서만 indexing
    • cluster table로 이용되지 못함
    • 병렬 작업 불가능
    • 분산/복제/분할 불가능
    • long/long raw/lob 지원 안됨
  • 일반 테이블과 IOT 테이블의 차이점
    • 일반 테이블은 ROW_ID로 행 구별 | IOT는 PK로 행 구별
    • 일반 테이블의 FULL SCAN 행이 리턴되는 순서 예측 가능 | IOT는 PK값의 순서에 따라 출력
    • IOT는 Unique 못 걸음
    • IOT는 일반 테이블보다 저장 공간 감소
    • IOT는 PK 필수로 생성
    • IOT의 Secondary Index는 PK값과 그것을 기반으로 하는 Universal Rowid (인덱스 구성 테이블의 논리적인 위치)
    • IOT는 일반 인덱스와 달리 UROWID를 통해 빠른 검색 가능

Promise

  • 아 이제 보이네
    • iife 방식으로 코드를 작성해 즉시 실행함수를 호출해서 뚝딱 스코프 벗어나지 않으면서 실행시킬 수 있도록!
      useEffect(() => {
        (async () => {
            const isUsingNotice = await gnbSerivceType.isUsingNotice();
            if (isUsingNotice) {
                setIsUsingNotice(true);
            } else {
                setIsUsingNotice(false);
            }
        })();
      }, [serviceType])
      
  • 헤맷던 promise ```typescript export abstract class ServiceTypeClass { abstract isUsingNotice(): Promise; }

    class NoticeApi { private static instance: NoticeApi; private noticeUsingServiceTypes: string[] | null = null;

    public static getInstance() { if (!NoticeApi.instance) { NoticeApi.instance = new NoticeApi(); } return NoticeApi.instance; }

    getNoticeServiceTypes = async () => { const response = await NoticeApiService.getServiceTypes(); this.noticeUsingServiceTypes = response.list; }

    isUsingNotice = async (serviceType: string): Promise => { if (this.noticeUsingServiceTypes == null) { await this.getNoticeServiceTypes(); } return this.noticeUsingServiceTypes?.some((st) => st === serviceType) || false; } }

    export class Test extends ServiceTypeClass { isUsingNotice = (): Promise => NoticeApi.getInstance().isUsingNotice('test') } ```

Promise resolve/reject & await/async

참고: https://springfall.cc/article/2022-11/easy-promise-async-await

  • 개요
    • JS에서는 함수를 전달할 수 있음
    • function a에 인자로 function b를 전달하더라도, b는 바로 실행되지 않음. b 함수는 a에서 언젠간~ 실행이 될것이다 하고 넘김
      • 이걸 콜백 함수라고 지칭
  • 비동기
    1. 흐름을 예측하기 어려움
    2. 콜백 지옥
      • 비동기 작업이 끝났을 뒤에 이어질 작업을 미리 부여하는 식으로 흐름을 제어할 수 밖에 없음
  • Promise

    1. Promise: 비동기 작업의 단위

      • Promise 생성자 안에 화살표 함수를 넘겨줘 (resolve, reject) => {} (executor로 지칭) js const promise1 = new Promise((resolve, reject) => { // 비동기 작업 });
      • resolve: 비동기 작업이 성공했어요!
      • reject: 비동기 작업이 실패했어요...
        • Promise 선언된 순간 비동기 작업 시작됨
      • new Promise()를 통해 기다리지 않고 바로 호출해버림!
    2. Promise가 끝난 다음: then, catch

      • then: 해당 promise가 성공했을때의 동작 지정
      • catch: 해당 promise가 실패했을때의 동작 지정
      • 체인 형태로 활용 가능 ```js const promise1 = new Promise((resolve, reject) => { resolve(); });

        promise1 .then(() => {console.log("then!")}) .catch(() => {console.log("catch!")});

        // then! js const promise1 = new Promise((resolve, reject) => { reject(); });

        promise1 .then(() => {console.log("then!")}) .catch(() => {console.log("catch!")});

        // catch! ```

    3. executor tips

      • executor 내부에서 에러가 throw 된다면 해당 에러로 reject가 수행됨
      • executor의 리턴값은 무시됨
      • 첫번째 reject, resolve 값만 유효
    • 정리
      • Promise: 비동기 작업을 생성/시작하는 부분(new Promise(...))과 작업 이후의 동작 지정 부분(then, catch)을 분리함으로써 유연한 설계 제공
      • Promise의 상태: pending, fulfilled, rejected
  • async/await

    • async: 함수를 선언할 때 붙여줄 수 있음
      • executor에서 몇가지 규칙 적용하면 new Promise(...)를 리턴하는 함수를 async 함수로 손쉽게 변환 가능
        • 함수에 async 키워드 붙임
        • new Promise... 없애고 executor 본문만
        • resolve(value)return value로 변경
        • reject(new Error(...))throw new Error(...)로 변경
      • async 함수의 리턴값은 무조건 Promise!
    • await: 프로미스 끝날때 까지 기다려주세요
      • async 내부에서만 사용 가능
      • resolve된 값을 사용하거나, reject되어 던저진 에러를 핸들링하거나
    • 둘 중 하나의 패턴을 고르세요~ ```js function setTimeoutPromise(delay) { return new Promise((resolve) => setTimeout(resolve, delay)); }

      async function startAsync() { await setTimeoutPromise(1000); await setTimeoutPromise(1000); console.log("A"); console.log("B"); }

      startAsync(); js function setTimeoutPromise(delay) { return new Promise((resolve) => setTimeout(resolve, delay)); }

      function startAsync() { setTimeoutPromise(1000) .then(() => setTimeoutPromise(1000)) .then(() => console.log("A")) .then(() => console.log("B")); }

      startAsync(); ```

  • Promise.all

    • 다음과 같은 끔찍한 코드 => 10초가 걸린다!! ```js function setTimeoutPromise(ms) { return new Promise((resolve, reject) => { setTimeout(() => resolve(), ms); }); }

      async function fetchAge(id) { await setTimeoutPromise(1000); console.log(${id} 사원 데이터 받아오기 완료!); return parseInt(Math.random() * 20, 10) + 25; }

      async function startAsyncJobs() { let ages = []; for (let i = 0; i < 10; i++) { let age = await fetchAge(i); ages.push(age); }

      console.log( 평균 나이는? ==> ${ ages.reduce((prev, current) => prev + current, 0) / ages.length }, ); }

      startAsyncJobs();

      /* 0 사원 데이터 받아오기 완료! 1 사원 데이터 받아오기 완료! 2 사원 데이터 받아오기 완료! 3 사원 데이터 받아오기 완료! 4 사원 데이터 받아오기 완료! 5 사원 데이터 받아오기 완료! 6 사원 데이터 받아오기 완료! 7 사원 데이터 받아오기 완료! 8 사원 데이터 받아오기 완료! 9 사원 데이터 받아오기 완료! 평균 나이는? ==> 33

      • */ ```
    • 정상적인 비동기로... ```js function setTimeoutPromise(delay) { return new Promise((resolve) => setTimeout(resolve, delay)); }

      async function fetchAge(id) { await setTimeoutPromise(1000); console.log(${id} 사원 데이터 받아오기 완료!); return Math.round(Math.random() * 20) + 25; }

      async function startAsyncJobs() { const ids = Array.from({ length: 10 }).map((_, index) => index);

      const promises = ids.map(fetchAge);

      const ages = await Promise.all(promises);

      console.log( 평균 나이는? ==> ${ ages.reduce((prev, current) => prev + current, 0) / ages.length }, ); }

      startAsyncJobs(); ```

    • 이를 통해 map 함수에다가 async 함수 넣으면 Promise 배열 얻는데 -> 이걸 Promise.all() 로 뚝!딱! 한큐에 처리가능

JS가 싱글쓰레드임에도 비동기로 처리할 수 있는 이유

참고: https://chanyeong.com/blog/post/44 참고: https://stitchcoding.tistory.com/44