제목 : 번들러와 Webpack
번들러에 대한 내용을 다루는 이유
웹 사이트 제작은 수많은 라이브러리와 프레임워크로 이루어 질 수 있다.
특히, 기존 웹 사이트 개발에 사용하던 단순 JavaScript, CSS 의 단점을 상쇄시킬 수 있는
여러 확장자 (EX - .sass
, .scss
, .jsx
, .tsx
등등) 가 탄생했으며,
TypeScript 의 적용으로 이 또한 JavaScript 로 변환해야 할 필요가 있다.
특히, 가장 중요한 것은 이러한 모듈들 간의 순서, 의존성이다.
우리는 현재 JSX, TSX 에서 scss
파일을 import
해오기도 하고,
작성된 컴포넌트들이 특정 컴포넌트를 가져오기 때문에, 이는 의존성이라고 부를 수 있다.
그렇다면, 엔트리 파일(시작 파일) 인 index.html
에 작성되어야 할 JavaScript 파일의 순서도
매우 중요 할 것이다.
또한, JSX, TSX 파일은 결국 JS 로 파싱되는데, 이 파일에서 import
해온
sass
, scss
와 같은 파일들도 결국 css
와 js
로 순서 의존성을 해결 해 줄 필요가 있다.
또한, Node.js 에서 사용하는 방식인 commonjs
모듈 임포트의 방식 또한 변환 해 줄 필요가 있다.
개발 편의성을 갖추기 위해 모듈식 코드 작성이 보편화 되었지만,
정작 이 파일이 어떻게 변환되는지는 공부하지 않은 나 자신을 반성한다.
이러한 역할을 해 주는 JS 번들러는 여러 프로그램이 존재한다.
Webpack, Parcel, esbuild, Vite, SWC, Turbopack 등등이 있다.
이 중, React 에서 흔히 사용되며, 거대한 커뮤니티를 가진 Webpack 을 중심으로 공부하려고 한다.
Webpack 이 거대한 커뮤니티를 가진 만큼, 다른 번들러도 비슷한 개념을 가지고 있을 것이라 생각하기 때문이다.
Webpack 의 컨셉
웹팩은 Modern JS 어플리케이션을 위한 "정적 모듈 번들러" 이다.
webpack 이 Application 을 처리 할 때,
내부적으로 프로젝트에 필요한 모든 모듈을 매핑하고, 하나 이상의 번들을 생성하는
"Dependency Graph" (의존성 그래프) 를 그린다.
의존성 그래프란
- 어떤 파일이 다른 파일에 의존(
require
orimport
) 할 때, webpack 은 이를 의존성으로 취급한다. - webpack 은 img 또는 web font 와 같은 코드가 아닌 Asset 을 가져와서, 의존성으로 제공 할 수 있다.
webpack 의 컨셉을 이해하기 위해서는 특정 핵심 개념들을 이해해야 한다고 공식문서에 작성되어 있다.
따라서, 문서에서 제공하는 핵심 개념들을 차례로 다뤄보자.
- Entry (엔트리)
- Output (출력물)
- Loaders (로더)
- Plugins (플러그인)
- Mode (모드)
- Browser Compatibility (브라우저 호환성)
Entry (엔트리) 란?
엔트리 포인트란, webpack 이 내부 Dependency Graph 를 생성하기 위해 사용해야 하는 모듈이다.
Webpack 은 엔트리 포인트가 의존하는 다른 모듈과 라이브러리를 찾아낸다.
이게 무슨 말인가?
의존성 그래프를 생성하기 위해 사용해야 하는 모듈을 엔트리 포인트로 등록해야 한다니,
이것이 무엇을 의미하는지 헷갈렸다.
따라서 나는 이전에 제작했던 테스트용 React + TS 를 켰고,
$ npm run eject
를 통해 react 내장 webpack 에서 외장으로 꺼내 직접적으로 볼 수 있게 만들었다.
먼저 말하자면, React + TS 의 엔트리 포인트는 index.tsx
이다.
물론, JS 환경이라면 index.jsx
가 될 수도, index.js
가 될 수도 있다.
React 에 내장되어 있는 webpack config 는 이미 수많은 코드로 구성되어 있었는데,
여기서 entry 에 대한 내용을 찾아 본 결과, index.tsx
를 가져오고 있었다.
그렇다면, 왜 index.tsx
인가?
우리가 React 에서 만든 컴포넌트는 모두 index.tsx
로 모인다.
역으로 말하자면, index.tsx
에서 시작한 파일들은 서로를 부르고 호출하는 과정에 따라,
결국 컴포넌트, 유틸 함수 등등 여러 파일에 대한 의존성 그래프를 만들 수 있다는 것이다.
index.tsx
는 App.tsx
를 부르고, App.tsx
는 또 다른 컴포넌트를 불러온다.
이 때, index.tsx
는 App.tsx
에 대한 의존성을 가지고 있다고 볼 수 있다.
Webpack 은 index.tsx
를 엔트리 포인트로 삼아 연결된 "모든" 의존성을 파악하는 것이다.
예시: - webpack.config.js
module.exports = {
entry: "./src/index.tsx"
}
// 위의 구문은 밑의 코드가 축약 된 형태라고 한다.
module.exports = {
entry : {
main : "./src/index.tsx"
}
}
// 혹은, 여러 개의 메인 엔트리가 존재하는 경우 배열로 제공 할 수도 있다.
module.exports = {
entry : ["./src/index.tsx", "./src/index2.tsx", ...],
// 여러 의존성 파일이 주입되고, 하나의 파일로 번들링한다. (청크 - chunk)
output : {
filename : "newBundle.js"
}
}
위에서 사용한 entry
는 "단일 엔트리 구문" 이라고 한다.
하나의 엔트리 포인트만 존재한다면, 애플리케이션과 도구에 대해 설정을 빠르게 할 수 있으나,
추후 설정을 확장 할 수 있는 유연성이 떨어지게 된다고 한다.
엔트리 포인트 설명 객체 (EntryDescription Object)
엔트리 포인트 객체에서 의존성 확립과 경로 설정에 대한 지정 옵션들이 존재하는데,
내가 직접 React 에서 npm run eject
후 본 옵션들이 있어
알아놔야 할 것 같다.
dependOn
: 현재 엔트리 포인트가 의존하는 또 다른 엔트리 포인트.
해당 엔트리 포인트는 먼저 로드해야 한다. (위에 쓰자)filename
: 디스크에 존재하는 출력 파일의 이름을 지정한다.import
: 시작 시 로드되는 모듈이다.library
: 현재 엔트리에서 라이브러리 번들링 시 라이브러리 옵션을 지정.runtime
: 런타임 청크의 이름이다. 설정 시 해당 이름의 런타임 청크가 생성되며,
설정되지 않는다면 기존 엔트리 포인트의 이름이 사용된다.publicPath
: 브라우저에서 참조 시, 이 엔트리의 출력 파일에 대한 공용 URL 주소를 지정한다.
유연성 확장과 의존성 설정
위에서 제시한 방식은 entry
를 단일 문자열, 혹은 배열로 사용했다.
공식문서에서 말하는 가장 확장 가능한 방식이란, 객체처럼 사용하는 것이다.
// 여러 엔트리를 확장 가능한 가장 유연한 방식이며, 엔트리끼리 참조가 가능.
module.exports = {
entry : {
app : "./src/app.js",
adminApp : "./src/adminApp.js"
}
}
// 특정 엔트리가 의존성을 가지는 경우
module.exports = {
entry : {
mainDepend : "mainDependFile.js",
app : {
dependOn : "mainDepend",
import : "./src/app.js"
}
}
}
React 의 엔트리 포인트는 위의 예시처럼 정확히 표기되지 않았는데,
다른 확장자를 대비하여 js 에 관련된 모든 확장자를 표기 해 놓고,
해당 확장자의 index.<확장자>
존재 시, 해당 파일을 엔트리 포인트로 삼을 수 있게 해 두었다.
Output (출력) 이란?
이 속성은 생성된 번들을 내보낼 위치, 그리고 파일의 이름을 지정하는 방법을 webpack 에서 알려준다.
"기본 출력" 파일의 경우 ./dist/main.js
로,
생성된 기타 파일의 경우 ./dist
폴더로 설정된다.
공식 문서에서 좋은 예제를 내 주었는데, 이를 활용해서 작성 해 보자.
const path = require("path");
module.exports = {
entry : "./src/index.tsx",
output : {
path : path.resolve(__dirname, "dist"),
filename : "static/js/bundle.js"
}
}
웹팩의 동작은 Node.js 환경에서 이루어지기 때문에,
commonjs 방식으로 모듈을 가져온다. -require
위의 예제는 내가 만약에 webpack.config.js
를 직접 작성했을 때,
컴포넌트 구성 JS 에 대한 영역이 /dist/static/js/bundle.js
에 위치하도록 만들었다.
물론, 이는 테스트 상황을 의미하고, 만약에 프로덕션이라면 해싱 기능을 넣어야 한다.
그리고 path
라이브러리는 매우 중요한데, 해당 라이브러리가 있어야 __dirname
과 같이,
현재 실행 디렉토리를 가져올 수 있다.
이 외에도 output
속성은 매우 많은 기능을 가지고 있는데, 이는 밑의 사이트에서 참조하면 된다.
https://webpack.kr/configuration/output
Loaders (로더) 란?
Loader 는 module.exports
에 직접적으로 바로 선언되는 형식은 아니고,
먼저 module
이라는 최상위 옵션을 두고,
rules
옵션을 module
내에 넣어 배열로 관리한다.
왜 로더 라는 개념을 만들어서 규칙을 통해 선언하나 싶었는데,
생각해 보니 애초에 webpack 은 JavaScript 와 JSON 파일만 이해한다고 한다.
webpack 이 다른 유형의 파일을 처리하거나 모듈로 변환하기 위해서는,
이를 JS 혹은 JSON 으로 바꿔주는 특정 모듈이 필요하다.
예를 들어, webpack 에서는 의존성 그래프를 만들기 위해 JS 로 인식한다.
이를 위해서, 각각의 확장자는 특정 loader 패키지를 가져와 JS 모듈로 만들어 버린다.
예를 들어, css
, ts
에 대한 의존성 그래프를 형성하기 위해,
webpack.config.js
module.exports = {
module : {
rules : [
{ test : /\.css$/, use : "css-loader" },
{ test : /\.ts$/, use : "ts-loader" }
]
}
}
먼저, webpack.config.js
에서도 npm 패키지로 위에 선언된 2 개의 패키지가 설치되어야 한다.
이 때, 개발 의존성으로 css-loader
, ts-loader
를 설치한다.
만약에 React 와 같이 템플릿이 만들어져 있는 경우, 이러한 로더들은 이미 npm 모듈로 설치되어 있다.
따라서, 로더는 이 2 개의 속성에 중점을 둔다.
test
: 변환이 필요한 파일들을 식별하는 속성 (Regex 사용)use
: 변환을 수행하는 데 사용되는 로더를 가르키는 속성
Plugins (플러그인) 이란?
나는 webpack 에서 플러그인 이라는 단어를 듣고 조금 난해하다고 생각했다.
개발 환경에서 생겨난 파일 의존성과 출력물을 생성하는 과정에서,
어떤 플러그인이 필요한 것인가?
webpack 에는 플러그인을 활용하여 번들을 최적화하거나,
정적 에셋들을 관리하고, 환경변수 주입과 같은 광범위한 작업을 수행 할 수 있다고 한다.
즉, webpack 에서의 플러그인은 기존 작업에서의 추가 옵션과 비슷한 의미인 것이다.
이 옵션은 다양한 객체를 new HtmlWebpackPlugin(...)
과 같은 형식으로 추가한다.
React 에서도 위의 옵션을 사용하는데, 이는 생성된 모든 번들을 삽입하여
어플리케이션 전용 HTML 파일을 생성한다는 것이다.
Mode 란?
Webpack 에는 내장된 환경 변수가 있는데, 바로 mode
로 설정할 수 있다.
development
, production
, none
이 있다.
webpack 에서 주는 모드에 따라 우리는 설정을 동적으로 설정할 수 있다.
Browser Compatibility (브라우저 호환성)
만약에 구형 브라우저를 지원하려면, Polyfill 을 지원해야 할 수도 있다.
Webpack 핵심 결과를 요약하자면,
웹 어플리케이션을 제작하기 위해 다양한 확장자, 외부 모듈인 npm,
심지어는 TypeScript, JSX, TSX, sass, scss 등등 여러 변환이 필요하다.
웹을 제작하기 편하게, 혹은 기능을 더 추가하기 위해 여러 파일과 모듈이 등장했는데,
웹에서 간단하게 사용하던 JavaScript 의 파일이 서로를 의존함에 따라,
의존성을 고려하지 않으면 웹에서 JS 파일이 제대로 실행되지 않는 상황이 발생한다.
이러한 의존성을 고려하여 웹 페이지에 의존성을 제대로 로드할 수 있는 순서대로 기입함과 동시에,
웹 사이트가 로드할 수 있는 js, css, html 로만 변환 해 준다.
이 때, 여러 컴포넌트로 나뉘어 있거나, 유틸로 작성되어 있는 js,
각 페이지의 스타일링을 위해 나뉘어 있는 scss, sass 를 css 로 만든다.
아주 간단히 얘기하자면, 개발 상황 --> 웹 페이지 로드 가능 이다.
Webpack 은 리액트에 내장되어 있는데, 이는 개발 환경에서 곧바로 소스코드 변환은 인지하여
해당 부분만 변경시켜 렌더링하기도 하고, 개발 상황과 제품 상황에 나뉘어 환경 변수를 주입하기도 한다.
이러한 역할을 집약하여 정의하자면, Bundler (번들러) 라고 한다.
Webpack 실습 In JavaScript
참고 공식문서
https://webpack.kr/guides/getting-started/
Webpack 의 컨셉 문서와 공식문서들을 통해 이것이 왜 사용되는지 알았지만,
사용법은 알지 못한다. 대부분의 경우, react-scripts
와 같은 편의성 스크립트 덕분에
딱히 번들링을 고려해야 할 필요는 없었기 때문이다.
그런데, TOAST UI 에서 고려해야 할 이유를 찾았다.
기본적인 리액트 환경에서는 크게 최적화의 필요성을 느끼기가 힘들다.
그도 그럴 것이, 초창기에는 작성한 파일의 수, 외부 모듈의 수가 적기 때문이다.
그러나, React 에 빌드된 목록을 보니, JavaScript 청크 1개, css 청크 1 개를 불러오고 있었다.
만약에 내가 이 프로젝트에서 로드되기까지 시간이 조금 걸리는 무거운 모듈을 추가하게 된다면,
브라우저에서는 단 1 개의 청크를 로드하기 위해 시간이 걸린다는 의미였다.
따라서, 가벼운 서비스 chunk 1 개, 무거운 모듈 chunk 1개로 나누어도 좋겠다는 생각을 했다.
이러한 생각에 이어 직접 webpack 프로젝트를 간단히 만들어 보기로 결정했다.
기본 셋업
만약에, 이 글을 보고 따라하시는 분이 있다면, node
, npm
, git
, ts-node
, typescript
등등,
기본 프로그램들이 다운되어 있다는 전제를 깔고 시작하겠습니다.
먼저, 프로젝트를 작성할 디렉토리를 하나 만든다. (나는 demo-webpack-js
)
→ <상위 디렉토리> $ mkdir demo-webpack-js # 프로젝트 디렉토리 생성
→ <....> $ cd demo-webpack-js # 생성된 프로젝트 디렉토리 이동
→ demo-webpack-js $ npm init -y # 이 디렉토리의 npm 초기화 - 기본 package.json 생성
# webpack 은 "개발 의존성" 이기 때문에,
# npm i -D ... or npm i --save-dev ... 으로 다운로드 한다.
→ demo-webpack-js $ npm i -D webpack webpack-cli
웹 프레임워크나 라이브러리의 경우, 자사의 번들러를 가지고 있거나, webpack 을 동봉한다.
그러나, 크로스플랫폼이나, 특정 모듈만을 타겟으로 제작 할 경우, webpack 을 따로 설치하여 개발 할 수 있다.
이후, 프로젝트에 이러한 파일과 디렉토리를 추가한다.
/프로젝트 디렉토리 루트
/node_modules
package.json
package-lock.json
+ /dist
+ index.html
+ /src
+ index.js
위의 예시는 브라우저를 타겟으로 하는 웹 애플리케이션의 경우를 말하는 것이다.
index.html
은 src
폴더에 있는 코드를 번들링하여 결과를 브라우저에 출력한다.
index.html
:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Testing JS Webpack</title>
</head>
<body>
<script src="main.js"></script>
</body>
</html>
그리고, JS 최적화를 위한 npm 모듈 lodash
를 설치한다.
➜ webpack-demo-js npm i lodash
added 1 package, and audited 120 packages in 983ms
17 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
우리가 생성했던 src/index.js
파일을 작성 해 보자.
import _ from "lodash";
function component() {
const element = document.createElement("div");
element.innerHTML = _.join(["Hello!", "Webpack World!"], ' ');
return element;
}
document.body.appendChild(component());
그리고, package.json
의 scripts
에 새로운 문장을 넣는다.
"scripts" : {
"test": "echo \"Error: no test specified\" && exit 1",
+ "build" : "webpack"
}
webpack
명령어가 참조 할 파일을 만들자 :
webpack.config.js
:
const path = require("path");
module.exports = {
entry : "./src/index.js",
output : {
filename : "main.js",
path : path.resolve(__dirname, "dist")
}
}
이후, 명령어를 실행한다 :
➜ webpack-demo-js npm run build
> webpack-demo@1.0.0 build
> webpack
asset main.js 69.5 KiB [compared for emit] [minimized] (name: main) 1 related asset
runtime modules 1010 bytes 5 modules
cacheable modules 532 KiB
./src/index.js 225 bytes [built] [code generated]
./node_modules/lodash/lodash.js 531 KiB [built] [code generated]
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
webpack 5.99.8 compiled with 1 warning in 1718 ms
이 명령어를 실행하면, 최종 폴더 계층은 이렇다 :
/프로젝트 디렉토리 루트
/node_modules
package.json
package-lock.json
/dist
index.html
+ main.js
+ main.js.LICENSE.txt
/src
index.js
webpack.config.js
여기서, 우리는 폴더 탐색기로, index.html
을 열어보자.
그렇다면, 우리는 이러한 결과를 볼 수 있다.
그래서, 우리가 index.html
에서 사용한 main.js
파일은 어떻게 되어 있을까?
/*! For license information please see main.js.LICENSE.txt */
(()=>{var n={543:function(n,t,r){var e;n=r.nmd(n),function(){var u,i="Expected a function",o="__lodash_hash_undefined__",f="__lodash_placeholder__",a=32,c=128,l=1/0,s=9007199254740991,h=NaN,p=4294967295,v=[["ary",c],["bind",1],["bindKey",2],["curry",8],["curryRight",16],["flip",512],["partial",a],["partialRight",64],["rearg",256]],_="[object Arguments]",g="[object Array]",y="[object Boolean]",d="[object Date]",b="[object Error]",w="[object Function]",m="[object GeneratorFunction]",x="[object Map]",j="[object Number]",A="[object Object]",k="[object Pr.........
// 이후로 50 줄이 더 존재
아니, src/index.js
에 작성 해 놓은 간단한 메서드 함수가 이렇게 변했다.
이를 "난독화" (Uglify) 라고 부르는데, 사람이 읽을 수 없게 만드는 것이다.
이러한 과정 속에서 Webpack 은 최적화도 수행한다.
난독화 된 파일은 사람이 읽을 수 없을 뿐, 우리가 작성한 index.js
와 동일하게 동작한다.
그렇기에, 우리는 dist/index.html
에서 결과물을 볼 수 있던 것이다.
Webpack 실습 With CSS
https://webpack.kr/guides/asset-management/
그렇다면, webpack 에서 css 는 어떻게 처리할까?
webpack 은 기본적으로 JavaScript, JSON 만 인식 할 수 있다.
그렇다면, 다양한 확장자 파일들과 상호작용을 어떻게 하고 있을까?
이는 NPM 에서 제공하는 <모듈이름>-loader
모듈을 다운로드 받아 webpack 에 적용해야 한다.
CSS 실습은 위에서 작업한 결과물을 기반으로 실행 해 볼 것이다.
Webpack 을 통해 단순 CSS 파일을 번들링 하기 위해서는 2 개의 의존성 모듈이 필요하다.
css-loader
- webpack 이 인식하기 위해 css 파일을 js 모듈로 변환함.style-loader
- 변환된 css-js 모듈을 인식하여<head>
내부에<style>
태그로 css 를 넣어줌.
➜ webpack-demo-js $ npm i -D style-loader css-loader
added 15 packages, and audited 135 packages in 3s
21 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
그리고, src
폴더 내부에 넣을 css
파일을 웹팩이 인식하게 만들기 위해
webpack.config.js
를 변경한다.
const path = require("path");
module.exports = {
entry : "./src/index.js",
output : {
filename : "main.js",
path : path.resolve(__dirname, "dist")
},
// 하위의 코드는 모두 추가된 코드들.
module : {
rules : [
{
test : /\.css$/,
use: ["style-loader", "css-loader"]
}
]
}
}
그리고, index.js
와 같은 위치를 가지는 style.css
를 만든다.
.hello {
color : blue;
}
이후, index.js
에 우리가 만들었던 element
에 새로운 클래스 이름을 추가해준다.
import _ from "lodash";
// 새로이 만든 css 파일을 여기서 "import" 해 준다. - webpack 에게 의존성을 알려주기 위해
import "./style.css";
function component() {
const element = document.createElement("div");
element.innerHTML = _.join(["Hello!", "Webpack World!"], ' ');
// 엘리먼트는 이제 새로운 클래스 이름 "hello" 를 가진다.
element.classList.add("hello");
return element;
}
document.body.appendChild(component());
그리고 이제 번들링 해 보자 :
➜ webpack-demo-js npm run build
> webpack-demo@1.0.0 build
> webpack
asset main.js 73.4 KiB [emitted] [minimized] (name: main) 1 related asset
runtime modules 1.02 KiB 6 modules
orphan modules 1.1 KiB [orphan] 1 module
cacheable modules 541 KiB
modules by path ./node_modules/ 539 KiB
modules by path ./node_modules/style-loader/dist/runtime/*.js 5.84 KiB
./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js 2.42 KiB [built] [code generated]
./node_modules/style-loader/dist/runtime/styleDomAPI.js 1.5 KiB [built] [code generated]
+ 4 modules
modules by path ./node_modules/css-loader/dist/runtime/*.js 2.31 KiB
./node_modules/css-loader/dist/runtime/noSourceMaps.js 64 bytes [built] [code generated]
./node_modules/css-loader/dist/runtime/api.js 2.25 KiB [built] [code generated]
./node_modules/lodash/lodash.js 531 KiB [built] [code generated]
modules by path ./src/ 1.82 KiB
./src/index.js + 1 modules 1.38 KiB [built] [code generated]
./node_modules/css-loader/dist/cjs.js!./src/style.css 455 bytes [built] [code generated]
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
webpack 5.99.8 compiled with 1 warning in 1988 ms
이제, dist/index.html
을 열어보면,
파란색으로 스타일링 된 결과를 볼 수 있다.
그런데, css 파일이 따로 출력물로 나오지 않았다?
위에서 언급했다시피, 우리는 모듈로 style-loader
를 사용했다.
이 로더는 런타임 시, css
파일을 head
로 넣어주는 역할을 한다.
즉, 파일로 출력하지 않고 아예 JS 를 이용하여 동적으로 넣어주는 역할을 한다.
따라서, index.html
에 단 하나의 파일 main.js
만 가져오고도 스타일링이 된 것이다.
Webpack 실습 With Output
위의 프로젝트를 그대로 사용 한 상태에서,
이번에는 단순히 main.js
에 모든 것을 넣지 않고,
파일을 분리 해 보자.
먼저, src
폴더에 print.js
라는 새로운 파일을 만든다.
export default function printTest() {
console.log("Method : PrintTest");
}
그리고 이 printTest()
메서드는 밑에서 만든 버튼에 의해 콘솔로 출력된다.
index.js
import _ from "lodash";
import "./style.css";
+ import printMe from "./print";
function component() {
const element = document.createElement("div");
+ const btn = document.createElement("button");
element.innerHTML = _.join(["Hello!", "Webpack World!"], ' ');
element.classList.add("hello");
+ btn.innerHTML = "버튼 클릭 시 콘솔에 문자열 출력됨";
+ btn.onclick = printMe;
+ element.appendChild(btn);
return element;
}
document.body.appendChild(component());
우리가 printMe 를 따로 생성하여 html 파일에 적용하기 위해 2 가지 방법이 존재한다.
- html 파일의
<head>
쪽에 추가한다. webpack.config.js
의module.exports
에plugins + HtmlWebpackPlugin
를 추가한다.
1 번은 우리가 파일 청크를 새로 생성 할 때 마다, 지정 해 주어야 한다.
그리고 특히, 나중에 파일 이름까지 해싱이 진행되면 직접 지정하기는 더 곤란해진다.
따라서, 우리가 작성한 파일들의 의존성을 알아서 파악하여 Webpack 이 html 파일로 작성 할 수 있도록,
plugins
에 작성 해 놓으면 된다.
일단, Webpack 이 플러그인을 가져 올 수 있도록 개발 의존성으로 플러그인을 설치한다 :
$ npm i -D html-webpack-plugin
변경된 webpack.config.js
const path = require("path");
+ const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
- entry : "./src/index.js",
+ entry: {
+ index : "./src/index.js",
+ print : "./src/print.js",
+ }
output : {
- filename : "main.js",
+ filename : "[name].bundle.js",
+ clean : true,
path : path.resolve(__dirname, "dist")
},
+ plugins: [
+ new HtmlWebpackPlugin({
+ title : "출력물 관리 테스팅"
+ }),
+ ]
module : {
rules : [
{
test : /\.css$/,
use: ["style-loader", "css-loader"]
}
]
}
}
위에 추가된 여러 옵션들에 대한 설명은 뒤로 잠깐 미루고, 결과를 보자 :
➜ webpack-demo-js $ npm run build
> webpack-demo@1.0.0 build
> webpack
asset index.bundle.js 73.4 KiB [emitted] [minimized] (name: index) 1 related asset
asset index.html 290 bytes [emitted]
asset print.bundle.js 23 bytes [emitted] [minimized] (name: print)
runtime modules 1.4 KiB 8 modules
orphan modules 1.1 KiB [orphan] 1 module
cacheable modules 541 KiB
modules by path ./node_modules/ 539 KiB
modules by path ./node_modules/style-loader/dist/runtime/*.js 5.84 KiB
./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js 2.42 KiB [built] [code generated]
+ 5 modules
modules by path ./node_modules/css-loader/dist/runtime/*.js 2.31 KiB
./node_modules/css-loader/dist/runtime/noSourceMaps.js 64 bytes [built] [code generated]
./node_modules/css-loader/dist/runtime/api.js 2.25 KiB [built] [code generated]
./node_modules/lodash/lodash.js 531 KiB [built] [code generated]
modules by path ./src/ 1.93 KiB
./src/index.js + 1 modules 1.41 KiB [built] [code generated]
./src/print.js 77 bytes [built] [code generated]
./node_modules/css-loader/dist/cjs.js!./src/style.css 455 bytes [built] [code generated]
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
webpack 5.99.8 compiled with 1 warning in 2215 ms
자꾸 경고가 뜨는데, 이는 mode
를 설정 해 주지 않아서 그렇다.
결과적으로 이런 폴더 형태를 구축한다 :
/프로젝트 폴더
/dist
+ index.html - clean 옵션으로 인해 항상 삭제 후 다시 생성됨
- main.js
- main.js.LICENSE.txt
+ index.bundle.js
+ index.bundle.js.LICENSE.txt
+ print.bundle.js
/node_modules
/src
index.js
+ print.js
style.css
package.json
package-lock.json
webpack.config.js
자, 이제 index.html
을 보자 :
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>출력물 관리 테스팅</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<script defer="defer" src="index.bundle.js"></script>
<script defer="defer" src="print.bundle.js"></script>
</head>
<body></body>
</html>
결과물
- 의존성을 파악하여, 자동으로
index.html
구축 - 번들링 빌드 시 마다, 항상 모든 파일 삭제 후 재구축
- 엔트리에 따라 여러 개의 청크 번들 파일로 분리 할 수 있음
위 리스트의 기능을 수행 할 수 있었던 것은, webpack.config.js
에 작성한 코드 덕분이다.
다시 코드를 보자 :
webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: {
index: "./src/index.js",
print: "./src/print.js",
},
output: {
filename: "[name].bundle.js",
clean: true,
path: path.resolve(__dirname, "dist"),
},
plugins: [
new HtmlWebpackPlugin({
title: "출력물 관리 테스팅",
}),
],
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
],
},
};
1. entry 의 기본 속성은 main 이다.
처음 Webpack 을 배우면서 다루었던 내용인데, 단일 엔트리(단일 문자열 or 배열 문자열) 로 작성했을 경우,
entry : {
main : "./src/index.js" // or ["./src/index.js", "./src/print.js"]
}
로 작성한 것과 다름이 없다.
하지만, entry
속성 배열을 활용하여 직접 key 를 지정 해 줄 경우, 확장 가능한 형태가 된다.
index
, print
라고 따로 속성 이름을 지어 준 이유는, 추후 2 개의 파일로 나누기 위함도 있다.
2. webpack 의 대괄호 기능은 다양하다
그 밑의 output
객체를 보자.
filename : "[name].bundle.js
라고 되어 있다.
이 의미는, 각 엔트리의 속성 key 를 name 으로 넣고, 이를 각각의 파일로 추출하겠다는 의미이다.
만약에 이 글을 자세히 읽고 있는 분이 있으시다면, (감사합니다)
dist/static/
폴더 내부의 파일 이름을 보자.
파일 이름이 해싱화 된 것을 볼 수 있다.
이것은 webpack
에서의 대괄호 기능을 이용 한 것인데,
직접 npm run eject
이후 webpack.config.js
를 살펴보면,
React 에서는 이러한 기본 대괄호 기능들을 사용한다.
[name]
: 엔트리의 속성 key 를 가져온다.[hash]
: 실행 시 랜덤 해시값을 넣는다.[contenthash:8]
: 컨텐츠의 내용에 따라 해시값을 생성하고, 이를 8 길이로 만든다.[ext]
: 해당 엔트리 속성에 할당된 파일의 확장자를 가져온다.
이를 통해 파일의 이름을 랜덤화 하거나, 규칙화 하여 내보낼 수 있다.
3. 결과 폴더를 청소하고 새로운 파일로 번들링하기
output
옵션에 clean : true
를 넣는다면, 이전 파일이 남지 않고 깔끔하게 삭제된다.
4. Webpack 은 번들링 과정에 필요한 다양한 외부 플러그인을 지원한다.
웹 어플리케이션의 빌드를 위해서, index.html
의 script
를 지속적으로 변경하는 수고로움을 덜어주려면,
HtmlWebpackPlugin
객체를 넣어 Webpack 에서 알아서 index.html
에 넣으라고 지시한다.
이 때, 위의 객체를 넣어주기 위해서 npm 에서 모듈을 다운로드 받아야 한다. html-webpack-plugin
이 외에도, css 최적화 청크, 난독화, 청크 최소화 등등 여러 플러그인이 외부에 존재한다.
이러한 플러그인을 사용하기 위해서는 npm 을 이용해 개발 의존성으로 다운로드 해야 한다.
Webpack 실습 In TypeScript
참고 공식문서
https://webpack.kr/guides/typescript/#basic-setup
이번에는 새로운 프로젝트 폴더를 생성하여 타입스크립트를 webpack 으로 번들링 해 볼 생각이다.
- 기존에 적용되어 있던
style-loader
를 해제하여 css 파일로 만들어 보기 - TS 적용으로 인한 옵션 추가 및 변경과 동시에, 내부 옵션에 집중
- 새로 추가되는
ts-loader
로 인해, 참조되는tsconfig.json
옵션에 집중
자! 나는 이제 새로운 프로젝트 webpack-demo-ts
를 만들었다.
기본적으로 만들었던 우리의 개발 의존성들을 생각 해 보자!
webpack
- 웹팩 코어 모듈webpack-cli
- 웹팩을 명령어로 실행 할 수 있게 해 주는 모듈html-webpack-plugin
- 의존성 선언된index.html
삭제 후, 다시 기입 해 주는 모듈css-loader
- webpack 은 js, json 만 이해 할 수 있으므로, css 파일을 js 모듈로 만듬- 부가설명 : 일반 js, ts 파일에서
import ..css 파일
되었을 경우, 이를 의존성으로 파악 후 js 모듈로 만듬.
- 부가설명 : 일반 js, ts 파일에서
style-loader
-css-loader
과 함께 콜라보레이션 하는 모듈- 부가설명 :
css-loader
만 적용되었을 경우, css 파일은 따로 번들링 후 추출된다.
그러나,style-loader
가 적용되었을 경우,index.html
런타임 시<head>
태그 내부에 스타일 태그를 생성하여 직접 넣어준다.
- 부가설명 :
- (개발 의존성 X)
lodash
- JS 에서 직접 iter 문, 객체 조정 시 많은 리소스가 낭비된다.
이를 아주 효율적으로 바꿔주는 의존성 - 개인적으로 진짜 중요하다 생각되서 이것도 문서로 만들 예정
그리고, 우리가 추가해야 할 개발 의존성이 존재한다.
이미 어느정도 npm 과 JS, TS 와의 상호작용에 대해서 알고 있는 분들은 아시겠지만,
위의 정보는 이미 package.json
에 dependencies
, devDependencies
에 명시되어 있다.
자! 그러면 typescript 환경에 필요한 개발 의존성은 무엇인가?
1. typescript
우리는 로컬 기기 자체에 typescript 를 설치하여 LSP 를 사용하기도 하지만,
현재 프로젝트에 "정확한" 버전 기입을 통해 프로젝트에 사용될 TS 버전을 명시한다.
특히, 버전을 "정확히" 명시하지 않는 이상, 최신 버전의 typescript 를 가져온다.
이를 통해 typescript
개발 의존성이 설치된 프로젝트는 레거시 메서드 및 클래스를 사용 할 가능성이 거의 없다.
2. ts-loader
이 loader 의 경우, 프로젝트에 존재하는 tsconfig.json
의 설정을 따른다.
타입스크립트 번들링을 위해서는 필수적인 로더이다.
프로젝트에 본격적으로 개발 의존성과 일반 의존성을 설치 해 보자 :
# npm 모듈 보관소 초기화 - "package.json" 생성
$ npm init -y
# 개발 의존성부터 설치
$ npm i -D webpack webpack-cli html-webpack-plugin css-loader typescript ts-loader
# 일반 의존성 설치
$ npm i lodash
# lodash 기본 타입 지원이 없어 개발 의존성으로 설치
$ npm i -D @types/lodash
이후, 보여지는 최종 package.json
은,
{
"name": "webpack-demo-ts",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"devDependencies": {
"@types/lodash": "^4.17.16",
"css-loader": "^7.1.2",
"html-webpack-plugin": "^5.6.3",
"ts-loader": "^9.5.2",
"typescript": "^5.8.3",
"webpack": "^5.99.8",
"webpack-cli": "^6.0.1"
},
"dependencies": {
"lodash": "^4.17.21"
}
}
이러하다.
그리고, 현재 디렉토리 형태를 이러하다.
/TS 프로젝트 루트
/node_modules
package.json
package-lock.json
그렇다면, 우리는 다시 이전과 같은 디렉토리를 생성 해 주자 :
/TS 프로젝트 루트
/node_modules
+ /dist
package.json
package-lock.json
+ /src
+ index.ts
+ webpack.config.js
이번에는 TypeScript 설정 파일이 필요하다.
두 가지 방식이 있는데,
- 직접
tsconfig.json
파일을 생성한다. tsc --init
명령을 통해서tsconfig.json
파일을 생성한다.
여기서 tsconfig.json
파일이 필요한 이유가 있는데,
이는 webpack
에서 사용될 ts-loader
는 tsconfig.json
설정을 기반으로 로딩하기 때문이고,
jsx
, tsx
와 같은 외부 확장자를 ts
, js
파일과 상호작용 할 수 있게 함에 있고,
작성된 TypeScript 파일이 어떤 환경을 위해 컴파일 될 지 결정되야 하기 때문이다.
(Ex - 크로스플랫폼?, 웹?, 어플리케이션?, 단순 모듈?, 등등)
또한, tsconfig.json
파일에서는 현재 JavaScript 정식 스펙에 포함되지 않은 기능들을 사용할 수도 있게 해 준다.
개발 과정 중에 유틸과 컴포넌트가 많아짐에 따라 폴더의 깊이가 깊어지면, 특정 컴포넌트 혹은 유틸을 불러 올 때 상대경로 문자열이 복잡해진다.
이를 paths
옵션을 통해 특정 경로를 매핑 할 수도 있다.
다시 돌아와서, tsconfig.json
설정 파일이 JS 로 컴파일 시 많은 역할을 해 준다는 것은 알겠다.
그런데, 이러한 역할들을 어떻게 일일히 기억 할 것인가?
맞다!
따라서, 나는 기존의 라이브러리, 혹은 프레임워크 프레임을 통해 npm 프로젝트를 시작하는 것이 아니라,
직접 커스텀하여 시작하는 경우, tsc --init
명령어를 통해 tsconfig.json
파일을 생성하는 것을 권장한다.
➜ webpack-demo-ts tsc --init
Created a new tsconfig.json with:
TS
target: es2016
module: commonjs
strict: true
esModuleInterop: true
skipLibCheck: true
forceConsistentCasingInFileNames: true
You can learn more at https://aka.ms/tsconfig
위의 명령 결과가 말해주는 것은, tsconfig.json
에 이 옵션들이 들어갔다는 것이다.
tsconfig.json
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite
// ...
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
//.....
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
// ......
}
}
너무나 주석이 많은 파일에 당황하겠지만,
해당 타입스크립트 프로젝트를 진행함에 있어 특정 옵션을 사용 할 때가 되면,
이러한 설명들이 정말 친절했구나 라는 것을 알게 된다.
위의 파일은 주석을 100 줄 정도는 없앤 결과이다.
사실, 이 tsconfig.json
의 옵션에 대해서는 따로 공부 해야 할 정도로 조금 양이 많다.
사실, TypeScript 빌드에서 가장 중요한 것은 module
, target
이다.
그 전에 tsc --init
으로 생겨난 tsconfig.json
의 기본 옵션의 의미에 대해서 알아보자 :
1. target
타입스크립트 파일들은 결국은 자바스크립트 파일로 컴파일 된다.
그런데, 자바스크립트 파일은 진화를 거쳐 지원하는 버전이 서로 다르다.
그 중, es5
, es6
, es2021
는 최종 컴파일 결과물이 실행될 환경과 기기에 맞춘 결과이다.
이는 버전이 업그레이드 됨에 따라, 우리가 알고 있던 기능이 추가되었기 때문이다.
아주 대표적으로, async
, await
과 같은 키워드가 있다.
이러한 구문은 es6
부터 지원되며, 만약 그 이하의 버전을 요구한다면 es5
를 사용한다.
이렇게 될 경우, 비동기 흐름문은 Polyfill 이라는 또 다른 컴파일 과정을 통해
동일한 역할을 하는 코드를 생산한다.
2. module
모듈은 현재 작성할 개발 코드가 "어떤 버전의 모듈을 사용할 것이냐"는 것이다.
여기서 작성되는 버전명 또한 위와 굉장히 유사하긴 한데, 웹 개발 템플릿 명령어로 생성했다면,
대부분 esnext
일 것이다.
그 이유가, 웹 템플릿 프로그램은 대부분 최신 트렌드를 따라가기에 모두 지원한다는 것도 있지만,
webpack
이나, webpack
과 같은 번들러와 호환되기 위해서는 최신 버전을 사용해야 한다.
또한, 이는 출력물이 어떤 역할을 하느냐에 따라 달라지는데,
WAS (백엔드) 역할을 할 경우, 모듈을 동기적으로 가져와야 하기에 require
가 되어야 하며,
웹 브라우저에 보여지는 사이트를 개발 할 경우, 브라우저에 모듈을 비동기적으로 가져오기 때문에 import
를 사용한다.
esnext
, commonjs
에 따라 출력물에 import
냐, require
인지, 달라진다.
하지만, 웹 개발 시 es?????
모듈을 사용한다.
3. strict
이는 정해진 타입을 엄격히 지키는지 확인하는 옵션으로,
타입스크립트를 제대로 사용한다면 true
가 사용된다.
4. esModuleInterop
이는 TypeScript 에서 사용되는 import
문 외에도
require
문을 사용하여 불러와야 하는 동기적 모듈이 허용되는지에 대한 옵션이다.
5. skipLibCheck
우리는 기존에 JavaScript 환경에서 사용하던 라이브러리들을 TypeScript 에서도 사용하는데,
타입 체크를 위해 @types/????
처럼 새롭게 개발 의존성을 설치하게 된다.
따라서 대부분의 NPM 모듈은 타입 체킹을 위한 모듈 @types/<기존 모듈 이름>
을 지원하는데,
이 프로젝트에 해당하는 node_modules
라이브러리의 타입 체킹 라이브러리도 확인을 해야 하냐는 것이다.
이를 true
로 선언하는 것이 보통인데,
만약 false
로 설정하게 되면, 수많은 라이브러리들이 서로 다르게 타입을 선언을 하는 경우가 존재하기 때문에,
굉장히 골머리를 썩힐 수 있다. 따라서, true
로 설정하여 라이브러리까지 타입 체킹을 하지 않는다.
6. forceConsistentCasingInFileNames
이 옵션은 특정 파일을 import
할 때, 대소문자를 엄격히 지키게 만드냐에 대한 옵션이다.
나는 보통 특정 모듈이나 라이브러리를 불러 올 때 대소문자를 지켜서 잘 불러오기 때문에,
true
로 놔두어도 되고, 중복 이름을 가진 파일을 대소문자로 나누지 않는다고 하면,
false
로 설정해도 된다.
자! 위의 설명을 보았다면, tsconfig.json
에서 추구하는 방향을 어느정도 알게 되었을 것이다.
그렇다면, 공식문서에서 제공하는 tsconfig.json
에 대한 내용을 살펴보자 :
{
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"module": "es6",
"target": "es5",
"jsx": "react",
"allowJs": true,
"moduleResolution": "node"
}
}
위의 설정을 그대로 tsconfig.json
에 붙여넣어 주자.
자.. 설명하지 않은 옵션을 설명하고 넘어 가 보자 :
1. outDir
프로젝트에서 컴파일되는 모든 ts
종류의 파일의 결과물을 어디에 배치 할 것이냐 묻는 것이다.
우리는 dist
라는 폴더에 출력물을 관리했으므로, outDir
이 ./dist
이다.
2. noImplicitAny
이는 타입스크립트를 제대로 사용하기 위해 필요한 옵션이기도 하다.
null, undefined 도 타입이 존재하는데, 굳이 특정 객체나 타입을 any
로 지정하여,
추후 이 데이터를 사용 할 사람에게 혼란을 줄 필요가 없다.
모든 데이터에 const test : any
이러한 형식으로 자유를 풀어주지 않는다.
3. jsx
이는 jsx
, tsx
파일에서 React 를 사용하기 위해 선언되는 옵션이다.
여기서 jsx
나, react-jsx
를 선택 할 수 있는데,
React 17 버전 이후로부터는 react-jsx
옵션을 통해,
하나의 파일마다 import React from "react"
할 필요 없이, 자동 임포트 해 준다.
4. allowJs
이는 타입스크립트 파일 외에도 일반 JavaScript 파일을 사용하는 것을 허용한다는 의미이다.
5. moduleResolution
우리가 import path from "path"
했던 것 처럼,
node
로 설정함으로서 기본 임포트 시 node_modules
에서 가져오는 것으로 알겠다는 의미이다.
오랜만에 옵션의 의미와 사용법에 대해서 다루니까 머리가 아프다.
사용하기 편리한 JS 가 오히려 CS 지식을 더 필요로 하는 것 같다는 생각이 든다.
TypeScript 버전의 webpack.config.js
기존에 우리가 진행했던 JavaScript + Webpack 프로젝트와는 설정이 조금 다르다.
이는 Webpack 이 tsconfig.json
을 같이 고려하면서 번들링하기 때문인데,
이 과정에서 새로운 확장자들이 고려되기 때문에 그렇다.
공식 문서 webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry : "./src/index.ts",
module : {
rules : [
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules/
},
]
},
resolve : {
extensions : ["tsx", "ts", "js"]
},
output : {
filename : "[name].bundle.js",
path : path.resolve(__dirname, "dist")
},
plugins: [
new HtmlWebpackPlugin()
],
}
일단, css-loader
npm 의존성에 대한 내용을 적지 않았는데,
이는 또 다른 모듈이 설치되어야 해서, 나중에 기입 후 설명 할 예정이다.
1. exclude 옵션은 무엇인가?
이건 정말 모르겠어서 gpt 에게 물어보았는데,
요즘 NPM 모듈들은 기본적으로 번들러 과정을 생각하여 번들링 된 결과물을 제공한다고 한다.
따라서, 우리가 내부에서 사용하는 node_modules 의존성을 딱히 번들링하지 않기 위해서라고 한다.
2. resolve 옵션은 무엇인가?
이는 번들링이 어떤 확장자를 인식하는가에 대한 설명이다.
예를 들어, 우리가 import component from "./Component";
처럼 가져오는데,
여기엔 확장자가 빠져 있다.
여기서 빠져 있는 확장자를 어떠한 확장자들로 자동으로 인식 할 것인가에 대한 내용이다.
3. /.tsx$/
는 무엇을 의미하는가?
Regex (Regular Expression - 정규표현식) 에서 사용하는 문법인데,
?
바로 앞에 붙은 문자가 있을 수도, 없을 수도 있다는 것이다.
즉, /\.ts$/
, /\.tsx$/
둘 다 의미하기에, .ts
, tsx
확장자를 전부 인식하여 로드한다.
코드를 작성 해 보자!
./src/index.ts
import * as _ from "lodash";
function testComponent(strArr : string[]) {
const element = document.createElement("div");
element.innerHTML = _.join(strArr, ' ');
return element;
}
const arr: string[] = ["Hello!", "Webpack Plus TypeScript!"];
document.body.appendChild(testComponent(arr));
그리고, 우리가 이전에 package.json
에 작성했던 스크립트를 넣는다.
{
// ....
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
// .....
}
이후 npm 명령어로 webpack-cli
를 실행 해 주자 :
➜ webpack-demo-ts $ npm run build
> webpack-demo-ts@1.0.0 build
> webpack
asset main.bundle.js 69.5 KiB [emitted] [minimized] (name: main) 1 related asset
asset index.html 221 bytes [emitted]
runtime modules 1010 bytes 5 modules
cacheable modules 532 KiB
./src/index.ts 275 bytes [built] [code generated]
./node_modules/lodash/lodash.js 531 KiB [built] [code generated]
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
webpack 5.99.8 compiled with 1 warning in 3624 ms
그리고, ./dist/index.html
파일을 열어보면,
정상적으로 작동하는 것을 볼 수 있다.
css 를 런타임이 아닌 파일로 따로 출력하기
사실, webpack 에 존재하는 옵션으로 최대한 해결 할 수 있으면 하려고 했는데,
아무리 생각해도 따로 개발용 플러그인을 설치해서 이를 적용하는 것이 훨씬 효율적이었다.
이유는, 플러그인 자체가 css, scss, sass 를 위한 모듈이었으며,
나중에 스타일링을 위해 서로 @url()
을 사용했을 때도, 의존성을 분석 해 주기 때문이었다.
mini-css-extract-plugin
이라는 npm 개발 의존성을 설치 해 주면 된다.
$ npm i -D mini-css-extract-plugin
이후, 웹팩 설정 파일을 변경한다.
webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
+ const MiniCssExtractPlugin = require("mini-css-extract-plugin")
module.exports = {
entry : "./src/index.ts",
module : {
rules : [
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules/
},
+ {
+ test : /\.(sa|sc|c)ss$/,
+ use: [
+ MiniCssExtractPlugin.loader,
+ "css-loader"
+ ]
+ }
]
},
resolve : {
extensions : ["tsx", "ts", "js"]
},
output : {
filename : "[name].bundle.js",
- path : path.resolve(__dirname, "dist")
+ path : path.resolve(__dirname, "dist/static"),
+ clean : true
},
plugins: [
new HtmlWebpackPlugin(),
+ new MiniCssExtractPlugin({
+ filename : "[name].[contenthash:8].css",
+ })
],
}
정규표현식을 위처럼 사용하면 scss
, sass
, css
모두 파싱이 가능하다.
경로를 바꾼 이유는, React 의 기본 js, css 파일 출력물이
dist/static
에 출력되기에 한번 해 보았다.
그리고, ./src/styles.css
파일을 생성하여 태그를 작성 해 보자.
./src/styles.css
.container {
color : skyblue;
}
이미 작성되어 있던 src/index.ts
에 코드를 조금 추가한다. (클래스 속성 추가)
import * as _ from "lodash";
+ import "./styles.css";
function testComponent(strArr : string[]) {
const element = document.createElement("div");
element.innerHTML = _.join(strArr, ' ');
+ element.className = "container"
return element;
}
const arr: string[] = ["Hello!", "Webpack Plus TypeScript!"];
document.body.appendChild(testComponent(arr));
➜ webpack-demo-ts npm run build
> webpack-demo-ts@1.0.0 build
> webpack
asset main.bundle.js 69.5 KiB [compared for emit] [minimized] (name: main) 1 related asset
asset index.html 269 bytes [emitted]
asset main.32ab1364.css 37 bytes [emitted] [immutable] (name: main)
Entrypoint main 69.6 KiB = main.bundle.js 69.5 KiB main.32ab1364.css 37 bytes
runtime modules 1.9 KiB 9 modules
orphan modules 2.81 KiB [orphan] 4 modules
cacheable modules 532 KiB (javascript) 36 bytes (css/mini-extract)
./src/index.ts 335 bytes [built] [code generated]
./node_modules/lodash/lodash.js 531 KiB [built] [code generated]
css ./node_modules/css-loader/dist/cjs.js!./src/styles.css 36 bytes [built] [code generated]
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
webpack 5.99.8 compiled with 1 warning in 3507 ms
결과물 :
경로를 바꾸고, clean
옵션을 넣었음에도, dist/index.html
, dist/...
가 사라지지 않고,
dist/static/...
에 파일들이 생성되었다.
따라서, dist.index.html
, dist/...
를 삭제한다.
이후 dist
형태 :
➜ dist tree
.
└── static
├── index.html
├── main.32ab1364.css
├── main.bundle.js
└── main.bundle.js.LICENSE.txt
2 directories, 4 files
index.html
:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Webpack App</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<script defer="defer" src="main.bundle.js"></script>
<link href="main.32ab1364.css" rel="stylesheet" />
</head>
<body></body>
</html>
HtmlWebpackPlugin
이 index.html
에 정상적으로 의존성 파일들을 추가해 주는 것을 볼 수 있다.
이 글을 작성하며 배운 것 정리
솔직히 말해서, 이번 글은 역대급으로 힘들고 지치는 주제였다.
왜 이렇게 지칠까 생각 해 보니, 현재 내가 추구하고 있는 공부 방식과,
webpack 을 쉽게 익히기 위한 방식이 상충되기 때문이었다.
나는 현재 React 라는 풍부한 웹 개발 라이브러리를 사용하기 위해,
중간 단계에서 사용되는 과정과 개발 도구들을 공부한다.
"이것이 왜 필요하고, 어떻게 사용해야 하는가?" 를 중심으로 공부한다.
Webpack 이라는 도구는 React 에 있어 거의 대부분 사용되어야 하는 도구인데,
따라서 "이것이 왜 필요한가?" 에 대한 부분은 명확히 이해했다.
웹팩은 다양한 확장자 파일을 인식하고 파싱하며, 웹에 소스를 넣을 때 의존성을 고려하기 때문이다.
또한, 코드 난독화를 통해 인간이 일반적으로 읽을 수 없게 만드는 것에 대해서도 감탄을 지어냈다.
개발 과정에서 jsx
, tsx
, scss
, sass
가 사용되는 것은 알았지만,
이것이 어떻게 컴파일 되는지에 대한 과정 또한 알았다. React 템플릿을 통해 개발한다면,
웹팩 번들러는 필수불가결한 프로그램이다.
그러나,
"어떻게 사용해야 하는가?" 에 대한 것이 너무 난해했다.
js, ts 실행 파일을 통해 webpack 설정을 한다는 것은 충분히 이해했지만,
여기서 사용되는 방법론 카테고리가 진짜 너무나도 많다고 느꼈다.
특히, webpack 이라는 프로그램에서 사용되는 옵션명,
그리고 옵션에 지정해야 하는 또 다른 옵션, 그리고 사용법이 제각각 달랐다.
또한, 파싱을 위해 loader 를 추가로 개발 의존성으로 설치해야 하며,
이 로더에 들어가야 할 옵션 또한 알고 있어야 했다.
게다가 정규표현식인 Regex 는 덤이었다.
하지만,
그럼에도 불구하고, 이 방식이 번들러에 있어서는 최선이라는 것이다.
무엇이 최선이냐고 한다면, "일반 개발자들이 건드릴 일 없이 번들링 하게 해 준다" 에 가깝다고 생각한다.
이를 충족시켜 주기 위해, "번들링" 이라는 것을 쉽게 하기 위하여 정말 많은 방법론을 적용했다.
결과적으로 말하자면,
웹 개발은 단순한 html, css, js 파일들만으로 이루어지지 않는 세상에서,
다양한 확장자와 프로그램을 연동하기 위해 번들러가 반드시 필요한 세상이다.
특히, js 파일과 css 파일 간의 의존성 생성도 하나의 이유이기도 하지만,
특히 프로젝트가 복잡해짐에 따라 서로 얽기고 섥힌 js 끼리의 의존성을 풀어주는 것이
번들러의 최대 장점이 아닐까 생각한다.
참고 사이트
TOAST UI 글 (번들러)
https://ui.toast.com/fe-guide/ko_BUNDLER
Webpack 공식 사이트
Github Gist (JavaScript Bundler 에 대해 이해하기)
https://gist.github.com/jeffminsungkim/9cd5c592dfe39a5eaae93ffcc1818cef
DEV 글 (The What, Why and How of JavaScript Bundlers)
https://dev.to/sayanide/the-what-why-and-how-of-javascript-bundlers-4po9
TOAST UI (의존성 관리)
'Node.js > 잡다 지식' 카테고리의 다른 글
Yarn 과 pnpm 패키지 매니저는 무엇일까? (3) | 2025.07.18 |
---|---|
.http 파일과 httpyac (1) | 2025.05.07 |
Jest 와 유닛 테스트 (4) | 2025.05.05 |
비밀번호는 왜 해싱할까? - With Node.js (0) | 2025.04.15 |
NestJS 의 Interceptor 는 무엇일까? - (NestInterceptor) (0) | 2025.04.12 |