Creating Aspects

Aspects are components used to extend Bit. Every Aspect is a component that uses the Aspect environment. Aspects are not just for extending Bit, but also for building your own composable applications.

Creating an aspect

Here is how we would create a basic Aspect using the official aspect env template.

For example:

bit create aspect hello-world
CopiedCopy

Your new component includes four new files: hello-world.aspect.ts, hello-world.main-runtime.ts and regular documentation, compositions and test files.

To use the aspect, configure it on the Workspace and use it from an another Aspect.

{
  "my-org.scope/my-aspect": {
    // aspect config goes here...
  }
}
CopiedCopy

Defining the API

Every Aspect can expose APIs for others to use, whether they are an other Aspect or an external consumer. Adding an API is as simple as adding a method to a class.

To start adding APIs to your Aspect, head over your main.runtime file, and add new methods to it.

Here is an example for a basic sayHello() API:

export class HelloWorldMain {
  sayHello() {
    return 'hello world!';
  }

  static slots = [];
  static dependencies = [];
  static runtime = MainRuntime;
  static async provider() {
    return new HelloWorldMain();
  }
}
CopiedCopy

Aspects are designed to be API first. We are using programmatic APIs as a base and then composing composed to more advanced interfaces like UI, RESTful, GraphQL or anything else that might be of interest.

Using dependencies

Since Bit is entirely built and composed from Aspects. That means every feature in Bit is first available as an API, and you can use these Aspects to extend Bit and build amazing development and collaboration experiences on top.

Every Aspect can use APIs provided by other Aspects by adding them as dependencies. This way you can get access to all of the components and APIs we used to compose Bit to build your own Aspect and extend Bit.

Here are a few examples of core components in the Bit toolchain that you can use as an API:


For example, if you wanted to use the CLI aspect to extend Bit with a new command, it is as simple as writing the following:

// import the CLI aspect and the type of CLI main runtime.
import { MainRuntime, CLIMain, CLIAspect } from '@teambit/cli';

export class MyAspectMain {
  // your aspect's API.
  sayHello() {
    return 'hello world!';
  }

  static slots = [];
  // define CLIAspect as a dependency for your Aspect.
  static dependencies = [CLIAspect];
  static runtime = MainRuntime;

  // CLI in the corresponding runtime will be injected to your provider.
  static async provider([cli]: [CLIMain]) {
    // register a new command into the Bit CLI.
    cli.register([new MyCommand()]);
    return new HelloWorldMain();
  }
}
CopiedCopy

Similarly, you could use the component aspect to obtain programmatic access to component objects, and to the generator aspect, in order to register a new component generator.

Aspects like the example below can be quite useful in various cases. You can use the GraphQL aspect to extend Bit with a new GraphQL schema, Express to add a new RESTful API, or the Compiler API to compile any component programmatically without being aware to its implementation.

Using configuration

Aspects can accept configuration from its users. To define a a config simply use a type to specify the config.

For example:

export type HelloWorldConfig = {
  worldName: string;
};

export class HelloWorldMain {
  constructor(private config: HelloWorldConfig) {}

  sayHello() {
    return `hello ${this.config.worldName}!`;
  }

  static dependencies = [CLIAspect];

  static defaultConfig = {
    worldName: 'world',
  };

  static runtime = MainRuntime;
  static async provider([cli]: [CLIMain], config: HelloWorldConfig) {
    cli.register([new MyCommand()]);
    return new HelloWorldMain(config);
  }
}
CopiedCopy

Our new worldName property can be provided in any configuration.

For example (in workspace.json or scope.json):

{
  "my-org.scope/my-aspect": {
    "worldName": "Bit"
  }
}
CopiedCopy

This will make the worldName configured here to be injected to the corresponding aspect.

Using slots

Slots can be used to make any Aspect extensible by other Aspects. It is used to make it easy for Aspects to be pluggable and allow for other Aspect to implement logic that extends them.

An example, could be the CLI aspect we used above; behind the scenes it uses a slot to contain all commands registered by aspects and essentially reads them upon the Bit bootstrap.

You can use Slots as a utility provided by Harmony to create pluggable APIs. To learn more about Slots, head here.

Using runtimes

Aspects can extend multiple runtimes. That allows Aspects to have cross-runtime responsibility over a certain feature it composes. An aspect can start with single runtime, and grow to more runtimes as needed.

Using runtimes a single aspect like the one above, can extend both the UI and CLI interafces of Bit and even add a new GraphQL schema to it.

Adding runtimes is easy as adding a new files

note

Every provider injects Harmony as the fourth argument. You can use that for edge cases where you need to get access to a certain Harmony aspect, without using dependency injection.