Dynamic import 적용예시 — 스크립트를 필요한 순간에 import하자
다음 우편번호 서비스와 아임포트 스크립트를 <head>에서 제거하자.
브라우저가 html을 파싱해서 렌더링을 할 때는 DOM 엘레멘트들을 한 줄 한 줄 읽어내려가며 동기적으로 처리합니다. 아래의 예시를 보겠습니다. 브라우저가 <script src="something.js"></script>
태그를 읽는 차례가 되었다면, 일반적으로 브라우저는 something.js
파일을 받고(fetch
), javascript파일을 실행(execution
)까지한 뒤에 그 다음 줄을 읽습니다.
예시
<head>
<script src="https://lib.com/lib.js"></script>
</head><body>
<h1>제목</h1>
<script src="something.js"></script>
<div>내용</div>
</body>
브라우저가 읽는 방식
// 1. head구나! 읽고,
<head> // 2. 엇 script네! https://lib.com/lib.js 으로 요청해서 파일 다운받고! 다운 다 받으면 파일(js)을 실행시키자! 다 읽고 다음 줄 읽자.
<script src="https://lib.com/lib.js"></script>// 3. head가 끝났군! 읽고,
</head>// 4. 오케이, body..! 읽고,
<body> // 5. 오케이 h1이고 안에 텍스트는 "제목"이군! 읽고,
<h1>제목</h1> // 6. 엇 script네! something.js 가져오고! 파일 내용 실행하자!
<script src="something.js"></script> // 7. 오케이 div이고 안에 텍스트는 "내용"이구만!
<div>내용</div>// 8. body가 끝났군! 읽음. 끝!
</body>
한 줄 한 줄 읽다가 <script>
를 만나면 이것을 fetch
와 execution
을 하고, 그걸 다 한 뒤에 그 다음 줄을 읽습니다. 위에 너무 많은 <script>가 있다면, 그걸 다 읽어 내려가느라 렌더링이 완료되기까지 시간이 무척 오래걸릴 것입니다. 그걸 우회하기 위해서는 defer
나 async
속성을 적절히 이용하면 되겠지만, 좀 더 나은 방법을 찾아보겠습니다.
그래서 반드시 꼭 필요한 경우가 아니라면 </body>
바로 위에 <script>
태그를 두는 것이 좋습니다. 유저가 느끼기에 빠르게 DOM이 렌더링이 되는 것처럼 느껴지게 하기 위함입니다.
그런데 </body>
위에 있다고 해도 결국 브라우저는 그것을 읽을 것이기 때문에, 렌더링을 빠르게 완료시키기 위해서는 지금 필요한 script만을 불러오도록 하는 것이 좋습니다.
기존 다노샵 html을 쪼오금 다듬어보았습니다 🤔
이번에 작업해본 것은 최적화를 위해 하나씩 시도를 하는 것들 중 하나입니다.
- 결국에는 bundle 파일 사이즈도 다이어트를 시켜야하고,
- webpack에서 entry에도 하나의 js파일이 아닌 여러 파일로 쪼개서 넣고,
- webpack output 과정에서도 적당한 크기에 맞춰서 파일을 쪼개서 bundle spliting을 하고,
- dynamic import를 활용해서 code spliting도 진행해야합니다.
하지만 이 일들을 한 번에 다 하기엔 현실적으로 어렵죠! 그래서 하나씩 해나가려고 합니다.
아임포트와 다음 우편번호 서비스 스크립트를 <head>에서 빼보자
그래서 일단 쉬운 것부터 도전해보았습니다. 기존 다노샵 코드의 <head> 영역에 아임포트 결제를 위한 스크립트
와 다음 우편번호 서비스(주소찾기)를 위한 스크립트
태그가 있었습니다. 이 2개의 스크립트를 위해서는 아래와 같이 총 3개의 스크립트가 심어져야합니다.
jQuery
: 아임포트가 아직 리액트를 공식 지원하지 않기 때문에 jQuery가 필요합니다. 1.0 이상부터 가능하다고 했지만(아임포트 github 문서), 모바일에서 실행할 때는 1.0으로 하면 결제모듈 실행이 안되더라고요(왜죠?) jQuery 버전을 2.0 이상으로 해야지 정상 동작하는 것 같았습니다. 버전에 대해서는 좀 더 실험을 해보고, PR을 보내보든지 해야겠네요!아임포트 스크립트
: 결제를 위해서는 아임포트 쓸거니까 필요하죠.다음 우편번호 서비스 스크립트
: 뭐 이것도 내가 구현할거 아니면.. 헤헤 필요하죠.
<head>
<script src="제이쿼리 CDN 주소"></script>
<script src="아임포트 스크립트 주소"></script>
<script src="다음 우편번호 서비스 스크립트 주소"></script>
</head>
그런데 이 3개의 스크립트가 꼭 head에 있을 필요는 없습니다. 다노샵에서는 결제는 주문서 페이지에서만 가능하기 때문에, 다른 페이지에 있을 때는 아임포트 스크립트를 굳이 브라우저가 읽게 할 필요가 없습니다. 그리고 다음 우편번호 서비스 역시 주소 찾을 때가 아니면 굳이당장 필요하지 않습니다.
<head>
에서 빼도, 필요한 스크립트이니까 어딘가 넣긴 넣어야할텐데, 어떤 방법이 있을까요?
👆방법 1.
스크립트 태그 위치를 </body>
위로 변경한다. (싫다 🙅🏻♀️)
✌️방법 2. (아임포트는 이 방식을 적용)
해당 component
가 mount될 때 스크립트 태그를 body에 append한다.
🖖방법 3. (다음 우편번호 서비스는 이 방식을 적용)
dynamic import 방식으로 필요한 순간에(주소 검색
버튼을 눌렀을 때) 스크립트를 설치한다.
아임포트는 componentDidMount
에서 script 설치
더 좋은 방법이 분명 있겠지만.. 저는 vanillaJS
로 스크립트를 body
에 append
시키는 함수를 만들고, 그 함수를 주문서 component의 componentDidMount
단계에서 호출했습니다.
jQuery
버전은 기존에 3.3.1(30KB) minify 버전을 사용하고 있었는데, 이번에 변경하면서 2.0(28.9KB) minify 버전으로 낮췄습니다. 원래 1.2 버전(14.6KB)으로 확 낮춰서 스크립트 용량도 줄이고 싶었는데, 모바일에서 아임포트를 통해 nicepay 모듈이 열리지 않는 이슈를 경험했습니다. 2.0버전으로 올려보니 제대로 동작하더라고요.
상품 상세페이지에서는 jQuery
가 설치되지 않았지만, 주문서 페이지에 접근하면 jQuery
가 설치됩니다(아임포트 스크립트도 이 때 설치됩니다). 필요한 순간에, 필요한 스크립트 파일을 받게되니 쪼오오오금이라도 최적화에 기여했다고 생각합니다.
주문서 페이지에서 결제하기 버튼을 누를 때 dynamic importing하는 방법도 고민했지만.. 주문서 페이지에서 대부분은 결제하기 버튼을 누를 것이고, 버튼을 누른 뒤에 jQuery
나 아임포트 스크립트
를 가져오면, 유저는 ‘결제가 되게 느리다!’라고 느낄 수도 있기 때문에 dynamic importing을 하지 않고 componentDidMount
단계에서 vanillaJS로 만든 함수를 호출하는 방식을 택했습니다.
다음 우편번호 서비스는 dynamic import로 importing
반면에 주소검색 버튼을 누른 것은 유저에 따라서는 누를 수도, 안 누를 수도 있는 버튼이기 때문에 dynamic importing을 했습니다. script를 심는 것은 역시 vanilaJS
로 했고, 스크립트가 다 로드된 뒤에 주소 검색창이 떠야하기 때문에 onload
속성을 이용했습니다.
기존에 <head />에 스크립트 삽입했을 때는, 무조건 어느 페이지에 있든지 그 스크립트를 받았습니다. 다음 우편번호 서비스를 위해서 2가지의 스크립트가 불려지는데요, postcode.v2.js(1.6KB)
와 191007.js(10KB — 코어 모듈이라고 함)
가 그것입니다.
하지만 이제는 주문서 페이지에서(또는 마이페이지의 배송지관리 메뉴에서) 주소검색
버튼을 눌렀을 경우에만 다음 주소찾기 스크립트 import하도록 수정했기 때문에 적은 용량차이지만 DOM을 조금이라도 빠르게 렌더링을 완료시킬 수 있게 되었습니다. (너므 신난다아 💃🕺👏)