React나 Vue를 사용하는 과정에서 늘 항상 함께하는 단어들은 웹팩(Webpack)과 바벨(Babel)이다. 개발을 해보겠다고 이것저것 사용해봤지만 퍼블리싱에 가까운, 겉핥기 수준에서의 장난에서 좀 벗어나고자 개념들을 이해해보려고 한다. 머리 속으로만 기억하면 또 까먹을것이 뻔하기 때문에 다시 개념을 정리하고 기록해두려 한다.
웹팩(Webpack)이란 자바스크립트 애플리케이션을 위한 모듈 번들러(module bundler)를 의미한다. 최신 프론트엔트 프레임워크(Framework)에서 가장 많이 사용된다. 그럼 프레임워크는 뭘까?
프레임워크 (Framework)
어떠한 목적을 달성하기 위해 복잡하게 얽혀있는 문제를 해결하기 위한 구조이며, 소프트웨어 개발에 있어 하나의 뼈대 역할을 한다. 흔히 라이브러리와 헷갈리는 경우도 있는데, 보통 프레임워크라고 하면 여러 기능을 가진 클래스와 라이브러리가 '특정 결과물을 구현하고자' 합쳐진 형태를 의미한다. 즉 라이브러리보다 상위의 그룹이며 대부분의 프레임워크들은 다양한 기능들을 지원하기 위해 많은 라이브러리를 가지고 있다.
ex) spring, django, express.js, vue.js
라이브러리 (Library)
개발을 하기 위해 필요한 것들을 미리 구현해 놓은 대상, 도구이다. 재사용이 가능한 기능을 미리 구현해두고 필요한 곳에 호출하여 사용할 수 있도록 만들어진 집합을 의미한다. 즉, 동작하는 완전한 프로그램이 아닌 특정한 부분 기능만을 수행하도록 제작된, 컴파일되어 기계어의 형태로 존재하는 프로그램이다. 이는 API(Application Programming Interface)와는 다른데, API는 프로그래밍 언어에서 라이브러리를 사용할 수 있도록 소스코드 수준에서 인터페이스를 노출시킨 것을 의미한다. API는 따로 정리하도록 하겠다.
ex ) jQuery, react.js
프레임워크는 틀이고, 그 안에 재사용이 가능하도록 만들어진 도구들을 라이브러리라고 한다. 둘의 가장 큰 차이점은 흐름을 누가 가지고 있냐에 있다. 프레임워크는 스스로 흐름을 가지고 있어 사용자로 하여금 코드를 프레임에 맞춰서 개발하도록 하지만, 라이브러리를 사용할 때는 사용자에게 흐름을 직접 제어하게 한다. 단편적인 예로 뷰에서 파일을 만들 때 .vue라는 형식에 맞춰 사용한다. 프레임워크는 프레임을 따라 개발하는 데 최적화된 형태일 뿐 아니라 라이브러리와 달리 더 많은 기능을 디폴트로 제공한다.
그렇다면 모듈과 번들러는 뭘까?
모듈 번들러 (Module Bundler)
모듈 번들러란 웹 애플리케이션을 구성하는 자원(html, css, js 등)을 모두 각가의 모듈로 보고, 이를 조합해서 병합된 하나의 결과물을 만드는 도구이다.
모듈 (Module)
모듈 프로그래밍에서 개별적인 기능을 하는 작은 단위를 의미하며 웹팩에서 모듈은 웹 애플리케이션을 구성하는 모든 자원을 의미한다. 자바스크립트 뿐만 아니라 html, css, images, font 등 많은 파일들을 모듈이라 한다.
번들러 (Bundler)
애플리케이션을 구성하는 모든 모듈을 병합하고 압축해서 만들어진 하나 이상의 파일을 번들이라고 하며, 이러한 동작을 모듈 번들링이라고 한다. 번들러를 사용하므로써 소스 코드를 모듈별로 작성할 수 있고, 모듈간 혹은 외부 라이브러리의 의존성을 쉽게 관리 할 수 있다.
개념적인 부분은 이해가 되었는데, 왜 그럼 모듈 번들링을 해야하는 걸까?
과거의 웹 애플리케이션에서 자바스크립트는 역할이 단순했기에 파일의 크기도 작았다. 하지만 이젠 기능이 복잡해지고 코드의 크기고 커지면서 하나로 관리하게 되었을 때 가독성 및 유지 관리에 문제가 발생하기 시작했다. 따라서 코드를 나누어 관리할 수 있는 모듈에 대한 필요성이 생기게 된 것이다. import/export 구문이 없던 이전 상황에는 <script> 태그만을 이용하게 되었다. 하지만 전역 스코프에 함수가 노출되면서 다른 파일에서 같은 변수이름을 사용할 수가 없었다. 그래서 즉시 실행 함수 표현(IIFE, Immediately Invoked Function Expression)을 통해 외부에서 접근이 불가하고, 정의되자마자 즉시 실행되어 결과값만을 저장하게 했다. 하지만 이는 모듈화의 해결책이 아니었다. 그리하여 점점 CommonJS, AMD, UMD 같은 모듈 시스템이 생겨나기 시작했다.
- CommonJS : 자바스크립트를 브라우저뿐만 아니라, 서버사이드 애플리케이션이나 데스크톱 애플리케이션에서도 사용하려고 조직한 자발적 워킹 그룹이다. 대표적으로 서버 사이드 플랫폼인 Node.js 에서 이를 사용한다.
exports 키워드로 모듈을 만들고 require() 함수로 불러오는 방식이다. - AMD(Asynchronous Module Definition) : 필요한 모듈을 네트워크를 이용해 내려받아야 하는 브라우저 환경 (비동기 상황) 에서도 자바스크립트 모듈을 사용하고자 하였다. 이 방식은 define 함수 내에 코드를 작성함으로써 스코프 분리가 가능하다.
- UMD(Universal Module Definition) : CommonJS와 AMD를 통합한 모듈 방식으로, 모듈 구현 방식이 둘로 나뉘기 때문에 서로 간의 호환성 문제가 있었고, 이를 해결하기 위해 등장했다.
- ES6 Module : export를 이용해 모듈을 만들고 import로 가져온다.
// math.js
export function sum(x, y) {
return x + y
}
// app.js
import * as math from "./math.js"
//import {sum} from "./math.js" -> sum만 가져오기 위한 방법
<!-- html -->
<script type="module" src="./src/app.js"></script>
script 태그에 type="module" 속성을 추가하면 모듈로 사용할 수 있다. app.js에서 math를 가져오기 때문에 math.js는 따로 로드 하지 않아도 된다. import, from, export, default 처럼 모듈 관리 전용 키워드를 사용하기 때문에 가독성이 좋고, 비동기 방식으로 작동하고 있어 모듈에서 실제 쓰이는 부분만 불러오기 때문에 성능과 메모리 부분에서 유리하다. 하지만 모든 브라우저에서 지원하지 않기 때문에 브라우저와 무관하게 사용할 수 있는 모듈이 필요했다. 때문에 웹팩과 바벨이 필요하다.
바벨 (Babel)
바벨은 자바스크립트의 컴파일러이다. 입력, 출력 모두 자바스크립트 코드로 되어있다. 최신 버전의 자바스크립트 문법을 이해하지 못하는 브라우저에게 바벨을 이용해 이해할 수 있는 문법으로 변환해준다. ES6, ES7 등 최신 문법을 사용해서 코딩을 할 수 있기 때문에 생산성이 향상된다.
// arrow function
[1, 2, 3].map((n) => n + 1);
// 문법을 지원하지 않는 경우 오류 발생
// SyntaxError : Unexpected token =>
// Babel을 통한 코드 문법 변경
[1, 2, 3].map(function (n) {
return n + 1;
});
웹팩 (Webpack)
웹팩은 최신 모듈 포맷 (CommonJS, AMD, ES6 Module)을 모두 지원하며 자바스크립트 뿐만 아니라 CSS, Image 파일 등의 리소스의 의존성도 관리해준다.
웹팩이 프로젝트를 빌드할 때 참고하는 설정 파일로 webpack.config.js라는 파일이 존재한다. 이 파일에는 entry, output, loaders, plugins 의 정보가 들어간다.
// webpack.config.js
const path = require("path")
module.exports = {
mode: "development",
entry: {
main: "./src/app.js",
},
output: {
filename: "main.js",
path: path.resolve("./dist"),
},
}
- Path
첫 줄 path는 경로를 만들어주며 Node.js에 기본으로 깔려있는 패키지를 의미한다. 웹팩이 노드에서 돌아가기 때문에 이 모듈도 노드형 모듈을 사용한 것이다. import path from "path" 와 같다. (모던 자바스크립트 파일이 아니라서 import를 사용할 수 없다.)
// webpack.config.js
const path = require("path")
package.json의 script 안에 "build" : "webpack"을 추가하여 npm run build를 실행하면, 결과물이 main.js 이고 dist 디렉토리가 생성되는 것을 확인할 수 있다. 따라서
<!-- index.html -->
<script src="./dist/main.js"></script>
위 코드 처럼 로드하면 된다.
- Mode
Mode에는 development, production, none 이 있는데 자신의 환경에 따라 선택할 수 있다.
// webpack.config.js
module.exports = {
mode: "development",
...
}
- Entry
Entry는 최초 진입점이다. 시작점 경로를 지정하는 옵션으로, 기본값은 ./src/index.js 이다. 다중엔트리 포인트를 설정할 수 있다.
// webpack.config.js
module.exports = {
entry: {
main: "./src/app.js",
},
}
- Output
Output은 번들링 결과물을 위치할 경로이다. path.resolve() 함수는 절대 경로 주소를 얻어온다.
// webpack.config.js
module.exports = {
output: {
filename: "main.js",
path: path.resolve("./dist"),
},
}
[name] 옵션은 entry에서 설정한 값을 포함하는데, 주로 다중 엔트리 포인트를 만들 때 사용한다.
// webpack.config.js
module.exports = {
entry: {
app: "./src/app.js",
search: "./src/search.js",
},
output: {
filename: "[name].js",
path: __dirname + "/dist",
},
}
- Loader
loader는 모듈 내부에 자리하고 있으며, 웹팩이 웹 애플리케이션을 해석할 때 자바스크립트 파일이 아닌 것들을 변환할 수 있도록 도와준다. 파일이 다른 언어 (ex. TypeScript)에서 JavaScript로 변환하거나 인라인 이미지를 데이터 URL로 로드할 수 있다. 또한 자바스크립트 모듈에서 직접 CSS파일을 import 할 수 있다.
// webpack.config.js
module.exports = {
...
},
module: {
rules: [
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
],
}, {
test: /\.vue$/,
loader: 'vue-loader',
options: {
loaders: {
}
}
},
...
]
}
};
test 에 사용된 것은 정규표현식(Regular Expression)으로 로더를 적용할 파일 유형이다. .css로 끝나는 모든 파일을 찾아 적용한다. 똑같은 방식으로 /\.(scss|sass)$/ 를 이용해서 scss와 sass 파일을 찾을 수 있다. use는 해당 파일에 적용할 로더의 이름으로 위 코드에서는 css-loader, vue-style-loader 등을 적용하는 걸 알 수 있다. 적용 후 빌드하면, ./dist/main.js에서 css코드가 js로 변환된 것을 알 수 있다. 이때 순서는 1) css-loader 2) vue-style-loader 순으로 적용된다.
- Plugin
Plugin은 웹팩의 기본적인 동작에 추가적인 기능을 제공하는 속성이다. 로더는 파일을 해석하고 변환하는 반면, 플러그인은 결과물의 형태를 바꾸는 역할을 한다. 클래스 형태로 정의하고 apply 라는 메소드를 정의하며, event hook을 tap 안에 지정한다.
// helloworld-plugin.js
class HelloWorldPlugin {
apply(compiler) {
compiler.hooks.done.tap("Hello World Plugin", stats => {
/* stats is passed as argument when done hook is tapped. */
console.log("Hello World!")
})
}
}
module.exports = HelloWorldPlugin
그 다음 플러그인을 사용하려면 webpack.config.js 의 plugins 배열에 생성자 함수로 생성한 객체 인스턴스를 포함한다.
// webpack.config.js
const path = require("path");
const HelloWorldPlugin = require("./helloworld-plugin");
module.exports = {
mode: "development",
entry: {
...
},
output: {
...
},
module: {
...
},
plugins: [new HelloWorldPlugin()],
}
$ npm run build
> test3@1.0.0 build /Users/who/Documents/test
> webpack
Hello World!
Hash: b6f189fa57dc5a2bc07
...
웹팩과 바벨을 알아보려다 기본적의 용어 및 개념까지 정리하게 되었다. 엘리스의 토끼굴에 빠지지 않아야 한다는 말이 크게 와 닿았다. 다른 프로젝트나 무엇인가를 해야하는데 용어를 모르면 전혀 진행할 수 없겠구나 싶었다. 아직도 갈 길이 멀다.😂
'코딩 > 정말 나는 잘 알고 있을까?' 카테고리의 다른 글
Git pull - Fatal: Not possible to fast-forward, aborting (0) | 2022.05.31 |
---|---|
Git pull 에러 (0) | 2022.05.31 |
Git - Mac에서 Git 사용하기 (0) | 2022.05.21 |
GIt - Github, Gitlab (3) (0) | 2022.05.17 |
GIt - Github, Gitlab (2) (0) | 2022.05.17 |