[Dart翻译]Dart构建--我们在哪里,我们如何来到这里,我们要去哪里?

842 阅读8分钟

原文地址:lessworsemorebetter.com/2018/04/17/…

原文作者:github.com/natebosch

发布时间:2018年4月17日

我们所在的地方:pub build(barback)

当barback被编写出来的时候,它的目的是为了满足web开发中的一个小需求:比如sass编译,或者将sprites压缩成一张图片。这是一个非常灵活的系统,而且事实证明它对更密集的构建步骤非常有用,比如Angular编译器。不过灵活性是有代价的。如果任何文件可以在构建的任何步骤中被重写(或删除),那么就需要注意不要让其他构建步骤读取它,直到它 "稳定"。Barback在增加保护的同时,还增加了一个重要的限制--在包Foo中运行的Transformer不能读取包Bar中的任何文件,直到Bar上的所有Transformer都完成。这在Dart源文件的上下文中是有问题的,因为有类型推理等特性。为了理解一个Dart源文件的 "意义",还需要理解来自导入库的细节。如果两个运行在不同包中的Transformer想要理解一个Dart库的解析意义,他们必须读取跨包的导入库--当这些导入库跨包边界循环时,就会造成Barback的死锁。在另一个完成之前,两个Transformer都不允许读取导入的Dart库,而在能够读取文件之前,两个Transformer都不会完成。

这个根本性的限制意味着4版本之前的Angular编译器不得不欺骗系统。Barback不让它读取源Dart文件--但Angular变换器也在这些源文件上运行,并且会(希望)在需要信息的点之前就已经读取了它们。Angular开始将它所需要的东西存储在(有效的)全局变量中,这些变量在不同包的运行中是共享的。这就引入了一个竞赛条件--它依赖于所有包中的变换器一起启动,直到全局数据被填充后才到达某个点。

当我们重写Angular编译器以使用分析器和执行器深度类型解析时,我们遇到了一个路障。为了解析类型,我们需要给分析器提供转义的Dart导入,但是如果有包循环,我们就不能这么做。我们被迫不允许使用Angular变换器的包循环,并发现性能受到了极大的影响。我们只能进行工作,直到我们需要从依赖关系中读取资产--在使用分析器的情况下,这意味着立即。我们在不同的包中序列化了angular transformer的工作,因为在Barback让我们读入我们的transitive imports之前,我们不能在一个包中开始做任何事情。

Pub的预期用例也不包括需要很长时间的编译步骤。有有限的缓存和交叉运行的增量编译。事实上,对单个文件的样子没有一个一致的视图,这使得这些很难或不可能添加进去。

我们是如何来到这里的:用package:build弥补差距

当Google内部的团队正在构建越来越大的项目时,我们也遇到了其他的困难,将pub的 "无论何时写什么 "的方法与bazel的更严格的静态分析构建图整合在一起。我们不能把pub的模型做成增量式或模块化--它必须是整体式的。Bazel不允许你重写文件。源文件不能被改变,任何产生输出的事情都需要在一个构建步骤中发生。我们写的package:build有一个更严格的模型,它可以让我们在运行时有一套类似于bazel的限制,同时当我们想在该构建环境中运行时,可以轻松的shimming到pub接口。随着时间的推移,我们逐渐采用了除了 bazel 限制外,还采用了定义的方式,增加了 bazel 的静态分析性。我们不再执行Dart代码来确定将被写入的文件,而是转向配置元数据,它说的是给定的输入扩展可以输出什么输出扩展。Builder的概念对笔者来说限制较多,但给了我们更多的灵活性,使其与构建系统整合。我们可以将一个单一的Builder实现以三种方式运行它--在pub中作为一个Transformer,在Bazel构建中作为一个构建规则,或者通过使用build_runner直接写入源代码树,用于小型的本地专用构建器。

我们对build包的长期目标是让外部用户能够像我们的内部用户一样使用bazel构建系统的全部功能。我们为dazel构建了一个原型--一个能够获取构建者配置元数据并生成Dart项目所需的Starlark构建规则和BUILD文件的工具。我们用这种方法遇到了一些障碍。

  • 在内部,我们只需要在unix主机上构建Dart,但一些Dart用户在Windows上开发。要把我们的 bazel 构建规则和工具迁移到跨平台上,需要花费很多精力。
  • bazel构建系统,尽管试图隐藏它的复杂性,但对于与更简单的pub build一起工作的小型项目来说,仍然感觉重量级。

我们的处境很艰难。我们的开发编译器在没有被紧密集成到构建系统中的情况下,使用起来非常棘手。外部的Angular构建速度太慢,大多数用户无法接受,而且每次启动pub serve时都要支付生成angular代码的成本。我们可以用 bazel 给出很好的体验,但只针对一小部分用户的项目。

我们现在的情况: build_runner作为一个完整的构建系统

虽然它最初是为了满足构建者的一小部分用例而写的--在一个单一的包中生成代码,并打算与包一起发布--但我们确实有一个概念验证的构建系统,它是纯Dart的,并遵循一个更严格的(读作 "更容易优化")模型。我们已经投入了大量的精力,使build_runner成为一个完全能够用于Dart项目的构建系统。我们主要在两个方面进行了改进。

  • 我们扩展了build_runner的功能,使其可以在所有的包中运行构建,并且可以在不打算发布的文件中运行。
  • 我们改进了build_runner的可用性,使其更像pub build,自动发现Builders,而不是在手动构建脚本中表达所有需 求。

在Dart 2中,我们将web项目的构建完全过渡到build_runner(我们称CLI为webdev),并且我们已经放弃了对pubbuildserver命令的支持。

我们的目标是:更好、更快的构建。

更简单

我们将更多的配置和复杂性推给Builder的作者,这样终端用户就不需要做手工工作了。大多数Builders可以根据依赖关系自动启用,Builder的配置也有足够的表达能力来自动决定工作顺序等事情。Builder作者也可以决定开发模式和发布模式的默认选项,所以启用dart2js编译只需要--release!

更好的

我们已经证明了build_runner在许多项目中使用一组小的Builder是有效的。我们将致力于扩大快乐之路的范围,减少围绕它的尖锐边缘。在完成从 pub 迁移到 build_runner 的过程中,我们将探索在 package:build 范式的限制下工作的模式。

我们还将努力改进与其他工具的集成,比如分析服务器。与pub build不同的是,生成的assets保存在磁盘上而不是内存中。其他工具可以看到这些文件,这使得它们更容易检查、调试和理解。

更快

我们希望快速地移动到一个工作的端到端系统--当我们可以重用代码时,我们就这样做了。我们使用的一些API都是在考虑到pub兼容性的情况下编写的。即使一个Builder不会覆盖一个文件,我们也不能确定其他的Transformer不会,所以我们需要谨慎。现在我们有很多改进的空间,我们可以专注于构建系统,有一个静态分析的模型。

更强

尽管它的限制,我们正在专注于确保新的构建系统具有正确的通用性的Dart。事实上,它能够在没有任何硬编码知识的情况下运行我们的网络编译器--它们就像其他生成器一样出现在系统中--这在Barback中是不可能的。这样做的好处是,我们可以更换实现,比如说为node.js调整的编译器,而不需要改变构建系统本身。


通过www.DeepL.com/Translator (免费版)翻译