본문으로 건너뛰기

넷플릭스 - 클로버필드 패러독스 리뷰

· 약 1분

클로버필드 패러독스

클로버필드 전작들의 떡밥 회수용이라길래 기대했다.

리뷰

  • 처음에 뉴스 인터뷰처럼 나오는 과학자? 의 말을 잘 들으면 그게 결말이다.
  • 뒷부분에 깜짝 놀랄만한 게 있다.
  • 그래비티와 선샤인을 합쳐놓은 느낌이다.
  • 펜도럼처럼 긴장감이 있진 않다.

줄거리

  • 에너지원을 찾기위해 우주정거장에서 입자가속기를 돌렸는데
  • 지구가 눈앞에서 사라졌는데 알고보니 카시오페이아 자리 옆까지 우주정거장이 이동한 거였다.
  • 근데 양자가 얽혀서 다른 시공간의 우주였다.
  • 우주선을 힘들게 복구해 가속기를 돌려 원래 시공간으로 돌아온다.
  • 그런데 원래 지구는 가속기 때문에 상상하지 못한 일이 일어나있다.

넷플릭스 - 익스팅션 종의 구원자 리뷰

· 약 1분

익스팅션: 종의 구원자

넷플릭스를 결제하고 SF 탭을 방황하다가 본 첫 번째 영화다.

되게 재밌어 보였다.

리뷰

  • 주인공 (마이클 페나) 때문에 집중이 잘 되지 않았다.
  • 연기는 잘 하는데 자꾸 엔트맨 친구 짠돌이 사장 이미지가 생각난다.
  • 반전은 충격적이지 않았다. 프레데터인 줄 알았더니...
  • 건너 뛰면서 볼 정도로 흥미는 떨어진다.

줄거리

  • 주인공은 환상을 자주 본다. (도입부 30%가 이 장면이다.)
  • 와이프도 보스도 정신 착란과 불면증으로 의심해 안 믿어준다.
  • 결국 실제로 일어났다.
  • 열린 결말

Array map, filter, values 분석

· 약 2분

Array map, filter, values 분석

아래 데이터로 php 와 js 의 다른 점을 확인해보자. id 가 3 이상인 id 만 추출하고 싶었다.

users.json
[
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "[email protected]"
},
{
"id": 2,
"name": "Ervin Howell",
"username": "Antonette",
"email": "[email protected]"
},
{
"id": 3,
"name": "Clementine Bauch",
"username": "Samantha",
"email": "[email protected]"
},
{
"id": 4,
"name": "Patricia Lebsack",
"username": "Karianne",
"email": "[email protected]"
},
{
"id": 5,
"name": "Chelsey Dietrich",
"username": "Kamren",
"email": "[email protected]"
}
]

AS-IS

JS

머리 속으로 돌려본 원래 느낌은 이랬다.

let userIdxs = users.map((user) => {
if (user.id >= 3) {
return user;
}
});

// userIdxs [ null, null, 3, 4, 5 ]

userIdxs = userIdxs.filter(Boolean);

// userIdxs [ 3, 4, 5 ]

php

생각 없이 짜면 array_map 을 먼저 사용할 수 있다.

$userIdxs = array_map(function ($user) {
if ($user['id'] >= 3) {
return $user['id'];
}
}, $users);

// userIdxs [ null, null, 3, 4, 5 ]
// 참담한 결과가 나왔다.

$userIdxs = array_filter($userIdxs);

// 필터를 먹여도 id: 3의 인덱스는 2이다.
// 이걸 해결하려면 array_values 를 한번 더 사용한다.
$userIdxs = array_values($userIdxs);
// userIdxs [ 3, 4, 5 ]

array_values(array_filter(array_map())) 과 같이 호출할 수 있긴 하다.

너무 지저분했다.

TO-BE

조금만 생각해도 FP 의 개념에 어긋남을 느낄 수 있다. 범위를 줄이고 나서 해당 값을 추출하는 게 맞다.

JS2

const userIdxs = users.filter((user) => user.id >= 3).map((user) => user.id);

// userIdxs [ 3, 4, 5 ]

php2

array_values 를 쓰지 않고도 깔끔한 코딩이 가능하다.

$userIdxs = array_filter($users, function ($user) {
return $user['id'] >= 3;
});

