npm package: @microbackend/plugin-core
Description: Micro-backend core plugin
Author: Hai Pham
Latest version: 1.0.1

# @microbackend/plugin-core

The core plugin that contains extensible interfaces and extension logic. This
plugin must be included in all `microbackend`-based projects.

## Installation

```shell
yarn add @microbackend/plugin-core
npm install @microbackend/plugin-core
```

## Usage

In your project's webpack configuration file (for examle, `webpack.config.ts`):

```typescript
import createPluginConfig, {
  mergeConfigurations,
} from "@microbackend/plugin-core/build/webpack.build";
import "./src/interface/microbackend";

const webpackConfiguration = mergeConfigurations(
  createPluginConfig({ environment: process.env.NODE_ENV }),
  {
    // Your own project's webpack configuration here.
  }
);

export default webpackConfiguration;
```

### Extension patterns

An extension pattern represents the process of extending the capabilities of a
module using plugins.

`microbackend` uses `typescript` and `webpack` exclusively for building. At
compilation time, the webpack module rules in `@microbackend/plugin-core` look
for the files corresponding to each extension pattern, and import them into
the main extensible module.

For example, let's consider the extensible module
`@microbackend/plugin-core/src/extension/app.ts`. When we run `webpack` to build
the project, it looks for all files that match
`[pluginName]/src/extension/app.ts`, extract their exports and import them into
`@microbackend/plugin-core/src/extension/app.ts`, resulting in the
following pre-bundling file contents:

```typescript
// @microbackend/plugin-core/src/extension/app.ts
import pluginname from "pluginname/src/extension/app.ts";
export { pluginName };
```

The function `extendMicrobackendApplication` in `@microbackend/plugin-core` then
takes all these exports and merge them into the provided
`IMicrobackendApp` object using `Object.defineProperty`.

### Extension pattern example

For illustration purposes, create the following files in your project:

- `src/extension/app.ts`:

```typescript
// src/extension/app.ts
declare module "@microbackend/plugin-core" {
  interface IMicrobackendApp {
    readonly appProperty1: () => number;
    readonly appProperty2: number;
  }
}

export function appProperty1(): number {
  return 1;
}

export default {
  get appProperty2(): number {
    return 2;
  },
};
```

- `src/extension/request.ts`:

```typescript
import { IMicrobackendRequest } from "@microbackend/plugin-core";

export function appProperty1(this: IMicrobackendRequest): number {
  // Now the application object already possesses the "appProperty1" extension.
  return this.app.appProperty1();
}

export default {
  get appProperty2(): number {
    // Now the application object already possesses the "appProperty2" extension.
    return (this as unknown as IMicrobackendRequest).app.appProperty2;
  },
};
```

- `src/extension/lifecycle.ts`:

```typescript
import { MicrobackendLifecycle } from "@microbackend/plugin-core";

export default class MainLifecycle extends MicrobackendLifecycle {
  override initialize() {
    console.log("Initialized");
  }

  override deinitialize() {
    console.log("Deinitialized");
  }
}
```

- `src/extension/config.ts`:

```typescript
import { MicrobackendConfig } from "@microbackend/plugin-core";

export default class CommonConfig extends MicrobackendConfig {
  get config(): MicrobackendConfig["config"] {
    return () => {
      // Accessible via app.config.config1.
      return { config1: 1 };
    };
  }
}
```

So now, `IMicrobackendApp` has two additional properties `appProperty1` and
`appProperty2`, as does `IMicrobackendRequest`.

### Supported extension patterns

- `IMicrobackendApp`, supported by `extension/app`: An interface
  representing an application. For example, this could be `express.Application`
  if we are using the plugin `@microbackend/plugin-express`.

- `IMicrobackendRequest`, supported by `extension/request`: An interface
  representing a request. For example, this could be `express.Request` if we are
  using the plugin `@microbackend/plugin-express`. A microbackend request could
  contain properties such as `body`, `headers`, `params`, `query` etc.

- `IMicrobackendService`, supported by `extension/service`: An interface
  representing services that handle business logic, and are available to access
  using `request.service`. If the service extension is a folder (i.e.
  `extension/service`), the service object can contain namespaces based on its
  subfolders. For example:

  ```typescript
  // extension/service/auth/service1.ts
  export default class Service {
    method1() {}
  }

  // extension/service/logic/service2.ts
  export default class Service {
    method2() {}
  }

  // elsewhere
  request.service.auth.service1.method1();
  request.service.logic.service2.method2();
  ```

  Please note that only service extension modules in the current project are
  included in `req.service`.

- `IMicrobackendLifecycle`, supported by `extension/lifecycle`: An object
  that contains methods to hook into the application's lifecycle events, such
  as `init` and `deinit`.

