실습한 github 주소 : https://github.com/kdeun1/webpack-mini-sample
index.html과 index.js 파일을 만든 후 HTML파일 안에서 JS파일의 스크립트 코드를 넣는 방식은 예전 방식이다. <meta charset="UTF-8">
태그를 사용하여 한글이 나타날 수 있도록 문자셋 속성(해당 HTML 문서의 문자 인코딩 방식)을 UTF-8로 사용한다.
HTML과 JS를 분리하는 것은 관리적인 면에서 좋다. 또한, 외부 라이브러리를 cdn 방식으로도 많이 사용했다.
JS파일과 HTML파일의 종속때문에, 독립적이지 않아 HTML 의존성이 심해 JS파일은 HTML없이 정상적으로 작동할 수 없다. 외부 라이브러리의 추가로 인해 JS의 전역 스코프가 오염되는 단점이 존재한다. 그리고 외부 라이브러리를 추가할 때마다 HTML에 계속 코드를 추가해야한다.
CDN은 Content Delivery Network의 약자이다. 가장 간단하게 라이브러리를 임포트할 수 있는 방법이며, 따로 설치하지 않아도 된다. 라이브러리 트래픽을 내 서버에서 받아낼 필요가 없으며 전 세계 어디에서 접근해도 균일한 속도를 내줄 수 있다. 하지만, CDN 서버가 무너지거나 인터넷이 안되는 경우에는 CDN을 이용하는 모든 웹 사이트가 동작하지 않을 수 있다.
직접 다운 받는 방법은 인터넷이 없는 환경에서 사용할 수 있다. 하지만 직접 일일이 다운받아야하며, 자동화가 되지 않는다.
기본적으로 Node.js로 만들어진 프로젝트를 관리하는 도구이며, NPM(Node.js Package Manager)를 도입하면 우리가 원하는 라이브러리를 체계적으로 관리할 수 있다.
npm init -y
명령어를 실행한다. 이때, -y는 yes로 일괄 적용하는 명령어이다. 명령어를 실행하면 package.json 파일이 생성되는데, 외부 라이브러리를 체계적으로 관리하기 위함이다.
Node.js는 단순히 브라우저 환경이 아닌 환경에서도 JS를 실행할 수 있게 해주는 자바스크립트 런타임이다. 웹 개발에 이용되는 JS 프로젝트는 번들링, 컴파일이라는 과정을 거쳐서 배포된다. 번들링은 작성한 모든 파일을 하나로 합치는 행위인데, 웹 브라우저 내부의 JS 엔진이 갖는 한계를 해결하기 위함이다. 다양한 웹 브라우저의 크로스 브라이징을 지원하기 위해 번들링은 필수라고 볼 수 있다. 개발 시에 타입스크립트와 SASS 등의 프리프로세서로 개발하는 것을 HTML, CSS, JS만 해석할 수 있는 웹 브라우저가 읽을 수 있도록 번들링 해줘야한다. 또한, 최적화도 할 수 있으며, 자동화된 개발환경을 구축할 수 있다.
Node.js를 설치하면 NPM이 같이 설치되며, NPM은 package.json을 이용하여 프로젝트의 메타 정보를 관리하며, 의존성을 변경할 수 있고 script를 이용하여 명령어를 등록/자동화 할 수 있다.
package.json 파일에 있는 버전은 버전 관리를 위해서 Semantic Versioning이라는 방식을 사용한다. Major Version.Minor Version.Patch Version의 형태이다.
버전을 적는 방법은 4가지가 존재한다.
Node.js에서는 일반적으로 캐럿(^)을 이용하여 버전을 명시한다.
모듈이 없었던 ES5에서는 전역 스코프의 오염 문제가 존재하였으며, IIFE(즉시 실행 함수 표현, Immediately Invoked Function Expression)를 통해 전역 스코프 오염 문제를 해결하면서 개발되어왔다. ES5까지 <script src="경로"></script>
를 이용하여 HTML에 JS파일을 추가하였다.
ES6(ES2015) 부터 모듈이라는 개념이 도입되었는데, 다른 프로그래밍 언어처럼 다른 파일에서 코딩한 부분을 불러와서 이용할 수 있게 만들어주는 것이다.
모듈을 통해 전역 스코프 오염을 방지하게 되었는데 대표적인 모듈의 종류로는 AMD
와 CommonJS
가 있다.
JS를 사용하는 모든 환경에서 모듈을 제공하는 것이 목표이며 Node.js에서 사용하는 방법이다. exports
라는 키워드로 모듈을 내보내고, require()
함수로 모듈을 불러온다.
Asynchronous Module Definition의 약자로 비동기로 로딩되는 환경에서 모듈을 사용하는 것을 목표로 한다.
Universal Module Definition의 약자이며, AMD를 기반으로 CommonJS 방식까지 지원하는 통합 형태이다.
이처럼 많은 모듈에 대한 스펙이 제안되다가 ES6부터 표준 모델 시스템을 만들었다. 지금은 바벨과 웹팩으로 모듈 시스템을 사용하는 방식이 일반적이다.
webpack.config.js 파일에 설정을 입력한다. webpack은 개발자가 작성한 파일들을 번들링(작성한 모든 파일을 하나로 합치는 행위)한다.
웹팩에서 파일을 합칠 때는 파일을 합치는 시작점이 되는 파일이 필요하다. 웹 자원을 변환하기 위해 필요한 최초 시작(진입)점이자 자바스크립트 파일 경로이다.
엔트리 포인트는 여러 곳이 될 수도 있다. 엔트리 포인트를 분리하는 경우는 SPA가 아닌 특정 페이지로 진입했을 때, 서버에서 해당 정보를 내려주는 형태의 MPA(멀티 페이지 어플리케이션)에 적합하다.
만들어진 최종 파일을 내보내는 옵션이다. 번들링 후에 탄생한 파일이다.
filename은 최종파일명 옵션이다.
path는 폴더이며, 이 때, node에서 제공하는 path 모듈(파일이나 폴더의 경로 작업을 위한 유틸)을 활용한다. 현재 경로 하위(__dirname
)에 dist라는 폴더를 의미
src 폴더를 생성하고 폴더 내부에 util.js 파일을 만든다.
이제 webpack을 실행해본다.
main.js 파일을 index.html에서 사용
build 후에 dist폴더 내 js파일만 배포하는게 아니라 html파일도 함께 존재해야 한다. 그래야 dist폴더만 배포할 경우 완벽하게 된다.
npm i html-webpack-plugin 명령어를 입력한다.
[new HtmlWebpackPlugin()]
를 추가한다.HtmlWebpackPlugin은 webpack 번들을 제공하는 HTML 파일생성을 단순화한다. 파라미터에는 객체가 들어가는데 옵션을 설정할 수 있다.
new HtmlWebpackPlugin({ template: './index.html' }) 설정 후 다시 빌드
<script>
태그를 추가하여 js파일을 넣을 필요가 없다. 자동으로 <script>
태그를 만들어준다.코드를 수정할 때마다 웹팩 명령어를 매 번 실행해줘야한다. 개발하기 쉽게 서버를 띄워주는 역할을 한다.
npm i webpack-dev-server -D 명령어를 입력한다.
npm (run) start 명령어를 입력한다.
npm run build 명령어를 입력한다.
webpack 5는 uglifyJs대신 terser-webpack-plugin이 사용되었다. uglify 플러그인이 ES6+이후로 지원하지 않기 때문에 현재 deprecated 상태이며 terser가 선택되었다.
웹팩은 파일을 전처리하기 위해 로더를 사용한다. JS 파일이 아닌 다른 정적인 리소스들을 번들링하기 위해 로더를 사용한다. 웹팩의 로더는 CSS, 이미지, 폰트, TS 등 JS 언어가 아닌 다른 무언가를 번들링하기 위해 사용된다.
자주 사용되는 로더는 다음과 같다.
로더가 파일 단위를 처리한다면, 플러그인은 번들된 결과물을 처리한다. 번들된 JS를 난독화나 테스트 추출 용도로 사용한다. 플러그인은 웹팩의 backbone이라 불러운다. 로더가 못하는 일도 해줄 수 있고, 웹팩을 공개적으로 많은 플러그인들을 제공해준다.
자주 사용되는 플러그인은 다음과 같다.
npm i -D style-loader css-loader 명령어를 입력한다. (internal css 방식)
<style>
태그로 만들어서 <head>
에 넣어준다. 이 때, css-loader와 함께 사용된다. 번들된 css를 CSSOM으로 변경시킨다.webpack.config.js 내 css 확장자에 대한 세팅
['style-loader', 'css-loader']
의 값이 들어가며, 역순방향(우측에서 좌측으로)으로 loader가 적용된다.src 폴더에 style.css 파일을 만들고 적용한다.
<head>
내부에 <style>
이 들어가있다.src/header.css 파일을 생성하고 적용한다.
<head>
내부에 <style>
이 하나 더 추가되어있다.<style>
태그가 늘어난다.style-loader, css-loader를 사용하여 css 파일을 리터럴 방식으로 html에 추가하는 방식보다는 css 파일을 별도로 만들어서 가져오는 형태를 적용할 수 있다. (external css 방식)
webpack.config.js에서 세팅을 해준다.
[MiniCssExtractPlugin.loader, 'css-loader']
로 변경해준다. <head>
태그에 <style>
을 추가하는 방식이 아니라 외부에서 가져오는 역할을 하기 때문에 style-loader 대신에 MiniCssExtractPlugin.loader로 변경하였다.npm run build 명령어로 빌드를 해본다.
<link href="common.css" rel="stylesheet" />
로 파일을 불러오는 것을 알 수 있다.npm i -D file-loader 명령어를 실행한다.
이미지 파일을 import해서 html에 추가해보자
webpack.config.js 파일에서 방금 설치한 file-loader를 세팅해보자.
['file-loader']
}를 추가한다.npm i -D clean-webpack-plugin 명령어를 실행한다.
clean-webpack-plugin은 성공적으로 빌드 시 output.path 디렉토리에 있는 모든 파일과 사용하지 않는 모든 웹팩 자산들을 제거해주는 플러그인이다.
html-webpack-plugin이나 mini-css-extract-plugin과 같은 플러그인과 달리 clean-webpack-plugin은 default export가 설정되어있지 않아 object destructuring해서 가져와야한다.
npm i -D webpack-bundle-analyzer 명령어를 실행한다.
서버 실행 시 분석창이 매번 열리는데, 이를 설정 값을 통해 원하는 html파일명과 열리지 않게 설정한다.
애셋 모듈은 로더를 추가로 구성하지 않아도 애셋 파일(폰트, 아이콘 등)을 사용할 수 있도록 해주는 모듈이다. Asset Modules는 Asset 파일들을 처리하는 방식들을 모아놓은 모듈이고, 정의하는 방식에 따라 브라우저가 한 번에 다운로드 하는 파일의 개수, 파일의 용량을 결정한다.
webpack 5 이전에는 아래의 로더를 사용했다.
위의 로더를 대체하기 위해서 애셋 모듈에는 4가지 새로운 모듈이 추가되었다.
babel은 최신 자바스크립트를 ES5 버전에서도 돌아갈 수 있도록 변환(transpiling)해준다. 최신 버전의 자바스크립트로 개발하더라도 ES5 지원하는 다른 브라우저에서 돌아갈 수 있도록 처리를 해준다.
여러 브라우저에서 작성한 코드가 정상적으로 돌아갈 수 있도록 만드는 크로스 브라우징을 지원하게 만든다.
바벨은 transpiler라고 부른다. JAVA파일을 class파일로 빌드 변환과 달리 추상화 수준이 같은 코드로 트랜스파일링해준다.
실무 프로젝트에서는 바벨의 수많은 플러그인을 사용해야 하는데, 하나하나 설치하기에는 노가다를 해야한다. 목적에 맞게 프리셋(preset)을 사용하여 시간을 줄여준다.
바벨 공식문서에서 설정 파일명은 v7.8.0이상이라면 babel.config.json이고, 아닌 경우에는 babel.config.js이다. 현재 설치된 바벨의 버전이 7.8이상이므로 babel.config.json 파일을 프로젝트 최상단에 생성한다.
Webpack5의 babel-loader를 사용한다. 이 패키지를 사용하면 Babel, Webpack을 사용하여 JS 파일을 트랜스파일링할 수 있다.
new Promise(); 라는 코드는 ES6에서 나온 문법이다. 이를 babel로 트랜스파일링해도 그대로 변환된다. Promise, Object.assign, Array.from 등의 문법은 ES5로 대체할 기능이 없기 때문에 그대로 남아있다.
JS 최신 문법은 IE와 같은 오래된 브라우저에서 정상적으로 동작/지원하지 않는다. 이처럼 새롭게 추가된 객체나 메소드 등을 사용하기 위해 Polyfill이라 불리우는 코드 조각들을 추가해서 해결해야한다.
즉, babel을 이용해 최신 자바스크립트를 컴파일 타임에서 트랜스파일링한다. 모던 JS 코드를 구 표준을 준수하는 코드로 바꿔준다.
일부 기능들은 polyfill을 이용해 브라우저가 실행되는 시점인 런타임에 등록되지 않은 메소드나 함수을 주입한다. Polyfill은 JS의 Syntax로 읽히지만, 정의되어있지 않은 객체들을 정의해주는 개념이다.
사용하지 않는 폴리필도 불러오기 때문에 번들의 크기가 커지며, Babel 7.4.0부터 deprecated되었다.
폴리필하기 위한 core-js/stable과 트랜스파일된 생성기 기능을 사용하는데 필요한 regenerator-runtime/runtime이 Babel 7.4.0버전부터 직접 import해서 사용하는 방법을 권장한다. 하지만 이 방식은 전역 스코프를 오염시키는 문제가 있다.
babel.config.json 설정파일에 useBuiltIns 옵션에 core-js에서 필요한 폴리필만 import해서 사용한다.
하지만 이 방법은 전역 스코프가 오염되는 방법때문에 선호하지 않는다.
npm i -D @babel/plugin-transform-runtime 명령어를 입력한다.
babel 7.4부터 transform-runtime 플러그인이 core-js@3 지원을 시작했다. 이전 버전과 달리 v3은 인스턴스 메소드를 지원한다.
참조 : 바벨 공식문서 > transform-runtime 플러그인의 core-js설명[https://babeljs.io/docs/en/babel-plugin-transform-runtime#corejs]
transform-runtime 플러그인을 도입하여 기존 바벨 설정 파일 개선
useBuiltIns 옵션에 의해 직접 삽입되었던 core-js 폴리필 대신에 core-js-pure 폴리필(전역 스코프를 오엽시키지 않는 core-js 버전)이 삽입된다.
babel v7에서 useBuiltIns 옵션이 제거되었다. preset-env 프리셋의 useBuiltIns 옵션을 제거하였다. @babel/plugin-transform-runtime 플러그인을 추가하고 옵션으로 corejs: 3을 설정한다.
공식문서에 따라 corejs옵션의 3버전을 사용하려면 npm install --save @babel/runtime-corejs3 명령어를 입력한다.
preset-env와 다르게 target 옵션을 지원하지 않아 브라우저 상황에 맞게 폴리필 최적화를 할 수 없다고 한다.
바벨 설정 후 웹팩빌드를 하더라도 IE에서 오류가 발생한다. 이는 화살표 함수가 남아있기 때문이다.
webpack v5부터 트랜스파일링을 기본적으로 ES6로 한다. 그렇다면 babel이 아니라 webpack의 설정의 target 옵션을 활용해야한다. webpack.config.js 파일의 target을 ['web', 'es5']로 설정해준다.
참고 : ES11같은 오래된 브라우저에서의 지원[https://webpack.js.org/migrate/5/#need-to-support-an-older-browser-like-ie-11]