$userIdxs = array_map(function ($user) {
return $user['id'];
}, $userIdxs);

// userIdxs [ 3, 4, 5 ]

lara

물론 더 멋진 방법이 있다.

$userIdxs = collect($users)
->filter(function ($user) {
return $user['id'] >= 3;
})
->map(function ($user) {
return $user['id'];
})
->all();

// userIdxs [ 3, 4, 5 ]

여담

php, js, java, python 을 넘나들다보니 사용하는 언어의 흐름에 대한 개념이 1/n 로 줄어드는 것 같다.

IaaS, PaaS, SaaS 란?

· 약 1분

IaaS, PaaS, Sass

아이아스, 파스, 싸스 언제 들어도 헷갈리는 단어 3종 세트를 쉽게 파헤쳐보자.

다이어그램

image from hexo 출처: 5 tips if you are considering cloud-based BI

이 그림이 모든 걸 나타내준다. 아이아스는 DevOps(또는 운영자), 파스는 개발자, 싸스는 엔드유저(또는 사용자) 용이다.

IaaS

  • Infrastructure as a Service
  • 사용할 준비가 된 컴퓨터 및 네트워크 하드웨어
  • 클라우드에서 리눅스 서버를 받는 것

PaaS

  • Platform as a Service
  • 제공된 프레임워크 또는 스택에서 실행되는 소프트웨어
  • 구글 앱엔진, 아마존 엘라스틱 빈스톡 등

SaaS

  • Software as a Service
  • 웹사이트로 제공되는 응용프로그램
  • CMS 등
  • 나이스해 보이고 싶어서 끼워 맞춘 용어

Gitment 사용하기

· 약 2분

Gitment 사용하기

블로그에 붙힐만한 댓글 라이브러리로는 Disqus, Commento, livere 등이 있지만 깃헙 페이지라 Gitment를 사용하고 싶었다.

사전 준비

Github > Settings > Developer settings > OAuth Apps 메뉴로 들어가 새로운 OAuth App 을 만들어준다.

준비

Client ID 와 Client Secret 을 저장해 놓고 Authorization callback URL 은 Homepage URL 과 같은 주소를 입력한다.

## Application name
GracefulLight

## Homepage URL
https://gracefullight.github.io

## Application description
GracefulLights Blog

## Authorization callback URL
https://gracefullight.github.io

소스 추가

원하는 페이지에 소스를 추가한다.

<script src="https://cdn.jsdelivr.net/npm/[email protected]/js/browser/bluebird.core.min.js"></script>
<section class="comments" id="comments">
<div id="gitment_thread"></div>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/style/default.css"
/>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gitment.browser.js"></script>
<script>
var gitment = new Gitment({
id: "Gitment 를 구분할 아이디 (ex: 페이지 제목 또는 포스팅 일시)",
owner: "github 아이디 (ex: gracefullight)",
repo: "repository 명 (ex: gracefullight.github.io)",
oauth: {
client_id: "위에서 발급 받은 client_id",
client_secret: "위에서 발급 받은 client_secret",
},
});
gitment.render("gitment_thread");
</script>
</section>

gitment.min.js 파일은 없으므로, 직접 minify 해서 사용하면 된다. bluebird (promise) core 를 추가한 이유는, IE 에서 gitment 를 지원해야하기 때문이다.

옵션 관리

위 4개 옵션 외에 추가로 옵션을 더 줄 수 있다. desc 와 labels 정도가 추가되면 좋을 것 같다.

댓글 쓰기

깃허브 아이디로 로그인한 뒤 Initialize Comments 를 누르고 댓글을 작성하면 된다.

Nuxt에 ThirdParty js (particles.js) 추가하기

· 약 2분

시작하기 앞서

Nuxt Project 에 Particles.js 를 붙히고 싶었다. 어떻게하면 쉽게 붙힐 수 있을까 하다가 멀리 돌아오게 된 삽질기다.

시작

vue-plugin 사용

공식 문서 를 봤었고 Vue Plugin일 경우 너무나 쉽게 추가가 가능한 것처럼 보였다. 얼른 vue-particles 를 설치하고 플러그인을 만들어 등록했다.

만들고

vue-particles.js
import Vue from "vue";
import VueParticles from "vue-particles";

Vue.use(VueParticles);

등록했다.

