NX控制台被点亮
#javascript #vscode #nx #lit

在过去的几周中,我们重建了NX Console最喜欢的功能之一:生成UI。它看起来更好,加载更快和初步的研究表明,使用它也使您更快乐;)

您可以通过安装用于VSCODE和JETBRAINS IDES的最新版本的NX控制台来使用它! ð7

如果您想了解有关重写及其背后的动机的更多信息,那么这是您的博客文章!我们将涉及这些主题等等:

  • 我们为什么选择重写?
  • 什么点亮,为什么我们在Angular上使用它?
  • 重写是如何进行的,哪些点亮功能对我们很重要?
  • 表现前后的性能是什么样的?

如果您喜欢视频格式,请查看我们的YouTube视频ð

背景:为什么要从角到点亮?

NX控制台的简短历史

让我们回到过去:NX控制台已经存在了一段时间。 It first launched in 2018-然后称为Angular Console-作为独立的电子应用程序,可让您从图形接口运行角示意图和构建器。当然,它是用角度建造的,看起来像这样:

Screenshot of the original Angular Console electron app

在2019年,it was ported to a VSCode extension拥有现在熟悉的UI和对独立应用程序的支持。

在2020年,添加了对整个NX生态系统的支持,将其重命名为NX控制台,并开始了惊人的转换:今天,NX控制台不仅仅是一种形式 - 它将NX紧密地集成到您的IDE中,为您提供。您需要的项目的概述,然后将任务运行放置。

重写点亮

这种演变为NX控制台的可用性以及我们可以向开发人员提供的价值带来了重大改进,但并非没有提出自己的挑战。不可避免地,代码库随着时间的推移而变得复杂而复杂 - 它的运行情况发生了变化,产品的范围改变了,但技术仍然相同。添加小功能或修复错误变得越来越耗时,每个公关都使问题更加复杂。

UI看起来有些过时,并且仅考虑了VScode的构建 - 当添加对喷气桥的支持时,这变得非常明显。虽然生成UI的基于网络的性质使我们能够在新环境中重复使用代码,但设计不合适。

此外,我们开始质疑我们对角的使用。 Angular是构建Web应用程序的绝佳框架,在Angular Console的原始上下文中,它具有很大的意义:由Angular工程师构建的工具 - 当然是用Angular编写的。但是情况发生了变化,Angular开始对我们需要的东西感到过度杀伤。 Angular即将开箱即用。但是,对于我们的简单形式,我们不需要路由,HTTP请求或模块来组织我们的代码。样板和顶部角度引入的量很明显。

因此,最终,我们决定在Lit中拉插头并重写整个内容。

lit是一个建在网络组件之上的轻量级框架,添加了您需要快乐和富有成效的东西:反应性,声明性模板和少数周到的功能,以减少样板并使您的工作更轻松 - 取自他们的文档)。我们以前曾将其用于build the Nx Cloud view,并对简单的设置和出色的DX感到满意。因此,在这里重复使用它的决定很容易,因为它使我们能够重复使用代码,构建工具和专业知识。重写还使我们有机会改善两个支持的IDE的设计,并为整个UI提供干净的新涂层。

Screenshot of the new, reworked generate ui

在深入研究细节之前,让我们先看看NX控制台的一般体系结构。

NX控制台架构概述

nx控制台由3个核心部分组成:

  • nxls 是基于Language Server Protocol (LSP)的语言服务器,充当NX控制台的大脑。它分析您的NX工作区并提供有关它的信息,包括代码完成等。
  • 生成UI 是运行NX发电机的基于表单的视图。
  • 平台特定的包装纸。这些用打字稿和Kotlin编写,然后将NX控制台的其余部分连接到IDE特定的API。将其他部分分开大大减少了我们必须编写的重复代码的数量,以支持多个IDE

这种体系结构的模块化意味着我们可以快速切换新版本的生成UI,而不会显着影响代码库的其余部分 - 只有实际呈现UI并与之通信的零件必须稍微调整。它还使我们能够确保向后兼容:旧生成UI仍然可以通过设置中的功能切换可用。

如果您想更深入地深入研究,那么在NX控制台的体系结构以及它的构建方式上还有更多资源:

