Trend of JavaScript

제공

2020년과 이후 JavaScript의 동향 – JavaScript(ECMAScript)

2020.08.04|46420

ECMAScript 6 에디터이기도 했던 Allen Wirfs-Brock과 Brendan Eich는 매우 간헐적(13~15년 주기)으로 열리는 The History of Programming Languages Conference에서 지난 20년(1995~2015)간의 JavaScript 역사를 다룬 190페이지 분량의 방대한 내용의 문서 JavaScript: the first 20 years를 3년의 작업 시간을 거쳐 완성 후 공개했습니다(참고: Allen Wirfs-Brock Thoughts and Things).

이 문서는 JavaScript의 기원과 Microsoft의 JScript와 같은 변형의 등장, 첫 TC39 미팅부터 Harmony에서 ECMAScript 2015까지의 긴 여정을 담고 있습니다. 이는 JavaScript가 역사적으로 다른 언어들이 이룬 업적과 비교하더라도 대등한 위치에 올라섰음을 공표하는 마침표와도 같아 보입니다.

이제 매해 거의 대다수의 개발자 대상 설문조사 발표에서 가장 인기있는 언어로 JavaScript가 1위를 차지했다는 소식은 마치 연례 행사와도 같아서 감흥이 사라진 지 오래입니다. 어느 시점에선가 1위를 차지하지 못한다면 오히려 큰 뉴스가 될 수도 있을 것입니다.

그림 1 2020 JetBrains 개발자 에코시스템(좌), 2020 StackOverflow Popular Technologies(중), 2020 네이버 개발자 대상 내부 설문 조사(우)

이 글에서는 JavaScript의 역사적 순간에 따라 구분될 수 있는 큰 주기들을 살펴보고, TC39의 변화 그리고 최신 ECMAScript 명세들인 2020, 2021과 아직 완료되지 않았지만 흥미로운 새로운 명세에 대한 내용을 살펴보겠습니다.

이 글의 내용은 2020년 7월을 기준으로 작성되었습니다. 글을 읽는 시점에 따라 글의 내용과 사실이 다를 수 있습니다.

2020년, 새로운 전환점의 시작

JavaScript는 약 10년마다 커다란 변화가 이루어져 왔으며 이를 기반으로 2020년을 세 번째 전환점으로 보는 시각도 있다(참고: The Third Age of JavaScript).