nuxt.config.js
module.exports = {
plugins: ["~/plugins/vue-particles"],
};

그런데 화면에 Particle이 보이지 않는다.

no-ssr

구글링을 하니, 플러그인에 no-ssr 옵션을 주면 해결이 된다고 한다.

nuxt.config.js
module.exports = {
plugins: [
{
src: "~/plugins/vue-particles",
ssr: false,
},
],
};
Particles.vue
<template>
<no-ssr>
<vue-particles />
</no-ssr>
</template>

대충 이런식으로 코딩했더니 화면에서 볼 수 있게 되었다.

그런데... IE11에서는 스크립트 오류가 발생하기 시작했다.

IE 오류

vue-particles 자체에 const 구문을 사용하고 있기 때문에, no-ssr 옵션을 준다면 번들링 시 로직을 건너 뛰기에, 크롬에서는 실행이 되지만 IE에서는 실행되지 않는 치명적인 오류가 발생했다.

그래서 다른 방법을 시도해봤다.

script 삽입하기

nuxt.config.js 에서 head 태그를 이용하면 스크립트를 추가할 수 있고, window.particlesJS 처럼 전역 변수로 참조하면 될 줄 알았다.

하지만 번들링 시 window 객체가 없어 aframe 벤더가 필요하다고 오류가 발생했다. 쓸데 없는 리소스를 추가해야되니 여기서 멈추었다.

해결

process.browser

window-document-undefined 문서에 따르면 이런 참조 문제를 해결할 수 있다고 한다.

Particles.vue
<template>
<vue-particles />
</template>
<script>
if (process.browser) {
require("vue-particles");
}

export default {
mounted() {
if (window.particlesJS) {
window.particlesJS.load();
}
},
};
</script>

위와 같이 로직을 변경해주니 IE11 에서도 정상 작동하였다.

여담

vue-particles 의 문서엔 완벽한 nuxt 호환이라 되어있지만 예외적인 상황이 있는 듯 하다.

thefuck 설치하기

· 약 2분

thefuck

이게 뭐죠

커맨드로 명령어를 실행하다보면 생각보다 오타를 많이 치게 된다 tab tab 자동완성 기능과 Oh-My-Zsh 의 syntax highlighting 기능을 사용한다면 충분히 커버가 가능할 것 같지만 현실은 그렇지 않다.

## git checkout master를 치고 싶었으나..
$ git checktou master
git: 'checktou' is not a git command. See 'git --help'.

The most similar command is
checkout

thefuck 을 활용하면 오타가 나 분노한 나의 마음을 표출할 수 있다.

예제

설치

다들 python 3.6 버전 정도는 갖고 있다고 가정한다.

pip install thefuck

alias 설정

fuck 만 쳐도 thefuck 의 기능을 사용하기 위해 alias 설정을 해주자.

git bash

bash_profile 에 아래처럼 추가해준다.

vi ~/.bash_profile

eval "$(thefuck --alias)"

환경 변수 설정

  1. Win+R > sysdm.cpl
  2. 고급 > 환경 변수 > 시스템 변수 > 새로만들기 에서 PYTHONIOENCODING 값을 utf-8로 준다.
  3. 확인 ✔️ X 3

powershell

ps 를 주로 사용한다면 설치 후 $PROFILE 파일에 값을 넣어주면 된다.

PS > Notepad $PROFILE

$env:PYTHONIOENCODING="utf-8"
iex "$(thefuck --alias)"

profile 파일이 없다고 오류가 발생하면, 아래 명령어로 profile 파일을 만들어주자.

PS > New-item -type file -force $PROFILE

활용하기

$ git checkuot master
git: 'checkuot' is not a git command. See 'git --help'.

The most similar command is
checkout

$ fuck
git checkout master [enter/↑/↓/ctrl+c]
Already on 'master'
Your branch is up to date with 'origin/master'.

오타 걱정 없이 쉘 커맨드를 실행할 수 있게 되었다.

git merge 후 binary 파일 충돌시

· 약 1분

바이너리 파일은 diff 가 안 되서 확인을 할 수가 없는데, merge 시에 내 바이너리를 쓸 지 받은 것의 바이너리를 쓸 지 여부를 지정해주면 된다.

merge strategy