迁移到点燃:一步一步

要重建我们的UI,我们首先需要一个新的点亮应用程序才能进行。虽然没有本机NX插件用于LIT,但生成我们需要的代码仍然很简单:

nx generate @nx/web:app --name generate-ui-v2

这为我们生成了一个整个项目,其中包括tsconfig.jsonindex.htmlmain.ts和A project.json,我们的NX特定配置居住。

我还安装了几个依赖项:

  • @nx/esbuild插件,因为我喜欢快速的构建时间ðï≥
  • tailwindcss,因为我不喜欢写CSSðä«
  • @vscode/webview-ui-toolkit,因为它对我有用的所有vscode工作ðü>

这确实是NX闪耀的地方,因为它允许您采用这些工具并快速将它们修补在一起并构建一条完全可以完成您需要的管道。它还使您可以直观地思考工作空间。这就是我用于构建LIT应用程序的任务图最终的样子:

Nx task graph containing three nodes

您可以看到三个构建步骤:

  • generate-ui-v2:_build使用esbuild将我用打字稿编写的我的点亮组件捆绑并吐出main.js文件
  • generate-ui-v2:extract-dependencies将我们需要的第三方资产复制到dist文件夹中。现在,它只是Codicons .css.ttf文件。
  • generate-ui-v2:build终于在捆绑的代码上奔向。这也可以使用postCss或自定义的esbuild插件来完成,但是直接运行尾风很容易,所以为什么要使事情复杂化?

ðâ€有不同的方法来为您自己的工作空间生成此可视化:

  • 在VSCODE中,使用NX项目视图或Nx: Focus task in Graph Action
  • 在喷气桥IDE中,使用NX工具窗或上下文菜单
  • 在命令行中,运行nx build {{your project}} --graph

在NX控制台的较大上下文中,当您构建VSCODE扩展时会发生什么

Nx task graph for building the VSCode extension, containing more nodes

您可以看到,在将它们全部组合到一个vscode扩展名中之前,我们仍在构建新的和旧生成的UI以及NX Cloud WebView。

构建表格

LIT是一个非常小的库,可在Web组件和消息等浏览器本地功能(例如Web组件和消息)上提供有用的抽象。这是一个用打字稿编写的简单点亮组件,看起来像:

// main.ts
@customElement('root-element')
export class Root extends LitElement {
  render() {
    return html`<p> Hello World </p>`;
  } 
}

//index.html
<!DOCTYPE html>
<html lang="en">
    <body>
        <script type="module" src="main.js"></script>
        <root-element></root-element>
    </body>
</html>

如果您需要将信息传递到DOM,则使用普通事件,并且可以设置属性以将信息发送给后代。查看Lit Docs,以了解有关render()方法如何工作的更多信息,如何利用反应性,阴影DOM等等。

ðâ€本节中的所有代码样本均已为简短和清晰度进行了调整。因此,您在任何地方都不会找到此确切的代码,但是它很好地证明了概念

使用反应控制器与IDE进行通信

要与主机IDE进行通信,我们能够重复使用上一个UI的几乎所有逻辑(有关更多详细信息,请参阅上一篇博客文章中的Communicating with IntelliJ)。我们使用了Reactive Controller,而不是揭示我们组件可以消耗的可观察到的服务。控制器是LIT的整洁功能 - 它们将组件挂钩,并且可以代表其请求DOM更新。这消除了对可观察到的流和订阅的需求,同时保持通信代码是独立的。查看此示例:

// main.ts
@customElement('root-element')
export class Root extends LitElement {
    icc: IdeCommunicationController;

  constructor() {
    super();
    this.icc = new IdeCommunicationController(this);
  }

  render() {
    return html`${JSON.stringify(this.icc.generatorSchema)}`;
  } 
}

// ide-communication-controller.ts
export class IdeCommunicationController implements ReactiveController {
  generatorSchema: GeneratorSchema | undefined;

    constructor(private host: ReactiveControllerHost) {}

    // ...

    private handleMessageFromIde(message: InputMessage) {
        // ...
        this.generatorSchema = message.payload;
        this.host.requestUpdate()
    }
}

