바벨
바벨은 크로스브라우징의 혼란을 해결해 준다. 특히 ECMAScript2015+ 로 작성한 코드를 모든 브라우저에서 동작하도록 호환성을 지켜준다. 여기에는 타입스크립트나, 리액트에서 주로 사용하는 JSX처럼 다른 언어로 분류되는 것도 포함된다. 바벨의 설치는 npm install @babel/core @babel/cli 명령어로 설치한다. (@babel/cli는 바벨 명령어를 터미널에서 실행하게 해 준다.)
바벨은 총 3단계로 빌드를 진행한다.
- 파싱 : 코드를 AST(Abstract Syntax Tree)로 변환하는 단계이다. AST는 간단하게 말하면 코드의 프로그램을 나타내는 Node 트리 라고 할 수 있다.
- 변환 : AST를 변경하는 것이 변환 단계이다.
- 출력 : 변환된 결과물을 출력하는 단계이다.
바벨 사용방법
node_modules/.bin/babel 로 직접 실행해줘도 되고
npx babel (실행할 파일) 로 실행할 수 있다. (npx는 설치한 모듈을 바로 실행한다.)
테스트를 위해 루트경로에 아래와 같이 app.js를 만들고 실행해보자. (npx babel app.js)
// app.js
const alert = (msg) => window.alert(msg)
결과를 보면 빌드 이전과 별 다른것이 없다. 즉 변환이 되지 않았는데 변환 작업은 플러그인 이라는 것이 담당한다.
플러그인
바벨은 코드를 받아서 코드를 반환한다. 바벨함수를 정의한다고 하면 아래와 같을 것이다.
const babel = code => code
이렇게 바벨은 파싱과 출력을 담당하고 변환 작업은 플러그인이 처리한다.
npx babel --help 명령어를 통해 사용방법도 알아보자.
Options:
-f, --filename [filename] The filename to use when reading from stdin. This will be used in source-maps, errors etc.
--presets [list] A comma-separated list of preset names.
--plugins [list] A comma-separated list of plugin names.
--config-file [path] Path to a .babelrc file to use.
--env-name [name] The name of the 'env' to use when loading configs and plugins. Defaults to the value of BABEL_ENV, or else NODE_ENV, or else 'development'.
--root-mode [mode] The project-root resolution mode. One of 'root' (the default), 'upward', or 'upward-optional'.
--source-type [script|module]
--no-babelrc Whether or not to look up .babelrc and .babelignore files.
--ignore [list] List of glob paths to **not** compile.
--only [list] List of glob paths to **only** compile.
--no-highlight-code Enable or disable ANSI syntax highlighting of code frames. (on by default)
--no-comments Write comments to generated output. (true by default)
--retain-lines Retain line numbers. This will result in really ugly code.
--compact [true|false|auto] Do not include superfluous whitespace characters and line terminators.
--minified Save as many bytes when printing. (false by default)
--auxiliary-comment-before [string] Print a comment before any injected non-user code.
--auxiliary-comment-after [string] Print a comment after any injected non-user code.
-s, --source-maps [true|false|inline|both]
--source-map-target [string] Set `file` on returned source map.
--source-file-name [string] Set `sources[0]` on returned source map.
--source-root [filename] The root from which all sources are relative.
--module-root [filename] Optional prefix for the AMD module formatter that will be prepended to the filename on module definitions.
-M, --module-ids Insert an explicit id for modules.
--module-id [string] Specify a custom name for module ids.
-x, --extensions [extensions] List of extensions to compile when a directory has been the input. [.es6,.js,.es,.jsx,.mjs]
--keep-file-extension Preserve the file extensions of the input files.
-w, --watch Recompile files on changes.
--skip-initial-build Do not compile files before watching.
-o, --out-file [out] Compile all input files into a single file.
-d, --out-dir [out] Compile an input directory of modules into an output directory.
--relative Compile into an output directory relative to input directory or file. Requires --out-dir [out]
-D, --copy-files When compiling a directory copy over non-compilable files.
--include-dotfiles Include dotfiles when compiling and copying non-compilable files.
--no-copy-ignored Exclude ignored files when copying non-compilable files.
--verbose Log everything. This option conflicts with --quiet
--quiet Don't log anything. This option conflicts with --verbose
--delete-dir-on-start Delete the out directory before compilation.
--out-file-extension [string] Use a specific extension for the output files
-V, --version output the version number
-h, --help output usage information
커스텀 플러그인
루트경로에 my-babel-plugin.js 파일을 만들고 아래와 같이 작성한다.
// my-babel-plugin.js
module.exports = function myBabelPlugin() {
return {
// 커스텀 플러그인을 만들 때에는 visitor 객체를 반환해 주어야 한다.
visitor: {
Identifier(path) {
const name = path.node.name
// 바벨이 만든 AST 노드를 출력한다
console.log('Identifier() name:', name)
// 변환작업: 코드 문자열을 역순으로 변환한다
//path.node.name = name.split('').reverse().join('')
},
},
}
}
커스텀 플러그인을 만들땐 visitor 객체를 반환해 주어야 한다.
바벨 help 문서를 보면 --plugins [list] 로 플러그인을 추가하는 것을 볼 수 있다.
그럼 방금 만들었던 my-babel-plugin.js 커스텀 플러그인을 적용하여 실행해보자.
npx babel app.js --plugins './my-babel-plugin.js' 를 실행하면 된다.
그 다음으로 위 코드에서 주석처리 되어 있는 변환작업 부분을 주석을 풀고 실행해 보자.
파싱된 항목들이 역순으로 반환된 것을 볼 수 있다.
그 다음으로 const를 var로 변경해주는 플러그인을 작성해 보겠다.
// my-babel-plugin.js
module.exports = function myBabelPlugin() {
return {
// 커스텀 플러그인을 만들 때에는 visitor 객체를 반환해 주어야 한다.
visitor: {
VariableDeclaration(path) {
console.log('VariableDeclaration() kind:', path.node.kind) // const
// const를 var로 변경하는 부분
if (path.node.kind === 'const') {
path.node.kind = 'var'
}
},
},
}
}
path.node.kind가 const일 경우 var로 변환하는 코드이다.
플러그인 사용하기
transform-block-scoping
방금 const -> var 로 변환해주는 플러그인을 간단하게 작성해 보았는데 이미 바벨에는 이러한 플러그인이 존재한다. babeljs.io/docs/en/babel-plugin-transform-block-scoping 를 보면 사용방법을 알 수 있다.
npm install @babel/plugin-transform-block-scoping 로 설치하자.
그 후 위에 커스텀으로 만들었던 my-babel-plugin.js 대신 방금 설치한 플러그인을 사용해 보도록 하겠다.
사용방법은 커스텀 플러그인을 사용했던 것 처럼 --plugins 옵션 뒤에 명시해주면 된다.
npx babel app.js --plugins @babel/plugin-transform-block-scoping
transform-arrow-functions
이렇게 const가 var로 변환 되었지만 아직 화살표 함수는 변환되지 않았다. 별도의 플러그인이 필요한데 babeljs.io/docs/en/babel-plugin-transform-arrow-functions 여기서 사용방법을 알 수 있다.
마찬가지로 npm install @babel/plugin-transform-arrow-functions 로 설치한다.
npx babel app.js --plugins @babel/plugin-transform-block-scoping --plugins @babel/plugin-transform-arrow-functions 로 2개의 플러그인을 적용하여 실행 시켜보면 화살표 함수도 일반 함수로 변환된 것을 볼 수 있다.
transform-strict-mode
다음으로 ECMAScript5 에서부터 지원하는 엄격 모드를 사용하는게 안전하므로 'use-strict' 구문을 추가해주는 babeljs.io/docs/en/babel-plugin-transform-strict-mode플러그인도 설치해보자
npm install @babel/plugin-transform-strict-mode 로 설치한다.
npx babel app.js --plugins @babel/plugin-transform-block-scoping --plugins @babel/plugin-transform-arrow-functions --plugins @babel/plugin-transform-strict-mod 로 실행한다.
하지만 플러그인이 점점 많아 질수록 명령어가 길어지면서 지저분 해지기 때문에 별도의 설정파일로 분리하도록 한다. webpack.config.js 를 사용했던 것처럼 babel.config.js를 사용할 수 있다. (babeljs.io/docs/en/config-files#project-wide-configuration)
// babel.config.js
module.exports = {
plugins: [
'@babel/plugin-transform-block-scoping',
'@babel/plugin-transform-arrow-functions',
'@babel/plugin-transform-strict-mode',
],
}
설정파일을 작성한 뒤에 npx babel app.js 로 실행해보면 해당 플러그인 들이 반영된다.
프리셋
이렇게 EMCA2015+ 로 코딩할 때 필요한 플러그인을 일일이 설정하는 일은 비효율 적이다.
프리셋이란 목적에 맞게 여러가지 플러그인을 세트로 모아놓은 것을 말한다.
커스텀 프리셋
위에 사용했던 3개의 플러그인을 하나의 프리셋으로 만들어보자.
// my-babel-preset.js
module.exports = function myBabelPreset() {
return {
plugins: [
'@babel/plugin-transform-arrow-functions',
'@babel/plugin-transform-block-scoping',
'@babel/plugin-transform-strict-mode',
],
}
}
// babel.config.js
module.exports = {
presets: ['./my-babel-preset.js'],
}
프리셋을 만들고 babel.config.js에서 기존에 있던 plugins를 지우고 presets에 추가한 프리셋을 명시한다.
프리셋 사용하기
바벨은 목적에 따라 몇 가지 프리셋을 제공한다.
- preset-env : ECMAScript2015+ 변환할 때 사용
- preset-flow
- preset-react
- preset-typescript
이제 인터넷익스플로러에서도 소스가 구동되도록 먼저 preset-env를 사용해 본다.
npm install @babel/preset-env 로 설치하고 babel.config.js 에서 해당 프리셋을 적용한다.
module.exports = {
presets: ['@babel/preset-env'],
}
구동 결과는 동일하게 잘 변환 되었다.
env 프리셋 설정과 폴리필
타겟 브라우저
만약 코드가 크롬 최신 버전만 지원한다고 하면. 인터넷 익스플로러를 위한 코드 변환은 불필요 하다. target 옵션에 브라우저 버전명만 지정하면 env 프리셋은 이에 맞는 플러그인을 찾아 최적의 코드를 출력한다.
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
chrome: '87', // 크롬 87 버전 까지 지원하는 코드라는 의미.
},
},
],
],
}
실행 결과를 보면 크롬 87 버전에서는 블록 스코핑과 화살표 함수를 지원하기 때문에 코드를 변환하지 않았다. 만약 인터넷익스플로러도 지원해야 한다고 하면 브라우저 정보만 하나 더 추가해주면 된다.
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
chrome: '87', // 크롬 87 버전 까지 지원하는 코드라는 의미
ie: '11', // ie 11 까지 지원하는 코드
},
},
],
],
}
그럼 ie에서도 호환되는 코드로 변환이 되었다.
폴리필
변환과 조금은 다른 폴리필에 대해 알아보자.
// app.js
new Promise()
먼저 ECAMScript2015 문법인 Promise를 사용하여 바벨을 돌려보면 target에 ie가 주어져있음에도 불구하고 ie에서 지원하지 않는 Promise 문법이 그대로 반환 되었다.
바벨은 ECMAScript2015+ 를 ECMAScript5 버전으로 변환할 수 있는 것만 빌드한다. 그렇지 못한 것들은 폴리필 이라고 부르는 것을 추가해서 해결한다. 예를들어 화살표 함수 같은경우 일반 함수로 대체할 수 있기 때문에 변환이 가능하여 바벨이 변환하지만 Promise는 대체할 수 없기 때문이다.
// babel.config.js
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
chrome: '87', // 크롬 87 버전 까지 지원하는 코드라는 의미
ie: '11', // ie 11 까지 지원하는 코드
},
useBuiltIns: 'usage', // 'entry', false
corejs: {
version: 2,
},
},
],
],
}
폴리필을 추가하고 corejs의 버전을 명시한 다음에 실행시키면
corejs 에서 Promise를 가져오는 구문이 추가가 되었다.
'프론트엔드 개발환경' 카테고리의 다른 글
autoprefixer (0) | 2021.05.10 |
---|---|
Prettier (0) | 2021.01.12 |
ESLint (0) | 2021.01.12 |
바벨을 웹팩과 통합하기 (0) | 2021.01.07 |
webpack 의 기본 (0) | 2021.01.05 |
댓글