## 받아온 브랜치의 바이너리 파일을 사용
$ git merge -X theirs origin/브랜치

## 내 로컬 바이너리 파일을 사용
$ git merge -X ours origin/브랜치

gc 해제

이렇게 받다보면 unlink of file failed 란 오류가 발생할 수가 있는데, git 가비지 컬렉터 옵션을 해당 프로젝트에서만 꺼주면 된다. 기능에는 아무 문제 없다. 궁금하면 git help gc를 해보시길..

## gc 기능 끄기
$ git config --local gc.auto 0

youtube-dl로 유튜브 무료 다운로드하기

· 약 3분

유튜브, 비메오 등의 영상을 소장하고 싶거나 좋은 노래를 다운받아서 듣고 싶을 때 많은 고민을 하게 된다 Youtube Free Download 같은 사이트에서 광고를 한참 보다가 한개씩 다운받아야하고, 영상일 경우 저화질만 받을 수 있다

이 고민거리를 한 방에 해결해주는 Youtube-DL(DownLoad)이 있다 시작해보자.

설치

커맨드와 매뉴얼 설치방법으로 나눴다.

윈도우

scoop 설치

쉽게 설치하기 위해서 윈도우 패키지 매니저인 Scoop을 먼저 설치하자. 시작표시줄 우측 클릭 후 속성에 들어가면 win + x 키를 누를 때 관리자 파워쉘로 접속할 수 있는 설정이 있다. 설정을 체크해주고, win + xa 키를 누르면 관리자 파워쉘로 접근한다.

PS > iex (new-object net.webclient).downloadstring('https://get.scoop.sh')

패키지 설치

## ffmpeg 은 음악, 영상 파일 포맷 변환기이다.
$ scoop install ffmpeg youtube-dl

수동 설치

위에 커맨드가 너무 어렵다면 매뉴얼대로 설치해보자. 여기를 누르면 다운받는다 (사실 이게 더 쉽지만 쉘로 설치하는게 뭔가 나이스하니까)

비디오를 오디오로 변경해주기위해 ffmpeg를 다운받아야한다. 링크를 따라가서 Download Build 버튼을 클릭해 압축을 풀어주자

ffmpeg/bin 안에 youtube-dl.exe 를 넣어준다

환경 변수 설정

  1. Win+R > sysdm.cpl
  2. 고급 > 환경 변수 > 시스템 변수 > Path 선택 > 편집 > 새로만들기 에서 ffmpeg/bin 경로를 넣어준다.
  3. D:\ffmpeg\bin
  4. 확인 ✔️ X 3

Win+R > cmd를 친 뒤에 테스트를 해본다, 아니면 Win+x > cmd (or Powershell) 한 다음 열어도 된다.

## ffmpeg 세팅 확인
$ ffmpeg -version

ffmpeg version N-90433-g5b31dd1c6b Copyright (c) 2000-2018 the FFmpeg developers
built with gcc 7.3.0 (GCC)

## youtube-dl 세팅 확인
$ youtube-dl --version

2018.03.14

## brew 로 한 번에 설치가 가능하다.
$ brew install youtube-dl

사용법

mp4 다운로드

$ youtube-dl 주소

## mp4 best quality로 다운받고 싶다면
$ youtube-dl 주소 -f 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best'

## 4K 영상을 다운받고 싶다면
$ youtube-dl 주소 -f '(bestvideo[vcodec=vp9]/bestvideo[ext=mp4]/bestvideo)+bestaudio/best'

## 비공개 비밀번호 입력 영상이라면
$ youtube-dl 주소 --video-password 비밀번호

## 로그인하고 받아야되는 영상이라면
$ youtube-dl 주소 -u 아이디 -p 비밀번호

mp3 다운로드

## audio-quality는 기본값이 5고 0으로 갈수록 좋은 음질
$ youtube-dl 주소 -x --audio-format mp3 --audio-quality 0

예시

트위터의 경우 동영상 주소 복사 버튼을 클릭하면 나오는 주소로도 다운로드가 가능하다.

## 영상 다운로드
$ youtube-dl https://www.youtube.com/watch?v=IlSY06XGu3Y

## mp3 다운로드 일반음질 (128K)
$ youtube-dl https://www.youtube.com/watch?v=nfs8NYg7yQM -x --audio-format mp3