그림 2 JavaScript의 변화 주기(출처: https://www.swyx.io/writing/js-third-age/)

약 10년 주기로 변화가 일어났다는 주장의 내용은 다음과 같다.

첫 번째 주기: 1997~2007년

JavaScript 첫 등장과 다양한 DOM/helper 라이브러리들(Dojo, Mootools, jQuery 등)의 등장으로 동적인 웹 개발이 시작되었던 시기라 할 수 있다.

표준화에 대한 중요성이 크게 부각되지 않았지만, 언어적 표준화(ECMAScript 1)가 시작되었다. 이후 ECMAScript 3/3.1을 통해 대중적인 인기가 생기기 시작했고, ECMAScript 4를 통해 커다란 변화를 꾀하기 위해 노력했지만 참여자들 간의 합의를 이끌어내는 데 실패한 후, 아주 긴 시간 동안 언어적 발전이 정체되어 있던 시기였다.

참고 – ECMAScript 4: The missing version – ECMAScript archives: ECMAScript development 1996–2015 공개된 많은 내용은 이전에는 공개되지 않았던 것이다.

두 번째 주기: 2009~2019년

첫 번째 주기는 JavaScript 언어적 측면의 완성을 위해 노력했던 시기라면, 두 번째 주기는 사용자들이 JavaScript를 탐험하고 새로운 영역으로 확장시킨 시기라 할 수 있다.

ECMAScript 5 발표가 이뤄졌고, 오늘날 생태계의 핵심을 이루는 Node.js, NPM, Transpiler, 빌드/번들러, 다양한 프레임워크가 등장하여 한 번 더 큰 도약을 이루어낸 시기였다.

세 번째 주기: 2020년 이후

새로운 주기에서는 레거시 영역의 정리, 그리고 여러 도구로 인해 형성된 레이어의 제거가 이뤄질 것으로 예측된다.

첫 번째로, CommonJS/AMD(RequireJS)와 같은 비표준적 모듈 사용에 의존하는 수많은 생태계가 ECMAScript 모듈(이하 ESM)로 전환될 것으로 예측된다.

또한 다양한 JavaScript 도구가 언어적으로 빌트인되어야 한다는 관점에서의 접근도 희미해질 수 있다. 타입 사용(안정성)을 통한 성능 향상은 무시하기 어려워졌고, 그 동안의 ‘JavaScript를 위한 JavaScript'(JavaScript 도구는 JavaScript로 개발되어야 한다는 접근 방법)와 같은 이상적 접근 방식은 이미 TypeScript를 통해 틀렸음이 증명되었다.

그 외에도 Deno나 Relay의 경우처럼, 코어 JavaScript 도구에 JavaScript가 아닌 새로운 언어인 Rust가 사용되고 있다.

Rust에 대해 학습해 보고 싶다면 온라인 학습 사이트 Tour to Rust를 참고한다(한국어로도 제공된다).

그림 3 2023년까지 대다수의 유명 JavaScript 도구들은 JavaScript로 작성되지 않을 것이라는 전망(출처: https://twitter.com/aweary/status/1001874375472168960)

Deno는 런타임 사용 시 당연시되던 다양한 공통 도구에 대한 레이어를 제거했다. 테스트, linting, 포매팅과 번들링을 한 개의 바이너리에 모두 포함시켰고 또한 TypeScript 기반이므로 관련된 도구들의 레이어 또한 불필요해졌다.

강동윤 님이 개발하고 있는 JavaScript/TypeScript transpiler인 swc는 Rust로 작성되었는데, 이는 JavaScript로 작성된 Babel과 비교해 볼 수 있는 좋은 예시라 할 수 있다.

Babel의 Transpile은 다음의 일련의 과정을 거쳐 수행된다.

그림 4 Babel의 Transpile 과정(출처: Understanding ASTs by Building Your Own Babel Plugin)

  • Parse: @babel/parser
    JavaScript 코드를 읽어들이고, AST(Abstract Syntax Tree)로 파싱한다.
  • Transform: @babel/traverse
    파싱된 AST를 순회하며, 사용자가 설정한 Babel 설정값에 따라 AST 노드를 변경한다.
  • Generate: @babel/generator
    수정된 AST를 통해 JavaScript 코드를 생성한다.

위의 과정에 사용된 도구는 모두 JavaScript로 작성되어 있으며, 이는 위에서 언급한 ‘JavaScript를 위한 JavaScript’ 이상적 접근에 충실하다 할 수 있다.

그에 반해 swc의 경우, 모든 도구는 Rust로 작성되어 Babel과 비교해 훨씬 뛰어난 성능을 보여준다. 싱글 코어에서 수행된 자체 벤치마크 결과에서는 Babel과 비교해 무려 18배 빨랐다.

JavaScript의 종말?

소프트웨어 개발자이자 사상가이기도 한 Gary Bernhardt는 2014년 PyCon 콘퍼런스에서 “JavaScript 탄생과 죽음“(The Birth & Death of JavaScript)이라는 세션을 통해 JavaScript의 언어적 탄생 배경과 2014년 시점까지의 상황을 기반으로 한 미래의 가상적 모습을 예측했다(발표 영상: The Birth & Death of JavaScript).

그는 이 발표에서 JavaScript의 영향력은 2035년까지일 것으로 설정(어디까지나 가상적 예측)했는데, 이 예측이 사실이라고 가정해 본다면 JavaScript의 영향력은 위의 주기를 기반으로 볼 때 마지막 주기에 들어섰다고 할 수 있을 것이다.

Brendan Eich는 항상 JavaScript에 베팅하라는 얘기도 했지만, “Always Bet on JS – and WASM“와 같이 WASM이 JavaScript를 대신해 유니버설 VM(universal virtual machine)을 완성시킬 것이라는 의견을 밝히기도 했었다.

그림 5 유니버설 VM을 완성시킬 WASM이라는 트윗(출처: https://twitter.com/BrendanEich/status/1001307081725562882)

웹 엔진의 다양성은 도전받고 있나?

2018년 12월, Microsoft Edge 브라우저의 Chromium 전환에 대한 놀라운 소식이 전해진 뒤로, 2020년 6월 Windows Update를 통해 모든 사용자에게 새로운 Chromium 기반의 Edge 브라우저를 제공하기 시작했다(참고: Microsoft’s new Edge browser now rolling out via Windows Update).

이제 개발자들은 서로 다른 렌더링/JavaScript 엔진의 서로 다른 표준 지원에 따른 크로스브라우징 문제로 인한 고난의 짐을 하나 내려놓을 수 있게 되었다. 그러나 이것이 꼭 긍정적인 영향만을 가져다 줄 것인지와 웹의 다양성 면에서의 우려에 대한 목소리가 있었음을 2019년과 이후 JavaScript의 동향 – JavaScript(ECMAScript) > Microsoft Edge의 Chromium 채택에서 언급했었다.

이제 그 다양성(긍정적 의미에서)을 이루던 큰 축인 Internet Explorer 11은 결국 시간의 문제일 뿐, 국내뿐만 아니라 전 세계적으로도 서서히 생명력을 잃어가고 있다(참고: IE11 Mainstream End Of Life in Oct 2020).

image-20200713-185603-679.png

그림 6 전 세계 Internet Explorer 11 점유율(출처: Statcounter)(좌), Internet Explorer 11 한국 점유율(우)

WebKit의 경우 iOS의 기본 브라우저로 사용된다는 점 외에는 크게 힘을 얻지 못하고 있지만, GNOME의 Epiphany 브라우저가 WebKit 기반이며, Sony의 콘솔 게임기인 PS4는 브라우저뿐만 아니라 시스템 내 다양한 UI 또한 WebKit 기반으로 개발되고 있다(참고: https://doc.dl.playstation.net/doc/ps4-oss/webkit.html).

WebKit이 다방면에서 사용되고 있는 것처럼 보이지만, Chromium의 영향력에 비교해 보면 대등한 위치에 있다고 할 수는 없을 것이다. 이제 Internet Explorer를 제외하면, 사실상 오늘날의 웹은 3개의 주요 엔진만이 남게 된다. Chromium은 이들 중 가장 큰 영향력을 보유하고 있으며 앞으로도 상당 기간 그럴 것이다.

이 상황이 과거의 IE 시절과 유사한 독과점 시절로 회귀하도록 만들지는 않겠지만, 다양성을 통한 긍정적 경쟁이라는 측면에서는 점점 더 멀어져 가고 있는 것은 사실이다.

참고: Web Engine Diversity and Ecosystem Health

표 1 주요 브라우저 렌더링/JavaScript 엔진

Browser/ProjectRendering EngineJavaScript Engine
IE/old EdgeTrident(~IE11)
EdgeHTML
ChakraCore
Chrome/ChromiumBlinkV8
Safari/WebkitWebKitJavaScriptCore
Firefox/MozillaGeckoServoSpiderMonkey

JavaScript 엔진(SpiderMonkey)이 어떻게 동작하는지 알고 싶다면 Compiler Compiler: A Twitch series about working on a JavaScript engine을 참고한다.

Internet Explorer의 렌더링 엔진 Trident의 지난 기록과 도입했던 몇몇 혁신적인 기술적 내용을 알고 싶다면 Today, the Trident Era Ends를 참고한다.

모듈 지원

2015년 ECMAScript 6를 통해 표준 모듈 스펙이 정의되었지만, 호환성과 기타 다른 이슈들로 인해 현재 생태계는 다양한 포맷의 모듈을 혼합해 사용되고 있다.

얼마나 많은 형태의 모듈 포맷들이 존재하고 사용되고 있을까? Understanding (all) JavaScript module formats and tools에서는 현존하는 모든 모듈의 정의와 예제 코드를 볼 수 있으며, 이는 프런트엔드 개발의 복잡성을 보여주는 또다른 단면이라고 할 수도 있다.

사용하고 있는 모듈이 ESM을 지원하지 않지만 ESM으로 import 하고 싶다면 어떻게 해야 할까? npm 레지스트리에 등록되어 있다면, jspm을 통해 브라우저에서 네이티브 모듈 문법을 사용해 별도의 설치 없이 바로 import할 수도 있다.

// https://jspm.org/#url-patterns
import module from 'https://jspm.dev/NPM_패키지_이름';  
 <script type="module">
  // Statically:
  import babel from 'https://jspm.dev/@babel/core';
  console.log(babel);

  // Dynamically:
  (async () => {
    console.log(await import('//jspm.dev/lodash@4/clone'));
  })();
</script>  

그러나 결국 위에서 언급했던 것과 같이, 새로운 주기에서는 이러한 복잡한 비표준적 모듈들의 전환이 이뤄질 것으로 보인다. CommonJS를 기본 모듈 포맷으로 사용했던 Node.js는 ESM에 대한 지원을 지속적으로 향상시켜 나가고 있다.

Node.js 모듈 로딩

Node.js 12.17.0부터 실험적 플래그 사용 없이 모듈이 지원되기 시작했다(참고: Node v12.17.0 (LTS)).

package.json 파일의 type 필드값이 module로 지정되어 있다면, 해당 package.json 파일의 스코프에 해당되는 .mjs.js 확장자의 파일은 ESM으로 인식되고 처리된다(.mjs 파일은 이와 상관없이 항상 ESM으로 인식된다).

// package.json
{
    "type": "module",
}

Node.js 실행 시 --input-type=module 플래그를 지정해도 결과는 같다. Node.js에서의 더욱 자세한 ESM 모듈 인지와 처리 과정은 ESM Resolution Algorithm을 참고한다.

import 구문을 통해 JSON 또는 WASM을 참조하는 경우는 --experimental-json-modules 또는 --experimental-wasm-modules 플래그 지정이 필요하다. 자세한 내용은 ESM Experimental JSON Modules을 참고한다.

ESM 지원이 확장되면 번들러를 사용하지 않아도 될까?

모던 브라우저만을 타겟팅하는 경우라면, ESM은 거의 대다수의 브라우저에서 네이티브하게 지원된다. 그렇다면 이제 더이상 번들러를 사용할 필요가 없는것 아닐까?

그렇지 않다. import/export 구문은 정적으로 분석이 가능하기 때문에, 번들러는 코드를 분석해 사용되지 않는 export를 제거해 최적화할 수 있기 때문이다.

image-20200716-111232-476.png

그림 7 약 300개 모듈로 구성된 라이브러리의 크롬 브라우저 로딩 파이프라인 테스트(출처: ES module loading)

참고: Performance recommentations: Keep bundling

TC39: 누구나 표준에 참여할 수 있다.

명세는 단순히 TC39 내의 결정을 통해서만 이뤄지진 않는다. TC39의 공식 활동은 아니지만 일부 위원회 멤버들은 외부의 관련 개발/커뮤니티 그룹(교육, 프레임워크, 도구/Transpiler)과의 협의를 위해 JS outreach groups를 통해 초기 단계의 명세(~Stage 2)와 Stage 3+에 대한 정보 전달 등의 활동을 하고 있기도 하다. 이는 과거의 비효율적, 그리고 투명하지 않던 활동들에 개선해 더욱 많은 이들의 의견을 청취하기 위함으로 보인다.

그림 8 과거 제안들의 진행을 다루던 방식(출처: What TC39 is doing and how you can hlep)

외부 전문가 초빙에 대한 정책이 추가되었고 이를 통해 Babel, SystemJS, Deno 등의 관계자가 초빙되기도 했다(참고: dotJS 2019 – Daniel Ehrenberg – TC39: How we work, what we are working on & how you can get involved).

이제 TC39 내의 다양한 논의는 접근성이 떨어지는 메일링 리스트 형식의 커뮤니케이션에서 벗어나, 온라인 포럼 서비스인 discourse를 사용해 TC39 discourse 페이지에서 누구나 새로운 명세를 제안하거나 기존 명세에 대한 질문, 토론 등에 참여할 수 있도록 개선되었다.

그림 9 TC39 discourse 페이지

TC39는 무엇을 그리고 어떻게 일하고 있을까?

TC39 기술위원회가 어떻게 일하고 있는지 궁금하다면 [DRAFT] Documentation of how TC39 operates and how to participate에서 확인할 수 있다. TC39가 제안들을 다루는 방식, 기술위원회 미팅 진행, 관리 등 일하는 방식에 대해 자세하게 설명한 문서로, 새로운 제안을 작성하는 방법과 각 단계에 따른 접근 방법을 설명하며, 그 외 TC39 미팅 참여 방법과 멤버로서 참여 등에 대한 부분들도 다루고 있어서 기술적 이슈 외에도 세부적이고 투명한 운영 방식을 엿볼 수 있기도 하다.

또한 TC39 멤버들이 참여한 다음의 발표 세션들도 이해를 돕는 데 좋은 참고 자료이다.

TC39 기술위원회 그들은 누구인가?

TC39는 JavaScript의 표준 명세인 ECMAScript의 명세들을 논의하고 표준을 제정하는 실질적인 기구라 할 수 있다. TC39 멤버는 People · Ecma TC39에서 확인할 수 있다.

다음의 인터뷰에서 그들이 어떻게 TC39 멤버가 되었는지 등 기술적 내용과 TC39 활동을 일부 엿볼 수 있는 내용을 확인할 수 있다.

ECMAScript 명세 문서의 이해

ECMAScript 6부터 일 년 단위 릴리스(yearly release)로 변경된 이후로, 해마다 연도별 명세가 완성되고 공개되고 있다. 이제 명세의 버전의 중요성이 이전과는 다르다는 사실은 모두가 알고 있을 것이다. 중요한 것은 어떤 명세가 제안되었는지, 어떻게 논의가 진행되었는지, 그리고 최종적(Stage 4)으로 표준 명세가 되었는지이다.

사실, 명세를 모두 읽고 이해하는 것은 쉽지 않다. 그러나 명세의 등장 배경을 알고 기술적으로 더욱 깊이 이해하기 위해선 명세를 읽는 것이 필요한데, Chromium JavaScript 엔진인 V8 팀이 공개한 “ECMA 명세 이해하기”(Understanding the ECMAScript spec) 시리즈 글은 도움이 될 수 있을 것이다.

추가로, 개인 개발자인 Timothy Gu가 정리한 How to Read the ECMAScript Specification도 같이 참고할 수 있다.

ECMAScript 2020

지난 6월18일 ECMAScript 2020(ECMA-262 11번째 에디션)이 공식적으로 릴리스되었다. ECMAScript 2020의 주요 스펙 설명은 이미 많은 곳에서 다뤄졌으므로, 명세를 가볍게 살펴보는 수준에서 다루겠다.

String.prototype.matchAll

String.prototype.match() 메서드와 유사하게 작동한다. String.prototype.matchAll() 메서드는 g(global)/y(sticky) 플래그가 사용된 정규 표현식과 매치되는 문자열과 세부 정보를 포함하는 iterator를 반환한다.

const str = "test1test2";  
const rx = /t(e)(st(\d?))/g;

str.match(rx);  // ["test1", "test2"]

for (const match of str.matchAll(rx)) {  
    // 1: ["test1", "e", "st1", "1", index: 0, input: "test1test2"]
    // 2: ["test2", "e", "st2", "2", index: 5, input: "test1test2"]
    match;
}

더욱 자세한 내용은 2019년과 이후 JavaScript의 동향 – JavaScript(ECMAScript) 글의 ‘ECMAScript 2020’ 절을 참고한다.

동적 import() 구문

Promise 기반의 import() 구문은 JavaScript 모듈을 동적으로 로딩한다. 모든 주요 브라우저(데스크톱/모바일)에서 지원되고 있기 때문에, 호환성에 대한 걱정 없이 바로 사용할 수 있다.

import("./myModule.mjs")  
    .then(module => {
        ...
    });

// using async/await
(async () => {
    const module = await import("./myModule.mjs");
    ...
})();

더욱 자세한 내용은 2019년과 이후 JavaScript의 동향 – JavaScript(ECMAScript) 글의 ‘모듈 지원 > 동적 import()’ 절을 참고한다.

import.meta

현재 실행 중인 모듈에 호스트에서 사용 가능한 메타데이터 객체를 설정한다.

<script type="module">  
  import './index.mjs?someURLInfo=5';
</script>  
// index.mjs
new URL(import.meta.url).searchParams.get('someURLInfo'); // 5  

globalThis

globalThis는 환경에 따라 다른 최상위 객체(브라우저의 경우 window, Node.js의 경우 global 등) 접근 방법의 상이함으로 인한 불편을 해소, 모든 환경에서 동일 객체를 사용해 접근할 수 있게 한다. 모든 주요 브라우저에서 지원된다.

globalThis.setTimeout;  // window.setTimeout  

더욱 자세한 내용은 2019년과 이후 JavaScript의 동향 – JavaScript(ECMAScript) 글의 ‘globalThis’ 절을 참고한다.

BigInt

새로운 원시(Primitive) 숫자 타입으로, 그간 JavaScript에서 사용 가능한 최대 정수 값(Number.MAXSAFEINTEGER) 이상의 값을 사용할 수 있다.

사용 방법은 BigInt() 함수로 감싸거나 또는 숫자 뒤에 n을 덧붙이면 된다.

Number.MAX_SAFE_INTEGER  
// 9007199254740991

Number.MAX_SAFE_INTEGER + 10 - 10  
// 9007199254740990

BigInt(Number.MAX_SAFE_INTEGER) + 10n - 10n  
// 9007199254740991n

Promise.allSettled

나열된 여러 개의 Promise 모음이 모두 처리(settled)(이행(fulfilled) 또는 거부(rejected))된 후, 각 Promise의 결과 값의 컬렉션을 반환한다.

const promise1 = Promise.resolve(3);  
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'foo'));

Promise.allSettled([promise1, promise2]).  
  then((results) => results.forEach((result) => console.log(result.status)));

// 결과 값:
// "fulfilled"
// "rejected"

for-in mechanics

명확하지 않았던 for-in 열거 순서에 대한 방식을 정의한다.

var o = {  
  p1: 'p1',
  p2: 'p2',
  p3: 'p3',
};

var keys = [];  
for (var key in o) {  
  if (key === 'p1') {
    o.p4 = 'p4';
  }
  keys.push(key);
}

// ['p1', 'p2', 'p3']
// IE8의 경우: ['p1', 'p2', 'p3', 'p4']
console.log(keys);  

Optional Chaining (?.)

새로운 연산자인 ?.는 연결된 체인의 속성 값이 nullish(null 또는 undefined)인 경우, 오류를 발생시키는 대신 undefined를 반환한다.

const title = data && data.article && data.article.title

// 위의 표현식은 다음의 optional chaining 연산자를 통해 동일하게 표현될 수 있다.
const title = data?.article?.title

// data.article.title 값이 존재하는 경우, title 값을 반환
// 그렇지 않은 경우에는 undefined를 반환

Nullish coalescing Operator (??)

새로운 연산자인 ??는 좌측 값이 null 또는 undefined인 경우에만 평가한다.

"" || "default value"
// default value

null ?? "default value"  
// default value

"" ?? "default value"
// ""

false ?? "default value"  
// false

export * as ns from “mod”;

모듈을 모두 import한 후, 새로운 이름(namespace)로 export할 수 있게 하는 문법이다.

export * as ns from "mod";  

ECMAScript 2021

String.prototype.replaceAll

문자열 전체에 대한 치환을 하려면 현재는 정규식에 ‘g’ 플래그를 사용해야 하고, 특수 문자등에 대해 escape 처리등이 필요하다. 이러한 불편함을 제거하기 위해 .replaceAll()이 제안되었다.

'x'.replace('', '_');  
// '_x'

'xxx'.replace(/(?:)/g, '_');  
// '_x_x_x_'

'xxx'.replaceAll('', '_');  
// '_x_x_x_'

Promise.any

나열된 여러 개의 Promise 모음 중, 첫 번째로 이행(fulfilled)되고 처리(resolved)된 Promise를 반환한다.

const promise1 = Promise.reject(0);  
const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'quick'));  
const promise3 = new Promise((resolve) => setTimeout(resolve, 500, 'slow'));

