초보자가 공부하며 끄적인 글입니다 ㅎㅎ; 보실 분은 참고만 해주세요.
Promise와 async await 각각 ES6와 ES8에 포함된 문법입니다. modern 브라우저에서는 둘 다 완벽하게 지원한다.(MDN 참고)
하지만 IE 때문에 ES5로 트랜스파일 해줘야하는 경우가 발생한다. IE 점유율 10%미만이라 들었는데 그만 버리면 안 되냐고 ㅠㅠ
그래서 오늘은 Promise와 async await이 ES5로 변환했을 때 어떻게 바뀌는지 알아보기로 했다. 일단 babel을 사용해야 하기 때문에 babel 설정을 해주었다. babel 기본 설정은 다음을 참고했다.
package.json
{
"name": "mypractice",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "babel src -d dist"
},
"author": "",
"license": "ISC",
"dependencies": {},
"devDependencies": {
"@babel/cli": "^7.10.3",
"@babel/core": "^7.10.3",
"@babel/preset-env": "^7.10.3"
}
}
그리고 나서 src 폴더 밑에 test.js라는 파일을 만들어 다음과 같은 코드를 작성하였다.
src/test.js
let promise = new Promise((resolve, reject) => {
console.log(1);
return resolve(1);
});
promise.then((value) => console.log(value)).catch((e) => console.error(e));
async function testFunc() {
let value = await promise;
console.log(`async ${value}`);
}
testFunc();
간단한 promise와 async await 함수를 작성하였다. 대략 10줄이 조금 넘는 코드다. 이것을 babel을 통해서 ES5문법으로 바꿔 보았다. num run build 실행.
그랬더니 코드가 다음과 같이 변했다.
dist/test.js
"use strict";
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
var promise = new Promise(function (resolve, reject) {
console.log(1);
return resolve(1);
});
promise.then(function (value) {
return console.log(value);
})["catch"](function (e) {
return console.error(e);
});
function testFunc() {
return _testFunc.apply(this, arguments);
}
function _testFunc() {
_testFunc = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
var value;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return promise;
case 2:
value = _context.sent;
console.log("async ".concat(value));
case 4:
case "end":
return _context.stop();
}
}
}, _callee);
}));
return _testFunc.apply(this, arguments);
}
testFunc();
10줄이 조금 넘었던 코드가 40여줄로 변했다. 거의 4배나 상승!! 코드를 보면 asnyc await은 뭐 새로운 함수들도 정의 되고 while 문과 switch문을 써가며 복잡하게 바뀌었다. 그런데 Promise는 거의 바뀐게 없다. 왜냐하면 ES5에는 없는 객체이기 때문이다.
babel은 문법을 변환 해줄 뿐 없던 것을 추가해주지는 않는다. 없던 것을 추가해주기 위해서는 polyfill을 추가해야한다. babel 7부터는 polyfill을 추가하는 방법이 조금 달라진 것 같다. 다음을 참고하여 polyfill을 추가해봤고 실행해보았다.
@babel/polyfill 은 babel 7.4부터 deprecated 됐기 때문에 corejs를 통해 실행해야한다.
npm i -D core-js
를 통해 core-js를 설치한다.
그러면 현재 package.json은 이 상태가 된다.
package.json
{
"name": "mypractice",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "babel src -d dist"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.10.3",
"@babel/core": "^7.10.3",
"@babel/preset-env": "^7.10.3",
"core-js": "^3.6.5"
}
}
그리고 .babelrc에 다음과 같은 설정을 넣어주면 된다.
.baelrc
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"browsers": ["last 2 versions", "ie >= 11"]
},
"useBuiltIns": "usage",
"corejs": 3,
"shippedProposals": true
}
]
]
}
그러면 이제 다시 npm run build를 해보자.
dist/test.js에 다음과 같이 코드가 바뀐 것을 확인할 수 있을 것이다.
dist/test.js
"use strict";
require("core-js/modules/es.object.to-string");
require("core-js/modules/es.promise");
require("regenerator-runtime/runtime");
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
var promise = new Promise(function (resolve, reject) {
console.log(1);
return resolve(1);
});
promise.then(function (value) {
return console.log(value);
}).catch(function (e) {
return console.error(e);
});
function testFunc() {
return _testFunc.apply(this, arguments);
}
function _testFunc() {
_testFunc = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
var value;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return promise;
case 2:
value = _context.sent;
console.log("async ".concat(value));
case 4:
case "end":
return _context.stop();
}
}
}, _callee);
}));
return _testFunc.apply(this, arguments);
}
testFunc();
제일 상단에 corejs에서 es.promise와 es.object.to-string을 가지고 온 것을 볼 수 있다. es5에는 Promise 객체와
같은 template 문자열이 없기 때문에 추가해준 것이다. regenerator-runtime은 core-js에 포함되어 있으므로 따로 설치해줄 필요없다.
이렇게 해주고 나서 index.html에 포함해서 실행시켜 봤더니 require도 없다고 하고 import로 고쳐보아도 해당 파일들이 없다며 잘 안된다.... 그래서 webpack까지 도입해서 실행해보았다.
webpack을 도입하면서 다음과 같이 파일들을 바꾸었다.
package.json
{
"name": "mypractice",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "webpack-dev-server --open --mode development",
"build": "webpack --mode production",
"babel": "babel src -d dist"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.10.3",
"@babel/core": "^7.10.3",
"@babel/preset-env": "^7.10.3",
"babel-loader": "^8.1.0",
"clean-webpack-plugin": "^3.0.0",
"html-webpack-plugin": "^4.3.0",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0",
"core-js": "^3.6.5"
}
}
webpack.config.js
const path = require('path');
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = (env, options) => {
const config = {
entry: { app: ['./src/index.js'] },
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.js$/,
include: [path.resolve(__dirname, 'src')],
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
],
},
optimization: { // 코드 스플리팅 해줌.
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
};
if (options.mode === 'development') {
config.plugins = [
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
template: path.join(__dirname, '/public/index.html'),
showErrors: true,
}),
];
config.devtool = 'inline-source-map';
config.devServer = { hot: true, contentBase: './dist' };
} else {
config.plugins = [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: path.join(__dirname, '/public/index.html'),
showErrors: true,
}),
];
}
return config;
};
그리고나서 src/public/index.html 파일을 만들어주고 (기본 값들만 들어간 파일)
num run dev
로 실행해주었다.
다음과 같이 잘 되는 것을 볼 수 있었다.
npm run build
를 하게 되면
다음과 같은 파일들이 생기게 된다. app.bundle.js에는 내가 실제로 사용한 es5로 바뀐 문법들이 들어가게 되고 vendors.bundle.js에 corejs로 추가한 여러 값들이 들어간다. 그래서 app.bundle.js에서 Promise라던지 Array의 여러 내부 메소드들을 사용할 수 있게 되는 것이다.
vendors.bundle.js를 보면 regenerationRuntime 같은 함수들이 추가 되어있는 것을 볼 수 있는데 덕분에 IE에서도 작동할 수 있게 된다. target을 IE를 뺀 브라우저들로 잡게 되면 regeneratorRuntime 같은 함수들을 추가 하지 않는 것을 확인할 수 있다!
IE 포함일 때 vendors.bundle.js의 맨 아래 부분
IE에서도 되는 거 확인
Chrome 55 이상일 때 맨 아래 부분
IE에서 오류 발생
크롬은 너무나 잘 된다
corejs3 버전을 사용하면 polyfill을 추가해 IE에서도 Promise나 Array.includes 같은 메소드들을 사용할 수 있다.
Promise는 ES5로 바뀌더라도 그대로이고 polyfill을 통해 Promise 객체를 추가해주어야한다.
async await는 polyfill을 추가해주지 않아도 바뀌며 알아서 따로 async 관련 함수를 만들어주고 while, switch 문 등을 활용해서 만들어준다.
웹팩 및 바벨 설정은 어렵다