## mp3 다운로드 고음질 (VBR)
$ youtube-dl https://www.youtube.com/watch?v=nfs8NYg7yQM -x --audio-format mp3 --audio-quality 0

버전 업데이트

명령어 하나로 셀프 업데이트가 가능하다.

## 다운로드 오류 발생시 업데이트하면 해결된다.
$ youtube-dl -U

지원하는 사이트

유튜브 말고 수많은 사이트의 리소스 다운이 가능하다. 사이트 목록은 여기서 확인 가능하다.

웹팩이 모듈을 불러오는 슈도코드

· 약 6분

잊기 전에 슈도코드를 정리해놓자.

슈도코드

var 전체모듈 = [
function () {
const 합계함수 = (a, b) => a + b;
return 합계함수;
},

function () {
const 내부_합계함수 = 전체모듈[0]();
const 합계 = 내부_합계함수(10, 20);
console.log(합계);
return 합계;
},
];

const 시작모듈_인덱스 = 1;
전체모듈[시작모듈_인덱스]();

해석

배열에 다 때려넣고 호출해서 사용하는 방법이다 물론 내부는 더 복잡하다, 코드 스플리팅이 된다면 더더욱.

복잡한 내부

https://github.com/hg-pyun/minipack-kr/blob/master/src/minipack.js
/**
* @source https://github.com/hg-pyun/minipack-kr/blob/master/src/minipack.js
*
* 모듈 번들러들은 작은 코드 조각들을 웹 브라우저에서 실행될 수 있는 크고 복잡한 파일로 컴파일합니다.
* 이 작은 조각들은 단지 자바스크립트 파일들일 뿐이며, 이들 사이의 종속성은 모듈 시스템에 의해 표현됩니다
* (https://webpack.js.org/concepts/modules).
*
* 모듈 번들러들은 entry file 이라는 개념을 가지고 있습니다. 브라우저에 스크립트 태그를 몇개 추가하여
* 실행하는 대신, 번들 담당자에게 응용 프로그램의 메인 파일이 무엇인지 알려 줍니다. 이 파일이 어플리케이션을
* 실행하는 진입점이 됩니다.
*
* 번들러는 entry file의 의존성을 분석합니다. 그리고 그 다음 파일의 의존성을 파악합니다.
* 이 작업은 애플리케이션의 모든 모듈과 각 모듈이 서로 어떻게 의존하는지 파악할 때까지 반복됩니다.
*
* 이러한 프로젝트에 대한 이해를 종속성 그래프라 부릅니다.
*
* 이 예제에서는 종속성 그래프를 만들고 이 그래프를 사용하여 모든 모듈들을 하나의 번들로 패키징 합니다.
* 그럼 시작해 보겠습니다 :)
*
* 참고: 이 예제는 매우 단순화되어 있습니다. 순환 참조, 캐싱 모듈, 파싱 최적화 등에 대한 내용은 생략
* 하여 가능한가 단순하게 만들었습니다.
*/

const fs = require("fs");
const path = require("path");
const babylon = require("babylon");
const traverse = require("babel-traverse").default;
const { transformFromAst } = require("babel-core");

let ID = 0;