- `IMicrobackendConfig`, supported by `extension/config`: An interface
  representing the current application's configurations, and is accessible via
  `app.config`.

- Environment-specific extension overrides, supported by
  `extension/env_overrides/{environment}`: Anything that can be extended
  normally, such as application, request, configuration etc, can be overridden
  depending on the current environment. The environment value depends on what
  we pass to `createPluginConfig`, so we are free to declare any value (not just
  development/production) - it could be prd/dev/local/test/e2e.

  For example, if we want to serve different configurations in development and
  production, we can do it like so:

  ```typescript
  // extension/env_overrides/development/config.ts
  import { MicrobackendConfig } from "@microbackend/plugin-core";

  export default class CommonConfig extends MicrobackendConfig {
    get config(): MicrobackendConfig["config"] {
      return () => {
        // If the current environment is development, this config will be picked
        // and merged with the common configuration (if any).
        return { config1: 2 };
      };
    }
  }

  // extension/env_overrides/production/config.ts
  import { MicrobackendConfig } from "@microbackend/plugin-core";

  export default class CommonConfig extends MicrobackendConfig {
    get config(): MicrobackendConfig["config"] {
      return () => {
        // If the current environment is production, this config will be picked
        // and merged with the common configuration (if any).
        return { config1: 3 };
      };
    }
  }
  ```

The corresponding functions for these extensions are:

- `enhanceMicrobackendApp`
- `enhanceMicrobackendRequest`
- `createMicrobackendLifecycle`

Since this plugin can work for any application that adheres to an
Application/Request model, you can import and use these functions in your
project like so:

```typescript
import {
  createMicrobackendLifecycle,
  enhanceMicrobackendApp,
  enhanceMicrobackendRequest,
} from "@microbackend/plugin-core";

class Application {}
class Request {}

const application = new Application();
enhanceMicrobackendApp(application);

application.onRequest((request: Request) => {
  enhanceMicrobackendRequest(request);
});

const lifecycle = createMicrobackendLifecycle({ application });

lifecycle.initialize().then(() => {
  return application.start({
    onEnd: async () => {
      await lifecycle.deinitialize();
    },
  });
});
```

However, you should not have to call them manually to set up the extensions.
Instead, please use one of the following framework-specific plugins:

- [`@microbackend/plugin-express`](https://www.npmjs.com/package/@microbackend/plugin-express).

### Creating a new extension pattern

To create a new extension pattern:

- Create a new file under `src/extension` in the plugin or project you are
  working on, such as `src/extension/a.ts`.

- Populate this file with the following contents:

```typescript
/** Replace with imports */
export default {};
```

- The `/** Replace with imports */` placeholder is necessary for webpack to
  substitute the correct import/export statements.

- Create a `webpack.build.ts` (unless it is already available), and import
  `createMicrobackendExtension` from
  `@microbackend/plugin-core/webpack.build.config`. Call this function, provide
  the necessary arguments, and merge the resulting configuration with your
  existing configuration (using
  `@microbackend/plugin-core/webpack.build.ts > mergeConfigurations`).

- For a practical example, please check out
  `/package/plugin_graphql_core/webpack.build.ts`.

## Creating a plugin

A plugin in an installable package that can be consumed in a `microbackend`-based
application. Each plugin generally have the following structure:

```
/src
  /extension          // contains extension modules
    /app.ts           // contains application extensions
    /request.ts       // contains request extensions
    /config.ts        // contains configuration extensions
    /lifecycle.ts     // contains lifecycle extensions
    /service.ts       // contains service extensions
  /index.ts           // contains type declarations and other exports
/package.json         // contains plugin package information
/tsconfig.json        // contains TypeScript configurations
/webpack.build.ts     // contains webpack configurations that will be merged
                      // during compilation. Please note that this config is not
                      // used to build the plugin itself, but to build the
                      // microbackend application.
```

## Installing a plugin

To add a new plugin, there are two ways:

### Via the `microbackend` CLI tool (recommended)

Check out the CLI package [here](https://www.npmjs.com/package/@microbackend/cli).
You can install and use it as follows:

```shell
yarn add @microbackend/cli
npm install @microbackend/cli

npx microbackend plugin add @microbackend/plugin-example-1 @microbackend/plugin-example-2
```

### Manually (not recommended)

You need to install the correct package from npm:

```shell
yarn add @microbackend/plugin-example
npm install @microbackend/plugin-example
```

Since `@microbackend/plugin-core` reads microbackend meta information from your
project's `package.json`, add the new plugin to `microbackend > plugin` section:

```json
{
  "microbackend": {
    "plugins": ["@microbackend/plugin-core", "@microbackend/plugin-example"]
  }
}
```