@teambit/harmony
Version:
abstract extension system
251 lines (198 loc) • 6.15 kB
text/mdx
---
description: Harmony is an abstract extension system you can use to make any software extendable and composable. It is the engine that drives Bit extensibility and composability.
labels: ['aspect', 'composition', 'extendability']
---
## Basic features
Harmony allows you to implement Aspects as the main building blocks that compose your software. Each Aspect has:
* Dependency declaration - Where you declare other Aspects as dependencies.
* Dependency injection - An Aspect gets providers for all its dependencies.
* Slots - An easy way for an Aspect to provide API for another aspect to hook into any of its processes.
* Multiple runtimes - Aspect can annotate in which runtimes it can run (for example providing functionality to run on the server and on the browser).
## Harmony config file
Use a config file to describe your desired composition of Aspects and their configuration. Harmony uses a basic JSON syntax, where each key is an aspect.
```json
{
"teambit.dependencies/dependency-resolver": {}, // load an Aspect without providing configuration
"teambit.workspace/workspace": { // Load an Aspect with configuration
"name": "bit",
"icon": "https://static.bit.dev/bit-logo.svg",
}
```
## Define an aspect
Harmony Aspect is an object that implements the `ExtensionManifest` interface
```js
export type ExtensionManifest = {
/**
* Aspect's name.
*/
name: string;
/**
* Aspect unique ID.
*/
id?: string;
/**
* Array of dependencies.
* These other Aspects to be installed and resolved prior to this Aspect activation.
*/
dependencies?: ExtensionManifest[];
/**
* Reference to the Aspect factory function.
*/
provider?: ProviderFn;
/**
* Default Aspect configuration.
*/
defaultConfig?: object;
/**
* Alias to the provider.
*/
provide?: ProviderFn;
/**
* Array of slots the Aspect exposes.
*/
slots?: SlotProvider<unknown>[],
/**
* Additional keys which might be expected by other Aspects.
*/
[key: string]: any;
}
```
## Programmatic API
### Static API
```js
// Create instance of harmony with the provided aspects, on specific runtime, with provided config
static async load(aspects: Aspect[], runtime: string, globalConfig: GlobalConfig) {
```
### Instance API
```js
// load an Aspect into the dependency graph
async load(extensions: ExtensionManifest[])
// set extensions during Harmony runtime.
async set(extensions: ExtensionManifest[])
export type RequireFn = (aspect: Extension, runtime: RuntimeDefinition) => Promise<void>;
// Start harmony.
// The require function is a function that get's the aspect and its runtime, and knows to require/load it
async run(requireFn?: RequireFn)
```
## Assumptions
* Harmony does not allow circular dependencies between Aspects.
<!--
-------------------------for reference only - to remove ------------------
## composition model
Harmony proposes a graph composition model.
composition model should allow:
- full control over composition including dependency composition.
- easy overrides mechanism for configs.
- full encapsulation of an extension.
bit.config.js
```js
import FlowSchemaPlugin from '@bit/plugins.flow-schema';
export default () => {
return [ // returns an array of PluginInstance
[FlowSchemaPlugin, {
}],
[ReactSchemaPlugin, {
eslintrc: './.eslintrc'
}]
];
}
```
configured extensions in bit.json
```json
{
"extensions": {
"bit.envs/eslint@0.0.4": {
"eslintrc": "./.eslintrc"
},
"bit.envs/babel@0.0.1": {
"babelrc": "./.babelrc",
"strict": true,
"__alias": "compile"
},
"bit.envs/webpack@0.0.1": {
"config": "./webpack.config.js",
"mode": "prod",
"__alias": "bundle"
},
"bit.envs/mocha@0.0.1": {
"reporter": "json",
"mochaOptions": "./mocha.opts",
"__alias": "test-mocha"
}
},
"pipes": {
"tag": [""]
},
"dist": {
"target": "",
"entry": ""
}
}
```
### Open questions
- How do I install a Bit extensions? Where the component is configured?
- Is the extension installed with Bit/NPM?
- What's the impl. behind `bit use <plugin/extension name>`
- maybe an composition model can co-exist as json and js? what are the tradeoffs?
## extension registration
```js
import { register } from 'harmony';
import { DocGen } from '@bit/bit.exts.doc-gen';
const extensions = register([DocGen]);
```
## lifecycle event invocation
```js
import { invoke } from 'harmony';
invoke('tag', ...data);
```
## lifecycle event registration
```js
import { Lifecycle } from 'harmony';
(Tag);
function tag() {
}
```
## state / schema management
```ts
(Tag)
function tag(component: Component) {
const docs = component.get('docs'); // returns `Maybe` type?
const docs: Docs = docs.get();
}
```
## function extension
```js
export default function foo() {
context.a
}
```
## configuration
an extension can declaratively ask for configuration type. this can be reflected
- extensions can be configured during instantiation.
- configuration types can be built from multiple sources
## context
- standard context can be shared between all extensions in the same instance.
## extension composition
- hook invocation from an extension
- extension dependencies? how can an extension declare a dependency as part of its execution?
- contextual/namespaced hooks
- config api
## extension resolution
```js
import { resolve } from 'harmony';
const extension = resolve('doc-gen');
```
## Questions (?)
- can extensions be added/configured during runtime? DI
- is there a difference between extension composition to registration?
- how to make autocomplete work for hooks?
- how to avoid unintentional hook invocation (decorators?)
- how to declare a new hook..
- schema validator
- dev experience of an extension.
- how do manage different runtime environments? should I? Why is that relevant?
- runtime capsules?
- how can one extension run from both server and client? how capsule is related?
- in which process extensions will run from? also, what will happen from the backend?
-
-->