개발 환경 설정 - pozafly/surviving-developer GitHub Wiki
개발 환경 설정은 크게 2가지로 나뉩니다. Webpack 설정과 Babel 설정입니다.
이는 이슈 #5에 따른 설정입니다. feature/2-webpack-babel-setting브랜치에서 작업 되었습니다.
요약
-
Entry, Output 경로 설정
-
모듈 Loader 설정
- style-sheet
- assets
- babel
-
Plugin 설정
- HTML 파일 template 설정
- CSS 파일 외부 생성 설정
- 배포를 위한 난독화 및 압축 플러그인 설정
-
Source Map 설정
-
Optimization 설정
-
Webpack 설정 파일 모드 별 분리
- .env 파일 모드 별 분리
-
Babel IE 11 기준 설정
-
postcss의 autoprefixer 설정
- webpack.dev.js : 개발 환경에서 사용할 config 파일
- webpack.prod.js : 배포 환경에서 사용할 config 파일
- webpack.common.js : 개발, 배포 환경에서 공통으로 사용할 config 파일
- webpack.config.js : common 파일과 환경별 파일을 조합해 최종 설정 파일 추출
// webpack.config.js
const { merge } = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const productionConfig = require('./webpack.prod.js');
const developmentConfig = require('./webpack.dev.js');
module.exports = (env, args) => {
switch (args.mode) {
case 'development':
return merge(commonConfig, developmentConfig);
case 'production':
return merge(commonConfig, productionConfig);
default:
throw new Error('No matching configuration was found!');
}
};
webpack-merge 패키지를 사용해 환경 별로 파일을 나누어 진행했습니다.
// package.json
"scripts": {
"build": "cross-env webpack --progress --mode production",
"dev": "cross-env webpack serve --open --progress --mode development",
}
- cross-env를 사용해 OS에 관계없이 명령어를 실행할 수 있습니다.
- package.json에서
--mode
옵션을 통해 배포 모드와 개발 모드를 구분할 수 있습니다. 이는process.env.NODE_ENV
를 사용하기 위함입니다.
// webpack.config.js
module.exports = {
(...)
entry: {
main: './src/index.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name]_[chunkhash:8].js',
clean: true,
},
}
- entry : JavaScript 파일 진입점입니다. 이곳의
main
은 ouput의 [name]과 매칭됩니다. 여러 개의 entry가 올 수 있습니다.
- path : build될 결과물이 나올 디렉토리 경로와 이름을 지정해주었습니다.
- filename : JavaScript 청크 파일의 이름입니다.
- [name] : entry 파일 이름입니다.
- [chunkhash:8] : entry 파일 기준으로 연관된 JavaScript 파일이 변경되었을 경우만 hash를 변경합니다. 8자로 제한하였습니다.
- clean : build시 생성될 폴더를 제거하고 새로운 빌드 폴더를 생성해줍니다.
clean-webpack-plugin
을 의존하고 있습니다.
loader는 모듈(images, fonts, css, ...)을 JavaScript 파일 내부로 가져올 수 있도록 합니다. 이는 JavaScript를 통해 모듈을 가공하거나 필요한 처리를 하기 위해서입니다.
module: {
(...)
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: 'style-loader',
options: { injectType: 'singletonStyleTag' },
}
'css-loader',
'postcss-loader',
'sass-loader',
],
},
{
test: /\.(png|svg|jpe?g|gif|webp)$/i,
// 기본적으로 8kb 이하라면 url-loader로, 이상이면 file-loader로 동작
type: 'asset',
generator: {
filename: 'assets/images/[name]_[contenthash:8][ext]',
},
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
},
},
},
],
},
- style-loader : html의 head 태그 내부에
<style>
태그로 css 파일을 주입합니다. (빠르게 로드하므로 개발모드에서만 사용)- 파일 별 여러 개의 style 태그를 넣기보다 하나의 style 태그에 넣도록 options를 조정해주었습니다.
- MiniCssExtractPlugin : css 파일을 외부 시트 파일로 만들어줍니다. 여러 entry가 생겼을 경우를 대비하였습니다. (배포 모드에서만 사용)
- css-loader : JavaScript 파일 내부의 import문으로 css 파일을 불러옵니다.
- postcss-loader : css파일을 후처리합니다.
autoprefixer
를 사용하기 위해 load 했습니다. - sass-loader : sass 또는 scss 파일을 JavaScript 파일로 불러오며, css 파일로 전처리 합니다.
※ loader의 use 배열 뒤에 정의된 loader부터 적용 됩니다.
- webpack5 부터는 url-loader와 file-loader를 사용하지 않습니다.
- webpack 자체에서 지원하기 때문입니다.
-
type: 'asset'
을 명시해주면, 기본적으로 8kb 이하라면 url-loader로, 이상이면 file-loader로 동작합니다. 참고1, 참고2- url-loader : 이미지를 base64로 인코딩하여 img 태그의 src 속성에 넣어줌.
- file-loader : 파일 자체의 형태로 나타나며 output의
assetModuleFilename
경로에 hash와 함께 저장됨.
- babel-loader : babel을 설정하기 위한 로더입니다. JavaScript 파일을 브라우저 상황에 맞게 트랜스파일링 합니다.
- options-cacheDirectory를 통해
node_modules/.cache/babel-loader
디렉토리에 캐시되도록 설정했습니다.
module: {
(...)
plugins: [
new HtmlWebpackPlugin({
template: 'index.html',
}),
new MiniCssExtractPlugin({
filename: '[name]_[contenthash:8].css',
}),
new Dotenv({ path: './env/.env.production' }),
]
},
- HtmlWebpackPlugin : output에 번들링 된 모듈이 링크된 HTML 파일을 생성해줍니다. template에 지정된 html 기준으로 생성해주도록 설정했습니다.
- MiniCssExtractPlugin : 배포 모드에서 사용됩니다. MiniCssExtractPlugin Loader를 사용하기 위해 plugin도 적어주었습니다.
-
[name]_[contenthash:8].css
: contenthash는 변경된 파일의 hash 만 변경시켜준다는 의미입니다.
-
module: {
(...)
devtool: devMode ? 'inline-source-map' : 'source-map', // 디버깅을 위한 소스맵
},
- inline-source-map :
.map
파일을 생성하지 않고, 소스맵을 DataUrl로 번들에 추가합니다. 웹팩추천 소스맵입니다. 개발 모드에만 사용합니다. - source-map :
.map
파일이 생성되며 생략없는 모든 소스맵이 포함됩니다. 배포 모드에만 사용합니다. - 참고
module: {
(...)
optimization: {
minimizer: [new CssMinimizerPlugin(), new TerserPlugin()],
runtimeChunk: 'single',
},
},
- CssMinimizerPlugin : css를 한줄로 압축합니다. MiniCssExtractPlugin와 함께 사용해야 합니다.
- TerserPlugin : 난독화(mangle) 및 소스코드 압축(compressor) 플러그인 입니다.
- runtimeChunk : 모듈 변들링 할 때 생성되는 코드를 실행하는데 필요한 코드입니다. single은 현재 JavaScript 파일의 chunk가 1개이므로 runtime을 공유할 일이 없으므로 single로 주었습니다.
기존에는 babel polyfill(@babel/polyfill
)은 deprecated 되었습니다. 2가지 문제점이 있습니다.
- core-js/es6를 전역에서 import하기 때문에 사용하지 않는 polyfill도 번들 포함
- @babel/polyfill은 딱 한번만 import 해야 한다. 두 개 이상이 import 되면 오류 발생
전역 스코프를 오염시키는 문제가 있기 때문에 아래 두개의 패키지를 사용합니다.
- core-js -> ES6+ 코드를 ES5 코드로 변환하는 역할
- regenerator-runtime/runtime ->
async/await
를 포함한 제너레이터 함수를 폴리필
preset-env는 ECMAScript2015+를 변환할 때 사용합니다.
바벨 설정 파일입니다. 참고 - 카카오 babel 설정
// .babelrc
{
"presets": [
[
"@babel/preset-env",
{
"targets": "ie 11",
"useBuiltIns": "usage", // 폴리필 사용 방식 지정
"corejs": {
"version": 3 // 폴리필 버전 지정
}
}
]
]
}
※ useBuiltIns : 어떤 방식으로 폴리필을 사용할지 설정하는 옵션. [참고](https://so-so.dev/web/you-dont-know-polyfill/)
- false : 폴리필을 생성하지 않는다.
- entry : transpile 하는 시작점에 import된 core-js, regenerator-funtime 모듈을 babelrc에서 지정한 target에 맞게 변경한다.
- target이 구형 브라우저일 경우 과도한 polyfill이 추가되어 최신 브라우저에서도 큰 bundle이 생성된다.
- usage : 실제 코드에서 사용하는 polyfill만 import 하는 설정이다.
- 사용하는 code만 polyfill 대상으로 보기 때문에 node_modules의 dependency에서 polyfill이 적용되지 않은 코드가 있다면 에러가 발생할 수 있다.
※ 프로젝트에 사용한 방법은 usage, core-js 3
- 이번 프로젝트에서 종속성 설치가 지양되므로 usage로 설정했습니다.
- core-js는 ECMAScript 새로운 feature가 더 많이 지원되므로 3으로 설정했습니다.
만약 node_modules 안에 있는 패키지에서 usage를 사용할 때 오류가 난다면, babel-loader 설정에서 exclude
해주었던 설정을 아래와 같이 조금만 바꿔주면 됩니다.
//webpack.config.js
module.exports = {
...,
module: {
rules: [{
test: /\.(js)$/,
exclude: /node_modules\/(?!(패키지명|패키지명|패키지명))/, // here
use: {
loader: 'babel-loader'
},
}]}}
babel.config.js
는 공식 문서에 따르면 구성하는 api가 노출됩니다. 이런 방식은 캐싱과 관련 복잡성을 증가시키므로, .babelrc, 보다는 좋지않다고 합니다. (.babelrc, babel.config.json은 같음) 또한 .babelrc를 루트 디렉토리에 두면 더 확장성 있는 설정이 가능합니다. Storybook은 빌드시 루트 디렉토리의 babelrc를 찾아 적용할 수 있기 때문입니다. 링크1, 링크2
단, 웹팩 모드 별로 다르게 주고 싶다면 webpack의 babel-loader에 .babelrc 파일 내용을 직접 설정해주면 됩니다.
postcss는 autoprefixer를 사용하기 위해 설정했습니다. postcss.config.js 파일을 추가하여 설정해줄 수 있지만, 내용이 많지 않아 webpack.config.js의 postcss-loader에 설정하였습니다. postcssPresetEnv는 autoprefixer를 사용하기 위한 플러그인.
{
loader: 'postcss-loader',
options: {
postcssOptions: {
// autoprefix를 사용하기 위한 플러그인
plugins: [postcssPresetEnv()],
},
},
},
autoprefixer를 설정하기 위해서는 browserslist 설정을 추가로 해주어야 합니다. 이는 브라우저의 어느 수준까지 설정이 될 것인지를 설정하기 위함입니다. package.json 파일에 추가로 설정해주었습니다.
// package.json
"browserslist": {
"production": [
"IE 10"
],
"development": [
"last 1 chrome version",
"last 1 firefox version"
]
}
※ babel 설정 또한, browserslist를 참고 하는데요, .babelrc
파일에서 @babel/preset-env
이 있다면 .babelrc
파일이 우선권을 가져갑니다. 링크