5.7kb의 TypeScript 컴파일러 제작

30370 단어 TypeScripttech
세상에 Type Script 컴파일러가 너무 커서 만들었어요.
https://github.com/mizchi/mints
여기서 해봐.jsx와 jsxpragma의 지원도 했기 때문에preact도 움직이고 있습니다.
https://mints-playground.netlify.app/

실시 방침

  • 첫 번째 치수 구성
  • 어쨌든 경량
  • mints 자체가 다른 코드를 구축할 때의 속도가 아니라는 것을 주의
  • 현황은 정확한 오류 보고가 없습니다.오류 메시지를 사용하면 구축 크기가 증가합니다.
  • 빈 줄과 유형 정보만 제거하면
  • ES5로의 전환 또는 commonjos로의 전환이 이루어지지 않았음
  • enum,constructor,jsx만transform의 특수 대응
  • 진지한 문법 분석을 하지 않았다
  • binary expression(예를 들어 1+1*2)은 조합 순서를 분석하지 않았습니다.틀만 떼면 필요 없어
  • 목표는prettier로 포맷된 코드를 컴파일할 수 있는 것이다
  • foo;와 같은expression statement에 대해서는 반드시 번호를 나눠야 한다.
  • 왜 그랬어


    웹에서 여러 차례 이동하는 Type Script+React의 plag 라운드를 만들었지만, 이때 문제가 된 것은 Type Script 컴파일러의 구축 사이즈였다.페이지의 간단한 TS 코드도 커다란 컴파일러가 필요하기 때문에 TS 컴파일러를 페이지에 쉽게 묻을 수 없습니다.또 개발 환경으로 타입 스크립트의 2.9MB씩 번들일 때마다 체험도 좋지 않았다.
    이는 본가의 Type Script가 브라우저에서 이동하지만 구축 사이즈와 간단한 컴파일 이외에 다양한 정적 분석을 고려하지 않았기 때문이다. 그러나 TS 코드 유형을 낮추는 것은 너무 지나치다.TS 호스트는 오래된 namespace로 작성되었으며 treeshake도 작동하지 않습니다.
    bundlephobia.우리는 각종 컴파일러의 크기를 비교했다.이것은 gzip 앞 사이즈입니다.
  • TypeScript: 2.9MB
  • @babel/core + @babel/preset-typescript: 1.8MB
  • sucrase: 176kb
  • mints: 15kb
  • esbuild-wasm: 8.4MB
  • @swc/wasm: 16MB
  • 이렇게 작다면 게임 라운드뿐 아니라 건물 제한이 있는 엣지 워커에서도 다양한 용도가 있다.

    JS로 쓴 이유.


    Rust+Wasm 연습으로 하려고 했는데 Wasm의 경우 핵심 가동시간만 6kb가 되고 nom 같은 파라 콤비네이터를 사용하는 프로그램 라이브러리는 의존성이 더 부풀어 오른다.
    Geal/nom: Rust parser combinator framework
    전체 스크래치로 쓰면 될 것 같지만, 익숙하지 않아 건물 크기를 조절하기 어려워 일단 습관적인 타입 스크립트로 실시하기로 했다.
    제가 쓴 소감인데 솔직히 JS라면 문자열을 효율적으로 처리하기 어려웠어요.내부에 있는 UInt16 Aray의 경우 정규 표현식을 추가하려는 경우가 여러 번 있지만 JS의 경우 매번 인코딩해야 합니다.
    javascript - Why is array.prototype.slice() so slow on sub-classed arrays? - Stack Overflow
    열심히 Rust제 Type Script Para를 Rome에게 맡기면 됩니다.
    Rome will be written in Rust 🦀

    mints


    이번에 구현된 Type Script Compuiler입니다.
    import {tranform} from "@mizchi/mints";
    const code = `const x: number = 1;`;
    transform(code).result; // => const x=1;
    
    이것만 사용합니다.API를 변경해야 할 수도 있습니다.

    pargen


    기존 라이브러리를 사용하지 않고 전용 파트너를 만들었습니다.
    mints/packages/pargen at main · mizchi/mints
    Pargen은 LL(k)+packrat cache의 파트너입니다.pegjs 코치입니다.
    mints의 5.7kb 중 Pargen은 2kb입니다.혹은 처음에 Pargen을 만들면 복잡한 것도 괜찮다고 느껴 TS파라를 써봤다.
    내부에$seq(),$or(),$repeat(),$not()의 기본 명령이 있어 이를 조합하여 문법을 정의한다.
    예를 들어mintsconst x: number = 1, y;의 대입문의 해석 정의는 다음과 같다.
    읽으면서
    const assign = $def(() => $seq(["=", $not([">"]), _s, anyExpression]));
    const variableStatement = $def(() =>
      $seq([
        $seq([declareType, __]),
        $repeat_seq([
          destructive,
          _s,
          $skip_opt(_typeAnnotation),
          $opt($seq([_s, assign])),
          _s,
          ",",
          _s,
        ]),
        _s,
        destructive,
        _s,
        $skip_opt(_typeAnnotation),
        $opt($seq([_s, assign])),
      ])
    );
    
    $skip(...) 이런 직접token은 이런 전용 처리를 버리고, 이에 따라 타입 스크립트의 유형 정보를 제거하면서 버린다.$skip_opt()optional의skip이기 때문에 글씨를 쓰든 안 쓰든 상관없습니다.어쨌든 처리에 있어서는 무시당할 것이다.
    해석을 위해 객체의 이름을 지정할 수 있습니다.예를 들어 엔움의 퍼스와 변형은 다음과 같다.
    const enumStatement = $def(() =>
      $seq(
        [
          K_ENUM,
          __,
          ["enumName", identifier],
          _,
          K_BLACE_OPEN,
          _,
          [
            "items",
            $repeat_seq([
              //
              ["ident", identifier],
              ["assign", $opt(enumAssign)],
              _s,
              $skip(","),
              _s,
            ]),
          ],
          [
            "last",
            $opt(
              $seq([
                ["ident", identifier],
                ["assign", $opt(enumAssign)],
                _s,
                $skip_opt(","),
              ])
            ),
          ],
          _,
          K_BLACE_CLOSE,
        ],
        (input: {
          enumName: string;
          items: Array<{ ident: string; assign?: string }>;
          last?: { ident: string; assign?: string };
        }) => {
          let baseValue = 0;
          let out = `const ${input.enumName}={`;
          for (const item of [
            ...input.items,
            ...(input.last ? [input.last] : []),
          ]) {
            let nextValue: string | number;
            if (item.assign) {
              const num = Number(item.assign);
              if (isNaN(num)) {
                nextValue = item.assign as string;
              } else {
                // reset base value
                nextValue = num;
                baseValue = num + 1;
              }
            } else {
              nextValue = baseValue;
              baseValue++;
            }
            const nextValueKey =
              typeof nextValue === "number" ? `"${nextValue}"` : nextValue;
            out += `${item.ident}:${nextValue},${nextValueKey}:"${item.ident}",`;
          }
          return out + "};";
        }
      )
    );
    

    과제.


    TS 비 JS Superrset 섹션


    TS는 JS의 하이퍼설정을 강조하지만 일부 코드의 변형을 동반한 설치도 있다.네.
    그 중에서 decorator와namespace는 사용 빈도가 낮고 + 대체 수단이 있기 때문에 지원하지 않으며 다른 것은 실현되었다.

    with


    펀치 형, with 문은 지원되지 않습니다.
    jsx<button disabled></button>와 같이 오른쪽 가장자리 값의 생략은 진정한 문법을 지원하지 않습니다.
    jsx가 방금 만들어져서 아직 디버깅을 제대로 하지 못했기 때문에 BUG가 많은 것 같아요.

    멀티바이트 문자 수


    만약'봉화134071;'같은 문자 코드가 비교적 큰 일부 문자열을 사용한다면 대화 기교에 편차가 있을 것이다.
    https://gyazo.com/3dcec3c366fabfa251b40a9cde92bd95

    앞으로 하고 싶은 거.


    성능


    현재 구축 속도는 tsc보다 몇 배 느리다.
    문법 정의에 힘쓰고 최적화기를 제작하며 예처리에서statement를 분할하고 병렬 구축 등을 한다.
    원래 Tokenize 단계에서 코드를 직접 읽지 않으면 좋을 것 같아요.
    결과적으로 현재 이런 예처리는 이미 존재한다.
    export function preprocess(input: string) {
      const { escaped, literals } = escapeLiteral(input);
      const out = escaped
        // delete inline comments /*...*/
        .replace(/\/\*(.*?)\*\//gmu, "")
        // delete line comments
        .replace(/(.*?)(\/\/.*)/gu, "$1")
        // delete redundunt semicollon except for statement
        .replace(/(?<!for\s?\()([\n\r\t; ]*;[\n\r\t ;]*)(?!\))/gmu, ";")
        // delete redundant whitespaces
        .replace(/[ \t]+/gmu, " ")
        // delete redundunt whitespaces after control token
        .replace(/([\}\{\(\)\n\r,;><])[\n\r\t]+\s*/gmu, (_, $1) => $1);
      return restoreEscaped(out, literals);
    }
    
    지금 3배 정도 늦어요.
    compileTsc [0] 75
    compileTsc [1] 21
    compileMints [0] 65
    compileMints [1] 54
    compileTsc [0] 34
    compileTsc [1] 23
    compileMints [0] 100
    compileMints [1] 62
    compileTsc [0] 36
    compileTsc [1] 32
    compileMints [0] 106
    compileMints [1] 84
    compileTsc [0] 132
    compileTsc [1] 96
    compileMints [0] 775
    compileMints [1] 575
    compileTsc [0] 92
    compileTsc [1] 73
    compileMints [0] 286
    compileMints [1] 249
    

    test262


    JS 등급에서 모두 만점을 받을 수 있도록 테스트 262를 통과하기 전에 노력해야 한다.
    tc39/test262: Official ECMAScript Conformance Test Suite

    읽기 쉬운 오류 메시지 생성


    지금은 내부의 잘못된 구조를 그대로 저장했을 뿐이다.이것은 주로 가정$or이 위에서 아래로 순서대로 테스트를 하는 것이지 인간이 읽는 데이터가 아니다.
    haskell의 Parsec는 가장 심각한 오류를 보고하는 실시 방식인 것 같은데, 이것을 모방하는 것은 어렵지 않은 것 같다.

    압축 구문 정의


    다른 발생기는 대부분 Pargen에 기술된 문법 정의를 JSONor Binary로 인코딩하면 구축 사이즈가 줄어든다.
    시험 제작 후 느낌은 15kb가 8kb 정도가 되면 Pargen의 문법 구조기와optimina도 최종 구조에서 제외할 수 있다는 것이다. 또한 구조기가 초기화될 때 진행된 문법 구조 부분도 없어지고 CPU 원가도 줄어들었다. 이렇게 말하지만 인코딩을 대체할 수 있다는 표현에 얽매여 설치의 자유도 줄어들었다.
    실제로 자유도가 줄어들면 곤란하지 않을 것이다. 바늘을 함수에 분배하면 따로 선언할 수 있겠지.

    PR 시작


    코드가 더러워서 죄송합니다. 5.7kb는 다른 특징이 없어서 재미있습니다.
    패키지릿의 퍼서는 이번이 처음이다. 졸렬한 실상이라 파라에게 니키 같은 조언을 해줄 수 있다면 기쁠 것 같다.
    다만 이 인코딩된 덮개는 통과할 수 없습니다!이런 내용도 괜찮다.
    mizchi/mints

    mints의 문법 정의


    https://github.com/mizchi/mints/blob/main/packages/mints/src/grammar.ts

    pargen의 parse 처리 주체


    https://github.com/mizchi/mints/blob/main/packages/pargen/src/compiler.ts
    잘 부탁드립니다.

    좋은 웹페이지 즐겨찾기