{"id":21855113,"url":"https://github.com/wix-incubator/repluggable","last_synced_at":"2025-08-20T03:30:49.132Z","repository":{"id":38426543,"uuid":"167932656","full_name":"wix-incubator/repluggable","owner":"wix-incubator","description":"Pluggable micro frontends in React+Redux apps","archived":false,"fork":false,"pushed_at":"2024-05-15T11:18:24.000Z","size":2103,"stargazers_count":166,"open_issues_count":20,"forks_count":13,"subscribers_count":142,"default_branch":"master","last_synced_at":"2024-05-16T21:04:10.947Z","etag":null,"topics":["dependency-injection","framework","frontend","inversion-of-control","micro-frontends","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wix-incubator.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-01-28T09:14:57.000Z","updated_at":"2024-07-15T10:53:34.336Z","dependencies_parsed_at":"2023-12-31T15:29:38.301Z","dependency_job_id":"811a8076-3300-4a92-a11b-9058d9c0842b","html_url":"https://github.com/wix-incubator/repluggable","commit_stats":null,"previous_names":["wix/repluggable"],"tags_count":586,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wix-incubator%2Frepluggable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wix-incubator%2Frepluggable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wix-incubator%2Frepluggable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wix-incubator%2Frepluggable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wix-incubator","download_url":"https://codeload.github.com/wix-incubator/repluggable/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":229929491,"owners_count":18146317,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dependency-injection","framework","frontend","inversion-of-control","micro-frontends","typescript"],"created_at":"2024-11-28T02:13:33.206Z","updated_at":"2024-12-19T06:09:07.919Z","avatar_url":"https://github.com/wix-incubator.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg class=\"logo\" src=\"https://raw.githubusercontent.com/wix/repluggable/master/media/repluggable-logo.svg\" height=\"280\"\u003e\n\u003c/p\u003e\n\n# Repluggable\n\n[![master build](https://github.com/wix/repluggable/workflows/Master%20Build/badge.svg)](https://github.com/wix/repluggable/actions?query=workflow%3A%22Master+Build%22)\n[![npm version](https://img.shields.io/npm/v/repluggable)](https://www.npmjs.com/package/repluggable)\n\nRepluggable is a library that's implementing inversion of control for front end applications and makes development of medium or high-complexity projects much easier. Currently Repluggable implements micro-frontends in a React+Redux app, with plans to make it framework-independent in the future.\n\nFunctionality of a Repluggable app is composed incrementally from a list of pluggable packages. Every package extends the already loaded ones by contributing new functionality into them. Sections of UI, contributed by a certain package, can be rendered anywhere and are not limited to dedicated subtree of DOM. All packages privately manage their state in a modular Redux store, which plays the role of common event mechanism. Packages interact with each other by contributing and consuming APIs, which are objects that implement declared interfaces. Packages can be plugged in and out at runtime without the need to reload a page. Check out the [architecture](#Architecture) section of the docs to learn more about the design decisions behind Repluggable.\n\nQuick docs links: [How-to](#How-to) | [Architecture and core concepts](#Architecture)\n\n# Getting started\n\nAll code in this README is in TypeScript.\n\n## Installation\n\nTo add Repluggable to an existing React+Redux application:\n\n```\n$ npm install repluggable\n```\n\n## Create a new Repluggable project\nRun the following commands:\n```\nnpx create-react-app your-app-name --template typescript\ncd your-app-name\nyarn add react@16.14.0 react-dom@16.14.0 @types/react@16.14.0 @types/react-dom@16.9.0 repluggable\nrm src/App*\nrm src/logo*\ncp -R node_modules/repluggable/examples/helloWorld/src/ ./src\nyarn start\n```\n\n## Writing a pluggable package\n\nA pluggable package is basically an array of entry points. An entry point is an object which contributes certain functionality to the application. Below is an example of a simple entry point.\n\n`foo.ts`\n```TypeScript\nimport { EntryPoint } from 'repluggable'\n\nexport const Foo : EntryPoint = {\n    name: 'FOO',\n\n    attach() {\n        console.log('FOO is here!')\n    }\n}\n```\n\nUsually, a pluggable package will be a separate npm project, which exports an array of entry points. But it isn't required - entry points can also be part of the main app.\n\n## Bootstrapping the main application\n\nMain application is the React+Redux app that's being composed from packages. Suppose we also have `bar.ts` implemented similarly to `Foo` above.\n\n`App.tsx`\n\n```TypeScript\nimport { createAppHost, AppMainView } from 'repluggable'\nimport { Foo } from './foo'\nimport { Bar } from './bar'\n\nconst host = createAppHost([\n    // the list of initially loaded packages\n    Foo,\n    Bar\n])\n\nReactDOM.render(\n    \u003cAppMainView host={host} /\u003e,\n    document.getElementById('root')\n)\n```\n\nWhen run, the application will print two messages to console, first 'FOO is here!', then 'BAR is here!'.\n\n# How-to\n\n## Developing main application\n\nThe main application is a React application, which uses the `repluggable` package.\n\nThe `index.ts` of the application must perform the following steps.\n\n1. Import from `repluggable`\n   ```TypeScript\n   import { createAppHost, AppMainView } from 'repluggable'\n   ```\n\n1. Provide loaders of pluggable packages. Below is an example of three packages, each loaded in a different way:\n   - `package-foo` is statically bundled with the main app\n   - `package-bar` is in a separate chunk (WebPack code splitting). We'll load it with dynamic import.\n   - `package-baz` is in an AMD module, deployed separately. We'll load it with RequireJS.\n\n   This is how the three packages are loaded:\n\n   ```TypeScript\n   import foo from 'package-foo'\n   const bar = () =\u003e import('package-bar').then(m =\u003e m.default)\n   const baz = require('package-baz')\n   ```\n\n1. Initialize `AppHost` with the packages:\n   ```TypeScript\n   const host = createAppHost([\n       foo,\n       baz\n   ])\n\n   // Later\n   void bar().then(p =\u003e host.addShells([p]))\n   ```\n\n1. Render `AppMainView` component, passing it the host:\n\n   ```TypeScript\n   ReactDOM.render(\n       \u003cAppMainView host={host} /\u003e,\n       document.getElementById('root')\n   )\n   ```\n\n### Full code\n\n```TypeScript\nimport ReactDOM from 'react-dom'\nimport { createAppHost, AppMainView } from 'repluggable'\n\nimport packageOne from 'package-one'\nconst packageTwo = () =\u003e import('package-two').then(m =\u003e m.default)\nconst packageThree = require('package-three')\n\nconst host = createAppHost([\n    packageOne,\n    packageThree\n])\n\nReactDOM.render(\n    \u003cAppMainView host={host} /\u003e,\n    document.getElementById('root')\n)\n\n// Sometime later\nvoid packageTwo().then(p =\u003e host.addShells([p]))\n```\n\n## Developing a pluggable package\n\n### Creating a package project\n\nA package project is a regular Node project.\n\nTypically, it is set up with TypeScript, React, and Redux. Such a project **must** include a `repluggable` dependency.\n\nThe rest of the configuration (Babel, WebPack, Jest, etc) heavily depends on the organization of your codebase and release pipeline, and is outside the scope of this README.\n\n### Creating entry points\n\nAs we mentioned before, each package must export one or more entry points in order to be loaded by the main app.\n\nAn entry point is an object which implements an `EntryPoint` interface:\n```TypeScript\nimport { EntryPoint } from 'repluggable'\n\nconst FooEntryPoint: EntryPoint = {\n\n    // required: specify unique name of the entry point\n    name: 'FOO',\n\n    // optional\n    getDependencyAPIs() {\n        return [\n            // DO list required API keys\n            BarAPI\n        ]\n    },\n\n    // optional\n    declareAPIs() {\n        // DO list API keys that will be contributed \n        return [\n            FooAPI\n        ]\n    },\n\n    // optional\n    attach(shell: Shell) {\n        // DO contribute APIs\n        // DO contribute reducers\n        // DO NOT consume APIs\n        // DO NOT access store\n        shell.contributeAPI(FooAPI, () =\u003e createFooAPI(shell))\n    },\n\n    // optional\n    extend(shell: Shell) {\n        // DO access store if necessary\n        shell.getStore().dispatch(....)\n        // DO consume APIs and contribute to other packages\n        shell.getAPI(BarAPI).contributeBarItem(() =\u003e \u003cFooItem /\u003e)\n    },\n\n    // optional\n    detach(shell: Shell) {\n        // DO perform any necessary cleanup\n    }\n}\n```\n\nThe `EntryPoint` interface consists of:\n- declarations: `name`, `getDependencies()`\n- lifecycle hooks: `attach()`, `extend()`, `detach()`\n\nThe lifecycle hooks receive a `Shell` object, which represents the `AppHost` for this specific entry point.\n\n### Exporting entry points\n\nThe default export of a package must be an array of its entry points. For example, in package root `index.ts` :\n\n```TypeScript\nimport { FooEntryPoint } from './fooEntryPoint'\nimport { BarEntryPoint } from './barEntryPoint'\n\nexport default [\n    FooEntryPoint,\n    BarEntryPoint\n]\n```\n\n### Creating an API\n\nTo create an API, perform these steps:\n\n1. Declare an API interface. For example:\n   ```TypeScript\n   export interface FooAPI {\n       doSomething(): void\n       doSomethingElse(what: string): Promise\u003cnumber\u003e\n   }\n   ```\n\n1. Declare an API key, which is a *const* named after the interface, as follows:\n   ```TypeScript\n   import { SlotKey } from 'repluggable'\n\n   export const FooAPI: SlotKey\u003cFooAPI\u003e = {\n       name: 'Foo API',\n       public: true\n   }\n   ```\n   Note that `public: true` is required if you plan to export your API outside of your package. The key must be declared in the same `.ts` file with the interface.\n\n1. Implement your API. For example:\n   ```TypeScript\n   export function createFooAPI(shell: Shell): FooAPI {\n       return {\n           doSomething(): void {\n               // ...\n           },\n           doSomethingElse(what: string): Promise\u003cnumber\u003e {\n               // ...\n           }\n       }\n   }\n   ```\n\n1. Contribute your API from an entry point `attach` function:\n    ```TypeScript\n    import { FooAPI, createFooAPI } from './fooApi'\n\n    const FooEntryPoint: EntryPoint = {\n\n        ...\n\n        attach(shell: Shell) {\n            shell.contributeAPI(FooAPI, () =\u003e createFooAPI(shell))\n        }\n\n        ...\n\n    }\n    ```\n\n1. Export your API from the package. For example, in the `index.ts` of your package:\n    ```TypeScript\n    export { FooAPI } from './fooApi'\n    ```\n\n### Managing state\n\nIn order to manage state in a package, you need to contribute one or more reducers.\n\nIn the example below, `FooEntryPoint` will contribute two reducers, `bazReducer` and `quxReducer`.\n\nTo contribute the reducers, perform these steps:\n\n1. Declare types that represent the state for each reducer:\n    ```TypeScript\n    // state managed by bazReducer\n    export interface BazState {\n        ...\n        xyzzy: number // for example\n    }\n\n    // state managed by quxReducer\n    export interface QuxState {\n        ...\n    }\n    ```\n\n1. Wrap these state types in a root state type. This root type determines the shape of the state in the entry point.\n\n    ```TypeScript\n    // the root type on entry point level\n    export interface FooState {\n        baz: BazState\n        qux: QuxState\n    }\n    ```\n\n1. Write the two reducers. For example, they can look like this:\n    ```TypeScript\n    function bazReducer(\n        state: BazState = { /* initial values */ },\n        action: Action)\n    {\n         ...\n    }\n\n    function quxReducer(\n        state: QuxState = { /* initial values */ },\n        action: Action)\n    {\n         ...\n    }\n    ```\n\n1. Contribute state in the entry point:\n\n    ```TypeScript\n    attach(shell: Shell) {\n        shell.contributeState\u003cFooState\u003e(() =\u003e ({\n            baz: bazReducer,\n            qux: quxReducer\n        }))\n    }\n    ```\n\n    Here, an argument passed to `contributeState()` is a reducer map object. This object contains all the same keys of `FooState` (the `baz` and `qux`), but this time the keys are assigned their respective reducers. Such derivation of reducers' map shape is enforced by the typings.\n\n1. Expose selectors and action dispatchers through APIs:\n\n    ```TypeScript\n    export interface FooAPI {\n        ...\n        getBazXyzzy(): number\n        setBazXyzzy(value: number): void\n        ...\n    }\n    ```\n\n    The above API allows to read and change the value of `xyzzy` in the `BazState`.\n\n    Note that neither of these two functions are passed the state or the `Store` object. This is because their implementations are already bound to the store of the `AppHost`:\n\n    ```TypeScript\n    const createFooAPI = (shell: Shell): FooAPI =\u003e {\n        // this returns a scoped wrapper over the full\n        // store of the main application\n        // IMPORTANT! the generic parameter (FooState)\n        // must match the one specified when contributing state!\n        // In our example, we did contributeState\u003cFooState\u003e(...)\n        const entryPointStore = shell.getStore\u003cFooState\u003e()\n\n        const getState = () =\u003e entryPointStore.getState()\n\n        return {\n            ...\n            // example of selector\n            getBazXyzzy(): number {\n                const state: FooState = getState()\n                return state.baz.xyzzy\n            },\n            // example of action dispatcher\n            setBazXyzzy(value: number): void {\n                entryPointStore.dispatch(BazActionCreators.setXyzzy(value))\n            }\n            ...\n        }\n    }\n    ```\n\n### Creating React components\n\nWhen creating a React component, we strongly recommend to follow the React-Redux pattern, and separate your component into a stateless render and a `connect` container.\n\nIn `repluggable`, components often need to consume APIs. Although APIs can be obtained through `Shell`, when it is passed to lifecycle hooks in your entry point, propagating them down the component hierarchy would be cumbersome.  \n\nA more elegant solution is to use `connectWithShell()` function instead of the regular `connect()`. This provides the connector with the ability to obtain APIs.\n\nThe example below illustrates how `connectWithShell()` is used. Suppose we want to create a component `\u003cFoo /\u003e`, which would render like this:\n\n```jsx\n(props) =\u003e (\n    \u003cdiv className=\"foo\"\u003e\n        \u003cdiv\u003e\n            \u003clabel\u003eXYZZY\u003c/label\u003e\n            \u003cinput\n                type=\"text\"\n                defaultValue={props.xyzzy}\n                onChange={e =\u003e props.setXyzzy(e.target.value)} /\u003e\n        \u003c/div\u003e\n        \u003cdiv\u003e\n            Current BAR = {props.bar}\n            \u003cbutton onClick={() =\u003e props.createNewBar()}\u003e\n                Create new BAR\n            \u003c/button\u003e\n        \u003c/div\u003e\n    \u003c/div\u003e\n)\n```\n\nIn order to implement such a component, follow these steps:\n\n1. Declare the type of state props, which is the object you return from `mapStateToProps`:\n    ```TypeScript\n    type FooStateProps = {\n        // retrieved from own package state\n        xyzzy: string\n        // retrieved from another package API\n        bar: number\n    }\n    ```\n\n1. Declare type of dispatch props, which is the object you return from `mapDispatchToProps`:\n    ```TypeScript\n    type FooDispatchProps = {\n        setXyzzy(newValue: string): void\n        createNewBar(): void\n    }\n    ```\n\n1. Write the stateless function component. Note that its props type is specified as `FooStateProps \u0026 FooDispatchProps`:\n    ```TypeScript\n    const FooSfc: React.SFC\u003cFooStateProps \u0026 FooDispatchProps\u003e =\n        (props) =\u003e (\n            \u003cdiv className=\"foo\"\u003e\n                ...\n            \u003c/div\u003e        \n        )\n    ```\n\n1. Write the connected container using `connectWithShell`. The latter differs from `connect` in that it passes `Shell` as the first parameter to `mapStateToProps` and `mapDispatchToProps`. The new parameter is followed by the regular parameters passed by `connect`. For example:\n\n    ```TypeScript\n    export const createFoo = (boundShell: Shell) =\u003e connectWithShell(\n        // mapStateToProps\n        // - shell: represents the associated entry point\n        // - the rest are regular parameters of mapStateToProps\n        (shell, state) =\u003e {\n            return {\n                // some properties can map from your own state\n                xyzzy: state.baz.xyzzy,\n                // some properties may come from other packages' APIs\n                bar: shell.getAPI(BarAPI).getCurrentBar()\n            }\n        },\n        // mapDispatchToProps\n        // - shell: represents the associated entry point\n        // - the rest are regular parameters of mapDispatchToProps\n        (shell, dispatch) =\u003e {\n            return {\n                // some actions may alter your own state\n                setXyzzy(newValue: string): void {\n                    dispatch(FooActionCreators.setXyzzy(newValue))\n                },\n                // others may request actions from other packages' APIs\n                createNewBar() {\n                    shell.getAPI(BarAPI).createNewBar()  \n                }\n            }\n        },\n        boundShell\n    )(FooSfc)\n    ```\n\n    The `Shell` parameter is extracted from React context `EntryPointContext`, which represents current package boundary for the component.\n\n\n### Exporting React components\n\nTBD (advanced topic)\n\n## Testing a package\n\nTBD\n\n## Local development\n\n### HMR (hot module replacement)\nFor a smooth local development experience, it's recommended to enable HMR, allowing packages' code to update immediately without the need to reload the entire page. (For more information on HMR, see [webpack's docs](https://webpack.js.org/concepts/hot-module-replacement))\n\nTo enable HMR, use Repluggable's `hot` util to wrap the export of your repluggable package. For example, in the package root `index.ts`:\n\n```ts\nimport { hot } from 'repluggable'\nimport { FooEntryPoint } from './fooEntryPoint'\nimport { BarEntryPoint } from './barEntryPoint'\n\nexport default hot(module, [\n    FooEntryPoint,\n    BarEntryPoint\n])\n```\n\n# Architecture\n\n`repluggable` allows composition of a React+Redux application entirely from a list of pluggable packages.\n\nThink of a package as a box of lego pieces - such as UI, state, and logic. When a package is plugged in, it contributes its pieces by connecting them to other pieces added earlier. In this way, the entire application is built up from connected pieces, much like a lego set.\n\nFor two pieces to connect, one piece defines a connection point (an _extension slot_) for another specific type of a piece. In order to connect, the other piece has to match the type of the slot. One slot can contain many pieces.\n\nPackages can be plugged in and out at runtime. Contributed pieces are added and removed from the application on the fly, without the need for a page to reload.\n\n## Main application\n\nThis is the application being composed, much like a lego set. We refer to it as _main application_.\n\nThe main application can be as small as an empty shell. Its functionality can be completely composed by the packages, where each plugged package contributes its pieces to the whole.\n\nThe minimal responsibilities of the main application are:\n\n- Initialize an `AppHost` object with a list of pluggable packages.\n   \u003e The `AppHost` object orchestrates lifecycle of the packages, handles cross-cutting concerns at package boundaries, and provides dependency injection to Redux-connected components.\n\n- Render `AppMainView` component, passing it the initialized `AppHost` in props.\n\n## Pluggable packages\n\nPluggable package (or simply _package_) is a regular Node package, which exports an array of _entry points_.\n\nThe packages are plugged in the order they are listed when passed to `AppHost`. Entry points are invoked in the list order of the packages, in the array order within the package.\n\n## Entry points\n\nEvery entry point contributes one or more pieces to the whole lego set of the application.\n\nExamples of contributed pieces include React components, panel item descriptors, UI command descriptors, etc, etc. They can be anything, provided that they are expected by the \"lego set\". Here _expected_ means that some package provides an API, through which it accepts contributions of this specific type.\n\nThere are also two kinds of contributions supported directly by `repluggable`: _APIs_ and _reducers_.\n\nBesides contributing lego pieces, entry points may contain additional lifecycle hooks.\n\n## APIs\n\nSome packages (providers) provide services to other packages (consumers). The services are provided through APIs. An API is an object, which implements a TypeScript interface, and is identified by an API key. An API key is another object declared as a const [TODO: link to example](), and exported from the package.\n\nIn general, APIs allow packages to extend other packages (consumers call APIs, which let them pass contributions to the provider), and otherwise interact. Moreover, APIs are the only allowed way of interaction between packages.\n\nIn order to provide an API, a provider package:\n- declares and exports an API interface and an API key\n- implements an API object according to the interface\n- contributes an API object under the key\n\nIn order to consume an API, a consumer package:\n- imports an API key and an API interface from the provider package\n- declares dependency on the API in relevant entry points\n- retrieves an API object by calling `getAPI` and passing it the API key [TODO: link to example]().\n\n## Reducers\n\n`repluggable` requires that all state of the application is managed in a Redux store. This ensures that all pieces are connected to a single event-driven mechanism. In turn, this  guarantees that pure React components mapped to values returned by APIs will re-render once these values change.\n\nA package that has state must contribute one or more reducers responsible for managing that state. If such a package contributes APIs, it can also include selectors and action dispatchers in the APIs.\n\nThe Redux store of the main application is combined from reducers, contributed by stateful packages.\n\n## Extension Slots\n\nWhen a package accepts contributions from other packages, it must store contributed pieces in some kind of an array.\n\n`repluggable` provides a \"smart\" array for this purpose, named _extension slot_. Extension slot is a generic object `ExtensionSlot\u003cT\u003e`, which accepts contributions of type `T`.\n\nIts additional responsibility is remembering which package and entry point each contribution was received from. This allows applying package boundaries and easily handling other cross-cutting concerns.\n\nExtension slots are implementation details of a package, and they should never be directly exposed outside of the package. Instead, a package:\n\n- internally initializes an extension slot for every kind or group of accepted contributions.\n- contributes an API that receives contributions from the outside and pushes them to an internal extension slot.\n\nWith that, the `AppHost` also tracks all existing extension slots. This approach allows for an easy implementation of application-wide aspects. For example, removal of a package with all of its contributions across an application.\n\n## Package boundaries in DOM\n\nEvery React component rendered under the `AppMainView` is associated with an _entry point context_.\n\nThe entry point context is a React context, which associates its children with a specific entry point, and thus the package that contains it.\n\nSuch association provides several aspects to the children:\n\n- performance measurements and errors reported by the children are automatically tagged with the entry point and the package\n\n- in Redux-connected components ([TODO: link to details]()):\n\n  - dependency injection (the `getAPI` function): all dependencies are resolved in the context of the entry point\n\n  - state scoping (the `state` in `mapStateToProps`, and `getState()` in thunks): returned state object is scoped to reducers contributed by the entry point.  \n\n  \u003e TODO: verify that getState() in thunks is actually scoped\n\n  - when rendering an extension slot of contributed React components: each component is rendered within the context of the entry point it was contributed by.\n\n## Progressive loading\n\nTo make application loading reliable and fast, `repluggable` allows flexible control over package loading process.\n\nThe loading process is abstracted from any concrete module system or loader. Packages can be in a monolith bundle, or loaded with dynamic imports, or with loaders like RequireJS. To add a package to an `AppHost`, all that's needed is a `Promise` of a package default export.\n\nPackages can be added to an `AppHost` at different stages:\n\n- During initialization of the `AppHost`\n- Right after the `AppMainView` was rendered for the first time\n- Lazily at any later time\n\nMoreover, `AppHost` allows separating the whole package into multiple entry points. Some of the entry points are added right as the package is added to the `AppHost`, while others can be added later.\n\nSuch separation allows incremental contribution of functional parts as they become ready. Some parts may need to dynamically load additional dependencies or request data from backends. Without the separation approach, a user won't be able to interact with any functionality of the package, until the entire package is initialized -- which would hurt the experience.\n\nIn addition, `AppHost` supports removal of previously added entry points or entire packages, at any time. Removal of a package means removal of all its entry points. When an entry point is removed, all contributions made from that entry point are removed too.\n\n## API dependencies\n\nSince APIs are contributed though entry points, their availability depends on the loading time of the provider package and a specific entry point within it. From a consumer package perspective, this creates a situation in which one or more of the APIs the package depends on may be temporarily unavailable.\n\n`AppHost` resolves that with the help of explicit dependency declarations. Every entry point must declare APIs which it's dependent on (including dependencies of all pieces contributed by the entry point). If any of the required APIs is unavailable, the entry point is put on hold. There are two possible cases:\n\n- Attempted to add an entry point, but some of required APIs weren't available: the entry point is put on hold, and will be added as soon as all required APIs are contributed.\n- An entry point was added, but then some of its required APIs became unavailable: the entry point will be removed together with all of its contributions, and put on hold. It will be added once again as soon as all required APIs are available.\n\nSuch approach guarantees that code dependent on an API from another package will not run unless that API is available.\n\n## Licenses\n\n3-rd party licenses are listed in [docs/3rd-party-licenses.md](docs/3rd-party-licenses.md)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwix-incubator%2Frepluggable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwix-incubator%2Frepluggable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwix-incubator%2Frepluggable/lists"}