UNPKG

@teambit/harmony

Version:
251 lines (198 loc) 6.15 kB
--- 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'; @Lifecycle(Tag); function tag() { } ``` ## state / schema management ```ts @Lifecycle(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? - -->