개발 코드가 변경될 때마다 반영을 위해 서버를 재시작해주는 것은 매우 비효율적이고 귀찮은 일인데, 이러한 부분 예방(?)을 위해서...
nodemon
을 이용하여 적용할 수도 있지만, 다음과 같은 이유로 webpack
을 사용하여 개발 환경을 재구성(?) 하였다.
- 다앙햔 브라우저 지원
- 번들링을 좀 더 원활하게 하기 위해
- HMR
- webpack5를 사용해보기 위해
Configure Webpack
먼저 가장 기본적인 webpack을 이용해서 다음과 같은 작업을 수행하도록 구현해보려고 한다.
- typescript compile
- express와 webpack 연동
Installation
webpack 관련 패키지들을 설치한다.
yarn add -D webpack webpack-cli
그리고 typescript를 이용하여 작성이 되어있으므로 babel-loader를 이용하여 트랜스파일을 하도록 설정한다.
- babel-loader : 트랜스파일링 하기위한 파일 loader
- @babel/core : babel config 파일을 읽어 해당 파일을 기반으로 트랜스파일링을 수행하기 위한 모듈
- @babel/preset-env : 트랜스파일링 기본 preset 모듈
- @babel/preset-typescript : 타입스크립트를 트랜스파일링 하기 위한 모듈
- core-js@3 : polyfill 설정.
- 런타임에서 실행된다.
- 기존의 @babel/polyfil은 deprecated 되었음
yarn add -D babel-loader @babel/core @babel/preset-env @babel/preset-typescript
yarn add core-js@3
babel.config.js
module.exports = (api) => {
api.cache(true);
return {
presets: [
[
'@babel/preset-env',
{
targets: '> 0.5%, not dead',
useBuiltIns: 'usage' /* 필요한 모듈만 가져오도록 설정 */,
corejs: {
/* configuration about polyfill */
version: 3,
proposals: true,
},
},
],
'@babel/preset-typescript',
],
plugins: [],
};
};
Modify tsconfig.js
babel을 이용해서는 트랜스파일링을 하고, tsc를 이용하여 타입을 검사하기 위해 기존 작성한 tsconfig.js
에 아래와 같은 설정을 적용해준다.
// tsconfig.js
"compilerOptions": {
//...SKIP
// tsc를 사용해 .js 파일이 아닌 .d.ts 파일이 생성되었는지 확인합니다.
"declaration": true,
"emitDeclarationOnly": true,
// Babel이 TypeScript 프로젝트의 파일을 안전하게 트랜스파일할 수 있는지 확인합니다.
"isolatedModules": true
//...SKIP
}
webpack.config.js
webpack.config.js
파일을 생성하고 아래와 같이 정의한다.
const webpack = require('webpack');
const path = require('path');
module.exports = {
mode: 'development',
target: 'web',
devtool: 'eval-cheap-source-map',
entry: {
app: path.resolve(__dirname, './src/frontend/static/js/index.ts'),
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[contenthash].js',
publicPath: '/',
// clear output directory before exporting bundle files
// don't need 'clean-webpack-plugin' anymore
clean: true,
},
module: {
rules: [
{
test: /\.[tj]sx?$/,
exclude: /node_modules/,
use: 'babel-loader',
},
],
},
plugins: [],
optimization: {},
resolve: {
modules: ['node_modules'],
extensions: ['.js', 'ts', '.json', '.jsx', '.tsx', '.css'],
},
};
Build Test
webpack 실행을 위한 script를 추가해주고, 바벨을 이용하여 하위 브라우저도 호환이 가능하도록 트랜스파일링된 번들 파일을 이용할 것이므로 package.json
에 추가해주었던 `"type": "module" 설정을 삭제해준다.
{
//...SKIP
"scripts": {
"build": "webpck"
}
//...SKIP
}
yarn build
를 수행하여 정상적으로 빌드가 되는 지 확인해본다.
❯ yarn build
(node:2041) ExperimentalWarning: Custom ESM Loaders is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
asset app.bundle.js 1.71 KiB [compared for emit] (name: app)
./src/frontend/static/js/index.ts 28 bytes [built] [code generated]
webpack 5.72.1 compiled successfully in 390 ms
빌드가 정상적으로 수행이 되면 output
에 정의한 경로에 번들링된 빌드 산출물이 생성된다.
❯ ls -al dist
total 8
drwxr-xr-x 3 user staff 96 5 11 22:33 .
drwxr-xr-x 23 user staff 736 5 11 22:34 ..
-rw-r--r-- 1 user staff 1751 5 11 22:33 app.bundle.js
빌드된 산출물을 이용하여 웹화면이 정상적으로 호출이 되는 지 확인하기 위해 src/frontend/index.html
을 열어서 script 파일의 경로를 수정한다.
<!-- index.html -->
<script type="module" src="../../dist/app.bundle.js"></script>
그리고 console 창을 열지 않고도 index.ts
에 작성된 스크립트가 정상 동작하는 지 확인해보기 위해 아래와 같이 작성하였다.
// index.ts
document.querySelector('#app').innerHTML = '<h1>Welcome!! Hello! World!!!</h1>';
다시 빌드를 한 뒤에 IDE에서 src/frontend/index.html
파일을 Live Server 기능을 이용하여 실행하여 확인해본다.
정상적으로 빌드가 되고 실행이 되었다면 아래와 같은 화면을 확인해볼 수 있다.
webpack을 이용하여 번들링 되도록 구성하였으니, 폴더 구조를 수정하였다.
Add CSS Loader
index.html
에 css 파일을 직접 정의하지 않고, js 스크립트 파일에서 직접 css 파일을 import하여 사용할 수 있도록 하기 위해 webpack에 설정을 추가하였다.
Installation
- css-loader : js 파일에서 css 파일을 읽어온다.
- style-loader : css-loader를 이용해 웹팩 의존성 트리에 추가된 string 값들을 DOM에 로 넣어준다.
css-loader로 읽어온 css 파일을 주입하기 위한 방법으로는 style-loader
또는 mini-css-extract-plugin
를 사용할 수 있는데, 각 플러그인에 대한 동작 차이는 다음과 같은 글들을 참고하면 좋을 것 같다.
- CSS 파일 개별 추출
- style-loader와 MiniCssExtractPlugin은 각각 언제 사용해야 할까?
- [ VanillaJS ] webpack5 - MiniCssExtractPlugin
- MINI CSS EXTRACTION PLUGIN
# webpack
yarn add -D css-loader style-loader
webpack.config.js
아래와 같이 css 모듈에 대한 설정을 추가해준다.
const webpack = require('webpack');
const path = require('path');
module.exports = {
//...SKIP
module: {
//...SKIP
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
//...SKIP
};
Modify Code
기존 작성된 코드에서 다음과 같은 부분들을 수정해준다.
index.html
에서 다음의 코드를 제거한다
<link rel="stylesheet" href="/static/css/index.css" />
index.css
파일을src/styles
폴더를 생성 후 해당 경로로 이동해주었다.index.js
를src/frontend
하위로 이동하고 css 파일 주입을 위한 구문을 추가해준다.
import './styles/index.css';
지금까지 수정된 프로젝트의 구조는 아래와 같다.
src/frontend
├── index.html
├── index.ts
└── styles
└── index.css
Build Test
build를 수행해서 Live Server를 통하여 확인해보면 css 설정이 적용된 것을 확인할 수 있다.
번들링된 entry 파일을 동적으로 삽입하기
빌드된 결과를 자동으로 index.html
파일에 주입해주도록 설정한다.
- html-webpack-plugin - entry를 html에 동적 삽입과 html 결과물을 내보내기 위한 플러그인
yarn add -D html-webpack-plugin
Template html
기존에 작성된 index.html
파일을 <project_root_dir>/public
폴더를 생성하여 해당 폴더로 이동 후, 아래와 같이 수정했다.
<!-- ./public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Single Page App (Vanilla JS)</title>
</head>
<body>
<div id="app"></div>
<!-- will be injected -->
</body>
</html>
webpack.config.js
html-webpack-plugin 설정을 추가해준다.
// webpack.config.js
//...SKIP
const HtmlWebpackPlugin = require("html-webpack-plugin");
//...SKIP
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "public/index.html"), // 읽어올 템플릿 경로 지정
publicPath: "",
}),
],
//...SKIP
webpack dev server
express를 이용하여 제공하던 서버를 webpack dev server를 이용하도록 설정한다. webpack dev server를 이용하게 되면 수정된 코드가 바로 컴파일 되어 반영될 수 있도록 하여 좀 더 원활한 개발 환경 설정이 가능하다.
Installation
webpack-dev-server 패키지를 설치한다.
yarn add -D webpack-dev-server
webpack.config.js
webpack 설정 파일에 devServer
설정을 추가해준다.
// webpack.config.js
//...SKIP
devServer: {
static: {
directory: path.join(__dirname, "public"),
},
compress: true,
open: true,
hot: true,
port: 8000,
},
};
package.json
에 아래와 같이 스크립트를 추가해준다.
"dev": "webpack serve",
아래와 같이 추가한 스크립트를 실행하면 지정한 port를 이용하여 개발 서버가 기동되고, 웹브라우저를 통하여 확인할 수 있다.
❯ yarn dev 22:22:08
(node:90683) ExperimentalWarning: Custom ESM Loaders is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
<i> [webpack-dev-server] Project is running at:
<i> [webpack-dev-server] Loopback: http://localhost:8000/
<i> [webpack-dev-server] On Your Network (IPv4): http://172.30.1.57:8000/
<i> [webpack-dev-server] On Your Network (IPv6): http://[fe80::1]:8000/
<i> [webpack-dev-server] Content not from webpack is served from '/Users/gloria/Documents/mio/cocoa-clone/public' directory
asset app.4ab386a129124154c594.js 585 KiB [emitted] [immutable] (name: app)
//...SKIP
이제 기존에 작성했던 아래의 파일들과 패키지는 필요없으니 삭제해준다.
- <project_root_dir>/src/server.ts
yarn remove express
그리고 폴더 구조를 아래와 같이 다시 재구성해주었다.
폴더 구조 수정에 따라 webpack.config.js
에서도 맞춰 수정해주어야한다.
.
├── README.md
├── babel.config.js
├── package.json
├── public
│ └── index.html
├── src
│ ├── index.ts
│ └── styles
│ └── index.css
├── tsconfig.json
├── webpack.config.js
└── yarn.lock
그리고 package.json의 스크립트도 아래와 같이 수정해주었다.
"dev": "webpack serve --mode=development",
"build": "webpack --mode=production"
Additional Webpack Plugins
ProgressPlugin
빌드 진행 상태를 progress로 표현하여 준다.
// webpack.config.js
//...SKIP
const childProcess = require("child_process");
//...SKIP
plugins: [
new webpack.ProgressPlugin({
// handler(percentage, message, ...args) {
// // custom logic
// },
}),
//...SKIP
],
//...SKIP
BannerPlugin
번들된 결과물에 빌드 정보나 커밋 버전등의 내용을 추가 할 때 사용하는 플러그인으로, webpack에서 기본 제공을 해주므로 별도 설치할 필요는 없다.
// webpack.config.js
//...SKIP
const childProcess = require("child_process");
//...SKIP
plugins: [
new webpack.BannerPlugin({
banner:`
Build Data: ${new Date().toLocaleString()}
Commit Version: ${childProcess.execSync('git rev-parse --short HEAD')}
Author: ${childProcess.execSync('git config user.name')}
`
}),
//...SKIP
],
//...SKIP