Skip to main content

构建系统

Backstage 构建系统是一系列构建和开发工具的集合,可帮助您对 Backstage 项目进行检查、测试、开发和最终发布。 构建系统的目的是提供与 Backstage 完美配合的开箱即用解决方案,让您专注于应用程序的构建,而不必花时间设置自己的工具。

构建系统设置是@backstage/cli并已包含在您使用@backstage/create-app例如React 脚本这是您使用创建 React 应用程序Backstage 构建系统在很大程度上依赖于 JavaScript 和 TypeScript 生态系统中现有的开源工具,例如Webpack,卷轴,玩笑ESLint.

设计考虑因素

在设计 Backstage 构建系统时,我们遵循了几个核心理念和约束条件。 首先,也是最重要的一点是,我们将开发体验放在第一位。 如果我们需要偷工减料或增加复杂性,我们会在其他领域这样做,但启动编辑器和迭代代码的体验应始终尽可能顺畅。

此外,还有一些硬性和软性要求:

  • 发布--应能构建和发布单个软件包 * 扩展--应能扩展到数百个大型软件包,而无需过长的等待时间 * 重载--开发流程应支持快速的保存热重载 * 简单--使用应简单,配置应保持在最低水平 * 通用--面向网络应用程序、同构软件包和 Node.js 的开发 * 现代--构建系统以现代环境为目标 * 编辑器--大多数编辑器都应提供类型检查和衬垫功能

在设计构建系统的过程中,现有工具无法支持这种需求收集,例如react-scripts由于需要结合 monorepo、出版和编辑支持进行扩展,我们采用了自己的专门设置。

结构

我们可以将 Backstage 内部的开发流程分为几个不同的步骤:

  • 格式化** - 对源代码应用一致的格式化 * 着色 - 分析源代码以查找潜在问题 * 类型检查 - 验证 TypeScript 类型是否有效 * 测试 - 对项目运行不同级别的测试套件 * 构建 - 在单个软件包中编译源代码 * 捆绑 - 将软件包及其所有依赖项合并为一个可用于生产的捆绑包

这些步骤通常是相互隔离的,每个步骤都专注于其特定的任务。 例如,我们不会在构建或捆绑的同时进行着色或类型检查。 这样做是为了提供更大的灵活性,避免重复工作,提高性能。 强烈建议您在开发 Backstage 时使用支持格式化、着色和类型检查的代码编辑器或集成开发环境。

下面我们就来详细了解一下这些步骤中的每一步,以及它们在典型的 Backstage 应用程序中是如何实现的。

包裹角色

要迁移现有项目,请参阅迁移指南

Backstage 构建系统使用软件包角色的概念,以帮助精简配置、提供实用程序和工具,并实现优化。 软件包角色是一个单独的字符串,用于标识软件包的用途,它定义在package.json每个软件包的

{
"name": "my-package",
"backstage": {
"role": "<role>"
},
..
}

这些是Backstage构建系统目前支持的可用角色:

| 角色 | 说明 | 示例 | | ---------------------- | -------------------------------------------- | -------------------------------------------- | 前端 | 绑定的前端应用程序 | 前端应用程序packages/app| | 后端 | | 捆绑后端应用程序 | | 后端应用程序packages/backend| 作为命令行界面使用的软件包@backstage/cli,@backstage/codemods| | web-library | 供其他软件包使用的网络库 | | | web-library@backstage/plugin-catalog-react| | node-library | 供其他软件包使用的 Node.js 库 | | | Node.js 数据库@backstage/plugin-techdocs-node| 其他软件包使用的同构库@backstage/plugin-permission-common| | 前端插件 | Backstage前端插件 | | 前端插件@backstage/plugin-scaffolder| | 前端插件模块 | Backstage前端插件模块 | | 前端插件模块@backstage/plugin-analytics-module-ga| | 后端插件 | Backstage后端插件 | | 后端插件@backstage/plugin-auth-backend| | 后端插件模块 | Backstage后端插件模块@backstage/plugin-search-backend-module-pg|