您可以看到root-element确实可以处理渲染表单内容,与IDE进行委派以及何时将DOM更新为控制器。

渲染表单字段 - 使用Mixins

UI的核心部分是形式。我们构建了各种输入:文本字段,复选框,(多)选择框和数组字段。尽管每个人都有独特的实现,但显示它们也将需要大量重复的代码。每个字段都需要标签和描述。每个字段都需要了解其验证状态,调度如何更改事件以及要设置的ARIA属性。为了保持该代码清洁干燥,我们使用了Class Mixins。 Mixins Arent确实是一个特定的特定功能,但我看不到它们在其他框架中使用。混合蛋白本质上是一个上课并返回另一个修改的类的工厂。查看此示例:

// field-mixin.ts
const Field = (superClass) => class extends superClass {
    // we can define (reactive) properties that every field is going to need
    @property()
    option: Option;

    protected get fieldId(): string {
      return `${this.option.name}-field`;
  }

    // we can define methods that should be available to all fields
    dispatchValue(value: string) {
        // ...
    }
};

// field-wrapper-mixin.ts
const FieldWrapper = (superClass) => class extends superClass {
    // we can define a render() method so that fields are all rendered the same
    protected render() {
        return html`
            <label for="${this.fieldId}">${this.option.name}</label>
            <p> ${this.option.description} </p>
            ${this.renderField()}
        `
    }
}

// input-field.ts
@customElement('input-field')
export class InputField extends FieldWrapper(Field(LitElement)) {
    renderField() {
        return html`
            <input 
                id="${this.fieldId}"
                @input="${(e) => this.dispatchValue(e.target.value)}"
            />
        `
    }
}

您可以看到每个组件和混合素都涉及整体逻辑的特定子集,从而使我们的代码保持清晰分离和可重复使用。例如,复选框是一种特殊情况,因为页面上的布局略有不同 - 没问题,我们只是编写了一个CheckboxWrapper,而不必担心更改复选框逻辑本身。

ð。正确键入混合蛋白很复杂,因此我遗漏了那部分。请参阅Mixins in Typescript了解更多信息或check out our source code on GitHub

注射服务和数据 - 点亮上下文

如果您来自角度世界,那么您可能会欣赏他们拥有的巨大依赖注入(DI)机制。您可以集中定义某些服务并在您的应用程序中重复使用它们,而无需考虑将道具从组件转移到组件 - DI系统会处理它。 LIT通过[@lit-labs/context]https://lit.dev/docs/data/context/)包装提供类似的东西。它基于Context Community Protocol,类似于反应上下文API。

在引擎盖下,它仍然可以处理普通的浏览器事件,但它可以将其抽象出来,因此您可以在应用程序上轻松共享数据和服务。

生成的UI在Vscode和Jetbrains IDE中呈现。为了看起来合适,组件需要知道它们所处的环境并相应地调整样式。我们可以将这些信息从组件传递到组件,而是可以使其在上下文上可用!并且有了适当的混音,阅读编辑器上下文也只需实现一次。

请查看以下示例:

// editor-context.ts
export const editorContext = createContext<'vscode' | 'intellij'>(
  Symbol('editor')
);

const EditorContext = (superClass) => class extends superClass {
    @consume({ context: editorContext })
    @state()
    editor: 'vscode' | 'intellij';
};

// ide-communication-controller.ts
export class IdeCommunicationController implements ReactiveController {
    // ...
    constructor(private host: ReactiveElement) {
        const editor = isVscode() ? 'vscode' : 'intellij';

        // provide the context to all DOM children of the host element
        new ContextProvider(host, {
          context: editorContext,
          initialValue: editor,
        });
    }
}

// some-component.ts
@customElement('some-component')
export class SomeComponent extends EditorContext(LitElement) {
  render() {
    return html`<p> I am rendered in ${this.editor}</p>`;
  } 
}

VSCODE WebView UI工具包

正如我们上面提到的那样,为什么我们重写UI的很大一部分是它在喷气式脑中看起来很不合时宜。当然,这仍然很有用,但是重要的是要确保形式正确。

