[typescript] iterator + for-of 를 es5 이하를 타겟으로 했을 때 잘못 컴파일 되는 문제
tsc 로 컴파일할때 항상 ts 파일만 컴파일하지는 않는다.
ts + js 가 섞인 프로젝트에서 allowJS 옵션을 통해 js 파일도 tsc input으로 사용될 수 있다.
그러나 이렇게 js 를 tsc 인풋으로 사용할 때는 일부 오류들이 표시되지 않아 문제를 인식할 수 없다.
그 중 얼마 전 발견한 하나의 케이스를 소개한다.
iterator + for-of
iterator는 iterable(반복 가능) 한 객체로 Array, Map 도 iterable 객체다.
iterable 객체는 for-of 를 이용해 순회 가능하다.
for (const str of ['1', '2']){
console.log(str);
}
이런식으로 of 뒤에 iterable 객체를 두고, of 앞의 변수에 할당하여 반복한다.
그런데 iterator 와 for-of 는 es6에서 도입된 문법이다.
es5 이하에서는 사용할 수 없는 문법이지만, target 을 es5로 설정하면 일반적인 for문으로 변경된다.
정상적으로 작동한다.
만약 iterator 객체와 함께 사용하게 되면 어떻게 될까?
//index.js
const searchParams = new URLSearchParams();
searchParams.append("foo", "bar");
for (const [key, value] of searchParams.entries()) {
console.log({ key, value });
}
url에서 searchParams를 다룰 때 사용하는 URLSearchParams 에서 entries() 함수는
현재 searchParams를 'Iterator 객체'로 반환한다.
$ node index.js
{ key: 'foo', value: 'bar' }
이를 node 로 실행시키면 {'foo' : 'bar'} 가 출력된다.
그런데 tsc로 index.js를 컴파일하고 난 다음 실행하면 아무것도 표시되지 않는다
$ yarn tsc
$ node dist/index.js
//아무것도 출력되지 않음
그 이유는 출력된 코드를 보면 알 수 있다.
"use strict";
var searchParams = new URLSearchParams();
searchParams.append("foo", "bar");
for (var _i = 0, _a = searchParams.entries(); _i < _a.length; _i++) {
var _b = _a[_i], key = _b[0], value = _b[1];
console.log({ key: key, value: value });
}
for-of는 es5 이하에서 지원하지 않으므로 일반 for문으로 변경한다.
for의 종료 조건은 _i < _a.length 인데, iterator 에는 length가 존재하지 않으므로
iterator에 몇 개가 들어있든지 즉시 반복이 종료되는 것이다.
tsc는 target 버전에 따라 호환 가능하도록 변환시켜주기는 하지만
babel 처럼 polyfill 을 지원하지는 않기 때문에 iterator가 length가 없다는 것을 모른다.
따라서 배열을 변환할때 처럼 종료 조건을 length로 해서 일반 for문으로 변경하여 문제가 된다.
정상적으로 작동하게 하려면
iterator 가 지원되는 버전인 es6 이상으로 target을 업그레이드하거나,
downlevelIteration 옵션을 사용하여 iterator에 대한 헬퍼 폴리필을 추가할 수 있다.
https://www.typescriptlang.org/tsconfig/#downlevelIteration
그런데 문제는 '우리는 오류 메시지조차 받지 못했다' 는 것이다.
오류를 미리 알 수 없을까?
보다시피 js 를 tsc의 입력으로 주었을 때 오류 메시지가 정상 표시되지 않는다.
만약 문제가 되는 코드를 js가 아닌 ts로 입력했다면
index.ts:4:28 - error TS2802: Type 'IterableIterator<[string, string]>' can only be iterated through when using the '--downlevelIteration' flag or with a '--target' of 'es2015' or higher.
4 for (const [key, value] of searchParams.entries()) {
~~~~~~~~~~~~~~~~~~~~~~
Found 1 error in index.ts:4
이런 식으로 es5 에서 지원되지 않는 타입임을 알려준다.
실제로 js를 tsc의 입력으로 사용하면 '문법 오류'를 제외하고는 아무런 에러가 발생하지 않는다.
따라서 tsc로 컴파일하는 대상이 js와 ts를 함께 사용한다면 다른 도구를 이용해 오류를 감지해야 한다.
예를 들면 eslint 를 사용하는 경우 아래 플러그인을 사용하여 현재 target으로 하는 es 버전에서 호환되지 않는 함수를 사용하려 할 때 lint error 를 발생시킬 수 있다.
https://github.com/amilajack/eslint-plugin-compat
또는 js를 tsc에 입력하기 전에 babel을 이용해 polyfill을 추가해준 후 tsc를 돌려 정상적으로 작동하게 할 수도 있다.
물론 가장 좋은 방법은 tsc 입력으로 ts를 사용하는 것이겠다...