# UI development

## Getting started

This command pulls the UI-related dependencies (notably, the NodeJS binary)
and installs the `node_modules` in `ui/node_modules`:

```bash
tools/install-build-deps --ui
```

### Build the UI

```bash
# Will build into ./out/ui by default. Can be changed with --out path/
# The final bundle will be available at ./ui/out/dist/.
# The build script creates a symlink from ./ui/out to $OUT_PATH/ui/.
ui/build
```

### Run the devserver

The devserver has a live reload functionality: once you make a change in
TypeScript files, the resulting code will be recompiled and the page is going to
reload automatically. By default, this logic uses a timeout in order to prevent
successive reloads on rapid changes. This logic can be disabled via
development-only "Rapid live reload" flag in the UI. Disabling it will reload
the page earlier, at the cost of sometimes making multiple reloads in a row.

```bash
# This will automatically build the UI. There is no need to manually run
# ui/build before running ui/run-dev-server.
ui/run-dev-server
```

Navigate to http://localhost:10000/ to see the changes.

NOTE: If you made changes to Trace Processor you need to restart the server.

### Test the change

UI unit tests are located next to the functionality being tested, and have
`_unittest.ts` or `_jsdomtest.ts` suffixes. The following command runs all unit
tests:

```bash
ui/run-unittests
```

This command will perform the build first; which is not necessary if you
already have a development server running. In this case, to avoid interference
with the rebuild done by development server and to get the results faster, you
can use

```bash
ui/run-unittests --no-build
```

to skip the build steps.

Script `ui/run-unittests` also supports `--watch` parameter, which would
restart the testing when the underlying source files are changed. This can be
used in conjunction with `--no-build`, and on its own as well.

## Development environment

If you're looking for an IDE to write the TypeScript code, Visual Studio Code
works well out of the box. WebStorm or IntelliJ Idea Ultimate (Community does
not have JavaScript/TypeScript support) also work really well. The code is
located in the `ui` folder.

For VSCode users, we recommend using the eslint & prettier extensions to handle
this entirely from within the IDE. See the
[Formatting & Linting](#formatting-linting) section below on how to set this up.

### Formatting & Linting

We use `eslint` to lint TypeScript and JavaScript, and `prettier` to format
TypeScript, JavaScript, and SCSS.

To auto-format all source files, run ui/format-sources, which takes care of
running both prettier and eslint on the changed files:

```bash
# By default it formats only files that changed from the upstream Git branch
# (typically origin/main).
# Pass --all for formatting all files under ui/src
ui/format-sources
```

Presubmit checks require no formatting or linting issues, so fix all issues
using the commands above before submitting a patch.

## Mithril components

Perfetto UI uses the [Mithril](https://mithril.js.org/) library for rendering
the interface. The majority of the components in the codebase use
[class components](https://mithril.js.org/components.html#classes). When Mithril
is imported via `m` alias (as it is usually done in the codebase), the class
component should extend `m.ClassComponent`, which has an optional generic
parameter allowing the component to take inputs. The entry point of class
components is a `view` method, returning a tree of virtual DOM elements to be
rendered when the component is present on the page.

## Hints

### Component state

Local state of components can reside in class members and accessed directly in
methods via accessing `this`. State that is shared across different components
is stored in the `State` class definition, and should be modified via
implementing a new action in `src/common/actions.ts`. A new field added to
`State` should be initialized in `src/common/empty_state.ts`.

There are restrictions on what can be used in the global state: plain JS objects
are OK, but class instances are not (this limitation is due to state
serialization: the state should be a valid JSON object). If storing class
instances (like `Map` and `Set` data structures) is necessary, these can be
stored in the `NonSerializableState` portion of the state, that is omitted from
saving into JSON objects.
