Why did Deno change the core module from ts back to js

Let me talk about the reason first: the root cause is that ts cannot generate high-performance js code for the Deno runtime.


The discussion about replacing js has been a long time. This is a design document written by ry docs.google.com/documen

First of all, we need to clarify a misunderstanding: Deno did not give up TypeScript, Deno is still a safe TS/JS runtime.

After the last time Deno adjusted the architecture, two rust packages (crates), rusty_v8 and deno_typescript were added. Currently, Deno has completely removed the C++/C code. The ratio of each language is approximately:

  • TypeScript:64.7%

  • Rust:31.9%

  • JavaScript:1.4%

As you can see, Deno already contains some JavaScript code.

Let's take a look at several core modules (catalogs) of Deno:

  • deno/core: The code is mainly rust and js

  • deno/cli: all rust

  • deno/cli/ops: all rust

  • deno/cli/js: all typescript

  • deno/std/*: almost all are typescript, some high-performance modules are wasm(rust+js)

This time, only the files in the deno/cli/js/* directory were changed from ts back to js. I estimate by intuition that the proportion of ts in this part should be less than 1/3.

As for the reason, both the Chinese translation and the English original text are somewhat out of context + guessing. Slow compilation speed is one aspect, but not the root cause. The compilation speed of rustc is also very slow, but the generated code is of high quality. Tsc is indeed slow. If tsc can generate high-performance js code, then the time cost of this construction is acceptable. But currently, ts cannot generate high-performance js code for Deno runtime. In other words, the Deno team did not find a way for ts to generate high-performance js code.

Deno uses the latest version of ts and v8, and the goals of ts and v8 are both stage3. In theory, ts can be run directly on v8 by simply doing type erasure. Initially the Deno team thought so too.

But the actual use of ts is not so ideal.

I started fixing bugs for Deno at the end of 2018. In 2019, I started to maintain a few modules under deno/cli/js/web. At this time, the problem of ts was exposed.

For example, I URLSearchParams added these lines of code to the  API:

At first glance, this line of code is a bit superfluous. Therefore, in the PR, I repeatedly explained to ry URLSearchParams the description in the WHATWG specification  , and discussed it with him in private for a long time.

According to whatwg's url specification, parameters must be converted into strings. When using ts to write code, we have already defaulted that the parameter must be a string, and the number of parameters is 1. But the user can still pass in other parameters. For example, when the user uses Deno to run js code, the user can write that  url.get(1)Deno's behavior is out of compliance. Even if the user is using ts, the user can still write  url.get(1 as any)code.

My suggestion to ry at the time was to use js to write all test cases so that it would be easier to cover all paths in the specification. But ry still wants to use ts, so I added a lot of as unknown as stringcode in the unit test  .

At the end of last year (2019), I showed the wpt (Web Compatibility Test) report I did for Deno for ry in Beijing.

Me: There is still a lot of work to be done for web compatibility. I ported the wpt of nodejs to deno, which can be used as a guide for developing web api.

ry: What is the current progress of deno web api?

Me: About 60% more.

ry: Very few. Are there any difficulties?

Me: As we discussed on the Internet last time, ts has some quirks for writing web api.

At present, Deno has modified two sets of ts compilers, one is used when building deno, so that the construction of deno is completely separated from Node.js; the other is used in Deno runtime, which is deno run used when running  commands. In addition, Deno has a third set of ts compiler for  deno doccommands. This ts compiler is a swc developed using rust language. swc only does type erasure, not static type checking, and it is developed by rust, so the performance is very high.

Speaking of performance, let's talk about why ts can't be compiled into high-performance js code.

The ts mentioned above about the weg specification can be circumvented through some hack techniques. For example, my PR (Sorting non-existent params removes? From URL #2495) is just by casting a certain attribute of the class Access the private field of the attribute for any to implement the steps defined by the specification.

However, there is no very good way to solve the performance problem.

Let's continue to see  URLSearchParams, this class is defined as follows:

export class URLSearchParamsImpl implements URLSearchParams {
  #params: Array<[string, string]> = [];

  constructor(init: string | string[][] | Record<string, string> = "") {
    if (typeof init === "string") {
      this.#handleStringInitialization(init);
      return;
    }

    if (Array.isArray(init) || isIterable(init)) {
      this.#handleArrayInitialization(init);
      return;
    }

    if (Object(init) !== init) {
      return;
    }
...
...

Define the class  URLSearchParamsImpland implement the interface  URLSearchParams.

The problem is that when accessing URLSearchParams the  name attribute, it will output the wrong result:,  "URLSearchParamsImpl"this is not in compliance with the specification. In order to make the URL conform to the specification, we must use the following code:

Object.defineProperty(URLSearchParamsImpl, "name", { value: "URLSearchParams" });

For V8, class URLSearchParams the performance of this code is lower than that of direct writing  , because it will destroy the optimal optimization path of V8.

Then why can't Deno write directly  class URLSearchParams ? This is another historical issue, and it is a historical issue of ts.

Deno is written in es2019 (es10). Compared with Node.js written in es3, Deno is very modern. (Currently, Node.js has been partially upgraded to es6 and part of es5 has been retained; and Deno has basically been fully upgraded to es2020). Although the legacy problems of js have been solved, such as incompatibility with common js, etc., but the historical legacy of ts has been encountered and it cannot be directly defined  class URLSearchParams.

Initially, Deno's lib.deno.d.ts file was automatically generated from the .ts source code. However, as more and more web apis were added, there was a problem with this automatic generation of .d.ts. The first is that the generated .d.ts file is messy and not compact enough, but it can be used. The most serious problem is that the generated .d.ts file is different from the built-in lib.*.d.ts of TypeScript, and many ts source codes cannot produce the same declaration files as lib.dom.d.ts.

Using the above  URLSearchParams example, in the built-in lib.dom.d.ts of TypeScript, this type is defined as follows:

interface URLSearchParams {
   ...
}

declare var URLSearchParams: {
    prototype: URLSearchParams;
    new(init?: string[][] | Record<string, string> | string | URLSearchParams): URLSearchParams;
    toString(): string;
};

And ts can only generate the following declaration files:

declare class URLSearchParams {
    constructor(init?: string[][] | Record<string, string> | string | URLSearchParams);
    toString(): string;
}

The two should be equivalent (I am not particularly familiar with this, if not, please let me know).

Because the .d.ts file was generated earlier than es6, no class is used in lib.*.d.ts.

In the end, Deno also manually maintained 5 lib.*.d.ts files, and the above  URLSearchParamstypes are defined in lib.deno.shared_globals.d.ts. At this time, if we write again  class URLSearchParams , the type definition in the .d.ts file will be overwritten.

At present, most of Deno's web apis use this mode, which defines  the   properties that  XxxxImplimplement the  Xxxxinterface and then reset it  .XxxxImplname

If a certain technology brings xxx problems, we either solve the xxx problem with this technology, or use another technology that does not have xxx problems. This is the same as when rust changed from go to rust. Using go will bring double gc problems (go gc and v8 gc). Deno seemed to have 4 ways at the time: solve go gc, solve v8 gc, replace go, replace v8. In fact, there is only one way, replace go.

Now deno is facing a similar problem again. ts produced js code with performance problems. We knew where the performance problem was, but we couldn't solve it from the ts level, so we chose "handwritten js code".

The ts in deno/std has no performance problems in this regard, so there is no need to replace ts with js. But deno/std/hash has other performance problems. Deno's solution is to use wasm to rewrite this module.

Although some internal modules of deno have changed from ts back to js, ​​this does not mean that ts is not working. It just means that ts is not suitable for some specific scenarios. Do not completely deny ts for this reason. Use ts for most projects. The benefits are still great.

I think there are some scenarios that are not suitable for ts: the prototype chain is often modified, the attributes need to be dynamically added at runtime, and so on.

Deno's performance problems are due to the need to implement specific specifications and compatibility requirements for TypeScript's built-in lib.dom.d.ts, resulting in ts unable to generate high-performance code. For most projects, the js code produced by ts is still very good.

Guess you like

Origin blog.csdn.net/vCa54Lu0KV27w8ZZBd/article/details/107031323