보통 프론트엔드를 공부할 때,
HTML & CSS -> 자바스크립트 -> 자바스크립트 라이브러리(React, Vue, Angular..)순으로 익혀나간다.
하지만 나는 자바스크립트 기본 문법만 공부하고 바로 React로 넘어갔었다.
이유는 자바스크립트 공부가 너무 지겨웠기 때문이였다.
그래서 나는 프로젝트를 시작하고 파일을 구성할 때,
기능 or 역할별로 파일들을 분리해놓고 시작한다.
이런 방식을 자바스크립트에 적용하고 싶었으나 여러 문제가 발생하였고,
자바스크립트 모듈을 활용하여 해결하였다.
이 글에서 알아볼 것
- 자바스크립트 모듈
1. 자바스크립트 모듈
내가 위에서 말한것처럼 많은 개발자들이 개발할 때,
로직을 여러 파일로 나눠서 관리하고 개발한다.
왜냐하면,
기본적으로 가독성이 좋아지고, 문제가 발생하였을 때 해당부분을 중점적으로 찾으면 되기 때문에 오류 해결에도 도움이된다.
또한. 하나의 파일에 하나의 역할 - 책임이 존재하는 것이 바람직한 코드다.
하지만, 기존에는 자바스크립트를 통헤 개발할때 보통 html, css, javascript 각각 파일 하나씩 생성하고, 그안에 모든 로직을 넣었었다.
그러면 단순하게 자바스크립트를 위에서 말한대로 파일을 나눠서 작성하고, html 파일에서 모두 불러오면 해결될까?
아니다. 이러면 html 파일에서 불러올때 너무 많은 자바스크립트 파일을 불러오게되므로 순서 관리가(의존성 문제) 어려워져 프로젝트 자체가 깨질수있다.
그러면 어떻게 해야할까?
- 자바스크립트 모듈
- 다른 여러 언어에서 import, export를 통해 의존성을 연결하고, 참조하는 방식을 확인할수있다.
- 이러한 작업을 자바스크립트에서도 적용해줄수있는데, 그것이 바로 모듈이다.
- 모듈로 작업하기 전에 꼭 기억해야할 점은, 모듈로 설정하는 순간 모든 파일이 개별 스코프를 갖는다는 점이다. (전역 사용 불가능)
- 모듈로 전환하는 방법
- html 파일에서 불러온 스크립트중 루트 스크립트에 특정 속성을 추가하여, 모든 스크립트가 이 루트 스크립트를 참조로 한다고 알려야한다
- <script src="assets/scripts/app.js" defer type="module"></script>
- 특정 키워드 export를 통해 해당 스크립트 파일을 내보낼수있다.
- export ~~
- 이제 export된 파일을 다른 파일에서 가져올려면 특정 구문을 사용해야한다.
- import {Component} from "./Component.js”
- 여기서 뭘 export하고 어디서 import해야할지 헷갈릴수있는데, 최상위 루트 스크립트부터 차례로 해당 스크립트에 어떤 클래스, 함수등이 사용되는지 확인하면 된다.
- 즉, 사용되는 클래스, 함수가 있는 스크립트에서 export하고, 사용하는 스크립트에서 import하면된다
- html 파일에서 불러온 스크립트중 루트 스크립트에 특정 속성을 추가하여, 모든 스크립트가 이 루트 스크립트를 참조로 한다고 알려야한다
- 모듈 사용시 알아야할 점
- 모듈은 CORS 정책이라는 엄격한 기준을 적용하고 있다.
- 이 정책은 실행중인 페이지와 동일한 도메인에서만 스크립트를 다운할 수 있는 정책이다.
- 여기서 문제가 발생하는데, 저 정책이 적용되면 지금까지의 프로젝트 실행방식은 문제가 된다.
- 지금까지는 단순히 html 파일을 더블클릭하여 불러오는 파일 프로토콜을 사용했다.
- 하지만, 모듈은…CORS 정책은 웹 서버에 의존적인 기능이라 위의 방법으로는 문제가 발생한다.
- 따라서, 개발 서버가 필요해졌다
- 개발 서버 사용법
- npm install -g serve
- 터미널에서 프로젝트 폴더로 이동 후 serve 를 통해 서버를 연다
- 개발 중에는 계속 서버를 켜두어야한다.
- 모듈은 CORS 정책이라는 엄격한 기준을 적용하고 있다.
- 다양한 import, export 구문
- import * as DOMH from “경로”
- 경로에 있는 스크립트에 존재하는 모든 export를 다 가져와 객체로 묶어줌
- 이후 사용은 DOMH.~ 으로 사용가능
- import {ProjectItem as PI} from “경로”
- {원래 이름 as 별명}
- export default class {~~~
- 만약 스크립트에서 내보내려는 것이 하나라면 export 뒤에 default 키워드를 붙이면, 클래스명(or 함수명)을 생략해도 자동으로 해당 기능을 가져온다
- 한 스크립트에는 하나의 default만 존재해야한다
- import DH from “~~”
- 만약 스크립트에서 default로 내보내는것과 그냥 내보내는것이 둘다 존재한다고 가정하면, 기존의 import {~} 구문은 오류가 발생한다
- 이때 {}없이 임의의 이름으로 import하면 DH가 default를 붙여서 export한 기능에 대한 import가 된다..
- import DH, {default가 아닌 다른 기능} from “경로”으로 다 가져올수도있다
- import * as DOMH from “경로”
- 동적 import
- 이제 모듈을 사용해서 효과적으로 파일을 분리하였고, import export를 통해 의존성도 연결하였다..하지만 문제가 있는데,
- 큰 애플리케이션에서 모듈을 사용하면 수많은 스크립트 파일이 존재하게 될것이고,, 이것들이 각각 요청을 보낸다면 성능 문제가 발생할 수 있다
- 어떤 스크립트는 항상 필요하겠지만, 어떤 스크립트는 가끔 필요할 수 있다(예를들어 버튼이 눌릴때만 실행되는 스크립트)
- 1번의 문제는 자바스크립트 라이브러리인 Webpack을 이용한 번들링으로 해결할 수 있다.
- 2번의 문제를 해결하는 방법이 동적 import다
- 이제 모듈을 사용해서 효과적으로 파일을 분리하였고, import export를 통해 의존성도 연결하였다..하지만 문제가 있는데,
- 다른 여러 언어에서 import, export를 통해 의존성을 연결하고, 참조하는 방식을 확인할수있다.
import("./Tooltip.js").then((module) => {
const tooltip = new module.Tooltip(
() => {
this.hasActiveTooltip = false;
},
tooltipText,
this.id
);
tooltip.attach();
this.hasActiveTooltip = true;
})
위의 코드처럼 import("경로") 로 작성해주면 된다.
*참고
- 모듈 스코프
- 모듈을 사용하기 전에는 전역 변수등을 정의하면 해당 변수가 자동으로 window 객체에 추가되어 어디서나 사용가능했다.
- 하지만 모듈을 사용하면, 모듈은 자신만의 스코프를 가지고, 만약 이것을 다른곳에서 사용하고 싶다면 import, export해야한다
- 이를 증명되는것이, 최상위 루트 스크립트에서 console.log(this)를 찍었을때 원래라면 window 객체가 출력되었겠지만, 모듈 사용시 undefind가 출력된다 (왜냐하면 모듈은 기본적으로 엄격 모드가 적용중이다)
- 그럼 만약, import export가 불가능한 상황에서 전역 변수등을 꼭 공유해야하는 상황이라면 어떻게 해야할까?
- globalThis 사용
- globalThis.DUMMY = “messi” 로 전역에 저장하고
- globalThis.DUMMY로 전역에서 사용
- globalThis 사용