Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Breaking change]: Extension Manifest Type Architecture + Constants/Modal Tokens/Types moved to their package #22

Open
1 of 2 tasks
nielslyngsoe opened this issue Oct 25, 2024 · 3 comments
Labels
category/breaking category/source-incompatible Source code may encounter a breaking change in behavior when targeting the new version. cms/release/15.0.0 status/announcement

Comments

@nielslyngsoe
Copy link
Member

nielslyngsoe commented Oct 25, 2024

Description

In Umbraco 15, various parts are moved into their respective package, instead of declared centrally in the core, which means many import map/paths are not valid any longer in v.15.

This is done to enable extensions to bring their parts in the same maner as the Core. Enabling Extension Types from Packages to be available for other Packages as long as that Package is declared as a dependency. More on that below.

Imports:
Modal Tokens, Constants, Various TypeScript Types & Extension Manifest Types are moved into their respective package/import map. This means you need to update their import path — Ideally use an IDE that can update this automatically. Remove the import and ask the IDE to import again ( Follow the guide in TypeScript configuration below. This will have a positive effect on how well your IDE can pick up the Import Paths.)

Example for the Link Picker Modal Token:

Before it was imported here:
import { UMB_LINK_PICKER_MODAL } from '@umbraco-cms/backoffice/modal';

Now it will be imported from here:
import { UMB_LINK_PICKER_MODAL } from '@umbraco-cms/backoffice/multi-url-picker';

TypeScript configuration:
To be able to use the new Extension Manifest Types, you will need to update the tsconfig.json to include the extension-types from backoffice.

To do so add "@umbraco-cms/backoffice/extension-types" to compilerOptions->types

{
    "compilerOptions": {
      ...
      "types": [
        "@umbraco-cms/backoffice/extension-types"
      ]
    }
  }

If you want to be using extension types from other packages, they should be included in the same maner.
(Notice if you are a package author, and would like others to extend your Package, you should expose the declared Extension Manifest Types via such import map)

Using Extension Manifest Types:
The use of ManifestTypes, should be replaced with a global type called UmbExtensionManifest (which does not need an import once the TypeScript config includes the backoffice extension types. — The main reasoning for a global type is that this type is not exclusive for Core Extension Types, making it possible for other Packages to declare Manifest Types.

If you are looking for specific types, then they must correct their import path to their respective package/import map.
But notice how the specific types are not knowledgable of the additional Kinds of its type, so to be able to use Kinds you must use the UmbExtensionManifest type.

Example for the Dashboard Extension Manifest Type:

Before it was imported here:
import type { ManifestDashboard } from '@umbraco-cms/backoffice/extension-registry';

Now it will be imported from here:
import type { ManifestDashboard } from '@umbraco-cms/backoffice/dashboard';

Declaring Extension Manifest Types:
When declaring a new Extension Manifest Type, it must be appended to the Global Extension Manifest Type Map. Here is an example:

export interface PrefixAnimalExtentionManifestType extends ManifestBase {
	type: 'prefixAnimal';
}

declare global {
	interface UmbExtensionManifestMap {
		PrefixAnimalExtentionManifestType: PrefixAnimalExtentionManifestType;
	}
}

Make sure both your manifest-type and your property-name in the UmbExtensionManifestMap is unique (use a unique Prefix for your extension types.)

Declaring Extension Condition Configuration Types:
When declaring a new Extension Condition Type, you must now ensure to additionally declare a configuration type in a global type map specifically for Condition Configuration Types.
Even if you do not have any configuration properties — otherwise the condition will not show up as a valid option.

Example of a Condition Configuration Type:

export type PrefixAnimalTypeConditionConfig = UmbConditionConfigBase<'Prefix.Condition.AnimalType'> & {
	animalType: string;
};

declare global {
	interface UmbExtensionConditionConfigMap {
		PrefixAnimalTypeConditionConfig: PrefixAnimalTypeConditionConfig;
	}
}

Notice 'Prefix.Condition.AnimalType' is the alias of the Condition.

Version

Umbraco 15

Previous behavior

Had collected things in one core package, such imports will change.

New behavior

All Extension Manifest Types via a Global Type called UmbExtensionManifest. Other things from their respective package.

Type of breaking change

  • Binary incompatible: Existing binaries may encounter a breaking change in behavior, such as failure to load/execute or different run-time behavior.
  • Source incompatible: Source code may encounter a breaking change in behavior when targeting the new runtime/component/SDK, such as compile errors or different run-time behavior.

Reason for change

Package developers had near to no good opportunities when declaring their own Extension Manifest Types.
This gives them ability to easily declare such and opens up for Packages to extend Packages.

Recommended action

Use UmbExtensionManifest instead of ManifestTypes. Configure your tsconfig for global types. Update various imports to import from the respective package of the part.

Affected APIs

Extension Registry, Modal Tokens, Constants and TypeScript Types, Import Maps.

@enkelmedia
Copy link

enkelmedia commented Nov 21, 2024

@nielslyngsoe Thanks for a great summary of the changes needed for packages on v15!

I have a package for which I'll make it possible for other packages/extensions to provide extensions. I have this working on v14 and now looking at migrating to v15.

It would be very valuable to understand the reasoning behind the changes made in the core so that package developers can understand how to best provide types in scenarios like the one above.

I have a couple of questions and I would love to get your feedback.

On a general level, what did you gain by moving all extension types to @umbraco-cms/backoffice/extension-types and why did you decide to do it?

If I understand things right, extension types used to be exported together with the "feature" that hosted the extension. Basically they would be scattered around the code base. If it better in any way to keep them all in one place?

What does this actually do? Does it do a implicit import of all types in from @umbraco-cms/backoffice/extension-types?

{
    "compilerOptions": {
      ...
      "types": [
        "@umbraco-cms/backoffice/extension-types"
      ]
    }
  }

Are there any guidelines or recommendations around this for package developers like me that want to expose types for others to use?

Thank you so much!

@nielslyngsoe
Copy link
Member Author

nielslyngsoe commented Dec 10, 2024

Hi @enkelmedia Thanks for asking — at first I read the article above and did a few optimizations as some of the sentences could be rephrased for the better. Not that I changed much, but it hopefully makes it easier for others to read.

  1. The main goal is to not have a central registry.
    The central registry could not be extended, meaning if a package came with more Extension Types or even worse when bringing Conditions or Kinds. Then these would previously not appear as valid choices when using our Types.
    This change enables anyone to append Extension Types to this Global Map of Extension Types.
    Meaning now you can have a project that consists of multiple packages, some who may provide more Extension Types. And with this, you would when writing manifests(in TypeScript) be presented with all the valid types. An accumulated union of Extension Types and not having to type-cast your way as previously.
    (maybe I should mention it also helps us avoid having circular dependencies in the project)

  2. Secondary to this, it enables us to pack the core features as packages. One step closer to actually separate Umbraco CMS into smaller packages. Imagine each section begin something you would opt into to install. In the same way, when we develop a new feature. We would be able to serve it as a package first, but still having implementations writing Manifests of types that originated from that package, in the same way as if it was in the Core. And if it gets merged into Core, that would not be breaking for implementations of it.
    Again we will play the same game as Packages, enabling Packages to play our game.

  3. The global import is what ensures that TypeScript collected all the Globally declared Extension Types. Its a single type export of all types. If not done, then your project would not know that it would have to import Global Types from various import maps of ours.
    Similar would have to be done by a Package that provides their own Extension Types, and if you like that Packages Extension Types to take part in your Types, of you project, you would have to globally import that types. Imagine this:

    "compilerOptions": {
      ...
      "types": [
        "@umbraco-cms/backoffice/extension-types",
        "@umbraco-commerce/extension-types",
        "@umbraco-engage/extension-types",
        "@cookie-tracktor/extension-types"
      ]
    }
  }