// 우선 file path를 받는 함수를 생성하고
// 파일을 내용을 읽고, 종속성을 추출합니다.
function createAsset(filename) {
// 파일의 내용을 문자열로 읽습니다.
const content = fs.readFileSync(filename, "utf-8");

// 이제 이 파일이 어떤 파일에 종속되는지 알아보겠습니다. 우리는 import 문자열을 보고 의존성을
// 파악할 수 있습니다 하지만, 이것은 단순한 접근법이어서, 대신에 자바스크립트 파서를 사용하겠습니다.

// 자바스크립트 파서들은 자바스크립트 코드를 읽고 이해할 수 있도록 도와주는 툴입니다.
// 파서는 AST(abstract syntax tree)라는 좀더 추상화된 모델을 생성합니다.
//
// AST에 대해 이해하려면 AST Explorer(https://astexplorer.net)을 꼭 보기를 강력하게 추천합니다.
// AST가 어떻게 이루어져 있는지 확인할 수 있습니다.
//
// AST는 우리의 코드에 대해 많은 정보를 가지고 있습니다. 우리는 쿼리를 이용하여
// 우리의 코드가 하려는 일에 대해 이해할 수 있습니다.
const ast = babylon.parse(content, {
sourceType: "module",
});

// 이 배열은 현재 모듈의 의존성을 상대 경로로 가지고 있을 것입니다.
const dependencies = [];

// 우리는 AST 순회를 통해 각각의 모듈들이 어떤 의존성을 가지고 있는지 이해하려 합니다.
// 이것을 통해 AST안에서 모든 import keyword 선언을 파악할 수 있습니다.
traverse(ast, {
// ECMAScript 모듈들은 정적이므로 매우 파악하기 쉽습니다.이는 변수를 가져올 수 없거나 조건부로
// 다른 모듈을 가져올 수 없음을 의미합니다. import 구분을 볼 때 마다 카운팅을 하고 의존성을 가지고
// 있는 것으로 간주 할 수 있습니다.
ImportDeclaration: ({ node }) => {
// import 구문마다 dependencies 배열에 값을 추가합니다.
dependencies.push(node.source.value);
},
});

// 또한 간단한 카운터를 이용하여 이 모듈에 고유 식별자를 할당합니다.
const id = ID++;

// 우리는 일부 브라우저에서만 지원하는 ECMAScript module들이나 기능들을 사용할 가능성도 있습니다.
// 우리가 만드는 번들이 모든 브라우저에서 돌아가도록 Babel을 이용해서 transpile할 수 있습니다
// (https://babeljs.io 참고).
//
// `presets` 옵션은 Babel이 어떻게 우리 코드를 바꿀지에 대해 결정합니다. 우리는 `babel-preset-env`
// 를 이용하여 대부분의 브라우저에서 우리의 코드를 사용할 수 있도록 바꾸도록 하겠습니다.
const { code } = transformFromAst(ast, null, {
presets: ["env"],
});

// 이 모듈에 대한 정보를 return 합니다.
return {
id,
filename,
dependencies,
code,
};
}

// 이제 단일 모듈의 종속성을 추출할 수 있으므로, entry file의 의존성을 추출하는 것부터 시작하겠습니다.
// 이 작업은 애플리케이션의 모든 모듈과 각 모듈이 서로 어떻게 의존하는지를 파악할 때까지 계속 진행할 것입니다.
// 이 작업의 결과물을 의존성 그래프라 부릅니다.
function createGraph(entry) {
// entry file부터 분석을 시작합니다.
const mainAsset = createAsset(entry);

// queue를 사용해서 모든 asset의 의존성을 분석하도록 하겠습니다. 이 작업을 위해
// entry asset을 가지고 있는 배열을 정의합니다.
const queue = [mainAsset];

// 여기서 queue의 반복을 위해 `for ... of` 반복문을 사용합니다. 처음에는 queue가 asset을 하나만
// 가지고 있지만 작업이 반복되는 동안에 새로운 asset들을 queue에 추가합니다. 이 반복문은 queue가
// 비어질 때 까지 계속됩니다.
for (const asset of queue) {
// 모든 asset들은 의존성이 있는 모듈에 대한 상대경로들을 리스트로 가지고 있습니다. 우리는 그 리스트를
// 순회하면서 `createAsset()`함수로 분석하고, 아래 객체를 통하여 모듈들의 의존성을 추척할 것입니다.
asset.mapping = {};

// 이것은 이 모듈이 있는 디렉토리입니다.
const dirname = path.dirname(asset.filename);

// 종속성에 대한 상대 경로 리스트를 순회합니다.
asset.dependencies.forEach((relativePath) => {
// `createAsset()` 함수는 절대 경로가 필요합니다. dependencies 배열은 상대 경로를 가지고
// 있는 배열입니다. 이러한 경로들은 모듈이 import된 file에 따라 상대적입니다. 따라서 부모 asset의
// 경로를 이용해서 상대 경로를 절대경로로 바꿔야 합니다.
const absolutePath = path.join(dirname, relativePath);

// asset의 내용울 분석하고, 내용을 읽고, 의존성을 추출합니다.
const child = createAsset(absolutePath);

// `asset`의 의존성은 `child`에게 달려있습니다. 우리는 `mapping` 객체에 relativePath와 child.id를
// 이용해서 관계를 표현할 수 있습니다.
asset.mapping[relativePath] = child.id;

// 마지막으로 child asset을 queue에 추가하여 구문 분석이 반복되도록 합니다.
queue.push(child);
});
}

// 이 시점에서 queue는 애플리케이션의 모든 모듈이 포함된 배열입니다. 이것이 우리가 그래프를 표현하는 방법입니다.
return queue;
}