Promise.any([promise1, promise2, promise3])  
  .then((value) => console.log(value)); // "quick"

WeakRefs

WeakRefs 객체는 약한 참조(weak reference)를 갖는 객체를 저장하며, 향후 참조되지 않지만 GC 대상에 포함되지 못하는 상황을 예방하기 위해 사용된다.

WeakMap(), WeakSet() 그리고 WeakRef()로 구성된다.

function counter(element) {  
    const ref = new WeakRef(element);
    let count = 0;

    const interval = setInterval(() => {
        const element = ref.deref();

        if (element) {
            element.textContent = ++count;

        // 대상 element가 사라지면 아래 블록이 실행
        } else {
            console.log("The element is gone.");
            clearInterval(interval);
        }
    }, 1000);
}

counter(document.getElementById("counter"));

// 일정시간 뒤에 element를 제거
setTimeout(() => {  
    document.getElementById("counter").remove();
}, 3000);

Logical Assignment Operators

논리 연산자와 할당 표현식의 혼합 사용에 대한 제안이다.

// "Or Or Equals" (or, the Mallet operator :wink:)
a ||= b;  
a || (a = b);

// "And And Equals"
a &&= b;  
a && (a = b);

// "QQ Equals"
a ??= b;  
a ?? (a = b);  

Numeric Separators