With that you can write this in your project, and the Extension Type coming from Umbraco Commerce would be a valid choice.

const manifest: UmbExtensionManifest = {
  type: 'ucAnalyticsWidget',
  ...
}

I'm sorry we do not have a guideline for this at the moment, but its a good idea.

Generally, its about making a Import Map that exposes the Extension Type Types. This can be a single file that contains the additions you want to make to the global interface UmbExtensionManifestMap, and the same for Conditions using this map UmbExtensionConditionConfigMap.

hope that answers your questions, if I missed please let me know. This is also helpfull for me in terms of getting the right knowledge to write a good documentation article about this.

Thanks in advance

@enkelmedia
Copy link

enkelmedia commented Dec 14, 2024

Hi!

Thank you Niels! For updating the answer and trying to make things more clear.

I still nee to straight things out =D Sorry for "stupid" questions :).

In the example your showing to the import is moved:

Before it was imported here:
import type { ManifestDashboard } from '@umbraco-cms/backoffice/extension-registry';

Now it will be imported from here:
import type { ManifestDashboard } from '@umbraco-cms/backoffice/dashboard';

Then your writing

The use of ManifestTypes, should be replaced with a global type called UmbExtensionManifest (which does not need an import once the TypeScript config includes the backoffice extension types.

As I understand that last part, any use of ManifestDashboard should be replaced with UmbExtensionManifest, is that correct? While the example just shows that ManifestDashboard has been moved.

So as a concrete example, when using a extension point in a manifest.ts file we need to change to something like this:

const before : ManifestWorkspace = {
  type: "workspace",
  kind : "routable",
  name: "My Cars Workspace",
  alias: "My.Workspace.Cars",
  api: () => import('./my-cars-workspace.context.js'),
  meta: {
    entityType : 'my-car'
  }
};

const after : UmbExtensionManifest = {
  type: "workspace",
  kind : "routable",
  name: "My Cars Workspace",
  alias: "My.Workspace.Cars",
  api: () => import('./my-cars-workspace.context.js'),
  meta: {
    entityType : 'my-car'
  }
}

Or should we keep using ManifestWorkspace in the example above? Just wondering, the ManifestWorkspace types are still there, is it "wrong" to use them (like in the before-sample above), are they going away?

I understand that as a package developer I need to ensure to export a import map like @mypackage/extention-types that consumers can add to the types-array in tsconfig.

The docs for v15 still shows a lot of examples that does not use UmbExtensionManifest in the examples so it's not super clear what this change means.

Cheers! :)

Edit:
Another thing worth mentioning (and documenting) is that adding the types-array to tsconfig will basically wipe any implicit imports from packages like @types/*** - after declaring that array I need to manually opt-in for them like so:

"types": [
  "@umbraco-cms/backoffice/extension-types",
  "@types/mocha",
  "@types/chart.js"
]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
category/breaking category/source-incompatible Source code may encounter a breaking change in behavior when targeting the new version. cms/release/15.0.0 status/announcement
Projects
None yet
Development

No branches or pull requests

2 participants