크롬,사파이 , 파이어폭스 , 엣지 등 대부분 브라우저는 ES6을 지원한다.
IE 11의 ES6 지원률을 매우 낮으며 매년 새롭게 도입되는 ES6+ 버전이나 제안단계의 ES제안 상양은 브라우저에 따라서 지원율이 제각각이다.
즉 ES6+와 ES.NEXT의 ECMAScript사양을 활용하려면 최신 사양으로 작성된 코드를 때에 따라서는 구형 브라우저에서도 동작시키기 위한 환경 구축이 필요하다.
대부분 프로젝트가 모듈을 사용하므로 모듈 로더도 필요한데 ESM은 대부분 브라우저에서 사용할 수 있긴 하지만 몇가지 이유로 별도의 모듈 로더를 사용하는것이 일반적이다.
1. 구형브라우저에서 ESM을 지원하지 않음
2. ESM을 사용해도 트랜스파일링이나 번들링이 필요함
3. ESM이 아직 지원하지 않는 기능이 있으며 해결되지 않은 몇가지 이슈들이 있다.
따라서 트랜스파일러은 Babel과 모듈 번들러 Webpack을 이용하여 ES6+/ES.NEXT 개발환경을 구축해 보고 이를 ES5사양의 소스코드로 바꿔보는 방법도 알아보자.
아래와 같이 ES6의 화살표 함수와 ES7의 지수연산자를 사용한 예시가 있다 생각해보자.
[1, 2, 3].map(n => n ** n);
IE와 같은 구형 브라우저는 위 기능을 지원하지 않을 수 있으며 위 코드를 ES5형식으로 변환할 수 있다.
"use strict";
[1, 2, 3].map(function (n) {
return Math.pow(n, n);
});
Babel을 최신사양의 소스코드를 ES5사양의 소스코드로 변환할 수 있다.
Babel 설치
npm을 통해서 babel을 설치한다.
먼저 명령어를 통해서 babel을 설치해준다.
Babel 프리셋 설치와 babel.config.json 설정 파일 작성
Babel을 사용하려면 @babel/preset-env를 설치해야 하며 Babel플러그인들을 모아둔 Babel 프리셋이라고 부른다.
@babel/preset-env는 필요한 플러그인들을 프로젝트 지원 환경에 맞춰 동적으로 결정해준다.
기본설정으로 둔다면 모든 ES6+/ES.NEXT 사양의 소스코드들을 변환한다
현재까지 설치된 라이브러리는 아래와 같다.
{
"name": "babel",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.23.0",
"@babel/core": "^7.23.0",
"@babel/preset-env": "^7.22.20"
}
}
프로젝트 루트폴더에 babel.config.json설정파일을 하나 만들고 아래와 같이 작성한다면 지금 설치한 Wbabel/preset-env를 사용할 수 있다.
{
"presets": ["@babel/preset-env"]
}
트랜스파일링
Babel명령어를 사용하면 소스코드들을 ES5코드로 바꿀 수 있다.
npm scripts 에 Babel CLI 명령어를 등록해보자.
{
"name": "babel",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "babel src/js -w -d dist/js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.23.0",
"@babel/core": "^7.23.0",
"@babel/preset-env": "^7.22.20"
}
}
npm build를 하면 src/js폴더에 있는 모든 JS파일들을 바꾼 후에 dist/js폴더에 저장하게 된다.
-w는 파일들의 변경을 감지하여 자동으로 트랜스파일하는것을 , -d는 트랜스파일링의 결과물이 저장될 파일을 지정한다.
이를 실험하기 위해서 src/js 폴더를 생성한 후 lib.js와 main.js를 만들어보자.
// src/js/lib.js
export const pi = Math.PI; // ES6 모듈
export function power(x, y) {
return x ** y; // ES7: 지수 연산자
}
// ES6 클래스
export class Foo {
#private = 10; // stage 3: 클래스 필드 정의 제안
foo() {
// stage 4: 객체 Rest/Spread 프로퍼티 제안
const { a, b, ...x } = { ...{ a: 1, b: 2 }, c: 3, d: 4 };
return { a, b, x };
}
bar() {
return this.#private;
}
}
// src/js/main.js
import { pi, power, Foo } from './lib';
console.log(pi);
console.log(power(pi, pi));
const f = new Foo();
console.log(f.foo());
console.log(f.bar());
이제 터미널을 통해서 트랜스파일링르 실행할 수 있다.
트랜스파일링이 잘 되었으며 실행도 잘 된다.
Node.js말고 브라우저에서도 잘 실행될까? 한번 테스트해보자.
<!DOCTYPE html>
<html>
<body>
<script src="dist/js/lib.js"></script>
<script src="dist/js/main.js"></script>
</body>
</html>
브라우저는 CommonJS 방식의 require 함수를 지원하지 않으므로 에러를 발생한다.
ESM을 사용하게 Babel을 설정할수도 있긴 하지만 앞에서 설명한 몇몇 문제가 있으므로 Webpack을 사용해보자.
Webpack
Webpack은 의존 관계에 있는 JS,CSS,이미지 등의 리소스들을 하나나 여러개의 파일로 번들링 해준다.
Webpack을 거치면 모듈들이 하나의 파일로 번들링되므로 별도의 모듈 로더가 필요없어지며 HTML 파일에서 script 태그로 여러개의 JS 파일을 로드해야 하는 번거로움도 사라진다.
Webpack 설치
터미널 명령어를 통해서 웹팩을 설치하자.
babel-loader 설치
Webpack이 모듈을 번들링하는 경우 소스코드를 ES5로 변경하도록 babel-loader를 설치한다.
npm scripts를 변경하여 기존 실행되던 Babel 대신 Webpack을 실행하도록 바꿔준다.
{
"name": "babel",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack -w"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.23.0",
"@babel/core": "^7.23.0",
"@babel/preset-env": "^7.22.20",
"babel-loader": "^9.1.3",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4"
}
}
webpack.config.js 파일 작성
Webpack이 실행되는 경우 참조하는 설정파일이다.
const path = require('path');
module.exports = {
// entry file
// https://webpack.js.org/configuration/entry-context/#entry
entry: './src/js/main.js',
// 번들링된 js 파일의 이름(filename)과 저장될 경로(path)를 지정
// https://webpack.js.org/configuration/output/#outputpath
// https://webpack.js.org/configuration/output/#outputfilename
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/bundle.js'
},
// https://webpack.js.org/configuration/module
module: {
rules: [
{
test: /\.js$/,
include: [
path.resolve(__dirname, 'src/js')
],
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['@babel/plugin-proposal-class-properties']
}
}
}
]
},
devtool: 'source-map',
// https://webpack.js.org/configuration/mode
mode: 'development'
};
내가 해서 그런가.. 책에서 하란대로 했더니 오류가 났다.
당황하지 말고 에러메시지를 봐보자. @babel/plugin-proposal-class-properties 가 없으니 이를 설치해주자. 책에 적힌 시점과 지금의 웹팩이나 바벨이 바뀌었기에 충분히 생길 수 있는 일이다.
해당 라이브러리를 설치해주고
babel.config.json도 잊지말고 바꿔주자.
{
"presets": ["@babel/preset-env"],
"plugins": ["@babel/plugin-proposal-class-properties"]
}
그리고 실행을 하면 웹팩이 잘 적용된것을 볼 수 있다.
dist의 bundle.js를 가보면 웹팩 적용이 잘 된것을 확인할 수 있다.
index.html에 연동된 js파일을 bundle파일로 바꿔주고 다시 실행을 해보자.
<!DOCTYPE html>
<html>
<body>
<script src="dist/js/bundle.js"></script>
</body>
</html>
브라우저에서도 문제없이 실행이 잘 되는것을 알 수 있다.
babel-polyfill
Babel을 사용하여 ES5로 사양의 소스코드로 바꿔도 브라우저가 지원하지 않는 코드가 남아있을 수 있다. Promise,Object.assign,Array.from 등은 ES5사양에 대체할 수단이 없기 때문이다.
위 함수들을 사용하면 어떻게 되는지 확인해보자.
// src/js/main.js
import { pi, power, Foo } from './lib';
console.log(pi);
console.log(power(pi, pi));
const f = new Foo();
console.log(f.foo());
console.log(f.bar());
// polyfill이 필요한 코드
console.log(new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 100);
}));
// polyfill이 필요한 코드
console.log(Object.assign({}, { x: 1 }, { y: 2 }));
// polyfill이 필요한 코드
console.log(Array.from([1, 2, 3], v => v + v));
파일을 위처럼 바꿔도 Object.assign과 Array.from이 남아있는것을 확인할 수 있다.
polyfill 을 설치하면 위 문제를 해결할 수 있다.
먼저 폴리필을 main.js에서 로드해야 한다.
// src/js/main.js
import "@babel/polyfill";
import { pi, power, Foo } from "./lib";
console.log(pi);
console.log(power(pi, pi));
const f = new Foo();
console.log(f.foo());
console.log(f.bar());
// polyfill이 필요한 코드
console.log(
new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 100);
})
);
// polyfill이 필요한 코드
console.log(Object.assign({}, { x: 1 }, { y: 2 }));
// polyfill이 필요한 코드
console.log(Array.from([1, 2, 3], (v) => v + v));
Webpack을 사용하는 경우에는 webpack.config.js파일의 entry 배열에 폴리필을 추가하면 되며 main.js는 건들지 않아도 된다.
const path = require("path");
module.exports = {
// entry file
// https://webpack.js.org/configuration/entry-context/#entry
entry: ["@babel/polyfill", "./src/js/main.js"],
// 번들링된 js 파일의 이름(filename)과 저장될 경로(path)를 지정
// https://webpack.js.org/configuration/output/#outputpath
// https://webpack.js.org/configuration/output/#outputfilename
output: {
path: path.resolve(__dirname, "dist"),
filename: "js/bundle.js",
},
// https://webpack.js.org/configuration/module
module: {
rules: [
{
test: /\.js$/,
include: [path.resolve(__dirname, "src/js")],
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
plugins: ["@babel/plugin-proposal-class-properties"],
},
},
},
],
},
devtool: "source-map",
// https://webpack.js.org/configuration/mode
mode: "development",
};
bundle.js를 보면 폴리필이 잘 추가된 것을 확인할 수 있다.
'FrontEnd > Deep Dive' 카테고리의 다른 글
[React] DeepDive 모던리액트(2) 동등 비교 (0) | 2023.12.14 |
---|---|
[React] DeepDive 모던 리액트 (1) 리액트의 역사 (0) | 2023.12.13 |
[JS] DeepDive(48) 모듈 (0) | 2023.09.27 |
[JS] DeepDive(47) 에러처리 (0) | 2023.09.27 |
[JS] DeepDive(46) 제너레이터와 async/await (0) | 2023.09.27 |