document.addEventListener('DOMContentLoaded', e => { // CSV 버튼에 이벤트 등록 document.querySelector('#csvDownloadButton').addEventListener('click', e => { e.preventDefault() getCSV('mycsv.csv') }) })
이제 빌드를 실행해보면 아래 스크린샷과 같이 bundle.js파일의 용량이 획기적으로 줄어든 것을 볼 수 있습니다. 용량이 1.6MB에서 667KB로 1/3정도로 줄어든 것을 볼 수 있습니다. 간단하죠?
하지만 여기에는 작은 함정이 있습니다. 바로 time, 즉 빌드시마다 걸리는 시간도 그에따라 늘어난 것인데요, 만약 여러분이 webpack-dev-server와 같이 실시간으로 파일을 감시하며 변화 발생시마다 빌드하는 방식을 사용하고 있다면 코드 한줄, 띄어쓰기 하나 수정한 정도로 무려 12초에 달하는 빌드 시간을 기다려야 합니다. (treeshaking 하기 전에는 3초정도밖에 걸리지 않았습니다.)
그래서 항상 treeshaking을 해주는 대신 빌드작업, 즉 서버에 실제로 배포하기 위해 bundle.js파일을 생성할 때만 treeshaking을 해주면 개발도 빠르고 실제 배포시에도 빠르게 작업이 가능합니다.
빌드할때만 사용하기
앞서 다뤘던 package.json파일 중 script부분 아래 build를 다음과 같이 수정해주세요.
파이썬의 버전 2와 3이 다른 것은 누구나 알고 2017년인 오늘은 대부분 Python3버전을 이용해 프로젝트를 진행합니다. 하지만 자바스크립트에 버전이 있고 새로운 기능이 나온다 하더라도 이 기능을 바로 사용하는 경우는 드뭅니다. 물론 node.js를 이용한다면 자바스크립트의 새로운 버전의 기능을 바로바로 이용해볼 수 있지만 프론트엔드 웹 개발을 할 경우 새로 만들어진 자바스크립트의 기능을 사용하는 것은 상당히 어렵습니다.
1 2 3 4 5 6
// 이런 문법은 사용하지 못합니다. const hello = 'world' const printHelloWorld = (e) => { console.log(e) } printHelloWorld(hello)
가장 큰 차이는 실행 환경의 문제인데요, 우리가 자주 사용하는 크롬브라우저의 경우에는 자동업데이트 기능이 내장되어있어 일반 사용자가 크롬브라우저를 실행만 해도 최신 버전을 이용하지만, 인터넷 익스플로러나 사파리와 같은 경우에는 많은 사용자가 OS에 설치되어있던 버전 그대로를 이용합니다. 물론 이렇게 사용하는 것도 심각한 문제를 가져오지는 않지만, 구형 브라우저들은 새로운 자바스크립트를 이해하지 못하기 때문에 이 브라우저를 사용하는 사용자들은 새로운 자바스크립트로 개발된 웹 사이트를 접속할 경우 전혀 다르게 혹은 완전히 동작하지 않는 페이지를 볼 수 있기 때문에 많은 일반 사용자를 대상으로 하는 서비스의 경우 새 버전의 자바스크립트를 사용해 개발한다는 것이 상당히 모험적인 성향이 강합니다.
글쓴 시점인 2017년 10월 최신 자바스크립트 버전은 ES2017로 ES8이라 불리는 버전입니다. 하지만 이건 정말 최신 버전의 자바스크립트이고, 중요한 변화가 등장한 버전이 2015년도에 발표된 ES2015, 다른 말로는 ES6이라고 불리는 자바스크립트입니다. 하지만 인터넷익스플로러를 포함한 대부분의 브라우저들이 지원하는 자바스크립트의 버전은 ES5로 이보다 한단계 낮은 버전을 사용합니다. 따라서 우리는 ES6혹은 그 이상 버전의 자바스크립트 코드들을 ES5의 아래 버전 자바스크립트로 변환해 사용하는 방법을 사용할 수 있습니다.
Babel
여기서 바로 Babel이 등장합니다. Babel은 최신 자바스크립트를 ES5버전에서도 돌아갈 수 있도록 변환(Transpiling)해줍니다. 우리가 자바스크립트 최신 버전의 멋진 기능을 이용하는 동안, Babel이 다른 브라우저에서도 돌아갈 수 있도록 처리를 모두 해주는 것이죠!
물론, Babel이 마법의 요술도구처럼 모든 최신 기능을 변환해주지는 못합니다. 하지만 아래 사진처럼 다양한 브라우저에 따라 최신 JavaScript문법 중 어떠 부분까지가 실행 가능한 범위인지 알려줍니다.
Webpack
ES6에서 새로 등장한 것 중 유용한 문법이 바로 import .. from ..구문입니다. 다른 언어에서의 import와 유사하게 경로(상대경로 혹은 절대경로)에서 js파일을 불러오는 방식으로 동작합니다.
예를들어 어떤 폴더 안에 Profile.js와 index.js파일이 있다고 생각해 봅시다.
하는일이라고는 name, email을 받는 것, 그리고 hello하는 함수밖에 없지만 우선 Profile이라는 class를 하나 만들었습니다.
여기서 Profile 클래스 앞에 export를 해 주었는데, export를 해 줘야 다른 파일에서 import가 가능합니다.
자, 아래와 같이 index.js파일을 하나 만들어 봅시다.
1 2 3 4 5
// index.js import { Profile } from'./Profile'
const pf = new Profile('Beomi', 'jun@beomi.net') console.log(pf.hello())
이 파일은 현재 경로의 Profile.js파일 중 Profile 클래스를 import해와 새로운 인스턴스를 만들어 사용합니다.
하지만 안타깝게도 이 index.js파일은 실행되지 않습니다. 아직 webpack으로 처리를 해주지 않았기 때문이죠!
webpack-dev-server
webpack은 파일을 모아 하나의 js파일로 만들어줍니다.(보통 bundle.js라는 이름을 많이 씁니다.) 하지만 실제 개발중 js파일을 수정할 때마다 Webpack을 실행해 번들작업을 해준다면 시간도 많이 걸리고 매우 귀찮습니다. 이를 보완해 주는 패키지가 바로 webpack-dev-server 인데요, 이 패키지를 사용하면 여러분이 실제 빌드를 해 bundle.js파일을 만들지 않아도 메모리 상에 가상의 bundle.js파일을 만들어 여러분이 웹 사이트를 띄울때 자동으로 번들된 js파일을 띄워줍니다. 그리고 소스가 수정될 때 마다 업데이트된(번들링된) bundle.js파일로 띄워주고 화면도 새로고침해줍니다!
NOTE: webpack-dev-server는 build를 자동으로 해주는 것은 아닙니다. 단지 미리 지정해둔 경로로 접근할 경우 (실제로는 파일이 없지만) bundle.js파일이 있는 것처럼 파일을 보내주는 역할을 맡습니다. 개발이 끝나고 실제 서버에 배포할때는 이 패키지 대신 실제 webpack을 통해 빌드 작업을 거친 최종 결과물을 서버에 올려야 합니다.
설치하기
우선 npm프로젝트를 생성해야 합니다. index.js파일을 만든 곳(어떤 폴더) 안에서 다음 명령어로 “이 폴더는 npm프로젝트를 이용하는 프로젝트다” 라는걸 알려주세요.
babel-loader는 webpack이 .js 파일들에 대해 babel을 실행하도록 만들어주고, babel-core는 babel이 실제 동작하는 코드이고, babel-preset-env는 babel이 동작할 때 지원범위가 어느정도까지 되어야 하는지에 대해 지정하도록 만들어주는 패키지입니다.
이렇게 설치를 진행하고 나면 Babel과 Webpack을 사용할 준비를 마친셈입니다.
NOTE: package.json뿐 아니라 package-lock.json파일도 함께 생길수 있습니다. 이 파일은 npm패키지들이 각각 수많은 의존성을 가지고 있기 때문에 의존성 패키지들을 다운받는 URL을 미리 모아둬 다른 컴퓨터에서 package.json을 통해 npm install로 패키지들을 설치시 훨씬 빠른 속도로 패키지를 받을 수 있도록 도와줍니다.
이제 설정파일 몇개를 만들고 수정해줘야 해요.
설정파일 건드리기
package.json
package.json파일은 파이썬 pip의 requirements.txt처럼 패키지버전 관리만 해주는 것이 아니라 npm와 결합해 특정 명령어를 실행하거나 npm 프로젝트의 환경을 담는 파일입니다.
위 파일은 entry에 현재 위치의 index.js파일을 들어가 모든 import를 찾아오고, module -> rules -> include에 있는 .js로 된 모든 파일을 babel로 처리해줍니다.(exclue에 있는 부분인 node_modules폴더와 dist폴더는 제외합니다.)
index.html
사실 우리는 아직 번들링된 js파일을 보여줄 HTML파일이 없습니다! 우선 bundle.js를 보여주기만 할 단순한 HTML파일을 하나 만들어 봅시다.(index.js와 같은 위치)
Inside `objFunction`: 13// 처음에 인자로 전달한 값을 받음 Inside `bar`: 13// Arrow Function에서 this는 일반 인자로 전달되었기 때문에 이미 값이 13로 지정됩니다.
즉, Arrow Function 안의 this는 objFunction의 this가 됩니다.
그리고 이 ArrowFunction은 this의 Scope를 바꾸고 싶지 않을 때 특히 유용합니다.
1 2 3 4 5 6 7 8 9 10
// ES5 function에서는 `this` Scope가 function안에 들어가면 변하기 때문에 새로운 변수를 만들어 씁니다. var someVar = this; getData(function(data) { someVar.data = data; });
// ES6 Arrow Function에서는 `this` Scope의 변화가 없기 때문에 `this`를 그대로 사용하면 됩니다. getData(data => { this.data = data; });
이와 같이 Arrow Function에서는 .bind method와 .call method를 사용할 수 없습니다.
즉, 비슷하게 보이지만 실제로 동작하는 것이 다르기 때문에 사용하는 때를 구별하는 것이 필요합니다.
Arrow Function은 new로 호출할 수 없다
ES6에서 함수는 callable한 것과 constructable한 것의 차이를 두고 있습니다.
만약 어떤 함수가 constructable하다면 new로 만들어야 합니다. 반면 함수가 callable하다면 일반적인 함수처럼 함수()식으로 호출하는 것이 가능합니다.
function newFunc() {}와 const newFunc = function() {}와 같은 방식으로 만든 함수는 callable하며 동시에 constructable합니다. 하지만 Arrow Function(() => {})은 callable하지만 constructable하지 않기때문에 호출만 가능합니다.