아주 긴 숫자 값을 다루는 경우, 값의 구분점이 존재하지 않기 때문에 값을 빠르게 인식하지 못하는 어려움을 해결하기 위해 숫자 구분값에 underscore(_) 문자를 사용하는 제안이다.

1000000000   // Is this a billion? a hundred millions? Ten millions?  
101475938.38 // what scale is this? what power of 10?

1_000_000_000 // 10억  
101_475_938.38  

새로운 또는 완료되지 않은 제안들

아직 논의 중인 제안 중에 흥미롭거나 또는 표준 명세로 발전할 것으로 예상되는 유력한 제안들 몇 가지를 살펴보겠다. 2019년 글에서 언급했던 클래스 필드 선언자와 관련된 제안들은 모두 변화 없이 Stage 3 단계에 머물러 있는 상태다.

KV Storage 모듈(Suspended)

2019년 글에서 소개한 첫 빌트인 모듈인 KV Storage는 현재 명세 논의에 대한 진행이 잠정 중단된 상태다.

Chrome Origin Trials에서 사용이 가능했으나, Origin Trials 종료와 함께 다른 브라우저 벤더들과 처음 제안자였던 Chromium 팀조차도 구현에 대한 관심을 나타내고 있지 않고 있다(참고: A proposal for an async key/value storage API for the web).