在VScode中,这要归功于VSCode Webview UI Toolkit,这很容易实现。这是由Microsoft提供的一组Web组件,其设计为看起来不错,可用于VSCODE WebViews。

Sample image of the VSCode Webview UI Toolkit showing the different components it provides
使用它,您可以免费获得本地外观,A11Y和主题感知样式!感谢所有建造它的人,这是一个巨大的帮助!

柏树的E2E测试

使用WebView的一个很大的好处是您可以使用巨大的JavaScript生态系统!为了确保以后不会引入任何回归,我们使用Cypress。我们可以模拟编辑器通信并提供不同的模式,请确保正确渲染表单,并将正确的消息发送回IDE。

虽然没有特定的柏树集成,但该工具本身是框架不可知的,因此它仍然可以很好地工作。使用koude22执行者为我们完成了大部分工作,因此设置也很快。

结果:比较性能

绩效的许多不同方面我们可以在两个实现之间进行比较。到目前为止,最大的一个不是真正的可量化:新UI的可维护性和外观。在我看来,在这两种环境中,它看起来更新鲜,更本地化。我们摆脱了很多旧版代码,新版本更容易推理和使用。

但是我们可以衡量一些东西,所以让我们谈论数字!

启动时间

启动像Angular这样的大框架需要时间,因此跳过,UI应该比以前更快地加载。

我们测量了在Vscode和Intellij中渲染@nx/angular:application发电机所有选项所需的中间时间。您可以看到结果非常明确,即使它们并没有产生巨大影响。

旧的UI(Angular) new UI(lit) 加速
VSCODE 65 ms 39 ms 〜1.7X
Intellij 189 MS 122 ms 〜1.5x

束大小

如前所述,Angular具有比LIT的功能更多的功能,因此建筑捆绑包将更大是有意义的。

我们能够将捆绑尺寸(W/O压缩)从约733 kb减少到282 kb,降低了约2,6倍。与网站不同,在加载页面时,捆绑包时需要将捆绑包装运给用户,NX控制台用户在安装插件时只需要下载一次即可。这意味着我们在安装后不受网络速度的影响,这使得捆绑尺寸不那么关键。

ðâ€由于几个角版的配置错误,因此我们在this tweet中报告的捆绑包大小过于膨胀。我们对其进行了纠正,但在规模和渲染时间方面仍然有些点状。

建立时间

虽然这对NX控制台的用户可能并不重要,但是构建项目所需的时间对我们的开发人员有所不同。

由于LIT只是不需要自定义编译器或构建工具的JavaScript文件,因此我们决定使用koude15(通过@nx/esbuild)(通过@nx/esbuild),该文件以GO编写并且非常快。另一方面,旧的UI使用了@angular-builders/custom-webpack:browser构建器,该构建器在引擎盖下使用了WebPack。

我们从大约3.5秒升至不到2秒的构建时间,这比我们预期的要少。由于我们还必须在文件上进行逆风,因此似乎有一些额外的esbuild速度被相对化。

展望未来

重建UI已铺平了道路,以减轻NX控制台的许多维护负担。它将使我们能够更快地构建新功能,从而为您提供最佳的开发人员体验。

具体来说,更新的架构使我们能够为NX控制台构建(仍然是秘密和WIP)插件。就像NX一样,总会有工作空间所独有的东西。我们希望使您可以轻松地扩展和修改NX控制台,以帮助您充分利用它。

因此,如果您有任何想法,请睁大眼睛以进行公告,并通过Github或Twitter告知我们!我们喜欢聊天。

还有一件事!

nx控制台是开发人员的工具,我们喜欢的一件事 - 键盘快捷键。因此,当然我们必须构建一些。除了对键盘友好且tabbable外,您还可以执行以下操作:

  • Cmd/Ctrl + Enter运行发电机
  • Cmd/Ctrl + Shift + Enter开始干燥
  • Cmd/Ctrl + S集中搜索栏并寻找特定选项。只是tab回到表格

如果较漂亮的UI和更好的性能尚未相信您,这肯定会! ð

了解更多

另外,如果您喜欢这个,请单击ð,并确保在Twitter上关注MaxNx,以获取更多信息!