下面介绍的大多数步骤都有相应的命令,可用作软件包脚本。 这些命令都可以在backstage-cli package这些命令可以这样使用:

{
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
..
}
}

格式化

格式设置完全在每个 Backstage 应用程序中进行,而不是 CLI 的一部分。 在使用@backstage/create-app格式由漂亮但每个应用程序都可以选择自己的格式化规则,并根据需要切换到不同的格式化器。

林汀

Backstage CLI 包括一个lint命令,它是对eslint它增加了一些无法通过配置设置的选项,例如包括.ts.tsx扩展名。lint命令只是提供了一个合理的默认设置,并不打算进行自定义。 如果想提供更多高级选项,可以调用eslint而不是直接

除了lint命令,后端 CLI 还包括一套基本 ESLint 配置,一套用于前端软件包,另一套用于后端软件包。 这些 lint 配置反过来又建立在来自于@spotify/web-scripts.

在标准的 Backstage 设置中,每个单独的软件包都有自己的 lint 配置,以及适用于整个项目的根配置。 每个软件包中的配置一开始都是根据软件包角色确定的标准配置,但也可以根据每个软件包的需要进行定制。

最低限度.eslintrc.js现在的配置是这样的

module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);

但您可以使用可选的第二个参数为每个软件包提供自定义重载:

module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, {
ignorePatterns: ['templates/'],
rules: {
'jest/expect-expect': 'off',
},
});

配置工厂还为扩展配置提供了实用工具,否则使用普通 ESLint 进行扩展会非常麻烦,特别是对于像no-restricted-syntax这些是可用的额外按键:

| 关键 | 说明 | | -------------------------- | ------------------------------------------------------------------ | | |tsRules| 适用于 TypeScript 文件的附加规则testRules| 适用于测试文件的附加规则restrictedImports| 添加到no-restricted-imports| |restrictedImportPatterns| 添加到其他模式no-restricted-imports| |restrictedSrcImports| 添加到no-restricted-imports在源文件中restrictedTestImports| 添加到no-restricted-imports在测试文件中restrictedSyntax| 添加到其他模式no-restricted-syntax| |restrictedSrcSyntax| 添加到其他模式no-restricted-syntax在源文件中restrictedTestSyntax| 添加到其他模式no-restricted-syntax在测试文件中

类型检查

与格式化一样,Backstage CLI 也没有自己的类型检查命令,但它有一个基本配置,其中既有推荐的默认值,也有构建系统工作所需的一些设置。

在 backstage 项目中,TypeScript 设置最值得注意的部分可能是整个项目是一个大的编译单元。 这是出于性能优化和易用性的考虑,因为将项目分解成更小的部分已被证明会导致更复杂的设置,而且整个项目的类型检查也会慢一个数量级。 为了使这种设置正常工作,每个包的入口点都需要指向 TypeScript 源文件,这反过来又会在发布过程中造成一些复杂问题,我们将在以下章节中讨论该部分.

类型检查一般配置为增量式,用于本地开发,其输出存储在dist-types这样,在运行tsc中禁用增量类型检查。tcs:fullYarn 脚本默认包含在任何 Backstage 应用程序中,供 CI 使用。

另一个默认使用的优化是跳过库类型的检查,这意味着 TypeScript 不会验证在node_modules禁用该检查可大大加快类型检查的速度,但归根结底,它仍然是一个重要的检查,不应完全省略,因为它根本不可能捕捉到本地开发过程中引入的问题。 我们选择在 CI 中通过tsc:full脚本,它将运行全面的类型检查,包括node_modules.

基于上述两个原因,我们认为高度建议使用tsc:full脚本在 CI 中运行类型检查。

测试

如上所述,Backstage CLI 使用玩笑Jest 是一个 JavaScript 测试框架,涵盖测试执行和断言。 Jest 在 Node.js 中执行所有测试,包括前端浏览器代码。 它使用的技巧是在 Node.js 虚拟机中使用各种预定义环境执行测试,例如基于jsdom有助于模仿浏览器 API 和行为。