Top-level await(Stage 3)

Top-level await는 모듈을 하나의 커다란 async 함수처럼 동작하게 한다.

Node 14.3.0 릴리스에서는 Top-level await 지원이 추가되었다(참고: Support for Top-Level Await).

// awaiting.mjs
import { process } from "./some-module.mjs";  
const dynamic = import(computedModuleSpecifier);  
const data = fetch(url);  
export const output = process((await dynamic).default, await data);

// usage.mjs
import { output } from "./awaiting.mjs";  
export function outputPlusValue(value) { return output + value }

console.log(outputPlusValue(100));

setTimeout(() => console.log(outputPlusValue(100), 1000);  

Pipeline Operator(Stage 1)

OCaml, Elm, Hack 등에서 사용하는 문법과 유사한 새로운 |> 연산자에 대한 제안이다. Stage 1 단계 상태이기 때문에 제안에 대한 단계별 논의 과정에 따라 문법과 세부적 내용이 변할 수 있음을 유의하기 바란다(참고: 제안 슬라이드)

let result = exclaim(  
    capitalize(
        doubleSay("hello")
    )
);

let result = "hello"  
    |> dobleSay
    |> capitalize
    |> exclaim;

Records & Tuples(Stage 1)

현재 불변성과 관련해 이미 라이브러리 형태로 접근하는 Immutable.jsImmer 등이 있지만, 복잡/어려운 디버깅, 데이터 접근을 위해 추가적인 메서드 호출 그리고 불변 데이터를 일반 데이터로 변환에 많은 비용이 소요될 수 있는 점은 라이브러리 영역에서 해결되기 어렵다.

이 제안은 새로운 2가지 형태의 깊은 불변성 데이터 구조(deeply immutable data structure)에 대한 것으로 원시(Primitive) 데이터 또는 Records/Tuples 만을 포함시킬 수 있다.

  • Record: 불변성 객체(Object-like) 구조 #{ x: 1, y: 2 }
  • Tupules: 불변성 배열(Array-like) 구조 #[1, 2, 3, 4]
const marketData = #[  
    #{ ticker: "AAPL", lastPrice: 195.855 },
    #{ ticker: "SPY", lastPrice: 286.53 },
];

