바벨 사용법과 웹팩 통합
지난시간 복습
바벨의 플러그인들을 모아놓은 것이 프리셋
이번 시간엔 실제로 바벨을 사용하는 방법에 대해 살펴보도록 하겠다.
프리셋 사용하기
바벨은 프리셋을 잘 사용해야된다.
바벨은 목적에 따라 몇 가지 프리셋을 제공한다.
- preset-env
- preset-flow
- preset-react
- preset-typescript
preset-env는 ECMAScript2015+를 변환할 때 사용한다.
바벨 7 이전 버전에는 연도별로 각 프리셋을 제공했지만(babel-reset-es2015, babel-reset-es2016, babel-reset-es2017, babel-reset-lates) 지금은 env
하나로 합쳐졌다.
무척 마음에드는 부분이다.
preset-flow / preset-react / preset-typescript 는 flow, 리액트, 타입스크립트를 변환하기 위한 프리셋이다.(es5로 변환)
IE 지원을 위해 env 프리셋을 사용해보자.
npm i -D @babel/preset-env
// babel.config.js
module.exports = {
presets: [
'@babel/preset-env'
]
}
npx babel app.js
ES6+ 코드가 ES5 코드로 변환된 것을 볼 수 있다.
use strict
와 const
그리고 애로우 펑션이 변경되었다.
실무에선 preset을 바로 가져다쓴다.
우리가 만들었던 커스텀 플러그인, 커스텀 프리셋을 사용하진 않는다.
타겟 브라우저
좀 더 상세하게 preset-env 사용방법을 탐구해보겠다.
env 프리셋 설정과 폴리필
과거에 제공했던 연도별 프리셋을 사용해 본 경험이 있다면 까다롭고 헷갈리는 설정 때문에 애를 먹었을지도 모르겠다.
그에 비해env
프리셋은 무척 단순하고 직관적인 사용법을 제공한다.
우리 코드가 크롬 최신 버전(2019년 12월 기준)만 지원한다고 하자.
그렇다면 IE를 위한 코드 변환은 불필요하다.
target
옵션에 브라우저 버전명만 지정하면 env
프리셋은 이에 맞는 플러그인들을 찾아 최적의 코드를 출력해낸다.
예를 들어, 아래와 같이 설정할 수 있다.
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
chrome: '79', // 크롬 79까지 지원하는 코드를 만든다
}
}
]
]
}
우리 예제에도 설정해보자.
아까와는 다르게 use strict
는 붙었지만 const
와 애로우 펑션은 그대로이다.
caniuse.com에서 검색해보면 된다.
크롭 버전 79에선 const
를 사용할 수 있기 때문에 굳이 변환하지 않았다.
애로우 펑션도 지원하기 때문에 변환하지 않았다.
하지만 반대로 IE는 애로우 펑션을 지원하지 않는다.
그리고 const
키워드도 인식하지 못한다.
그렇기 때문에 크롬뿐만 아니라 IE도 지원을 해야되는 거라면 target
객체에 다음과 같이 작성한다.
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
chrome: '79',
ie: '11'
}
}
]
]
}
이 상태에서 변환작업을하면 바벨은 크롬 79 버전과 IE11 버전을 모두 지원하는 코드로 변환작업을 한다.
npx babel app.js
폴리필
이번에는 성격이 조금 다른 폴리필에 대해 살펴보도록 하겠다.
app.js
파일을 이번엔 Promise
객체를 사용해서 작성해보자.
// app.js
new Promise();
npx babel app.js
보시면 ES6 문법으로 정의되어있는 new Promise()
코드가 변환되지 않고 그대로 나온 것을 볼 수 있다.
크롬은 당연히 Promise
를 지원한다.
반면에 IE를 보면 Promise
를 지원하지 않는다.
그래서 위와 같은 결과가 나오면 IE에선 오류가 날 것이다.
플러그인이 Promise
를 ECMAScript5 버전으로 변환할 것으로 기대했는데 예상과 다르다.
바벨은 ECMAScript2015+를 ECMAScript5 버전으로 변환할 수 있는 것만 변환한다.
그렇지 못한 것들은 폴리필이라고 부르는 코드조각을 추가해서 해결한다.
가령 ECMAScript2015+의 블록 스코핑은 ECMAScript5의 함수 스코핑으로 대체할 수 있다.
화살표 함수도 일반 함수로 대체할 수 있다.
이런 것들은 바벨이 변환해서 ECMAScript5 버전으로 결과물을 만든다.
한편 Promise
는 ECMAScript5 버전으로 대체할 수 없다.
다만 ECMAScript5 버전으로 구현할 수는 있다.
- [core-js.promise][]{:target=”_blank”}
이런 폴리필을 제공하는 대표적인 라이브러리가 core-js 라이브러리이다.
뿐만 아니라 babel polyfill이란 것도 있다.
최근에는 core-js를 많이 사용하는 편이다.
@babel/preset-env
프리셋 옵션으론 target
뿐만아니라 polyfill
을 사용할지 말지도 정할 수 있다.
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
chrome: '79', // 크롬 79까지 지원하는 코드를 만든다
ie: '11'
},
useBuiltIns: 'usage', // entry, false
corejs: {
version: 2 // 최신버전 3
}
}
]
]
}
usage
, entry
, false
의 차이점은 잘 모르겠다.
결과물이 약간 다르긴 하지만 일단 기본값인 useage
로 작성했다.
그리고 corejs
버전을 명시한다.
만약에 polyfill
라이브러리를 corejs
로 사용할거면 이것의 버전은 몇으로 할것인지를 정하는 것이다.
여전히 new Promise()
프로미스 객체를 쓰고 있지만, 그 위에 아까와 다른 require
함수가 기록되었다.
core-js
라이브러리에 promise
폴리필이 있는 파일을 가져오는 코드가 추가되었다.
실제로 이 코드가 브라우저에서 안전하게 돌아가려면 core-js
가 먼저 로딩 되어있어야 하고, 그 다음에 우리가 번들한 코드가 로드되어야 한다.
웹팩으로 통합하는 방법
실무 환경에서는 바벨을 직접 사용하는 것보다는 웹팩으로 통합해서 사용하는 것이 일반적이다.
로더 형태로 제공하는데 babel-loader가 그것이다.
npm i -D babel-loader
// webpack.config.js
const path = require('path');
const webpack = require('webpack');
const childProcess = require('child_process');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
mode: "development",
entry: {
main: "./app.js"
},
output: {
path: path.resolve('./dist'),
filename: "[name].js" // name - 위에 main 키네임을 가져온다.
},
module: {
rules: [
{
test: /\.css$/,
use: [
process.env.NODE_ENV === 'production' ?
MiniCssExtractPlugin.loader :
'style-loader',
'css-loader'
],
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'url-loader',
options: {
// publicPath: './dist',
name: '[name].[ext]?[hash]', // name - 원본파일이름
limit: 20000, // 20kb
}
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
}
]
},
plugins: [
new webpack.BannerPlugin({
banner: `
Build Date: ${new Date().toLocaleString()}
Commit Version: ${childProcess.execSync('git rev-parse --short HEAD')}
Author: ${childProcess.execSync('git config user.name')}
`
}),
new webpack.DefinePlugin({
TWO: JSON.stringify('1+1'),
'api.domain': JSON.stringify('http://dev.api.domain.com')
}),
new HtmlWebpackPlugin({
template: './src/index.html',
templateParameters: {
env: process.env.NODE_ENV === 'development' ? '(개발용)' : ''
},
minify: process.env.NODE_ENV === 'production' ? {
collapseWhitespace: true,
removeComments: true,
} : false
}),
new CleanWebpackPlugin(),
...(process.env.NODE_ENV === 'production' ? [
new MiniCssExtractPlugin({
filename: '[name].css' // 원본파일 이름 (이렇게 설정 안하면 해시값으로 내보냄), 근데 결국 js파일의 원본네임이니까 위의 main 키네임 가져오는 건 같음
}),
] : []
)
]
}
// app.js
const promise = new Promise(() => {
console.log('promise');
});
/\.js$/
스크립트 파일에 babel-loader를 적용한다.
그리고 /node_modules/
폴더는 바벨 작업할 필요가 없으니 exclude
에 명시해준다.
그리고 entry
부분도 ./app.js
로 수정해준다.
npm run build
모듈을 찾을 수 없다는 에러가 뜹니다.
우리가 app.js
의
const promise = new Promise(() => {
console.log('promise');
});
코드를 ES5로 변환시키면 아까 위에서 봤듯이 core-js
라이브러리를 require
하는 코드가 추가되었을 것이다.
그레고 웹팩은 해당 require
을 보고 core-js
를 가져오기 위해 찾으려고 하는데, node_modules
폴더 안에 core-js
가 없기 때문에 이러한 오류가 뜨는 것이다.
그렇기 때문에 core-js
를 설치해야된다.
npm i -D core-js@2
npm
으로 라이브러리를 설치할 때 특정 버전으로 설치하려면 @하고 뒤에 버전을 붙여주면 된다.
정리
바벨은 일관적인 방식으로 코딩하면서, 다양한 브라우저에서 돌아가는 어플리케이션을 만들기 위한 도구다.
바벨의 코어는 파싱과 출력만 담당하고 변환 작업은 플러그인이 처리한다.
여러 개의 플러그인을 모아놓은 세트를 프리셋이라고 하는데 ECMAScript+ 환경은 env 프리셋을 사용한다.
바벨이 변환하지 못하는 코드는 폴리필이라 부르는 코드조각을 불러와 결과물에 로딩해 해결한다.
babel-loader로 웹팩과 함께 사용하면 훨씬 단순하고 자동화된 프론트엔드 개발환경을 갖출 수 있다.
이전 강의에선 바벨이 동작하는 원리에 대해 설명해서 바벨 강의가 길었다고 느꼈을 수도 있다.
사실 바벨을 실무에서 적용하는 부분은 강의 내용이 짧았다.
웹팩의 로더 형태로, 즉, babel-loader를 써서 자바스크립트를 빌드했다.
사실 그래서 웹팩과 사용하면 매우 간단하다.