2019년 11월 30일~12월 1일, 2일간 일본 도쿄에서 열리는 JSCONF JP에 참석하였다. 강의를 들은 후부터 2년 정도가 지나서, 현재와는 다른 부분이 있을 수도 있으나 전체적으로 자바스크립트 개발자에게 도움이 되는 내용이기에 당시에 정리한 글을 올려본다.

THE STATE OF JAVASCRIPT

The State of Javascript가 만들어진 이유는 현대 javascript 환경에서 수많은 도구가 쏟아지며 개발자들이 고통받고 있고((javascript fatigue, 자바스크립트 피로), 이를 해소하기 위함이다. 무엇을 선택하고 사용할지 알기 쉬운 지표를 얻기 위해 개발자들을 대상으로 설문조사도 시행하고 있으며, 당시에는 2019년에 대한 설문조사도 막 끝났던 참이었다.

그리고 설문 중인 2019년의 결과를 살짝 보여주겠다며 javascript 개발 시에 사용되는 유명한 도구들의 사용 비율 변화를 보여주었다. 비율 변화 그래프에서 흥미 있었던 부분은 역시 직접 사용할 일이 많은 프레임워크 부분이었다. 현재 프로젝트에서도 사용 중인 React는 전체 사용자 비율이 천천히 올라가고 있지만, '사용하였으나 앞으로 사용하지 않을 예정'의 비율이 점점 많아지고 있었다.

Vue의 경우는 '들어본 적 없음'이 0%에 가까워지며 존재감을 드러내고 있다는 것을 나타내었으며, SVELTE는 프레임워크는 Vue 초기와 비슷한 양상을 보이고 있어 가까운 미래에 인기를 얻지 않을까 하는 견해를 보였다. 이 외에도 Next.js 사용률은 React와 함께 증가하며, typescript 사용자가 급격히 상승하는 경향을 보이는 등 재미있는 결과를 볼 수 있었다.

또한 The state of javascript의 성공을 디딤돌 삼아 The State Of CSS(https://stateofcss.com/)가 만들어졌으며, 동일한 개발자가 참여한 Best Of Javascript(https://bestofjs.org/) 또한 비슷한 취지에서 만들어진 페이지이다.

JAVASCRIPT AST 프로그래밍: 입문과 한걸음 더

자신이 Javascript로 개발을 했다면, 특히 eslint나 babel을 적용해본 사람이라면 AST를 자기도 모르게 접해본 적이 있다는 뜻이다. 발표자는 AST를 사람들에게 좀 더 친숙하게 만들고, 이걸로 무언가 해보자는 생각이 들게 하는 것이 이번 발표의 목적이라고 발언하였다.

AST란 Abstract Syntax Tree(추상적 구문 트리)의 줄임말이다. 풀어 쓰자면, javascript(그리고 대부분의 프로그래밍 언어)는 코드 텍스트를 한 번 파싱 한 후, 이를 해석하여 실행시킴으로써 우리가 원하는 결과를 노출시킨다. 이때 구문을 Tree 형태로 파싱 한 결과를 AST라고 부른다. javascript에서는 우리에게 친숙한 JSON 형태의 object tree로 표현된다.

AST를 다루기 위해서는 기본적으로 다음과 같은 도구를 사용한다.

  • parser → 코드를 쪼개서 AST로 만드는 도구
  • walker, traversal, visitor... → AST를 탐색해서 코드 조각을 찾기 위한 도구
  • unparser → 편집한 코드를 다시 붙이는 도구

이 도구들을 사용하면 다음과 같은 과정을 거쳐서 코드를 프로그래밍적으로 수정할 수 있다.

  • Javascript → (parser) → ASTJS → (탐색을 통한 코드 수정) → ASTJS → (unparser) → Javascript

다음 코드에서 무슨 일이 일어나는지 알아보자.

("b" + "a" + + "a" + "a").toLowerCase() // banana

코드를 실행시키면 바나나라는 결과가 나온다. 이 코드를 AST로 분석하면 왜 바나나가 되었는지 알 수 있다.

아래 코드를 참조해서 간단한 AST 파서를 만들어보았다.

//parser.js

const acorn = require('acorn'); // parser를 포함한 라이브러리
const code = process.argv[2];
const ast = acorn.parse(code);
console.log(JSON.stringify(ast));

이 코드를 아래처럼 실행시키면 AST로 만들어진 코드 구조를 볼 수 있다.

$node parser.js "("b" + "a" + + "a" + "a").toLowerCase()" | jq

{
  "type": "Program",
  "start": 0,
  "end": 31,
  "body": [
    {
      "type": "ExpressionStatement",
      "start": 0,
      "end": 31,
      "expression": {
        "type": "CallExpression",
        "start": 0,
        "end": 31,
        "callee": {
          "type": "MemberExpression",
          "start": 0,
          "end": 29,
          "object": {
            "type": "BinaryExpression",
            "start": 1,
            "end": 16,
            "left": {
              "type": "BinaryExpression",
              "start": 1,
              "end": 12,
              "left": {
                "type": "BinaryExpression",
                "start": 1,
                "end": 6,
                "left": {
                  "type": "Identifier",
                  "start": 1,
                  "end": 2,
                  "name": "b"
                },
                "operator": "+",
                "right": {
                  "type": "Identifier",
                  "start": 5,
                  "end": 6,
                  "name": "a"
                }
              },
              "operator": "+",
              "right": {
                "type": "UnaryExpression",
                "start": 9,
                "end": 12,
                "operator": "+",
                "prefix": true,
                "argument": {
                  "type": "Identifier",
                  "start": 11,
                  "end": 12,
                  "name": "a"
                }
              }
            },
            "operator": "+",
            "right": {
              "type": "Identifier",
              "start": 15,
              "end": 16,
              "name": "a"
            }
          },
          "property": {
            "type": "Identifier",
            "start": 18,
            "end": 29,
            "name": "toLowerCase"
          },
          "computed": false
        },
        "arguments": []
      }
    }
  ],
  "sourceType": "script"
}

모든 결과를 보지 않아도 AST에 대한 구조는 감이 올 것이다. 표현식의 좌변은 left, 우변은 right 속성으로 들어가며, type이 Identifiername 속성이, Expression이 들어가는 type에는 operator 속성이 있는 등 몇가지 특징을 찾을 수 있다.

이 결과에서 banana의 비밀을 찾을 수 있는데, 바로 UnaryExpression 부분이다. 여기에서 3번째 "a"에 대한 단항 연산을 하기 때문에, 'baNaNa'에 toLowercase를 적용하여 'banana'가 된다. parser 라이브러리에 따라 각 Object의 구성요소는 조금씩 다른데 expure이라는 라이브러리가 가장 표준에 가까운 라이브러리라고 한다.

이와 같이 AST를 잘 사용하면 코드 자체에 대한 디버깅이 가능하다. 이렇게 파싱 한 정보를 pipe형식 함수에 통과시킴으로써 코드 조각 하나하나에 대한 탐색도 할 수 있는데, 예를 들면 production 환경에서 console.log 함수를 제거하는 라이브러리를 만드는 것도 가능하다.

FOUR YEARS OF JS PROC-GEN

연사: Andy Hall

PROC-GEN(Procedural Generator)란 간단히 말하자면 어떤 컨텐츠를 알고리즘을 통해 만들어 내는 방법이다. 일반적으로는 게임에 주로 사용하는데, 가장 유명한 예로는 세계적으로 유명한 게임 "마인크래프트"가 있다. 이 게임의 맵은 어떤 알고리즘에 의거하여 지형을 적절하게 만들고 동식물이 알맞은 지형에 배치되도록 하며 나무나 건물 등이 올바른 형태를 가지지만 이런 구조물이 모두 동일하지는 않게 만들어진다.

PROC-GEN을 설명하기 위해서 발표는 네 단계를 거쳐 진행되었다.

첫번째, 알고리즘은 똑똑하지 않아도 된다.

발표자는 알고리즘을 사용해서 무언가를 만들 때 반드시 빠르고 좋은 알고리즘을 쓸 필요는 없다고 발언하였다. 극단적으로는 완전 랜덤으로 수백만 번을 반복하더라도 올바른 결과가 나오면 그것도 올바른 알고리즘이라고 생각한다고 하였다.

머신러닝이나 인공지능이라면 훌륭한 알고리즘을 만들어 내는 것이 중요하겠지만, proc-gen에서는 이미 있는 알고리즘을 창의적이고 반복적으로 사용하는 것이 중요하므로, 알고리즘 자체에 대해서 큰 고민을 하지 말라는 요지였다.

두 번째, Functional Space로 사고해라.

함수로 input에 어떠한 변화를 주었을 때 일정한 결과가 나온다면 그것만으로도 proc-gen을 잘 사용하는 것이라고 한다. 발표자는 이번엔 가상의 게임을 예시로 들었는데, 어떤 함수를 통해 proc-gen으로 몬스터의 종류를 만드는 과정을 보자.

위 예시에서는 makeSlime에 어떤 color 값을 input으로 준다면 input에 따라서 결과인 슬라임의 색이 변화할 수 있음을 보여준다. 여기서 슬라임에 색을 입히는 알고리즘이 사용되고 있는데, 만약 색에 더해 크기에 대한 input을 받고 이를 반영한다면 다음과 같이 2차원 공간에 매핑할 수 있다.

이처럼 proc-gen은 콘텐츠를 만들 때, input에 따라서 n차원 공간에 매핑하는 함수로 생각하면 된다고 한다. 만약 게임 안의 3차원 맵을 만든다고 하면 어렵게 들리지만, 내부에 어떠한 블랙박스 알고리즘이 있는 function(x, y, z)의 결과를 3차원 공간에 맵핑한 결과물이라고 생각하면 좀 더 간단할 것이다.

세 번째, 숨겨진 레이어를 사용하라

만약 proc-gen으로 음악을 만든다고 생각한다면 시간이라는 x축에 음의 높낮이를 매핑하는 작업이라고 할 수 있다. 그러나 input이 하나라면 반복적이고 단조로운 음악이 되어버리므로 듣는 사람 입장에서는 별로 즐겁지 않을 것이다.

음악적으로는 코드 진행이나 화음을 통해 이런 점을 극복해야 한다. 이걸 적용하면 input은 두 개, 세 개로 늘어나게 되지만 결과물은 여전히 시간에 대한 음의 높낮이로 그려진다. 결과 자체는 1차원이지만 매핑하는 알고리즘은 2차원 이상으로 늘어나게 되며, 여기서 화음이나 코드 진행 input을 발표자는 숨겨진 레이어라고 불렀다.

여기서 다시 앞서 말한 게임 "마인크래프트"를 예시로 들어주었는데, 3차원으로 이루어진 공간이지만 숲이나 들판 등 어떤 영역의 생태계라는 숨겨진 레이어가 적용되고 있다고 생각할 수 있다는 것이다.

넷째, 플레이그라운드를 만들어라

어쨌거나 proc-gen으로 무언가를 만드는 데에는 반복과 시행착오가 중요하기 때문에, 케이스에 따라 결과를 바로 볼 수 있는 플레이그라운드는 만들어 두는 것이 좋다. Proc-gen 뿐만 아니라 대부분에 해당하는 이야기이며, 이는 만들어지는 컨텐츠에 대한 유닛 테스트적인 의미도 겸하게 된다. 특히 javascript는 눈으로 보이는 테스트 환경을 만드는 데에 최적화되어있으므로 proc-gen을 사용해보기 좋은 언어라고 평가하였다.

WRAP-UP: High Performance Javascript

어떤 프로그래머든 최적화에 대한 고민이 있을 것이다. 웹의 속도가 빨라지면서 최적화의 중요성은 점점 늘고있는데, 이 세션에서는 Javascript의 구조를 알고, 각 구조에서 어떻게 최적화를 하고 javascript의 퍼포먼스를 높일 수 있나에 대하여 알아보았다.

먼저 Javascript에는 세가지 레이어가 있다.

  • Product: javascript로 만들어진 결과물 (application, library...)
  • Runtime: js를 실행하는 플랫폼(node.js, browser)
  • Engine: 언어를 파싱하고 컴파일한 후 실행하는 엔진(V8, SpiderMonkey...)

가장 하위 레이어인 Engine에서는 IC(인라인캐싱), SMI/HeapNumber 등을 사용해서 메모리를 줄이고 있고, Runtime 레이어에서는 비동기 실행, 코드 fetching 시간을 줄이려는 노력을 하고 있다. 실질적으로 개발하게 되는 Product단에서는 memorization이나 캐싱, 또는 알고리즘 개선으로 퍼포먼스를 높일 수 있다.

JS는 이미 Engine과 Runtime 단에서는 높은 수준의 최적화를 보여주고 있어, 이 부분에서 효과가 미미한 최적화(micro optimization)를 하기보다는 Product 최적화에 더 힘을 쏟는 것이 권장된다.

실질적으로 개발자가 할 수 있는 최적화에 대해서는 서버 사이드와 클라이언트 사이드로 나누어 설명되었다.

서버 사이드 자바스크립트라고 하면 일반적으로 node.js를 사용하므로, 설명도 node.js 기준으로 진행하였다. 웹 애플리케이션에서는 네트워크나 파일 시스템의 I/O가 일어나면서 병목현상이 일어나기 쉬운데, node.js는 single-threaded 기반이지만 EventLoop를 사용하여 비동기 논블로킹 방식을 가능하도록 하였다. 그러나 끝에 -Sync가 붙는 I/O함수를 실행하면 함수가 완료될 때까지 다른 작업이 불가하므로 이러한 함수의 사용은 자제해야 한다. (예: readFileSync 대신 readFile을 사용)

그러나 이런 I/O 함수가 아니더라도 긴 작업으로 병목현상이 일어날 수 있다. 이런 경우에는 timeout등을 사용하여 적절하게 스케줄링을 해줄 필요가 있다.(참조)

또한 streamAPI를 사용하면 I/O 도중에도 실시간으로 데이터를 받으므로 데이터가 커도 멈춤 없이 받아올 수 있고, 메모리를 효율적으로 사용 가능하며 브라우저에도 실시간으로 렌더링이 가능하다.

클라이언트 사이드에서는 일반적인 웹 퍼포먼스는 제외하고, 크롬 브라우저의 기능에 맞추어 설명이 진행되었다. 브라우저에서 자바스크립트를 실행할 때 가장 오래 걸리는 부분은 코드를 파싱 하고 컴파일하는 부분인데, 크롬은 이 부분을 최소한으로 줄이기 위해서 code caching을 사용해서 72시간 이내에 2번 이상 사용된 스크립트, 서비스 워커, 컴파일 결과를 모두 캐싱한다. 이후에 동일한 스크립트를 실행할 때는 파싱과 컴파일 과정을 모두 스킵할 수 있도록 되어 있다.

모던 브라우저에서는 모듈을 import/export를 가능하게 하는 ESModules를 기본적으로 지원한다. 그 중에서도 최신 크롬은 <link rel="preload" rel-"modulepreload"> rel속성에 preload라는 값을 지원하는데, 이는 브라우저에게 해당 리소스를 최우선으로 로드하도록 해준다. 스크립트 파일을 가져오는 modulepreload를 사용하면 실제로 코드를 실행하지 않아도 파싱을 해두고, 실행할 때에는 fetch와 compile만을 수행하도록 한다.

그 외에도 CPU의 여유가 있을 때 실행하도록 하는 idling(requestIdleCallback, cancelIdleCallback함수로 사용)이 있으며, 아예 코드를 늦게 파싱 하는 Lazy parsing 전략 등을 사용할 수 있다.

How To Boost Your Code With WebAssembly

웹 어셈블리는 브라우저 위에서 돌아가는 바이트로 이루어진 코드이다. 어셈블리 이름을 달고 있지만 실제 어셈블리처럼 기계어와 1:1로 대응되는 개념은 아니며, 실행을 위해서는 VM을 사용하거나 기계어로 한번 더 컴파일해야 하므로 JS보다는 빠르지만 기계어보다는 느리다. 웹 어셈블리가 등장하기 이전에 2012년부터 Emscripten을 사용하여 브라우저에서 C 혹은 C++을 컴파일하는 것 자체는 가능하였으나, JS로 CPU를 에뮬레이션 할 필요가 있었기 때문에 너무 느려서 실제로 쓰기는 힘들었다고 한다.

2015년부터 웹 어셈블리에 대한 논의가 시작되었고 2017년에는 모던 브라우저에서 버전 1.0(MVP)를 실제로 지원하기 시작하였으며, 2019년 현재에 이르기까지 WASI를 발표하는 등 활발하게 피처 추가 및 스펙 결정이 이루어지고 있다.

기능 자체는 JS와 크게 다를 것이 없으나, 속도 면에서 웹 어셈블리의 가치는 빛을 발한다. JS에서는 너무나도 느렸던 동작들을 훨씬 빠르게 끝낼 수 있다. 다만 C보다는 상당히 느린데, 그 이유는 안정성을 위해 정의되지 않은 동작(UB, undefined behavior)을 없앴기 때문이다. 예를 들면 C에서는 지정한 배열 크기보다 바깥을 참조하는 것은 정의되지 않은 동작이지만 웹 어셈블리에서는 확보한 메모리 내부라면 범위 바깥을 참조하는 동작까지 정의되어 있다. 그러나 이 안정성이 웹 어셈블리의 큰 장점으로 버그가 있어도 안심하고 실행할 수 있다는 특징을 만들어낸다. 이런 점을 종합해서 속도에 대해서는 Java에는 밀리지만 Javascript 보단 확실히 빠르다는 결론을 도출된다.

현재는 IE11을 제외한 대부분의 모던 브라우저에서 지원하고 있으며, eBay에서는 실제로 바코드 스캐너의 개발에 웹 어셈블리를 사용하고 있다. 또한 Bytecode Allience라는 단체에서는 웹 어셈블리를 Universal Binary로 성장시키려는 움직임을 보이고 있다.

JS 개발자가 웹 어셈블리를 좀 더 친숙하게 적용하는 방법 중 하나로는 AssemblyScript(이하 AS)를 사용하는 것이다. AS는 타입 스크립트를 문법을 기반으로 한 새로운 프로그래밍 언어로, 웹 어셈블리로 변환이 가능하며 npm을 통해 쉽게 세팅이 가능하다. 문법 등은 타입 스크립트에 가까우나 내용물은 C에 가깝다고 한다.

발표자는 자신이 느낀 AS로 코딩하면서 막히기 쉬운 점을 몇 가지 말해주었다.

  • C에서 메모리를 가리키는 포인터가 없는 대신 offset을 나타내는 usize 형만이 존재
  • load<u16>(), store <u16>()등이 little endian으로 규정되어있는 반면 JS의 Unit16 Array는 호스트의 endian을 따르므로 endian 호환이 안될 수 있음
    • 그러나 최근 환경에서는 대부분 리틀 엔디안을 사용하므로 크게 문제되지는 않을 것
  • 겉보기에는 typescript와 상당히 유사하므로 이에 따른 실수가 잦아질 수 있음

또한 웹 어셈블리만으로 Object나 DOM에 관련된 처리를 하기는 어렵고, Byte의 처리나 연산 처리를 했을 때 효과적으로 사용할 수 있을 것이라고 한다.

STREAMS API 제대로 이해하기

I/O에서 스트림이란 데이터를 '흐르는 것'으로 보고, 이 데이터의 처리 또는 처리를 위한 데이터 형식이다. node.js에서는 Stream API를 통해 데이터 흐름을 제어 가능하므로, Stream을 제어하는 자가 node.js를 지배한다고 해도 과언이 아니라고 한다.

기존에 Stream 없이 큰 데이터를 통신하면 데이터가 모두 전송될 때 까지 기다려야 했지만, Stream을 사용하면 개발자가 정의한 단위로 데이터를 분할하여 전송하는 것이 가능하다.

node.js에서 지원하는 Stream API는 크게 세가지로 나뉜다.

  • Readable Stream: ReadableStream 클래스의 constructor로 데이터 전체를 어떻게 분할하여 전송할지를 정하면(Underlying Source), 인스턴스로부터 Reader 객체를 받아 Promise를 반환하는 read() 함수를 통해 Stream 내부의 데이터를 읽어 들이는 것이 가능하다.
    • Readable은 (Node에서) 분할된 스트림 단위로 읽어 들일 수 있다는 뜻이다.
  • Writable Stream: WritableStream 클래스에 분할 데이터를 어떻게 가져올지(Underlying Sync)를 정하고, 인스턴스로부터 Writer 객체를 받아 Promise를 반환하는 write() 함수를 통해 Stream을 통해 흘러온 데이터를 쓰는 것이 가능하다.
    • Readable Stream과는 반대로 Node에서 스트림에 데이터 쓰기가 가능한 것을 Writable Stream이라고 부르고 있다.
  • Transform Stream: Transform Stream 클래스에서는 클래스 내부의 Writable Stream(클래스 외부의 ReadableStream → 클래스 내부의 WritableStream)으로부터 가져온 분할 데이터를 어떻게 변환할지 정하고(Transformer), 인스턴스로부터 ReadableStream객체와 WritableStream객체의 인스턴스를 가져오는 것이 가능하다.
    • Transform Stream은 Readable Stream과 Writable Stream 사이에서 데이터를 변환하는 Stream이다.

이 세 가지 스트림을 연결하기 위해서는 pipeTo 함수와 pipeThrough를 사용할 수 있다.

readableStream.pipeTo(writableStream)
transformStream.pipeTo(writableStream)

readableStream.pipeThrough(transformStream)

만약 ReadableStream과 WritableStream에서 불러온 활성화된 ReaderWriter가 이미 있는 경우, 해당 stream의 locked 속성은 true값이 되고 해당 stream이 잠겨 사용 불가 상태가 된다.

const readable = new ReadeableStream();
const reader = readable.getReader();
const writable = new WritableStream();
const writer = writable.getWriter();

readable.locked(); // true
writable.locked(); // true

readable.getReader() // error

같은 stream을 사용하고 싶은 경우 tee()함수를 사용할 수 있다.

const readable = usedStream;
const readable.tee() // 새로은 stream 인스턴스 두개를 반환한다. [instance1, instance2]

마지막으로 Stream과 관련된 기타 개념에 대한 설명으로 발표가 마무리되었다.

  • BackPressure: 데이터 전송이 밀려 chunk로 가득 차게 되는 경우 readable Stream에게 알려주는 시스템
  • Readable Stream에서 사용하는 underlying source에는 두 가지 타입이 있다.
    • pushSource: 준비가 된다면 언제든지 큐에 데이터를 날리는 타입
    • pullSource: 요청이 있을 때에만 데이터를 날리는 타입
  • Queuing Strategy: 각 스트림의 큐가 가지는 size와 size에 대한 최댓값을 설정 가능
    • highWaterMark: 스트림이 최대로 갖고 있을 수 있는 값(number)
    • size(chunk): 무엇을 size 기준으로 할지 정하는 값(number)
    • size 함수에서 return chunk.length구문이 있고 highWaterMark가 100이라면 chunk.length > 100인 문자열은 등록할 수 없음

React 애플리케이션의 라이선스 위반에 대해서

2017년 말 즈음 리액트 라이선스 정책이 바뀔뻔한 일이 있었다. 수많은 리액트 개발자의 반발 덕인지 다행히 MIT 라이선스로 정착되었지만, 정작 우리는 그 라이선스를 잘 지키고 있는 것일까?

발표자는 우리가 우리도 모르는 사이에 라이선스 위반을 하고 있을 수 있다고 하며, 특히 React 초기 세팅할 때 많이 쓰이는 create-react-apps 모듈은 production으로 실행하면 그것만으로도 라이선스 위반이 된다고 한다.

MIT 라이선스는 코드를 제한 없이 사용하는 대신 소프트웨어의 중요한 부분에 반드시 저작권자를 기재해야 하는 라이선스이다. 일반적으로는 install 하는 시점에서 코드 주석으로 처리되어 있기 때문에 별도의 처리를 하지 않아도 되지만, create-react-apps를 사용해서 production build를 하면 minify 되면서 주석이 사라지므로 별다른 처리를 하지 않으면 자신도 모르게 라이선스를 위반하게 된다.

해결책으로는 terser-webpack-plugin웹팩 플러그인을 사용할 것이 제시되었다. 다음과 같이 extractComments속성을 설정하면 라이선스 관련 주석을 모은 파일을 filename.LICENSE 이름으로 생성된다.

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        //...
                extractComments: true 
      }),
    ],
  },
};

또한 minify를 여러 번 실행하는 경우에도 라이선스 표기가 소실되는 케이스가 있으므로 주의해야 한다. unpkg를 통해 제공되는 react.production.min.js 파일조차 object-assign에 대한 라이선스 표기가 되어있지 않기 때문에 사용하면 라이선스 위반이 된다.

마지막으로 라이선스 위반을 하지 않았는지 체크하는 방법으로 license-checker모듈을 사용하는 방법이 있다. 다음과 같이 사용하면 각 모듈의 라이선스와 의존성을 출력해준다.

npm license-check --production --unknown

혼자서 프로젝트를 진행하다 보면 라이선스를 미처 생각하지 못할 때가 많다. 모든 라이선스를 일일이 살펴보지는 못해도 언급된 도구들을 사용해서 가능한 라이선스를 위반하지 않고 오픈소스 생태계를 지켜내는 것이 중요하다.

+ Recent posts