Record and Tuple Playground에서 온라인에서 직접 실행하고 테스트해 볼 수 있다.

Temporal (Stage 2)

Date 객체는 오랜 시간동안 날짜/시간을 다루기 위한 표준으로 사용되어 왔으나, 개발자들에게 많은 고통을 동시에 안겨주기도 했다.

새로운 Temporal 제안은 모던한 날짜/시간 API를 제공해 기존 Date 객체의 여러 불편 요소를 해소하는 것을 목표로 한다.

기본 원칙은 다음과 같다.

  • 모든 Tempral 객체는 불변하다.
  • 날짜는 지역 캘린더 시스템으로 표현되나, Proleptic Gregorian Calendar로 상호변환 가능해야 한다.
  • 모든 일의 시간값은 표준 24시간 표현식에 따라 처리된다.
  • Leap second는 표현되지 않는다.

제안 발의자 중 한 명인 Philipp Dunkel이 직접 설명하는 Podcast Philipp Dunkel on Temporal을 들어보라.

TypeScript

이제 TypeScript의 사용은 더이상 새로운 일이 아닐 정도로 널리 보편화되었다고 할 수 있다. JavaScript의 슈퍼셋으로, 새로운 ECMAScript 명세들의 도입은 명세의 공식 릴리스 이전에 이뤄지고 있기도 하다.