// 다음으로, 그래프를 이용하여 브라우저에서 실행할 수 있는 번들을 반환하는 함수를 정의합니다.
//
// 우리의 번들은 self-invoking(자신을 부를수 있는)함수를 가지고 있습니다.
//
// (function() {})()
//
// 이 함수는 하나의 인자만 받을 수 있습니다: 모든 모듈의 정보를 가지고 있는 그래프.
function bundle(graph) {
let modules = "";

// 이 함수를 구성하기 전에 매개 변수로 전달할 객체를 만들겠습니다. 반드시 알아둬야할 것은 우리가 만드는
// 스트링은 2개의 중괄호({})로 감싸져 있어야 한다는 것입니다. 우리는 다음과 같은 포멧으로 추가할
// 것입니다: `key: value,`.
graph.forEach((mod) => {
// 그래프안에 있는 모든 모듈들은 entry를 객체로 가지고 있습니다. 우리는 module의 id를
// 값에 대한 키로 사용합니다.(각 모듈마다 2개의 값이 있습니다.)
//
// 찻번째 값은 함수로 감싼 각 모듈의 코드입니다. 그 이유는 모듈의 scope를 지정해야 하기 때문입니다.
// 한 모듈에서 변수를 정의하면 다른 모듈이나 글로벌 scope에 영향을 주지 않아야 합니다.
//
// transpiled된 모듈들은 CommonJS 모듈 시스템을 사용합니다:
// 해당 모듈 시스템은 `require`, `module`, 그리고 `exports`를 통해 모듈화 합니다.
// 이 키워드들은 일반적으로 브라우저에서 사용할수 없으므로, 우리의 함수를 이용하여 주입해야 합니다.
//
// 두번째 값은 모듈간의 의존성 매핑을 stringify하는 것입니다. 다음과 같은 객체입니다.
// { './relative/path': 1 }.
//
// transpiled된 우리의 모듈들이 상대경로와 합께 `require()`를 호출하기 때문입니다. 이 함수를 호출하면
// 그래프에서 이 모듈의 상대 경로에 해당하는 모듈을 확인할 수 있습니다.
modules += `${mod.id}: [
function (require, module, exports) { ${mod.code} },
${JSON.stringify(mod.mapping)},
],`;
});

// 마지막으로 self-invoking 함수의 body를 만듭니다.
//
// `require()` 함수를 만들며 시작하겠습니다: 모듈 id를 받아 앞서 만든 모듈 오브젝트에서 `module`을
// 찾습니다. function wrapper와 맵핑 객체를 얻기위해 two-value 객체를 이용합니다.
//
// 모듈의 코드는 모듈의 id들 대신 상대경로와 함께 `require()`함수를 호출합니다. 우리가 만든 require 함수는
// id 받습니다. 또한 두개의 모듈은 동일한 상대 경로를 요구할 수 있지만, 실제론 두개의 다른 모듈들을
// 의미하게 됩니다.
//
// 이 문제를 해결하기 위해 별도의 `require` 함수를 제공합니다. 모듈의 맵핑 오브젝트를 이용하여 상대경로를 ids에 할당합니다.
// 맵핑 오브젝트는 구체적인 모듈을 가져오기 위한 용도로, 상대 경로와 모듈 ids를 맵핑합니다.
//
// 마지막으로, 모듈이 require 되었을 때 exports 객체의 값이 노출되어야 합니다. 따라서 모듈 코드에 의해 변환 된
// `exports` 객체는 `require()`로 반환됩니다.
const result = `
(function(modules) {
function require(id) {
const [fn, mapping] = modules[id];
function localRequire(name) {
return require(mapping[name]);
}
const module = { exports : {} };
fn(localRequire, module, module.exports);
return module.exports;
}
require(0);
})({${modules}})
`;

// 결과를 반환합니다. 만세! :)
return result;
}

const graph = createGraph("./example/entry.js");
const result = bundle(graph);

console.log(result);

여기의 번역된 내용을 확인해보자.