编程语言
首页 > 编程语言> > 为 ZK 框架自动执行从 JS 到 TS 的迁移

为 ZK 框架自动执行从 JS 到 TS 的迁移

作者:互联网

十多年来,ZK 一直是以服务器为中心的解决方案。近年来,我们注意到对云原生支持的需求,并将其作为我们即将推出的新版本 ZK 10 的主要目标。新功能将通过将大部分模型-视图-模型绑定转移到客户端来减轻服务器的负担,以便服务器端尽可能无状态。这带来了一些好处,例如减少服务器内存消耗、简化 ZK 10 群集后端的负载平衡,以及可能更容易与其他前端框架集成。

我们将这项工作称为“客户端 MVVM”。然而,这意味着JavaScript代码的巨大增长。正如我们已经意识到 JavaScript 更难维护一样,现在是我们让我们的 JavaScript 代码库更容易使用 50k 行代码的时候了。否则,用整个 MVVM 堆栈扩展现有的 JavaScript 代码将变得西西弗斯式的,如果不是不可能的话。我们开始研究为什么Java具有更高的生产力,以及如何为客户端带来相同的生产力。

为什么Java在大规模开发中击败了JavaScript?

Java 做了什么,使我们的生产力提高了 8 倍?我们的结论是,静态分析的可用性是主要因素。

我们早在程序执行之前就设计和编写程序,并且通常在编译之前。通常,我们通过修改源代码而不是修改编译器生成的机器代码或实时程序的内存来重构、实现新功能并修复错误。也就是说,程序员静态地(在执行之前)而不是动态地(在执行期间)分析程序。

静态分析不仅对人类来说更自然,而且静态分析也更容易自动化。如今,编译器不仅从源代码生成机器代码,而且还执行人类对源代码所做的分析,如名称解析、初始化保护、死代码分析等。

人类仍然可以对JavaScript代码进行静态分析。但是,如果没有自动静态分析器(编译器和 linter)的帮助,使用 JavaScript 代码进行推理变得非常容易出错且耗时。以下 JavaScript 函数返回什么值?它实际上是代替.惊讶?undefined1

JavaScript
function f() {
  return
    1
}

 

将其与Java进行比较,Java中有编译器来帮助我们“在键入时”进行推理。使用 TypeScript,编译器将执行“自动分号插入”分析,然后执行死代码分析,从而产生:

 

人类永远无法击败机器的细致。通过将这种单调但关键的任务委托给机器,我们可以释放大量时间,同时实现前所未有的可靠性。

我们如何为 JavaScript 启用静态分析?

我们评估了以下 6 个选项,并最终选择了 TypeScript,因为它具有广泛的 ECMA 标准一致性、对所有主流 JS 模块系统的完全支持以及庞大的生态系统。我们在文章末尾提供了它们的比较。这是一个简短的概要。

  1. Google的闭包编译器:所有类型都在JSDoc中指定,从而使代码膨胀并使内联类型断言非常笨拙。
  2. Facebook的Flow:与TypeScript相比,在工具和库方面是一个小得多的生态系统
  3. Microsoft的打字稿:最成熟、最完整的解决方案
  4. 斯卡拉.js:低于标准;发出的 JavaScript 代码
  5. ReScript:需要范式转向纯函数式编程;否则,非常有前途

半自动迁移到 TypeScript

在 TypeScript 迁移之前,我们的 JavaScript 代码主要由通过我们的临时函数进行原型继承组成,如左侧所示。我们打算将其转换为右侧语义等效的 TypeScript 片段。zk.$extends

JavaScript
Module.Class = zk.$extends(Super, {
  field: 1,
  field_: 2,
  _field: 3,

  $define: {
    field2: function () {
      // Do something in setter.
    },
  },

  $init: function() {},

  method: function() {},
  method_: function() {},
  _method: function() {},
}, {
  staticField: 1,
  staticField_: 2,
  _staticField: 3,

  staticMethod: function() {},
  staticMethod_: function() {},
  _staticMethod: function() {},
});

 

打字稿
export namespace Module {
  @decorator('meta-data')
  export class Class extends Super {
    public field = 1;
    protected field_ = 2;
    private _field = 3;

    private _field2?: T;
    public getField2(): T | undefined {
      return this._field2;
    }
    public setField2(field2: T): this {
      const old = this._field2;
      this._field2 = field2;
      if (old !== field2) {
        // Do something in setter.
      }
      return this;
    }

    public constructor() {
      super();
    }

    public method() {}
    protected method_() {}
    private _method() {}

    public static staticField = 1;
    protected static staticField_ = 2;
    private static _staticField = 3;

