ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2023-07-19 16:04:53  阅读:261  来源: 互联网

标签:JavaScript TypeScript Web框架


十多年来,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 的静态类型解决方案

谷歌的闭包编译器

  • 类型系统健全性未知;假定为不健全,因为声音类型系统很少见
  • @interface表示标称类型,表示结构类型@record
  • 所有类型注释都在导致代码膨胀的注释中指定,并且注释通常与代码不同步。
  • 此处列出的所有选项中最先进和最积极的代码优化
  • 在 GitHub 上查找更多信息

脸书的流程

  • 不健全型系统
  • ES6 类的名义类型和其他所有类的结构类型,不像 TypeScript,其中所有类型都是结构化的;而在Java中,所有类型都是名义上的
  • 与TypeScript相比,Flow在工具(兼容的格式化程序,linter,IDE插件)和库(TypeScript甚至具有DefinitelyTyped项目来托管NPM上的类型定义)方面具有更小的生态系统。
  • 在流文档中查找更多信息

Microsoft的打字稿

  • 支持所有 JavaScript 功能,并严格遵循 ECMA 标准,即使是细微之处:类字段和 TC39 装饰器
  • 所有主流JavaScript模块系统之间的无缝互操作:ES模块,CommonJS,AMD和UMD
  • 不健全型系统
  • 所有类型都是结构性的,这是静态建模动态类型的最自然方法,但将某些类型标记为名义类型的能力会很好。Flow 和闭包编译器在这方面具有优势。
  • 还支持注释中的闭包编译器样式类型注释
  • 一流的工具和庞大的生态系统;VSCode 的内置支持;因此,它的可用性几乎无处不在
  • 每个枚举变体都是一个单独的子类型,不像我们遇到过的所有其他类型系统,包括 Rust、Scala 3、Lean 4 和 Coq
  • 在 TypeScript 手册中查找更多信息

斯卡拉.js

  • 利用Scala 3的出色类型系统,这是合理的
  • 与任何 Scala 3 项目无缝共享构建脚本 (sbt) 和代码
  • 发出的JavaScript代码通常很臃肿,有时效率低于Closure Compiler,Flow和TypeScript。
  • 在 Scala.js 网站上了解更多信息

重写脚本

  • 被吹捧为有一个像Scala 3一样的声音类型系统(证据在哪里?),但ReScript的语法更接近JavaScript和OCaml。
  • 与 ML 系列中的所有语言一样,类型系统非常规则,允许高效的类型检查、快速的 JavaScript 发射和积极的优化。
  • 发出的 JavaScript 代码非常可读。这是ReScript的设计目标。
  • 通过 genType 与 TypeScript 互操作
  • 从 ReScript 10.1 开始,支持 async/await
  • 可能需要熟悉更高级的函数式编程技术和纯函数式数据结构
  • 在重写语言手册文档中了解更多信息

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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有