Backstage CLI 有自己的命令,可以帮助执行测试、backstage-cli test以及其自身的配置@backstage/cli/config/jest.js该命令是运行jest它的主要职责是确保使用所包含的配置,以及设置NODE_ENVTZ环境变量,并提供了一些合理的默认标志,如--watch在 Git 仓库中执行。

到目前为止,最大的工作量是由Backstage CLI 附带的 Jest 配置完成的。 它既负责提供默认的 Jest 配置,又允许在每个package.json如何在实践中做到这一点,将在Jest 配置节。

架构

构建过程的主要目的是为发布软件包做准备,但它也是后端捆绑过程的一部分。 由于它只在这两种情况下使用,任何不使用项目后端部分的 Backstage 应用程序可能根本不需要与构建过程交互。 不过,了解它的工作原理还是很有用的,因为所有已发布的 Backstage 软件包都是通过该过程构建的。

目前的构建使用卷轴并对每个软件包单独执行。package build命令,并适用于所有软件包角色(捆绑的角色除外)、frontendbackend.

联编过程可能有三种不同的输出:CommonJS 模块格式的 JavaScript、ECMAScript 模块格式的 JavaScript 以及类型声明。 每次调用联编命令都会将其中一个或多个输出写入dist文件夹,并复制样式表或图像等任何资产文件。 有关构建过程支持哪些语法和文件格式的更多详情,请参阅装载机部分.

在构建 CommonJS 或 ESM 输出时,构建命令将始终使用src/index.ts所有非相关模块的导入都被视为外部导入,这意味着 Rollup 编译只会编译软件包本身的源代码。 所有外部依赖包的导入语句,即使是在同一个 monorepo 中,也将保持不变。

类型定义的联编工作方式完全不同。 类型定义联编的入口点是软件包在dist-types这意味着,在构建任何包含类型定义的包之前,运行类型检查非常重要,并且必须在 TypeScript 配置中启用类型声明。 类型定义构建步骤的原因是,除从包中导出的类型外,剥离所有类型,留下一个更简洁的类型定义文件,并确保类型定义与生成的 JavaScript 同步。

捆绑

捆绑过程的目的是将多个软件包组合成一个运行时单元。 在前端和后端、本地开发和生产部署之间,捆绑的方式各不相同。 因此,我们将分别介绍这些情况的每一种组合。

前端开发

前端开发设置用于所有具有前端角色的软件包,并使用package start不同角色之间的唯一区别是,使用'frontend'角色使用src/index作为入口点,而其他角色则使用dev/index运行启动命令后,开发服务器将被设置为监听协议、主机和端口。app.baseUrl如果需要,也可以通过app.listen配置。

前端开发捆绑目前基于WebpackWebpack 开发服务器Webpack 配置本身在前端开发和生产捆绑之间差别不大,因此我们将在下面的生产部分深入探讨配置。 主要区别在于process.env.NODE_ENV设置为'development'禁用最小化,使用廉价源映射,以及React 刷新已启用。

如果您希望在 Webpack 流程中运行类型检查和工整,可以启用使用ForkTsCheckerWebpackPlugin通过传递--check尽管如上所述,在开发过程中处理这些检查的推荐方法是使用内置支持这些检查的编辑器。

前端生产

前端制作捆绑会创建典型的网络内容捆绑包,所有内容都包含在一个文件夹中,可随时提供静态服务。'frontend'角色,而且与开发捆绑不同的是,没有办法为单个插件构建生产捆绑包。 捆绑过程的输出将写入dist文件夹。

与开发捆绑一样,生产捆绑的基础是Webpack它使用HtmlWebpackPlugin以产生index.html可通过添加public/index.html模板可以访问两个全局常量、publicPath是捆绑包打算提供服务的公共基本路径,以及config的常规前端范围配置。@backstage/config.

