Merge pull request #8964 from backstage/rugvip/build-system
docs: introduce build system docs
This commit is contained in:
@@ -0,0 +1,508 @@
|
||||
---
|
||||
id: cli-build-system
|
||||
title: Build System
|
||||
description: A deep dive into the Backstage build system
|
||||
---
|
||||
|
||||
The Backstage build system is a collection of build and development tools that
|
||||
help you lint, test, develop and finally release your Backstage projects. The
|
||||
purpose of the build system is to provide an out-of-the-box solution that works
|
||||
well with Backstage and lets you focus on building an app rather than having to
|
||||
spend time setting up your own tooling.
|
||||
|
||||
The build system setup is part of the
|
||||
[@backstage/cli](https://www.npmjs.com/package/@backstage/cli), and already
|
||||
included in any project that you create using
|
||||
[@backstage/create-app](https://www.npmjs.com/package/@backstage/create-app). It
|
||||
is similar to for example
|
||||
[react-scripts](https://www.npmjs.com/package/react-scripts), which is the
|
||||
tooling you get with
|
||||
[create-react-app](https://github.com/facebook/create-react-app). The Backstage
|
||||
build system relies heavily on existing open source tools from the JavaScript
|
||||
and TypeScript ecosystem, such as [Webpack](https://webpack.js.org/),
|
||||
[Rollup](https://rollupjs.org/), [Jest](https://jestjs.io/), and
|
||||
[ESLint](https://eslint.org/).
|
||||
|
||||
## Design Considerations
|
||||
|
||||
There are a couple of core beliefs and constraints that guided the design of the
|
||||
Backstage build system. The first and most important is that we put the
|
||||
development experience first. If we need to cut corners or add complexity we do
|
||||
so in other areas, but the experience of firing up an editor and iterating on
|
||||
some code should always be as smooth as possible.
|
||||
|
||||
In addition, there are a number of hard and soft requirements:
|
||||
|
||||
- Monorepos - The build system should support multi-package workspaces
|
||||
- Publishing - It should be possible to build and publish individual packages
|
||||
- Scale - It should scale to hundreds of large packages without excessive wait
|
||||
times
|
||||
- Reloads - The development flow should support quick on-save hot reloads
|
||||
- Simple - Usage should simple and configuration should be kept minimal
|
||||
- Universal - Development towards both web applications, isomorphic packages,
|
||||
and Node.js
|
||||
- Modern - The build system targets modern environments
|
||||
- Editors - Type checking and linting should be available within most editors
|
||||
|
||||
During the design of the build system this collection of requirements was not
|
||||
something that was supported by existing tools like for example `react-scripts`.
|
||||
The requirements of scaling in combination of monorepo, publishing, and editor
|
||||
support led us to adopting our own specialized setup.
|
||||
|
||||
## Structure
|
||||
|
||||
We can divide the development flow within Backstage into a couple of different
|
||||
steps:
|
||||
|
||||
- **Formatting** - Applies a consistent formatting to your source code
|
||||
- **Linting** - Analyzes your source code for potential problems
|
||||
- **Type Checking** - Verifies that TypeScript types are valid
|
||||
- **Testing** - Runs different levels of test suites towards your project
|
||||
- **Building** - Compiles the source code in an individual package
|
||||
- **Bundling** - Combines a package and all of its dependencies into a
|
||||
production-ready bundle
|
||||
|
||||
These steps are generally kept isolated form each other, with each step focusing
|
||||
on its specific task. For example, we do not do linting or type checking
|
||||
together with the building or bundling. This is so that we can provide more
|
||||
flexibility and avoid duplicate work, improving performance. It is strongly
|
||||
recommended that as a part of developing withing Backstage you use a code editor
|
||||
or IDE that has support for formatting, linting, and type checking.
|
||||
|
||||
Let's dive into a detailed look at each of these steps and how they are
|
||||
implemented in a typical Backstage app.
|
||||
|
||||
## Formatting
|
||||
|
||||
The formatting setup lives completely within each Backstage application and is
|
||||
not part of the CLI. In an app created with `@backstage/create-app` the
|
||||
formatting is handled by [prettier](https://prettier.io/), but each application
|
||||
can choose their own formatting rules and switch to a different formatter if
|
||||
desired.
|
||||
|
||||
## Linting
|
||||
|
||||
The Backstage CLI includes a `lint` command, which is a thin wrapper around
|
||||
`eslint`. It adds a few options that can't be set through configuration, such as
|
||||
including the `.ts` and `.tsx` extensions in the set of linted files. The `lint`
|
||||
command simply provides a sane default and is not intended to be customizable.
|
||||
If you want to supply more advanced options you can invoke `eslint` directly
|
||||
instead.
|
||||
|
||||
In addition to the `lint` command, the Backstage CLI also includes a set of base
|
||||
ESLint configurations, one for frontend and one for backend packages. These lint
|
||||
configurations in turn build on top of the lint rules from
|
||||
[@spotify/web-scripts](https://github.com/spotify/web-scripts).
|
||||
|
||||
In a standard Backstage setup, each individual package has its own lint
|
||||
configuration, along with a root configuration that applies to the entire
|
||||
project. Each configuration is initially one that simply extends a base
|
||||
configuration provided by the Backstage CLI, but they can be customized to fit
|
||||
the needs of each package.
|
||||
|
||||
## Type Checking
|
||||
|
||||
Just like formatting, the Backstage CLI does not have its own command for type
|
||||
checking. It does however have a base configuration with both recommended
|
||||
defaults as well as some required settings for the build system to work.
|
||||
|
||||
Perhaps the most notable part about the TypeScript setup in Backstage projects
|
||||
is that the entire project is one big compilation unit. This is due to
|
||||
performance optimization as well as ease of use, since breaking projects down
|
||||
into smaller pieces has proven to both lead to a more complicated setup, as well
|
||||
as type checking of the entire project being an order of magnitude slower. In
|
||||
order to make this setup work, the entrypoint of each package needs to point to
|
||||
the TypeScript source files, which in turn causes some complications during
|
||||
publishing that we'll talk about in [that section](#publishing).
|
||||
|
||||
The type checking is generally configured to be incremental for local
|
||||
development, with the output stored in the `dist-types` folder in the repo root.
|
||||
This provides a significant speedup when running `tsc` multiple times locally,
|
||||
but it does make the initial run a little bit slower. Because of the slower
|
||||
initial run we disable incremental type checking in the `tcs:full` Yarn script
|
||||
that is included by default in any Backstage app and is intended for use in CI.
|
||||
|
||||
Another optimization that is used by default is to skip the checking of library
|
||||
types, this means that TypeScript will not verify that types within
|
||||
`node_modules` are sound. Disabling this check significantly speeds up type
|
||||
checking, but in the end it is still an important check that should not be
|
||||
completely omitted, it's simply unlikely to catch issues that are introduced
|
||||
during local development. What we opt for instead is to include the check in CI
|
||||
through the `tsc:full` script, which will run a full type check, including
|
||||
`node_modules`.
|
||||
|
||||
For the two reasons mentioned above, it is **highly** recommended to use the
|
||||
`tsc:full` script to run type checking in CI.
|
||||
|
||||
## Testing
|
||||
|
||||
As mentioned above, the Backstage CLI uses [Jest](https://jestjs.io/), which is
|
||||
a JavaScript test framework that covers both test execution and assertions. Jest
|
||||
executes all tests in Node.js, including frontend browser code. The trick it
|
||||
uses is to execute the tests in a Node.js VM using various predefined
|
||||
environments, such as one based on [`jsdom`](https://github.com/jsdom/jsdom)
|
||||
that helps mimic browser APIs and behavior.
|
||||
|
||||
The Backstage CLI has its own command that helps execute tests,
|
||||
`backstage-cli test`, as well as its own configuration at
|
||||
`@backstage/cli/config/jest.js`. The command is a relatively thin wrapper around
|
||||
running `jest` directly. Its main responsibility is to make sure the included
|
||||
configuration is used, as well setting the `NODE_ENV` and `TZ` environment
|
||||
variables, and provided some sane default flags like `--watch` if executed
|
||||
within a Git repository.
|
||||
|
||||
The by far biggest amount of work is done by the Jest configuration included
|
||||
with the Backstage CLI. It both takes care of providing a default Jest
|
||||
configuration, as well as allowing for configuration overrides to be defined in
|
||||
each `package.json`. How this can be done in practice is discussed in the
|
||||
[Jest configuration](#jest-configuration) section.
|
||||
|
||||
## Building
|
||||
|
||||
The primary purpose of the build process is to prepare packages for publishing,
|
||||
but it's also used as part of the backend bundling process. Since it's only used
|
||||
in these two cases, any Backstage app that does not use the Backend parts of the
|
||||
project may not need to interact with the build process at all. It can
|
||||
nevertheless be useful to know how it works, since all of the published
|
||||
Backstage packages are built using this process.
|
||||
|
||||
The build is currently using [Rollup](https://rollupjs.org/) and executes in
|
||||
isolation for each individual package. There are currently three different
|
||||
commands in the Backstage CLI that invokes the build process, `plugin:build`,
|
||||
`backend:build`, and simply `build`. The two former are pre-configured commands
|
||||
for frontend and backend plugins, while the `build` command provides more
|
||||
control over the output.
|
||||
|
||||
There are three different possible outputs of the build process: JavaScript in
|
||||
CommonJS module format, JavaScript in ECMAScript module format, and type
|
||||
declarations. Each invocation of a build command will write one or more of these
|
||||
outputs to the `dist` folder in the package, and in addition copy any asset
|
||||
files like stylesheets or images. For more details on what syntax and file
|
||||
formats are supported by the build process, see the [loaders section](#loaders).
|
||||
|
||||
When building CommonJS or ESM output, the build commands will always use
|
||||
`src/index.ts` as the entrypoint. All dependencies of the package will be marked
|
||||
as external, meaning that in general it is only the contents of the `src` folder
|
||||
that ends up being compiled and output to `dist`. All import statements of
|
||||
external dependencies, even within the same monorepo, will stay intact. The
|
||||
externalized dependencies are based on dependency information in `package.json`,
|
||||
which means it's important to keep it up to date.
|
||||
|
||||
The build of the type definitions works quite differently. The entrypoint of the
|
||||
type definition build is the relative location of the package within the
|
||||
`dist-types` folder in the project root. This means that it is important to run
|
||||
type checking before building any packages with type definitions, and that
|
||||
emitting type declarations must be enabled in the TypeScript configuration. The
|
||||
reason for the type definition build step is to strip out all types but the ones
|
||||
that are exported from the package, leaving a much cleaner type definition file
|
||||
and making sure that the type definitions are in sync with the generated
|
||||
JavaScript.
|
||||
|
||||
## Bundling
|
||||
|
||||
The goal of the bundling process is to combine multiple packages together into a
|
||||
single runtime unit. The way this is done varies between frontend and backend,
|
||||
as well as local development versus production deployment. Because of that we
|
||||
cover each combination of these cases separately.
|
||||
|
||||
### Frontend Development
|
||||
|
||||
There are two different commands that start the frontend development bundling:
|
||||
`app:serve`, which serves an app and uses `src/index` as the entrypoint, and
|
||||
`plugin:serve`, which serves a plugin and uses `dev/index` as the entrypoint.
|
||||
These are typically invoked via the `yarn start` script, and are intended for
|
||||
local development only. When running the bundle command, a development server
|
||||
will be set up that listens to the protocol, host and port set by `app.baseUrl`
|
||||
in the configuration. If needed it is also possible to override the listening
|
||||
options through the `app.listen` configuration.
|
||||
|
||||
The frontend development bundling is currently based on
|
||||
[Webpack](https://webpack.js.org/) and
|
||||
[Webpack Dev Server](https://webpack.js.org/configuration/dev-server/). The
|
||||
Webpack configuration itself varies very little between the frontend development
|
||||
and production bundling, so we'll dive more into the configuration in the
|
||||
production section below. The main differences are that `process.env.NODE_ENV`
|
||||
is set to `'development'`, minification is disabled, cheap source maps are used,
|
||||
and [React Hot Loader](https://github.com/gaearon/react-hot-loader) is enabled.
|
||||
|
||||
If you prefer to run type checking and linting as part of the Webpack process,
|
||||
you can enable usage of the
|
||||
[`ForkTsCheckerWebpackPlugin`](https://www.npmjs.com/package/fork-ts-checker-webpack-plugin)
|
||||
by passing the `--check` flag. Although as mentioned above, the recommended way
|
||||
to handle these checks during development is to use an editor that has built-in
|
||||
support for them instead.
|
||||
|
||||
### Frontend Production
|
||||
|
||||
The frontend production bundling creates your typical web content bundle, all
|
||||
contained within a single folder, ready for static serving. It is invoked using
|
||||
the `app:build` command, and unlike the development bundling there is no way to
|
||||
build a production bundle of an individual plugin. The output of the bundling
|
||||
process is written to the `dist` folder in the package.
|
||||
|
||||
Just like the development bundling, the production bundling is based on
|
||||
[Webpack](https://webpack.js.org/). It uses the
|
||||
[`HtmlWebpackPlugin`](https://webpack.js.org/plugins/html-webpack-plugin/) to
|
||||
generate the `index.html` entry point, and includes a default template that's
|
||||
included with the CLI. You can replace the bundled template by adding
|
||||
`public/index.html` to your app package. The template has access to two global
|
||||
constants, `publicPath` which is the public base path that the bundle is
|
||||
intended to be served at, as well as `config` which is your regular frontend
|
||||
scoped configuration from `@backstage/config`.
|
||||
|
||||
The Webpack configuration also includes a custom plugin for resolving packages
|
||||
correctly from linked in packages, the `ModuleScopePlugin` from
|
||||
[`react-dev-utils`](https://www.npmjs.com/package/react-dev-utils) which makes
|
||||
sure that imports don't reach outside the package, a few fallbacks for some
|
||||
Node.js modules like `'buffer'` and `'events'`, a plugin that writes the
|
||||
frontend configuration to the bundle as `process.env.APP_CONFIG` and build
|
||||
information as `process.env.BUILD_INFO`, and lastly minification handled by
|
||||
[esbuild](https://esbuild.github.io/) using the
|
||||
[`esbuild-loader`](https://npm.im/esbuild-loader). There are of course also a
|
||||
set of loaders configured, which you can read more about in the
|
||||
[loaders](#loaders) and [transpilation](#transpilation) sections.
|
||||
|
||||
The output of the bundling process is split into two categories of files with
|
||||
separate caching strategies. The first is a set of generic assets with plain
|
||||
names in the root of the `dist/` folder. You will want to serve these with
|
||||
short-lived caching or no caching at all. The second is a set of hashed static
|
||||
assets in the `dist/static/` folder, which you can configure to be cached for a
|
||||
much longer time.
|
||||
|
||||
The configuration of static assets is optimized for frequent changes and serving
|
||||
over HTTP 2.0. The assets are aggressively split into small chunks, which means
|
||||
the browser has to make a lot of small requests to load them. The upside is that
|
||||
changes to individual plugins and packages will invalidate a smaller number of
|
||||
files, thereby allowing for rapid development without much impact on the page
|
||||
load performance.
|
||||
|
||||
### Backend Development
|
||||
|
||||
The backend development bundling is also based on Webpack, but rather than
|
||||
starting up a web server, the backend is started up using the
|
||||
[`RunScriptWebpackPlugin`](https://www.npmjs.com/package/run-script-webpack-plugin).
|
||||
The reason for using Webpack for development of the backend is both that it is a
|
||||
convenient way to handle transpilation of a large set of packages, as well us
|
||||
allowing us to use hot module replacement and maintaining state while reloading
|
||||
individual backend modules. This is particularly useful when running the backend
|
||||
with in-memory SQLite as the database choice.
|
||||
|
||||
Except for executing in Node.js rather than a web server, the backend
|
||||
development bundling configuration is quite similar to the frontend one. It
|
||||
shares most of the Webpack configuration, including the transpilation setup.
|
||||
Some differences are that it does not inject any environment variables or node
|
||||
module fallbacks, and it uses
|
||||
[`webpack-node-externals`](https://www.npmjs.com/package/webpack-node-externals)
|
||||
to avoid bundling in dependency modules.
|
||||
|
||||
If you want to inspect the running Node.js process, the `--inspect` and
|
||||
`--inspect-brk` flags can be used, as they will be passed through as options to
|
||||
`node` execution.
|
||||
|
||||
### Backend Production
|
||||
|
||||
The backend production bundling uses a completely different setup than the other
|
||||
bundling options. Rather than using Webpack, the backend production bundling
|
||||
instead collects the backend packages and all of their local dependencies into a
|
||||
deployment archive. The archive is written to `dist/bundle.tar.gz`, and contains
|
||||
the packaged version of each of these packages. The layout of the packages in
|
||||
the archive is the same as the directory layout in the monorepo, and the bundle
|
||||
also contains the root `package.json` and `yarn.lock` files.
|
||||
|
||||
Note that before creating a production bundle you must first build all of the
|
||||
backend packages. This can be done automatically when executing the
|
||||
`backend:bundle` command by passing the `--build-dependencies` flag. It is an
|
||||
optional flag since it is quite common that the packages are already built
|
||||
earlier on in your build process, and building them again would result in
|
||||
duplicate work.
|
||||
|
||||
In order to use the bundle, you extract it into a directory, run
|
||||
`yarn install --production`, and then start the backend using your backend
|
||||
package as the Node.js entry point, for example `node packages/backend`.
|
||||
|
||||
The `dist/bundle.tar.gz` is accompanied by a `dist/skeleton.tar.gz`, which has
|
||||
the same layout, but only contains `package.json` files. This skeleton archive
|
||||
can be used to run a `yarn install` in environments that will benefit from the
|
||||
caching that this enables, such as Docker image builds. To use the skeleton
|
||||
archive you copy it over to the target directory along with the root
|
||||
`package.json` and `yarn.lock`, extract the archive, and then run
|
||||
`yarn install --production`. Your target directory will then have all
|
||||
dependencies installed, and as soon as you copy over and extract the contents of
|
||||
the `bundle.tar.gz` archive on top of it, the backend will be ready to run.
|
||||
|
||||
The following is an example of a `Dockerfile` that can be used to package the
|
||||
output of `backstage-cli backend:bundle` into an image:
|
||||
|
||||
```Dockerfile
|
||||
FROM node:14-buster-slim
|
||||
WORKDIR /app
|
||||
|
||||
COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./
|
||||
RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz
|
||||
|
||||
RUN yarn install --production --frozen-lockfile --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
|
||||
|
||||
The transpilers used by the Backstage CLI have been chosen according to the same
|
||||
design considerations that were mentioned above. A few specific requirements are
|
||||
of course support for TypeScript and JSX, but also React hot reloads or refresh,
|
||||
and hoisting of Jest mocks. The Backstage CLI also only targets up to date and
|
||||
modern browsers, so we actually want to keep the transpilation process as
|
||||
lightweight as possible, and leave most syntax intact.
|
||||
|
||||
Apart from these requirements, the deciding factor for which transpiler to use
|
||||
is their speed. The build process keeps the integration with the transpilers
|
||||
lightweight, without additional plugins or such. This enables us to switch out
|
||||
transpilers as new options and optimizations become available, and keep on using
|
||||
the best options that are available.
|
||||
|
||||
Our current selection of transpilers are [esbuild](https://esbuild.github.io/)
|
||||
and [Sucrase](https://github.com/alangpierce/sucrase). The reason we choose to
|
||||
use two transpilers is that esbuild is faster than Sucrase and produces slightly
|
||||
nicer output, but it does not have the same set of features, for example it does
|
||||
not support React hot reloading.
|
||||
|
||||
The benchmarking of the various options was done in
|
||||
[ts-build-bench](https://github.com/Rugvip/ts-build-bench). This benchmarking
|
||||
project allows for setups of different shapes and sizes of monorepos, but the
|
||||
setup we consider the most important in our case is a large monorepo with lots
|
||||
of medium to large packages that are being bundled with Webpack. Some rough
|
||||
findings have been that esbuild is the fastest option right now, with Sucrase
|
||||
following closely after and then [SWC](https://swc.rs/) closely after that.
|
||||
After those there's a pretty big gap up to the TypeScript compiler run in
|
||||
transpilation only mode, and lastly another jump up to Babel, being by far the
|
||||
slowest out of the transpilers we tested.
|
||||
|
||||
Something to note about these benchmarks is that they take the full Webpack
|
||||
bundling time into account. This means that even though some transpilation
|
||||
options may be orders of magnitude faster than others, the total time is not
|
||||
impacted in the same way as there are lots of other things that go into the
|
||||
bundling process. Still, switching from for example Babel to Sucrase is able to
|
||||
make the bundling anywhere from two to five times faster.
|
||||
|
||||
## Loaders
|
||||
|
||||
The Backstage CLI is configured to support a set of loaders throughout all parts
|
||||
of the build system, including the bundling, tests, builds, and type checking.
|
||||
Loaders are always selected based on the file extension. The following is a list
|
||||
of all supported file extensions:
|
||||
|
||||
| Extension | Exports | Purpose |
|
||||
| ----------- | --------------- | ----------------------------------------------------------------------------- |
|
||||
| `.ts` | Script Module | TypeScript |
|
||||
| `.tsx` | Script Module | TypeScript and XML |
|
||||
| `.js` | Script Module | JavaScript |
|
||||
| `.jsx` | Script Module | JavaScript and XML |
|
||||
| `.mjs` | Script Module | ECMAScript Module |
|
||||
| `.cjs` | Script Module | CommonJS Module |
|
||||
| `.json` | JSON Data | JSON Data |
|
||||
| `.yml` | JSON Data | YAML Data |
|
||||
| `.yaml` | JSON Data | YAML Data |
|
||||
| `.css` | classes | Style sheet |
|
||||
| `.eot` | URL Path | Font |
|
||||
| `.ttf` | URL Path | Font |
|
||||
| `.woff2` | URL Path | Font |
|
||||
| `.woff` | URL Path | Font |
|
||||
| `.bmp` | URL Path | Image |
|
||||
| `.gif` | URL Path | Image |
|
||||
| `.jpeg` | URL Path | Image |
|
||||
| `.jpg` | URL Path | Image |
|
||||
| `.png` | URL Path | Image |
|
||||
| `.svg` | URL Path | Image |
|
||||
| `.icon.svg` | React Component | SVG converted into a [MUI SvgIcon](https://mui.com/components/icons/#svgicon) |
|
||||
|
||||
## Jest Configuration
|
||||
|
||||
The Backstage CLI bundles its own Jest configuration file, which is used
|
||||
automatically when running `backstage-cli test`. It's available at
|
||||
`@backstage/cli/config/jest.js` and can be inspected
|
||||
[here](https://github.com/backstage/backstage/blob/master/packages/cli/config/jest.js).
|
||||
Usage of this configuration can be overridden either by passing a
|
||||
`--config <path>` flag to `backstage-cli test`, or placing a `jest.config.js` or
|
||||
`jest.config.ts` file in your package.
|
||||
|
||||
The built-in configuration brings a couple of benefits and features. The most
|
||||
important one being a baseline transformer and module configuration that enables
|
||||
support for the listed [loaders](#loaders) within tests. It will also
|
||||
automatically detect and use `src/setupTests.ts` if it exists, and provides a
|
||||
coverage configuration that works well with our selected transpilers.
|
||||
|
||||
The configuration also takes a project-wide approach, with the expectation most
|
||||
if not all packages within a monorepo will use the same base configuration. This
|
||||
allows for optimizations such as sharing the Jest transform cache across all
|
||||
packages in a monorepo, avoiding unnecessary transpilation. It also makes it
|
||||
possible to load in all Jest configurations at once, and with that run
|
||||
`yarn test <pattern>` from the root of a monorepo without having to set the
|
||||
working directory to the package that the test is in.
|
||||
|
||||
Where small customizations are needed, such as setting coverage thresholds or
|
||||
support for specific transforms, it is possible to override the Jest
|
||||
configuration through the `"jest"` field in `package.json`. These overrides will
|
||||
be loaded in from all `package.json` files in the directory ancestry, meaning
|
||||
that you can place common configuration in the `package.json` at the root of a
|
||||
monorepo. If multiple overrides are found, they will be merged together with
|
||||
configuration further down in the directory tree taking precedence.
|
||||
|
||||
The overrides in a single `package.json` may for example look like this:
|
||||
|
||||
```json
|
||||
"jest": {
|
||||
"coverageThreshold": {
|
||||
"global": {
|
||||
"functions": 100,
|
||||
"lines": 100,
|
||||
"statements": 100
|
||||
}
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
## Publishing
|
||||
|
||||
Package publishing is an optional part of the Backstage build system and not
|
||||
something you will need to worry about unless you are publishing packages to a
|
||||
registry. In order to publish a package, you first need to build it, which will
|
||||
populate the `dist` folder. Because the Backstage build system is optimized for
|
||||
local development along with our particular TypeScript and bundling setup, it is
|
||||
not possible to publish the package immediately at this point. This is because
|
||||
the entry points of the package will still be pointing to `src/index.ts`, but we
|
||||
want them to point to `dist/` in the published package.
|
||||
|
||||
In order to work around this, the Backstage CLI provides `prepack` and
|
||||
`postpack` commands that help prepare the package for publishing. These scripts
|
||||
are automatically run by Yarn before publishing a package.
|
||||
|
||||
The `prepack` command will take entry point fields in `"publishConfig"`, such as
|
||||
`"main"` and `"module"`, and move them to the top level of the `package.json`.
|
||||
This lets you point at the desired files in the `dist` folder during publishing.
|
||||
The `postpack` command will simply revert this change in order to leave your
|
||||
project clean.
|
||||
|
||||
The following is an excerpt of a typical setup of an isomorphic library package:
|
||||
|
||||
```json
|
||||
"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 build",
|
||||
"lint": "backstage-cli lint",
|
||||
"test": "backstage-cli test",
|
||||
"prepack": "backstage-cli prepack",
|
||||
"postpack": "backstage-cli postpack",
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"files": ["dist"],
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
id: cli-commands
|
||||
title: CLI Commands
|
||||
title: Commands
|
||||
description: Descriptions of all commands available in the CLI.
|
||||
---
|
||||
|
||||
@@ -173,40 +173,9 @@ Options:
|
||||
|
||||
Scope: `backend`
|
||||
|
||||
Bundle the backend and all of its local dependencies into a deployment archive.
|
||||
The archive is written to `dist/bundle.tar.gz`, and contains the packaged
|
||||
version of all dependencies of the target package, along with the target package
|
||||
itself. The layout of the packages in the archive is the same as the directory
|
||||
layout in the target monorepo, and the bundle also contains the root
|
||||
`package.json` and `yarn.lock`.
|
||||
|
||||
To use the bundle, extract it into a target directory, run
|
||||
`yarn install --production`, and then start the target backend package using for
|
||||
example `node package/backend`.
|
||||
|
||||
The `dist/bundle.tar.gz` is accompanied by a `dist/skeleton.tar.gz`, which has
|
||||
the same layout, but only contains `package.json` files and `yarn.lock`. This
|
||||
can be used to run a `yarn install` in environments that will benefit from the
|
||||
caching that this enables, such as Docker image builds. To use the skeleton
|
||||
archive, simply extract it first, run install, and then extract the main bundle.
|
||||
|
||||
The following is an example of a `Dockerfile` that can be used to package the
|
||||
output of `backstage-cli backend:bundle` into an image:
|
||||
|
||||
```Dockerfile
|
||||
FROM node:14-buster-slim
|
||||
WORKDIR /app
|
||||
|
||||
COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./
|
||||
RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz
|
||||
|
||||
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"]
|
||||
```
|
||||
Bundles the backend into a `dist/bundle.tar.gz` archive. See the
|
||||
[backend bundling](./cli-build-system.md#backend-production-bundling) build
|
||||
systems documentation for more details.
|
||||
|
||||
```text
|
||||
Usage: backstage-cli backend:bundle [options]
|
||||
|
||||
@@ -1,97 +1,41 @@
|
||||
---
|
||||
id: cli-overview
|
||||
title: CLI Overview
|
||||
title: Overview
|
||||
description: Overview of the Backstage CLI
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Backstage provides an opinionated set of tooling for both frontend and backend
|
||||
development. It is delivered through the
|
||||
[`@backstage/cli`](https://www.npmjs.com/package/@backstage/cli) package and
|
||||
executed either directly through `yarn backstage-cli <command>` or within
|
||||
`package.json` scripts. When creating an app using
|
||||
[`@backstage/create-app`](https://www.npmjs.com/package/@backstage/create-app)
|
||||
it contains package scripts for executing the most common commands.
|
||||
|
||||
Under the hood the CLI uses [Webpack](https://webpack.js.org/) for bundling,
|
||||
[Rollup](https://rollupjs.org/) for building packages,
|
||||
[Jest](https://jestjs.io/) for testing, and [eslint](https://eslint.org/) for
|
||||
linting. It also includes custom tooling for working within Backstage apps, for
|
||||
example for keeping the app up to date and verifying static configuration.
|
||||
|
||||
For a full list of CLI commands, see the [commands](./cli-commands.md) page.
|
||||
|
||||
## Introduction
|
||||
|
||||
A goal of Backstage is to provide a delightful developer experience in and
|
||||
around the project. Creating new apps and plugins should be simple, iteration
|
||||
speed should be fast, and the overhead of maintaining custom tooling should be
|
||||
minimal. As a part of accomplishing this goal, Backstage provides its own set of
|
||||
opinionated tooling, delivered primarily through the
|
||||
[`@backstage/cli`](https://www.npmjs.com/package/@backstage/cli) package.
|
||||
|
||||
The `@backstage/cli` package provides a single executable script,
|
||||
`backstage-cli`, which you can run directly with `yarn` or within a script in
|
||||
`package.json`. If you have a Backstage app set up, you can try out the
|
||||
following command to print the top-level help page of the CLI:
|
||||
|
||||
```text
|
||||
yarn backstage-cli --help
|
||||
```
|
||||
|
||||
If you are familiar with [`create-react-app`](https://create-react-app.dev/) you
|
||||
may recognize the pattern of bundling tooling up as a CLI, as it uses a package
|
||||
called [`react-scripts`](https://www.npmjs.com/package/react-scripts) to bring
|
||||
most of the functionality into the created project. The Backstage equivalent of
|
||||
`create-react-app` is
|
||||
minimal. As a part of accomplishing this goal, Backstage provides its own build
|
||||
system and tooling, delivered primarily through the
|
||||
[`@backstage/cli`](https://www.npmjs.com/package/@backstage/cli) package. When
|
||||
creating an app using
|
||||
[`@backstage/create-app`](https://www.npmjs.com/package/@backstage/create-app),
|
||||
and the equivalent of `react-scripts` is `@backstage/cli`. There are however a
|
||||
couple of key differences between the two. Most notably, Backstage apps are
|
||||
monorepos and the CLI is tailored for that environment. It provides tooling both
|
||||
for bundling and developing full end-user apps, but also for developing,
|
||||
building and publishing individual packages within the monorepo, as well as
|
||||
tooling that is more unique to Backstage, such as commands for working with
|
||||
static configuration.
|
||||
you receive a project that's already prepared with a typical setup and package
|
||||
scripts for executing the most common commands.
|
||||
|
||||
## Opinionated Tooling
|
||||
Under the hood the CLI uses [Webpack](https://webpack.js.org/) for bundling,
|
||||
[Rollup](https://rollupjs.org/) for building packages,
|
||||
[Jest](https://jestjs.io/) for testing, and [eslint](https://eslint.org/) for
|
||||
linting. It also includes tooling for working within Backstage apps, for example
|
||||
for keeping the app up to date and verifying static configuration. For a more
|
||||
in-depth look into the tooling, see the [build system](./cli-build-system.md)
|
||||
page, and for a list of commands, see the [commands](./cli-commands.md) page.
|
||||
|
||||
The Backstage CLI is highly opinionated in what tools are used and how they are
|
||||
configured. It is tailored for development in large TypeScript monorepos with
|
||||
hundreds of separate packages, but with the ability to have edits anywhere in
|
||||
the codebase reflected within a few seconds. The build output is also optimized
|
||||
for this setup, and aims to provide an excellent user experience with fast page
|
||||
load times in modern browsers, rather than a wide range of support.
|
||||
While the Backstage tooling is opinionated in how it works, it is also possible
|
||||
to use your own tooling either partially or fully. For example, the CLI provides
|
||||
a command for building a plugin package for publishing, but the output is a
|
||||
quite standard combination of transpiled JavaScript and TypeScript type
|
||||
declarations. The usage of the command from the CLI can therefore be augmented
|
||||
or replaced with other tools if necessary.
|
||||
|
||||
While the Backstage tooling is opinionated in how to develop and build packages,
|
||||
it is also possible to use your own tooling either partially or fully. For
|
||||
example, the CLI provides a command for building a plugin package for
|
||||
publishing, but the output is a quite standard combination of transpiled
|
||||
JavaScript and TypeScript type declarations. The usage of the command from the
|
||||
CLI can therefore easily be replaced with other tools if necessary.
|
||||
|
||||
Just like `react-scripts`, the Backstage CLI does not provide many hooks for
|
||||
overriding or customizing the build process. This is to allow for evolution of
|
||||
the CLI without having to take a wide API surface into account. This allows us
|
||||
to quickly iterate and improve the tooling, as well as to more easily keep
|
||||
dependencies up to date.
|
||||
|
||||
## Opinions & Goals
|
||||
|
||||
In no particular order, this is a list of opinions and goals that guide the
|
||||
design and development of the Backstage CLI:
|
||||
|
||||
- All you need for development is `yarn start`, there should be no need to
|
||||
manually build packages or run other separate tasks.
|
||||
- Development experience comes first. The toolchain is optimized for keeping
|
||||
development smooth, rather than making it easy to for example build and
|
||||
publish packages.
|
||||
- Type checking and linting is left for text editors and Continuous Integration.
|
||||
Most text editors provide tooling for these checks, and running them a second
|
||||
time during compilation slows down iteration speed and consumes more system
|
||||
resources.
|
||||
- Backstage is run in modern browsers. We keep transpilation lightweight and
|
||||
rely on modern technologies such as HTTP/2 to optimize frontend speed.
|
||||
The Backstage CLI intentionally does not provide many hooks for overriding or
|
||||
customizing the build process. This is to allow for evolution of the CLI without
|
||||
having to take a wide API surface into account. This allows us to iterate and
|
||||
improve the tooling, as well as to more easily keep the system up to date.
|
||||
|
||||
## Glossary
|
||||
|
||||
|
||||
Reference in New Issue
Block a user