ECMAScript 2020의 Optional Chaining, Nullish Coalescing 등은 2019년 11월의 TypeScript 3.7 릴리스에 반영되기도 했다.

그림 10 State of JS 2019: JS로 컴파일되는 언어 선호도 설문 조사

현재 프런트엔드 주요 프레임워크인 Vue.js는 Vue.js 3.0에서 TS로의 전환을 일찍이 발표했고, Angular는 이미 오래 전부터 2.0을 통해 TS를 기본적으로 사용하고 있다.

또한 작년부터 큰 주목을 받고 있는 Svelte도 TypeScript 지원을 발표했다(참고: Svelte <3 TypeScript).

React는 어떨까? React의 경우 자신들(Facebook)이 개발한 정적 타입 검사 도구인 Flow를 사용하고 있다. 하지만 주요 React 핵심 생태계 도구들은 이미 전환을 완료했거나 계획하고 있다.

그림 11 코어들이 TS로 재작성된 다양한 React 생태계 라이브러리들의 현황(출처: https://twitter.com/swyx/status/1260888049958838272)

TS의 영향력 확대는 무엇을 의미할까? 그간의 타입에 대한 언어적 특성에 대한 부족함에 대한 단점은 대다수 보완될 것이며, JavaScript의 발전과 TS는 상호적인 발전을 이루어가는 데 서로가 더욱 밀접한 관계를 유지하게 될 것으로 예측된다(참고: Tackling TypeScript: Upgrading from JavaScript).

Transpiler(Babel)는 계속 필요할까?

테스트 프레임워크인 AVA는 2020년 1월 릴리스한 v3에서 코어에서 Babel 사용을 제거했다. 이는 과거 Node.js의 모던 ECMAScript 문법들의 미지원에 기인했던 것으로, 오늘날 Node.js는 대부분의 모던 ECMAScript 문법들을 지원하기 때문에 더이상 별도의 transpile 과정이 필요없어진 때문이라고 그 이유를 밝혔다(참고: We’re proud to introduce AVA 3!).

Babel의 등장(2014년, 6o5)은 새로운 명세를 즉시, 바로 사용할 수 있도록 해주는 장점과 함께 웹 개발의 가장 큰 골칫덩어리였던 크로스브라우저 이슈를 단번에 해결했다. 오늘날 프로젝트(특히나 레거시 브라우저를 대상으로 하는 경우)에서 Babel을 사용하지 않는 경우는 거의 없다고 봐도 무방하다.

Babel 코어 메인터너인 Henry Zhu가 참여하는 Podcast The Babel Podcast에서는 TC39 멤버들과 Babel을 처음 개발했던 Sebastian McKenzie가 들려주는 흥미로운 얘기를 들어볼 수 있다.

그러나, AVA의 사례에서와 같이 적어도 Node.js만을 대상으로 경우라면 더는 사용이 필요하지 않을 것이다. 더욱이 그 기능이 점점 더 확대되면서 비례하게 증가하는 플러그인, preset 등에 대한 환경 설정은 골칫거리가 되어가고 있기도 하다(참고: You don’t need Babel with Node).

사실, 우리 모두는 Transpiler의 미래를 어느 정도 예측해 볼 수 있다고 생각된다. 대다수의 환경에서 문제없이 최신 JavaScript 문법들을 사용할 수 있다면 굳이 복잡하고 불편한 설정들로 인해 골치를 썩으려 하는 이는 없을 것이다.

하지만 Transpiler의 유용성은 단지 최신 명세를 변환시킨다는 것에서만 있지는 않다. 오늘날의 모던 프레임워크들에서 사용되는 새로운 문법(JSX와 같은) 처리 등과 같이 그 필요성은 앞으로도 계속 유지될 수도 있을 것이다. 언제나 미래를 예측하는 것은 어렵지만, 무엇이 되었든 코드 작성이 아닌 환경 설정에 너무 많은 시간을 보내는 것은 참기 어려운 일이긴 하다.

마치며

JavaScript 개발자에게 교과서와도 같은 엄청난 두께(한글판 1,200여 페이지)를 자랑하는 자바스크립트 완벽 가이드의 7번째 에디션이 곧 출간을 앞두고 있다.

image-20200716-111434-778.png

이전 에디션의 1,096페이지(영문 기준)에서 650페이지로 분량을 줄였고(주로 레퍼런스 영역을 삭제), 현재 트렌드에 맞추어 JavaScript 언어에 대한 것 외에 웹과 Node.js 그리고 JavaScript 생태계에 대한 내용도 포함될 예정이라고 한다(참고: Changes in the Seventh Edition of JavaScript: The Definitive Guide).

저자인 David Flanagan은 최근(2020/05)의 한 인터뷰에서 첫 번째 에디션 출간과 관련된 비하인드 스토리에 대한 질문에 다음과 같이 대답했다.

당시 Java를 둘러싼 커다란 논쟁 중 하나는 Java “applets”이 웹 브라우저에 동적 콘텐츠를 추가할 수 있는지였다. JavaScript는 유망해 보였고 (중략) 썬 마이크로시스템즈(Java를 개발한 회사로 현재는 오라클로부터 인수되었다) 엔지니어 중 한 명에게 브라우저 영역에서 Java보다 JavaScript가 더 중요해질 것 같다고 말했을 때 그는 비웃었다. 하지만 이 책의 7번째 에디션을 통해 나는 내가 옳았음을 생각하기 시작했다.

In those days the buzz around Java was that Java “applets” could add dynamic content to web browsers. JavaScript seemed like a promising … remember talking to an engineer from Sun Microsystems … When I told him I thought JavaScript might become more important in the browser than Java, he scoffed. But seven editions of my book later, I’m starting to think I was right(!)

‐ A Q&A with David Flanagan

2020년과 이후 JavaScript의 동향

Tag

박재성|네이버 Platform LabsNAVER에서 프론트엔드 기술 리서치와 오픈소스 차트 라이브러리 billboard.js 개발을 리딩하고 있습니다. 오픈소스 인식 개선과 신기술 탐구에 관심이 많으며, 다양한 기술 공유를 위해 노력하고 있습니다.NAVERfacebook

Copyright © NAVER Corp. All Rights Reserved.


코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다