在本篇,我会简单介绍一下Bazel是什么,我们怎么用它构建一个Typescript项目。如果你已经熟知Bazel解决什么问题,那么请跳到“用Bazel构建Typescript”一节。你可以在我的Github上找到例子!
谷歌管理着无数的源码。在每个独立项目间都有依赖,比如Google Cloud 依赖于 Angular & Angular Material,Angular Material 又依赖于 Angular, 最后它们都依赖 TypeScript.
在谷歌中,项目都有使用独立库(以及第三方库)的版本号。在公司内,这极大的简化了依赖管理。同时升级一个库,能影响所有依赖的项目,这样很方便。这样每个人都能从中得到好处,比如最新的安全更新,性能优化,Bug修复。这样做就意味着微软发布新的Typescript, 它会尽快的同步到谷歌内部,可以想你,Typescript的破坏性变更会影响许多代码!
为了验证库升级后没有原来代码,在内部的持续集成中需要重新构建所有的依赖项目,并执行相应测试。因为有无数的TS文件,无数行TS代码,如果把控不好,整个过程可能需要点时间。
多数情况,项目依赖其它项目,比如Google Cloud依赖它的UI和后端服务。当只更新Typescript时,既然后台服务不依赖Typescript,我们不希望它重新构建后台服务!
Bazel是什么
为了构建项目,谷歌开源了这个Bazel工具。它是一个强大的工具,它可以跟踪不同的包间的依赖,并构建目标。简单说,一个构建就是一个构建规则,比如: “构建一下Typescript库”; 从项目看,一个包对应着一个文件夹中的许多文件,它有清晰的依赖包。在Google Cloud的示例中,Bazel会对应下面这么一个依赖关系图:
简单说,我们把上图每一个块都对应一个构建目标。当其中某个块变化,Bazel会计算哪些包直接或间接依赖于它,并构建它们。在上面例子,如果TypeScript变化了,它会构建除了 back-end services之外的块。
下面是Bazel的酷的特性:
1、它有一个聪明的算法来计算依赖
2、它有独立的技术栈。你可以用同样的接口构建任意的事情。就像,已经有许多它的插件来服务于:Java,Go,Typescript,Javascript等等。
我们先看看第一条,基于一个项目的依赖图,Bazel会判断哪些构建目标是可以并行处理。但这个特性只在单元测试已经很好的定义了输入项和输出项,且它们不产生副作用时才有效。我们可以理解为它们是“纯函数”才行。这个“可被计算”的模型有一个好处,它很容易通过并行和缓存的方法,就减少计算量。Bazel就是这样的,它会对独立的构建任务缓存产生的输出结果,即使在云端的时候!
为什么要强调云端的缓存问题呢?如果Bazel能够构建后且缓存在云端,任何人都能利用这个构建结果。如果你是一个大公司肯定需要这样,即使小团队也能受益于它。Bazel并不是绑定于某个云平台的,这就是说你可以在Google Cloud, Azure, AWS, 或你自己的设备上获取缓存远程构建这个好处。
好吧,虚的讲了很多了,让我们看个例子吧!
用Bazel构建TypeScript
这个例子时在,我们构建一个小的ts项目,最终生成一个es5的js文件。 这个项目只有以下几个模块:
Lexer - 输入字符串后,返回一个token数组
Parser - 输入一个token数组。返回抽像语法树AST.
Interpreter - accepts an AST and evaluates it
Application - wires everything together - passes the program to the lexer, feeds the parser with the lexer’s output, and the interpreter with the produced AST
配置Workspace环境
我们的项目依赖npm包TypeScript, Bazel, 还有一个Bazel的TypeScript规则。 这和其它项目没什么不同的。所以我们现在深入WORKSPACE 这个配置文件一探究竟:
workspace(name = 'lang') http_archive( name = "build_bazel_rules_typescript", url = "https://github.com/bazelbuild/rules_typescript/archive/0.21.0.zip", strip_prefix = "rules_typescript-0.21.0", ) # Fetch our Bazel dependencies that aren't distributed on npm load("@build_bazel_rules_typescript//:package.bzl", "rules_typescript_dependencies") rules_typescript_dependencies() # Setup TypeScript toolchain load("@build_bazel_rules_typescript//:defs.bzl", "ts_setup_workspace") ts_setup_workspace() # Setup the Node.js toolchain load("@build_bazel_rules_nodejs//:defs.bzl", "node_repositories", "yarn_install") node_repositories() # Setup Bazel managed npm dependencies with the `yarn_install` rule. yarn_install( name = "npm", package_json = "//:package.json", yarn_lock = "//:yarn.lock", )
要知道这篇文章只是一个浅显的入门介绍。事实上,你可能从未自己管理过Bazel 配置.
上面这个文件使用的语言叫 Starlark,你可以认为它是Python语言的子集,这是我们声明的:
workspace 名称是 lang
项目使用 Bazel’s TypeScript rules.要记住它和我们声明在package.json 的版本号是一致的。
下一步,我们获取Bazel’s的依赖项 build_bazel_rules_typescript
设置 TypeScript workspace
设置 Bazel’s Node.js 的构建工具链. 它是由 Bazel team 维护的一组工具,所以我们可以在此使用 Node.js
最后我们声明一个规则让Bazel 管理 npm 依赖!
如果上面代码看不明白也没关系,只要记住 load
差不多是Node.js中的 require, 不同的是load 还可以从网络地址获取依赖!
配置编译目标
我们深入到更细的粒度上。我们把项目中独立的模块当成一个个 Bazel 包,在这每一个包中,我们定义一个编译目标。我们上面提到过,我们把每个包看作一个文件夹,它包含里面的文件以及一个构建规则。
每个文件夹都有一个BUILD
或 BUILD.bzl
文件。先看一下要项目中的BUILD文件:
package(default_visibility = ["//visibility:public"]) load("@build_bazel_rules_typescript//:defs.bzl", "ts_library") ts_library( name = "app", srcs = ["test.ts"], deps = ["//lexer", "//parser", "//interpreter"], ) load("@build_bazel_rules_nodejs//:defs.bzl", "rollup_bundle") rollup_bundle( name = "bundle", entry_point = "test.js", deps = [":app"], )
我们首先定义这个包是否对外可见,之后加载2个Bazel的规则:
ts_library
- 用它来编译Typescript文件
rollup_bundle
- 调用 Rollup.js 把各模块打包到一个文件里去。
ts_library
规则用来构建名为 app
的目标. 这个app的构建目标就是把各个模块串联到一起后的结果. 它依赖于 lexer
, parser
, and the interpreter
. 在这三个文件夹中,还都有着各自的 BUILD
文件,内容差不多也是上面样子. 比如 parser的内容
:
package(default_visibility = ["//visibility:public"]) load("@build_bazel_rules_typescript//:defs.bzl", "ts_library") ts_library( name = "parser", srcs = glob(["*.ts"]), deps = ["//lexer"], )
这里我们用glob
来限定源文件,另外这个模块还依赖着 lexer 模块
诸多依赖就有诸多构建目标,这可能带来混乱。Bazel 有个简洁的功能是依赖关系图必须是静态可分析的。我们能用Bazel查询语法,直接查询依赖关系图。来让我看看看它什么样子吧!
第一步,用 yarn
来安装 package.json
中的所有依赖包,这样就自动安装好 Bazel 。
yarn
启动bazel (可能要先安装graphviz才行)
./node_modules/.bin/bazel query --output=graph ... | dot -Tpng > graph.png
上面命令的输出结果如下:
我们看到各个目标间的依赖关系。
怎么监听项目文件的变化,及时进行rebuilding呢?为了这个目的,我们要安装运行 @bazel/ibazel 包
# Don’t forget yarn add -D @bazel/ibazel ./node_modules/.bin/ibazel build :app
通过ibazel
and bazel
包,在依赖直接或间接变化时,它会重新构建目标
注意,在上面命令中,我们构建的是 :app 的目标。这是因为在代码开发中,基于Bazel的Rollup.js规则,我们不可能直接编码影响到 :bundle
这个构建目标。(言外之意是, :bundle
完全是通过 :app
生成的)
现在让我们创建npm的命令缩写来代替 ./node_modules/.bin/bazel
or ./node_modules/.bin/ibazel
在package.json
添加下面两句话:
{ "name": "bazel-demo", "license": "MIT", "scripts": { "build": "bazel build :bundle", "watch": "ibazel build :app" }, "devDependencies": { "@bazel/bazel": "^0.19.1", "@bazel/typescript": "0.21.0", "typescript": "^3.1.6" } }
现在运行yarn build
来重新构建项目或 yarn watch
来监听并自动构建项目。
评论删除后,数据将无法恢复
评论(1)