Build Typescript Project with Bazel Chapter 2: File Structure
Tags: bazel
Published: 2020-03-30 19:55:08

---
title: Build Typescript Project with Bazel Chapter 2: File Structure
published: true
date: 2020-03-30 18:57:51 UTC
tags: Bazel
canonical_url: https://labs.thisdot.co/blog/build-typescript-project-with-bazel-chapter-2-file-structure
---

## Build Typescript Project with Bazel Chapter 2: File Structure

In the last [chapter](https://dev.to/thisdotmedia/build-typescript-project-with-bazel-chapter-1-bazel-introduction-1c9o), we introduced the basic concept of Bazel. In this blog, I would like to talk about the file structure of Bazel.

### Concept and Terminology

Before we introduce the file structure, we need to understand several key concepts and terminology in Bazel.

- Workspace
- Package
- Target
- Rule

These concepts, and terminology, are composed to `Build File`, which Bazel will analyze, and execute.

The basic relationship among these concepts looks like this ![graph](http://images.ctfassets.net/zojzzdop0fzx/1L9P8a6KOMNR1TqRzYJcrl/5b75094c9fe648b72094d31c7e9ad6ea/Screenshot_from_2020-03-15_22-45-19.png), we will discuss the details one by one.

#### Workspace

A "workspace" refers to the directories, which contain

1. The source files of the project.
2. Symbolic links contain the build output.

And the Bazel definition is in a file named `WORKSPACE`, or `WORKSPACE.bazel` at the root of the project directory. NOTE, one project can only have one `WORKSPACE` definition file.

Here is an example of the `WORKSPACE` file.

```
workspace(
    name = "com_thisdot_bazel_demo",
)

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

# Fetch rules_nodejs so we can install our npm dependencies
http_archive(
    name = "build_bazel_rules_nodejs",
    sha256 = "ad4be2c6f40f5af70c7edf294955f9d9a0222c8e2756109731b25f79ea2ccea0",
    urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/0.38.3/rules_nodejs-0.38.3.tar.gz"],
)

load("@build_bazel_rules_nodejs//:defs.bzl", "node_repositories", "yarn_install")

node_repositories()

yarn_install(
    name = "npm",
    package_json = "//:package.json",
    yarn_lock = "//:yarn.lock",
)

# Install all Bazel dependencies of the @npm npm packages
load("@npm//:install_bazel_dependencies.bzl", "install_bazel_dependencies")

install_bazel_dependencies()

# Setup the rules_typescript toolchain
load("@npm_bazel_typescript//:index.bzl", "ts_setup_workspace")

ts_setup_workspace()
```

In a `WORKSPACE` file, we should

1. Define the name of the workspace. The name should be unique globally, or at least unique in your organization. You could use the reverse dns name, such as `com_thisdot_bazel_demo`, or the name of the project on GitHub.
2. Install environment related packages, such as `yarn/npm/bazel`.
3. Setup toolchains needed to `build/test` the project, such as `typescript/karma`.

Once `WORKSPACE` is ready, application developers don't really need to touch this file.

#### Package

- The primary unit of code organization (something like module) in a repository
- Collection of related files and a specification of the dependencies among them
- Directory containing a file named BUILD or BUILD.bazel, residing beneath the top-level directory in the workspace
- A package includes all files in its directory, plus all subdirectories beneath it, except those which, themselves, contain a BUILD file

It is important to know how to split a project into `package`. It should be easy for the users to develop/test/share the unit of a `package`. If the unit is too big, the `package` has to be rebuilt on every package file change. If the unit is too small, it will be very hard to maintain and share. So, this is not an issue of `Bazel`. It is a general problem of project management.

In Bazel, every `package` will have a `BUILD.bazel` file, containing all of the `build`/`test`/`bundle` target definitions.

For example, here is a ![screenshot](http://images.ctfassets.net/zojzzdop0fzx/5IBxi47Pttab99Mqi1Srlk/92330b615770fa77b835ce251a4ae476/Screenshot_from_2020-03-15_22-48-05.png) of the [Angular](https://github.com/angular/angular) structure. Every directory under packages directory is a `package` of code organization, and also the build organization of Bazel.

Let's take a look at the file structure of `gulpjs` in [Angular](https://github.com/angular/angular), so we can have a better understanding about the difference between Bazel and gulpjs.

```
gulp.task('build-animations', () => {});;
gulp.task('build-core', () => {});
gulp.task('build-core-schematics', () => {});
```

In most cases,

- a gulpjs file doesn't have 1:1 relationship to the package directory.
- a gulpjs file can reference any files inside the project.

But for Bazel,

- Each `package` should have their own `BUILD.bazel` file.
- The `BUILD.bazel` can only reference the file inside the current `package`, and if the current package depends on other packages, we need to reference the Bazel build `target` from the other packages instead of the files directly.

Here is a Bazel Package directory structure in [Angular](https://github.com/angular/angular/issues) repo. ![Angular](http://images.ctfassets.net/zojzzdop0fzx/2ukLvwyybLz9cdjbuMIeb/fa9c71afcb44da025df5269c66bd4088/Screenshot_from_2020-03-15_22-51-24.png)

#### Build File

Before we talk about `target`, let's take a look at the content of a `BUILD.bazel` file.

```
package(default_visibility = ["//visibility:private"])

load("@npm_bazel_typescript//:index.bzl", "ts_library")

ts_library(
    name = "lib",
    srcs = [":lib.ts"],
    visibility = ["//visibility:public"],
)
```

The language of the `BUILD.bazel` file is `Starlark`.

- `Starlark` is a subset of `Python`.
- It is a very feature-limited language. A ton of `Python` features, such as `class`, `import`, `while`, `yield`, `lambda`, `is`, `raise`, are not supported.
- `Recursion` is not allowed.
- Most of Python's builtin methods are not supported.

So `Starlark` is a very very simple language, and only supports very limited `Python` syntax.

#### Target

The `BUILD.bazel` file contains build targets. Those `target`s are the definitions of the `build`, `test`, and `bundle` work we want to achieve.

The build target can represent:

- Files
- Rules

The target can also depend on other targets

- Circular dependencies are not allowed
- Two targets, generating the same output, will cause a problem
- Target dependency must be declared explicitly.

Let's see the previous sample,

```
package(default_visibility = ["//visibility:private"])

load("@npm_bazel_typescript//:index.bzl", "ts_library")

ts_library(
    name = "lib",
    srcs = [":lib.ts"],
    visibility = ["//visibility:public"],
)
```

Here, `ts_library` is a rule imported from `@npm_bazel_typescript` workspace, and `ts_library(name = "lib")` is a `target`. The name is `lib`, and this target defines the metadata for compiling the `lib.ts` with `ts_library` rule.

##### Label

Every target has a unique name called `label`. For example, if the `BUILD.bazel` file above is under `/lib` directory, then the label of the target is

```
@com_thisdot_bazel_demo//lib:lib
```

The label is composed of several parts.

1. the name of the workspace: `@com_thisdot_bazel_demo`.
2. the name of the package: `lib`.
3. the name of the target: `lib`.

So, the composition is `<workspace name>//<package name>:<target name>`.

Most of the times, the name of the workspace can be omitted, so the label above can also be expressed as `//lib:lib`.

Additionally, if the name of the target is the same as the package's name, the name of the target can also be omitted. Therefore, the label above can also be expressed as `//lib`.

NOTE: The label for the target needs to be unique in the workspace.

#### Visibility

We can also define the visibility to define whether the rule inside this package can be used by other packages.

```
package(default_visibility = ["//visibility:private"])
```

The visibility can be:

- `private`: the rules can be only used inside the current package.
- `public`: the rules can be used everywhere.
- `//some_package:package_scope`: the rules can only be used in the specified scope under `//some_package`. The `package_scope` can be: ` __pkg__ `/` __subpackages__ `/`package group`.

And if the rules in one package can be accessed from the other package, we can use `load` to import them. For example:

```
load("@npm_bazel_typescript//:index.bzl", "ts_library")
```

Here, we import the `ts_library` rule from the Bazel typescript package.

#### Target

- Target can be `Files` or `Rule`.
- Target has `input` and `output`. The `input` and `output` are known at build time.
- Target will only be rebuilt when `input` changes.

Let's take a look at `Rule` first.

##### Rule

The rule is just like a `function` or `macro`. It can accept `named parameters` as options. Just like in the previous post, calling a rule will not execute an action. It is just metadata. Bazel will decide what to do.

```
ts_library(
    name = "lib",
    srcs = [":lib.ts"],
    visibility = ["//visibility:public"],
)
```

So here, we use the `ts_library` rule to define a target, and the name is `lib`. The srcs is `lib.ts` in the same directory. The visibility is `public`, so this target can be accessed from the other packages.

##### Rule Naming

It is very important to follow the naming convention when you want to create your own rule.

- `*_binary`: executable programs in a given language (nodejs\_binary)
- `*_test`: special \_binary rule for testing
- `*_library`: compiled module for a given language (ts\_library)

##### Rule common attributes

Several common attributes exist in almost all rules.For example:

```
ts_library(
    name = "lib",
    srcs = [":index.ts"],
    tags = ["build-target"],
    visibility = ["//visibility:public"],
    deps = [
        ":date",
        ":user",
    ],
)
```

- name: unique name within this package
- srcs: inputs of the target, typically files
- deps: compile-time dependencies
- data: runtime dependencies
- testonly: target which should be executed only when running Bazel test
- visibility: specifies who can make a dependency on the given target

Let's see another example:

```
http_server(
   name = "prodserver",
   data = [
       "index.html",
       ":bundle",
       "styles.css",
   ],
)
```

Here, we use the `data` attribute. The `data` will only be used at runtime.It will not be analyzed by Bazel at build time.

So, in this blog, we introduced the basic Bazel structure concepts. In the next blog, we will introduce how to query Bazel targets.

_This Dot Labs is a modern web consultancy focused on helping companies realize their digital transformation efforts. For expert architectural guidance, training, or consulting in React, Angular, Vue, Web Components, GraphQL, Node, Bazel, or Polymer, visit [thisdotlabs.com](([https://www.thisdotlabs.com](https://www.thisdotlabs.com))._

_This Dot Media is focused on creating an inclusive and educational web for all. We keep you up to date with advancements in the modern web through events, podcasts, and free content. To learn, visit [thisdot.co](https://www.thisdot.co)._