alfresco-content-app/docs/extending/extension-format.md
Denys Vuika 7a5350a06d
reduce duplication and code improvements (#1707)
* reduce code duplication

* reduce duplication, fix license headers

* simplify code

* typings fixes

* update tests

* minor fixes

* markdown fixes

* revert changes
2020-12-11 15:47:17 +00:00

6.4 KiB

Title
Title
Extension format

Extension format

The format is represented by a JSON file with the structure similar to the following:

{
  "$id": "unique.id",
  "$name": "extension.name",
  "$version": "1.0.0",
  "$vendor": "author.name",
  "$license": "license",
  "$runtime": "1.5.0",
  "$description": "some description",

  "routes": [],
  "actions": [],
  "rules": [],
  "features": {}
}

Schema

You can find the JSON schema at the project root folder: extension.schema.json.

Tip: The Schema allows you to validate extension files, provides code completion and documentation hints.

{
  "$schema": "../../extension.schema.json",
  "$name": "app",
  "$version": "1.0.0"
}

Multiple files

You can have multiple extension files distributed separately. All additional files are linked via the $references property. The order of declaration defines the order of loading.

{
  "$schema": "../../extension.schema.json",
  "$name": "app",
  "$version": "1.0.0",
  "$references": ["plugin1.json", "plugin2.json"]
}

Note: All extension files are merged together at runtime. This allows plugins to overwrite the code from the main application or to alter other plugins.

Startup behavior

First, the root app.extensions.json is loaded by means of the special Loader service. The file can contain all the necessary declarations for an application to function. Extra plugin files are fully optional.

Next, the Loader traverses the $references metadata and loads additional files if provided. For the sake of speed the files are loaded in parallel, however once everything is loaded, they are applied in the order of declaration.

After all the external files are fetched, the Loader sorts them, removes the metadata properties and stacks the resulting JSON objects on top of each other.

Tip: Any top-level property name that starts with the $ symbol is considered metadata and does not participate in the merge process. That allows a plugin to carry extra information for maintenance and visualisation purposes, for example: $name, $version, $description, $license, etc.

Merging properties

There are no limits in the JSON structure and level of nesting. All objects are merged into a single set based on property keys and object IDs (for arrays).

Before: Plugin 1

{
  "$name": "plugin1",
  "plugin1.key": "value",
  "plugin1.text": "string"
}

Before: Plugin 2

{
  "$name": "plugin2",
  "plugin2.key": "value",
  "plugin1.text": "custom string"
}

Final result:

{
  "plugin1.key": "value",
  "plugin1.text": "custom string",
  "plugin2.key": "value"
}

Note that as a result we have two unique properties plugin1.key and plugin2.key, and also a plugin1.text that was first defined in the Plugin 1, but then overwritten by the Plugin 2.

Tip: JSON merging is a very powerful concept as it gives you the ability to alter any base application settings, or toggle features in other plugins without rebuilding the application or corresponding plugin libraries.

Merging objects

The complex objects are merged by properties. This process is recursive and has no limits for nesting levels.

Before: Plugin 1

{
  "$name": "plugin1",
  "features": {
    "title": "some title",
    "page1": {
      "title": "page 1"
    }
  }
}

Before: Plugin 2

{
  "$name": "plugin2",
  "features": {
    "page1": {
      "title": "custom title"
    },
    "page2": {
      "title": "page 2"
    }
  }
}

Final result:

{
  "features": {
    "title": "some title",
    "page1": {
      "title": "custom title"
    },
    "page2": {
      "title": "page 2"
    }
  }
}

You can see the unique properties get merged together in a single object. However the last non-unique property overwrites the previous value.

Using the current design it is not possible to delete any application property from the plugin. The loader engine only supports overwriting values. Many components however support the disabled property you can change using an external definition:

Before: Plugin 1

{
  "$name": "plugin1",
  "feature1": {
    "disabled": false,
    "text": "some-feature",
    "icon": "some-icon"
  }
}

Before: Plugin 2

{
  "$name": "plugin2",
  "feature1": {
    "disabled": true
  }
}

Final result:

{
  "feature1": {
    "disabled": true,
    "text": "some-feature",
    "icon": "some-icon"
  }
}

You can find more details in the Disabling Content section

Merging arrays

The extension Loader provides a special support for merging Arrays. By default, two collections will be merged into a single array unless objects have id properties.

Tip: If the array contains two objects with the same id property, the objects will be merged rather than appended.

Before: Plugin 1

{
  "$name": "plugin1",
  "features": [
    { "text": "common 1" },
    {
      "id": "page1",
      "text": "page 1"
    }
  ]
}

Before: Plugin 2

{
  "$name": "plugin2",
  "features": [
    { "text": "common 2" },
    {
      "id": "page1",
      "text": "custom page"
    }
  ]
}

Final result:

{
  "features": [
    { "text": "common 1" },
    { "text": "common 2" },
    {
      "id": "page1",
      "text": "custom page"
    }
  ]
}

Note that objects with the same page1 identifiers were merged while other unique entries were appended to the resulting array.

Disabling content

Most of the schema elements can be switched off by using the disabled property:

{
  "$schema": "../../extension.schema.json",
  "$name": "app",
  "$version": "1.0.0",

  "features": {
    "create": [
      {
        "id": "app.create.folder",
        "disabled": true,
        "order": 100,
        "icon": "create_new_folder",
        "title": "Create Folder"
      }
    ]
  }
}

This feature becomes handy when you want to disable existing functionality from within the external plugin.

In the example below, the plugin called plugin1 replaces standard app.create.folder menu exposed by the application with a custom one coming with the plugin.

{
    "$schema": "../../../extension.schema.json",
    "$version": "1.0.0",
    "$name": "plugin1",

    "features": {
        "create": [
            {
                "id": "app.create.folder",
                "disabled": true
            },
            {
                "id": "plugin1.create.folder",
                "title": "Create Folder"
            }
        ]
    }
}