Webpack 配置还包括一个自定义插件,用于从链接的软件包中正确解析软件包,即ModuleScopePluginReact-dev-utils可以确保导入不会超出包的范围,还可以为一些 Node.js 模块提供一些回退,例如'buffer''events'的插件,将前端配置写入捆绑包,作为process.env.APP_CONFIG最后由EESBUILD使用`esbuild-loader当然,还配置了一组加载器,您可以在装载机换位部分。

在构建过程中,还会设置以下常量:

process.env.NODE_ENV = 'production';
process.env.BUILD_INFO = {
cliVersion: '0.4.0', // The version of the CLI package
gitVersion: 'v0.4.0-86-ge54815618', // output of `git describe --always`
packageVersion: '1.0.5', // The version of the app package itself
timestamp: 1678900000000, // Date.now() when the build started
commit: 'e548156182a973ed4b459e18533afc22c85ffff8', // output of `git rev-parse HEAD`
};

捆绑过程的输出被分成两类文件,分别采用不同的缓存策略。 第一类是一组通用资产,名称简单,位于dist/您需要使用短时缓存或完全不使用缓存来提供这些内容。 第二种是一组散列静态资产,位于dist/static/文件夹,您可以将其配置为缓存更长的时间。

静态资产的配置针对频繁更改和通过 HTTP 2.0 提供服务进行了优化。 资产被积极地分割成小块,这意味着浏览器必须发出大量小请求才能加载它们。 这样做的好处是,对单个插件和软件包的更改将使较少数量的文件失效,从而允许快速开发而不会对页面加载性能造成太大影响。

后端开发

后端开发捆绑也基于 Webpack,但不是启动网络服务器,而是使用RunScriptWebpackPlugin在后端开发中使用 Webpack 的原因是,它既能方便地处理大量软件包的转换,又能让我们使用热模块替换,并在重载单个后端模块时保持状态。 这在使用内存 SQLite 作为数据库运行后端时尤其有用。

除了在 Node.js 而不是网络服务器中执行外,后端开发捆绑配置与前端非常相似。 它共享了大部分 Webpack 配置,包括转译设置。 一些不同之处在于,它不注入任何环境变量或 node 模块回退,并且使用webpack-node-externals以避免捆绑依赖模块。

如果要检查正在运行的 Node.js 进程,可使用--inspect--inspect-brk标记,因为它们将作为选项传递给node执行。

后端生产

后端生产捆绑使用的设置与其他捆绑选项完全不同。 后端生产捆绑不使用 Webpack,而是将后端软件包及其所有本地依赖包收集到一个部署归档中。 该归档被写入dist/bundle.tar.gz归档文件中的软件包布局与 monorepo 中的目录布局相同,并且捆绑包还包含根目录package.jsonyarn.lock文件

请注意,在创建生产捆绑包之前,您必须首先构建所有后端软件包。 这可以在执行backend:bundle命令,传递--build-dependencies标记,这是一个可选标记,因为通常情况下,软件包已经在构建过程的较早阶段构建好了,再次构建会造成重复工作。

要使用捆绑包,您需要将其解压缩到一个目录中,然后运行yarn install --production, 然后使用你的后端软件包作为 Node.js 入口点启动后端,例如node packages/backend.

dist/bundle.tar.gz伴随着dist/skeleton.tar.gz,其布局相同,但只包含package.json该骨架存档可用于运行yarn install要使用骨架归档文件,可将其复制到目标目录,同时将根目录中的package.jsonyarn.lock,解压缩存档,然后运行yarn install --production这样,您的目标目录就安装了所有依赖项,只要您复制并解压目标目录中的bundle.tar.gz在此基础上,Backstage就可以运行了。

下面以Dockerfile可以用来打包使用角色'backend'变成图像:

FROM node:18-bookworm-slim
WORKDIR /app

COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./
RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz

# install sqlite3 dependencies
RUN apt-get update && \
apt-get install -y libsqlite3-dev python3 cmake g++ && \
rm -rf /var/lib/apt/lists/* && \
yarn config set python /usr/bin/python3

RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)"

COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./
RUN tar xzf bundle.tar.gz && rm bundle.tar.gz

CMD ["node", "packages/backend"]

Transpilation

Backstage CLI 所使用的转码器是根据上述相同的设计考虑而选择的。 一些特定的要求当然包括支持 TypeScript 和 JSX,但也包括 React 热重载或刷新,以及 Jest mock 的吊装。 Backstage CLI 也只针对最新的现代浏览器,因此我们实际上希望尽可能保持转码过程的轻量级,并保留大部分语法。

除了这些要求外,使用哪种转译器的决定性因素还在于它们的速度。 编译过程保持了与转译器的轻量级集成,不需要额外的插件或其他东西。 这使我们能够在有新的选项和优化时更换转译器,并继续使用现有的最佳选项。

我们现有的转运车有EESBUILDSucrase我们选择使用两种转译器的原因是,esbuild 比 Sucrase 更快,输出结果也稍好,但它不具备相同的功能,例如它不支持 React 热重载。

各种方案的基准是在以下情况下确定的ts-build-bench该基准测试项目允许设置不同形状和大小的 monorepo,但在我们的案例中,我们认为最重要的设置是一个大型 monorepo,其中包含大量使用 Webpack 绑定的中大型软件包。 一些粗略的发现是,esbuild 是目前最快的选择,Sucrase 紧随其后,然后是SWC在此之后,TypeScript 编译器与仅在转译模式下运行的 TypeScript 编译器差距很大,最后又跳到了 Babel,它是迄今为止我们测试过的转译器中速度最慢的。

需要注意的是,这些基准将 Webpack 的全部捆绑时间都考虑在内了。 这意味着,即使某些转码选项可能比其他选项快几个数量级,但由于捆绑过程中还有很多其他事项,因此总时间受到的影响并不相同。 尽管如此,从 Babel 到 Sucrase 的转换仍能使捆绑速度提高 2 到 5 倍不等。

装载机

Backstage CLI 的配置支持一套加载程序,贯穿构建系统的所有部分,包括捆绑、测试、构建和类型检查。 加载程序总是根据文件扩展名进行选择。 以下是所有支持的文件扩展名列表:

| 扩展 | 出口 | 目的 | | --------- | ------------- | ------------------ | | | |.ts| 脚本模块.tsx| 脚本模块 | TypeScript 和 XML | | | | | TypeScript 和 XML.js| 脚本模块.jsx| 脚本模块.mjs| ECMAScript 模块.cjs| 脚本模块.json| JSON 数据.yml| JSON 数据 | YAML 数据 | | | JSON 数据.yaml| JSON 数据 | YAML 数据 | | | JSON 数据.css| 类 | 样式表 | | | 样式表.eot| URL 路径 | 字体 | | | |.ttf| URL 路径 | 字体 | | | |.woff2| URL 路径 | 字体 | | | |.woff| URL 路径 | 字体 | | | |.bmp| URL 路径 | 图像 | | |.gif| URL 路径 | 图像 | | |.jpeg| URL 路径 | 图像 | | |.jpg| URL 路径 | 图像 | | |.png| URL 路径 | 图像 | | |.svg| URL 路径 | 图像 | | |.md| URL 路径 | Markdown 文件

Jest 配置

Backstage CLI 捆绑了自己的 Jest 配置文件,运行时会自动使用backstage-cli test可在@backstage/cli/config/jest.js并可进行检查这里可以通过传递一个--config <path>标记为backstage-cli test或放置一个jest.config.jsjest.config.ts文件。

内置配置带来了一些优势和功能,其中最重要的是变压器和模块的基准配置,可支持下列功能装载机它还会自动检测和使用src/setupTests.ts如果存在的话,并提供与我们所选转译器配合良好的覆盖配置。 该配置还将为每个软件包角色检测适当的 Jest 环境,运行web-libraries"jsdom"环境node-libraries"node"等等。

该配置还采用了整个项目范围内的方法,希望一个 monorepo 中的大多数(如果不是全部)软件包都使用相同的基本配置。 这样可以进行优化,例如在 monorepo 中的所有软件包中共享 Jest 转换缓存,避免不必要的转译。 这样还可以一次性加载所有 Jest 配置,并运行yarn test <pattern>无需将工作目录设置为测试所在的软件包。

在需要进行小规模定制时,例如设置覆盖范围阈值或支持特定转换,可以通过"jest"字段中的package.json有关选项的完整列表,请参见Jest 文档这些重载将从所有package.json目录中的文件,这意味着您可以将常用配置放在package.json如果发现多个覆盖,它们将被合并在一起,目录树中更靠下的配置优先。

单个package.json例如,可以是这样的

"jest": {
"coverageThreshold": {
"global": {
"functions": 100,
"lines": 100,
"statements": 100
}
}
},

调试 Jest 测试

为了提高单元测试的工作效率,在集成开发环境中配置调试功能至关重要。 它将帮助您更快地找出问题的根本原因。

IntelliJ IDEA

  1. 通过以下方式更新 Jest 配置模板
  • 点击顶部面板上的 "编辑配置" * 在模态对话框中点击左下角的 "编辑配置模板.. "链接 * 在 "Jest包 "中,你必须指向jest模块的相对路径(IntelliJ会建议),即~/proj/backstage/node_modules/jest * 在 "Jest配置 "中,指向你的jest配置文件,使用绝对路径,即--config /Users/user/proj/backstage/packages/cli/config/jest.js --runInBand

点击 "描述 "或 "它 "上的绿色箭头,即可运行任何测试。

VS 代码

{
"jest.jestCommandLine": "node_modules/.bin/jest --config node_modules/@backstage/cli/config/jest.js",
// In a large repo like the Backstage main repo you likely want to disable
// watch mode and the initial test run too, leaving just manual and perhaps
// on-save test runs in place.
"jest.autoRun": {
"watch": false,
"onSave": "test-src-file"
}
}

用于 VS 代码调试的完整启动配置如下所示:

{
"type": "node",
"name": "vscode-jest-tests.v2",
"request": "launch",
"args": [
"repo",
"test",
"--runInBand",
"--watchAll=false",
"--testNamePattern",
"${jest.testNamePattern}",
"--runTestsByPath",
"${jest.testFile}"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
"program": "${workspaceFolder}/node_modules/.bin/backstage-cli"
}

出版

软件包发布是 Backstage 构建系统的一个可选部分,除非将软件包发布到注册表,否则无需担心。 要发布软件包,首先需要构建它,这将填充dist由于 Backstage 构建系统已针对本地开发以及我们特定的 TypeScript 和捆绑设置进行了优化,因此此时无法立即发布软件包。 这是因为软件包的入口点仍将指向src/index.ts但我们希望它们指向dist/在发布的软件包中。

为了解决这个问题,Backstage CLI 提供了prepackpostpack这些脚本会在发布软件包前由 Yarn 自动运行。

prepack命令将在"publishConfig"例如"main""module",并将它们移动到package.json这样,您就可以在dist在发布过程中postpack命令会简单地还原这一更改,使项目保持干净。

以下是同构库软件包的典型设置节选:

"main": "src/index.ts",
"types": "src/index.ts",
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"scripts": {
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"clean": "backstage-cli package clean",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack"
},
"files": ["dist"],

子路径出口

Backstage CLI 支持通过"exports"字段中的package.json例如,它可能是这样的:

"name": "@backstage/plugin-foo",
"exports": {
".": "./src/index.ts",
"./components": "./src/components.ts",
},

这样,您就可以导入在src/index.ts经由@backstage/plugins-foosrc/components.ts经由@backstage/plugins-foo/components注意不支持模式,这意味着导出内容可能不包含*通配符。

与Backstage CLI 构建系统的其他部分一样,该设置针对本地开发进行了优化,这也是为什么"exports"目标直接指向源文件。package build命令将检测"exports"字段,并自动生成相应的dist文件,以及prepublish命令将重写"exports"字段指向dist文件,并生成基于文件夹的入口点,以实现向后兼容。

目前通过typesVersions字段,因为还没有一种模块分辨率模式能与"exports"您可以制作typesVersions但它也会由migrate package-exports指挥。

要在现有软件包中添加子路径导出,只需添加所需的"exports"字段,然后运行以下命令:

yarn backstage-cli migrate package-exports