    public static staticMethod() {}
    protected static staticMethod_() {}
    private static _staticMethod() {}
  }
}

 

有数百个这样的案例,其中许多有近50个属性。如果我们要手动重写,不仅需要很长时间,而且会充满错别字。仔细检查,转换规则非常简单。它应该自动化!然后,该过程将快速可靠。

实际上,这是一个将原始JavaScript代码解析为抽象语法树(AST),根据一些特定规则修改AST,并将修改后的AST合并为格式化源代码的问题。

幸运的是,有jscodeshift可以解析和整合源代码,并为AST修改提供一组有用的API。此外,还有AST Explorer充当jscodeshift的实时IDE,因此我们可以高效地开发jscodeshift转换脚本。更好的是,我们可以编写一个自定义的打字稿-eslint 规则,该规则在存在 时生成 jscodeshift 脚本。然后,我们可以使用以下命令自动将转换应用于整个代码库。zk.$extendseslint --fix

让我们转到上面示例中的类型。由于 jscodeshift 为我们提供了无损 AST(包括注释),我们可以编写一个访问者来提取 JSDoc 的 JSDoc 是否可以找到它;如果没有,我们可以让访问者走进 的方法体并尝试推导出类型,例如,推断 be 如果返回值是与某个字符串的串联。如果仍然无济于事,请指定为 ,以便在应用 jscodeshift 后,TypeScript 编译器将警告我们类型不匹配。这样,我们可以在手动干预之前执行尽可能多的自动推理,并且由于我们的错误注入,编译器将准确地显示手动检查所需的部分。T@returngetter()getter()TTstringgetter()this._field2Tvoid

除了像 jscodeshift 这样只能在批处理模式下运行的整个文件转换之外,Typescript-eslint 项目还允许我们编写小而精确的规则,在 IDE 中实时更新源代码,如 VSCode。例如,我们可以编写一个规则,将以单个下划线开头或结尾的类或命名空间的属性标记为 ,以便文档提取工具和类型定义捆绑器可以忽略它们:@internal

打字稿
export namespace N {
  export function _helper() {}
  export class A {
    /**
     * Description ...
     */
    protected doSomething_() {}
  }
}
打字稿
export namespace N {
  /** @internal */
  export function _helper() {}
  export class A {
    /**
     * Description ...
     * @internal
     */
    protected doSomething_() {}
  }
}

 

对于上面的示例,必须确定属性关联 JSDoc 的存在、标记的预先存在以及插入标记的位置(如果缺少)。由于 typescript-eslint 还为我们提供了一个无损的 AST,因此很容易找到类或命名空间属性的关联 JSDoc。剩下的唯一重要的任务是解析、转换和合并 JSDoc 片段。幸运的是,这可以通过 TSDoc 解析器来实现。与第一个示例中通过 typescript-eslint 激活 jscodeshift 类似,第二个示例是在打字稿-eslint 规则匹配时将 JSDoc 转换委托给 TSDoc 解析器的情况。@internal@internal

有了足够的JavaScript,TypeScript及其构建系统的知识,人们可以利用jscodeshift,typescript-eslint,AST Explorer和TSDoc解析器来进一步保证一个人的代码库的语义,并尽可能使用方便的命令自动修复。静态分析的重要性怎么强调都不为过!eslint --fix

 

太棒了!Zk 10 已完全迁移到 TypeScript

对于 ZK 10,我们积极地使用 TypeScript 对代码库中的所有现有 JavaScript 代码进行了静态分析。我们不仅能够修复现有的错误(有些是自动的),这要归功于Typescript-eslint项目,它支持了许多额外的类型感知规则,我们还编写了自己的规则,并且我们保证将来永远不会再犯这些错误。这意味着ZK开发团队的精神负担更少,良心更好。eslint --fix

我们的客户端 MVVM 工作也变得更加易于管理,因为 TypeScript 到位。开发经验接近Java。事实上,某些方面甚至更好,因为 TypeScript 具有更好的类型收缩、结构类型、通过文字类型优化类型以及交集/联合类型。

至于我们的用户,ZK 10变得更加可靠。此外,我们的类型定义是免费提供的因此 ZK 10 用户可以轻松自信地自定义 ZK 前端组件。此外,用户还可以在执行期间使用客户端 MVVM 扩展其应用程序。在 ZK 10 中采用 TypeScript 进一步使我们能够在开发过程中扩展正确性。两者都是根本性的改进。

附件:比较 JavaScript 的静态类型解决方案

谷歌的闭包编译器

脸书的流程

Microsoft的打字稿

斯卡拉.js

重写脚本

标签:JavaScript,TypeScript,Web框架
来源: