From f075b5338663455de6a0367fae259ce65f95cbc8 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 13 Jan 2026 10:57:39 +0100 Subject: [PATCH 1/5] change to static import --- .../src/packages/documents/documents/manifests.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/manifests.ts index 6a27712f5a30..945b62b39346 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/manifests.ts @@ -21,6 +21,7 @@ import { manifests as urlManifests } from './url/manifests.js'; import { manifests as userPermissionManifests } from './user-permissions/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; import { manifests as allowEditInvariantFromNonDefaultManifests } from './allow-edit-invariant-from-non-default/manifests.js'; +import * as entryPointModule from './entry-point.js'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; @@ -52,6 +53,6 @@ export const manifests: Array = name: 'Document Backoffice Entry Point', alias: 'Umb.BackofficeEntryPoint.Document', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; From 0fbe3b768bd9a28aac7684ddeb5a9661143e073c Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 13 Jan 2026 11:02:04 +0100 Subject: [PATCH 2/5] add support for passing modules to manifest js property --- .../load-manifest-plain-js.function.ts | 20 +++++++++++-------- .../src/libs/extension-api/types/utils.ts | 5 ++++- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-plain-js.function.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-plain-js.function.ts index f829237d9add..6eb6855dc69e 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-plain-js.function.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-plain-js.function.ts @@ -1,23 +1,27 @@ -import type { JsLoaderProperty } from '../types/utils.js'; +import type { JsLoaderPromise } from '../types/utils.js'; /** * * @param property */ -export async function loadManifestPlainJs(property: JsLoaderProperty): Promise { - const propType = typeof property; - if (propType === 'function') { - // Promise function - const result = await (property as Exclude)(); +export async function loadManifestPlainJs( + property: string | JsLoaderPromise | JsType, +): Promise { + if (typeof property === 'function') { + // Promise function (dynamic import) + const result = await (property as JsLoaderPromise)(); if (typeof result === 'object' && result != null) { return result; } - } else if (propType === 'string') { + } else if (typeof property === 'string') { // Import string - const result = await (import(/* @vite-ignore */ property as string) as unknown as JsType); + const result = await (import(/* @vite-ignore */ property) as unknown as JsType); if (typeof result === 'object' && result != null) { return result; } + } else if (typeof property === 'object' && property != null) { + // Already resolved module object (statically imported) + return property; } return undefined; } diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/types/utils.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/types/utils.ts index 67534655a4f0..7744d9d5fff8 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/types/utils.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/types/utils.ts @@ -47,7 +47,10 @@ export type ElementAndApiLoaderPromise< // Property Types: export type CssLoaderProperty = string | CssLoaderPromise; -export type JsLoaderProperty = string | JsLoaderPromise; +export type JsLoaderProperty = + | string + | JsLoaderPromise + | (JsExportType extends object ? JsExportType : never); export type ElementLoaderProperty = | string | ElementLoaderPromise From 8b91a46cd9949a4c8ee59ffd8d48a51799343c85 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 13 Jan 2026 11:20:04 +0100 Subject: [PATCH 3/5] Replaces dynamic imports of entry-point.js with static imports across all manifests --- src/Umbraco.Web.UI.Client/src/packages/data-type/manifests.ts | 3 ++- .../src/packages/dictionary/manifests.ts | 3 ++- .../src/packages/documents/document-blueprints/manifests.ts | 3 ++- .../src/packages/documents/document-types/manifests.ts | 3 ++- src/Umbraco.Web.UI.Client/src/packages/language/manifests.ts | 3 ++- .../src/packages/media/media-types/manifests.ts | 3 ++- .../src/packages/media/media/manifests.ts | 3 ++- .../src/packages/media/umbraco-package.ts | 4 +++- .../src/packages/members/member-group/manifests.ts | 3 ++- .../src/packages/members/member-type/manifests.ts | 3 ++- .../src/packages/members/member/manifests.ts | 3 ++- .../src/packages/property-editors/umbraco-package.ts | 4 +++- .../src/packages/static-file/manifests.ts | 3 ++- .../src/packages/templating/partial-views/manifests.ts | 3 ++- .../src/packages/templating/scripts/manifests.ts | 3 ++- .../src/packages/templating/stylesheets/manifests.ts | 3 ++- .../src/packages/templating/templates/manifests.ts | 3 ++- .../src/packages/templating/umbraco-package.ts | 4 +++- .../src/packages/user/user-group/manifests.ts | 3 ++- src/Umbraco.Web.UI.Client/src/packages/user/user/manifests.ts | 3 ++- src/Umbraco.Web.UI.Client/src/packages/webhook/manifests.ts | 3 ++- 21 files changed, 45 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/data-type/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/data-type/manifests.ts index fc4b1cd4f332..4aaa67fa9b7b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/data-type/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/data-type/manifests.ts @@ -8,6 +8,7 @@ import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as searchProviderManifests } from './search/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; +import * as entryPointModule from './entry-point.js'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; export const manifests: Array = [ @@ -25,6 +26,6 @@ export const manifests: Array = name: 'Data Type Backoffice Entry Point', alias: 'Umb.EntryPoint.DataType', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/dictionary/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/dictionary/manifests.ts index 89c6ca29acab..d6edcf659bef 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/dictionary/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/dictionary/manifests.ts @@ -6,6 +6,7 @@ import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as searchManifests } from './search/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; +import * as entryPointModule from './entry-point.js'; export const manifests: Array = [ ...collectionManifests, @@ -20,6 +21,6 @@ export const manifests: Array = [ name: 'Dictionary Backoffice Entry Point', alias: 'Umb.EntryPoint.Dictionary', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/manifests.ts index 8f5b83adff30..e458f358178e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-blueprints/manifests.ts @@ -3,6 +3,7 @@ import { manifests as menuManifests } from './menu/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; +import * as entryPointModule from './entry-point.js'; export const manifests: Array = [ ...entityActionManifests, @@ -14,6 +15,6 @@ export const manifests: Array = [ name: 'Document Blueprint Backoffice Entry Point', alias: 'Umb.BackofficeEntryPoint.DocumentBlueprint', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/manifests.ts index 7334c63e55d3..ad43a5af6b7c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/document-types/manifests.ts @@ -7,6 +7,7 @@ import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as searchManifests } from './search/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; +import * as entryPointModule from './entry-point.js'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; export const manifests: Array = [ @@ -23,6 +24,6 @@ export const manifests: Array = name: 'Document Type Backoffice Entry Point', alias: 'Umb.EntryPoint.DocumentType', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/language/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/language/manifests.ts index a3c14a77c8e1..793a1c56d702 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/language/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/language/manifests.ts @@ -9,6 +9,7 @@ import { manifests as menuManifests } from './menu/manifests.js'; import { manifests as modalManifests } from './modals/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; +import * as entryPointModule from './entry-point.js'; import { UMB_WORKSPACE_CONDITION_ALIAS } from '@umbraco-cms/backoffice/workspace'; export const manifests: Array = [ @@ -39,6 +40,6 @@ export const manifests: Array = [ name: 'Language Backoffice Entry Point', alias: 'Umb.EntryPoint.Language', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/manifests.ts index dfc067a9802f..2c98262a0242 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media-types/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media-types/manifests.ts @@ -6,6 +6,7 @@ import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as searchManifests } from './search/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; +import * as entryPointModule from './entry-point.js'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; export const manifests: Array = [ @@ -21,6 +22,6 @@ export const manifests: Array = name: 'Media Type Backoffice Entry Point', alias: 'Umb.EntryPoint.MediaType', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts index 6c856738527f..86ae7a5d0330 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/media/manifests.ts @@ -15,6 +15,7 @@ import { manifests as sectionViewManifests } from './dashboard/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as urlManifests } from './url/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; +import * as entryPointModule from './entry-point.js'; export const manifests: Array = [ ...auditLogManifests, @@ -38,6 +39,6 @@ export const manifests: Array = [ name: 'Media Backoffice Entry Point', alias: 'Umb.EntryPoint.Media', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/media/umbraco-package.ts b/src/Umbraco.Web.UI.Client/src/packages/media/umbraco-package.ts index 931356f531e6..cc6cf7e2cde7 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/media/umbraco-package.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/media/umbraco-package.ts @@ -1,3 +1,5 @@ +import * as entryPointModule from './entry-point.js'; + export const name = 'Umbraco.Core.MediaManagement'; export const extensions = [ { @@ -10,6 +12,6 @@ export const extensions = [ name: 'Media Management Entry Point', alias: 'Umb.EntryPoint.MediaManagement', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-group/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-group/manifests.ts index e26968ae5fb4..1287ee6b694c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-group/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-group/manifests.ts @@ -5,6 +5,7 @@ import { manifests as menuItemManifests } from './menu-item/manifests.js'; import { manifests as propertyEditorManifests } from './property-editor/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; +import * as entryPointModule from './entry-point.js'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; @@ -20,6 +21,6 @@ export const manifests: Array = name: 'Member Group Backoffice Entry Point', alias: 'Umb.EntryPoint.MemberGroup', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/manifests.ts index 25af475adecc..2c66d4eea22a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member-type/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member-type/manifests.ts @@ -6,6 +6,7 @@ import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as searchManifests } from './search/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; +import * as entryPointModule from './entry-point.js'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; @@ -24,6 +25,6 @@ export const manifests: Array = name: 'Member Type Backoffice Entry Point', alias: 'Umb.BackofficeEntryPoint.MemberType', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/manifests.ts index 2d7e76445987..cc14b9eb9177 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/manifests.ts @@ -9,6 +9,7 @@ import { manifests as referenceManifests } from './reference/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as searchManifests } from './search/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; +import * as entryPointModule from './entry-point.js'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; @@ -28,6 +29,6 @@ export const manifests: Array = name: 'Member Backoffice Entry Point', alias: 'Umb.BackofficeEntryPoint.Member', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/umbraco-package.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/umbraco-package.ts index 0b84cb411c91..174214d2e59a 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/umbraco-package.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/umbraco-package.ts @@ -1,3 +1,5 @@ +import * as entryPointModule from './entry-point.js'; + export const name = 'Umbraco.Core.PropertyEditors'; export const extensions = [ { @@ -10,6 +12,6 @@ export const extensions = [ name: 'Property Editors Entry Point', alias: 'Umb.EntryPoint.PropertyEditors', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/static-file/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/static-file/manifests.ts index 688fafcd71fa..ad2dd1b5a754 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/static-file/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/static-file/manifests.ts @@ -1,6 +1,7 @@ import { manifests as propertyEditorManifests } from './property-editors/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; +import * as entryPointModule from './entry-point.js'; export const manifests: Array = [ ...propertyEditorManifests, @@ -10,6 +11,6 @@ export const manifests: Array = [ name: 'Static File Backoffice Entry Point', alias: 'Umb.EntryPoint.StaticFile', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/manifests.ts index 56e03267fc57..e64c8cbb5be5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/partial-views/manifests.ts @@ -3,6 +3,7 @@ import { manifests as menuManifests } from './menu/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as entityActionsManifests } from './entity-actions/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; +import * as entryPointModule from './entry-point.js'; export const manifests: Array = [ ...repositoryManifests, @@ -14,6 +15,6 @@ export const manifests: Array = [ name: 'Partial View Backoffice Entry Point', alias: 'Umb.EntryPoint.Partial View', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/manifests.ts index a81c0cd50053..a04f16a9b83c 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/scripts/manifests.ts @@ -3,6 +3,7 @@ import { manifests as menuManifests } from './menu/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; +import * as entryPointModule from './entry-point.js'; export const manifests: Array = [ ...entityActionsManifests, @@ -14,6 +15,6 @@ export const manifests: Array = [ name: 'Script Backoffice Entry Point', alias: 'Umb.EntryPoint.Script', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/manifests.ts index 22eee1a15880..43bc437ce36d 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/stylesheets/manifests.ts @@ -4,6 +4,7 @@ import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; import { manifests as entityActionManifests } from './entity-actions/manifests.js'; import { manifests as propertyEditorsManifests } from './property-editors/manifests.js'; +import * as entryPointModule from './entry-point.js'; export const manifests: Array = [ ...repositoryManifests, @@ -16,6 +17,6 @@ export const manifests: Array = [ name: 'Stylesheet Backoffice Entry Point', alias: 'Umb.EntryPoint.Stylesheet', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/manifests.ts index 681837237913..7806fc0051d0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/templates/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/templates/manifests.ts @@ -6,6 +6,7 @@ import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as searchManifests } from './search/manifests.js'; import { manifests as treeManifests } from './tree/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; +import * as entryPointModule from './entry-point.js'; export const manifests: Array = [ ...conditionsManifests, @@ -20,6 +21,6 @@ export const manifests: Array = [ name: 'Template Backoffice Entry Point', alias: 'Umb.EntryPoint.Template', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/templating/umbraco-package.ts b/src/Umbraco.Web.UI.Client/src/packages/templating/umbraco-package.ts index 0aafd8af6573..503576767be4 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/templating/umbraco-package.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/templating/umbraco-package.ts @@ -1,3 +1,5 @@ +import * as entryPointModule from './entry-point.js'; + export const name = 'Umbraco.Core.Templating'; export const extensions = [ { @@ -10,6 +12,6 @@ export const extensions = [ name: 'Template Management Backoffice Entry Point', alias: 'Umb.BackofficeEntryPoint.TemplateManagement', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/manifests.ts index 8eac0d0d6952..d18225e0a864 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/manifests.ts @@ -6,6 +6,7 @@ import { manifests as modalManifests } from './modals/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as sectionViewManifests } from './workspace/user-group-root/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; +import * as entryPointModule from './entry-point.js'; export const manifests: Array = [ ...collectionManifests, @@ -20,6 +21,6 @@ export const manifests: Array = [ name: 'User Group Backoffice Entry Point', alias: 'Umb.EntryPoint.UserGroup', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/manifests.ts index fdbbc46155f4..64263d6bbb8b 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/manifests.ts @@ -10,6 +10,7 @@ import { manifests as modalManifests } from './modals/manifests.js'; import { manifests as propertyEditorManifests } from './property-editor/manifests.js'; import { manifests as repositoryManifests } from './repository/manifests.js'; import { manifests as workspaceManifests } from './workspace/manifests.js'; +import * as entryPointModule from './entry-point.js'; import type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry'; @@ -30,6 +31,6 @@ export const manifests: Array = name: 'User Backoffice Entry Point', alias: 'Umb.EntryPoint.User', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/webhook/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/webhook/manifests.ts index 9ff4c59378f0..4d61331f2fbd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/webhook/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/webhook/manifests.ts @@ -2,6 +2,7 @@ import { manifests as webhookDeliveryManifests } from './webhook-delivery/manife import { manifests as webhookEventManifests } from './webhook-event/manifests.js'; import { manifests as webhookManifests } from './webhook/manifests.js'; import { manifests as webhookRootManifests } from './webhook-root/manifests.js'; +import * as entryPointModule from './entry-point.js'; export const manifests: Array = [ ...webhookDeliveryManifests, @@ -12,6 +13,6 @@ export const manifests: Array = [ name: 'Webhook Backoffice Entry Point', alias: 'Umb.EntryPoint.Webhook', type: 'backofficeEntryPoint', - js: () => import('./entry-point.js'), + js: entryPointModule, }, ]; From b912b7a9c8460c70fd9dbebb29c95335a2cb0f3a Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 14 Jan 2026 10:03:26 +0100 Subject: [PATCH 4/5] Support statically imported modules in loader functions Extended loadManifestApi and loadManifestElement to handle already resolved module objects (statically imported modules) in addition to dynamic imports. Updated type definitions in utils.ts to include module export types for loader properties. --- .../functions/load-manifest-api.function.ts | 15 +++++++++++---- .../functions/load-manifest-element.function.ts | 13 +++++++++++-- .../src/libs/extension-api/types/utils.ts | 7 ++++++- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-api.function.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-api.function.ts index e168e376a7e7..067409d2faf6 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-api.function.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-api.function.ts @@ -1,6 +1,7 @@ import type { UmbApi } from '../models/api.interface.js'; import type { ApiLoaderExports, + ApiLoaderPromise, ApiLoaderProperty, ClassConstructor, ElementAndApiLoaderProperty, @@ -20,10 +21,8 @@ export async function loadManifestApi( // Class Constructor return property as ClassConstructor; } else { - // Promise function - const result = await ( - property as Exclude, string>, ClassConstructor> - )(); + // Promise function (dynamic import) + const result = await (property as ApiLoaderPromise)(); if (typeof result === 'object' && result != null) { const exportValue = ('api' in result ? result.api : undefined) || @@ -43,6 +42,14 @@ export async function loadManifestApi( return exportValue; } } + } else if (propType === 'object' && property !== null) { + // Already resolved module object (statically imported) + const result = property as ApiLoaderExports; + const exportValue = + ('api' in result ? result.api : undefined) || ('default' in result ? result.default : undefined); + if (exportValue && typeof exportValue === 'function') { + return exportValue; + } } return undefined; } diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-element.function.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-element.function.ts index 9a08338dfbda..7e9c2015bf17 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-element.function.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-element.function.ts @@ -3,6 +3,7 @@ import type { ClassConstructor, ElementAndApiLoaderProperty, ElementLoaderExports, + ElementLoaderPromise, ElementLoaderProperty, } from '../types/utils.js'; @@ -19,8 +20,8 @@ export async function loadManifestElement( // Class Constructor return property as ClassConstructor; } else { - // Promise function - const result = await (property as Exclude, ClassConstructor>)(); + // Promise function (dynamic import) + const result = await (property as ElementLoaderPromise)(); if (typeof result === 'object' && result !== null) { const exportValue = ('element' in result ? result.element : undefined) || @@ -42,6 +43,14 @@ export async function loadManifestElement( return exportValue; } } + } else if (propType === 'object' && property !== null) { + // Already resolved module object (statically imported) + const result = property as ElementLoaderExports; + const exportValue = + ('element' in result ? result.element : undefined) || ('default' in result ? result.default : undefined); + if (exportValue && typeof exportValue === 'function') { + return exportValue; + } } return undefined; } diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/types/utils.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/types/utils.ts index 7744d9d5fff8..f0b75f9aadfc 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/extension-api/types/utils.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/types/utils.ts @@ -54,10 +54,12 @@ export type JsLoaderProperty = export type ElementLoaderProperty = | string | ElementLoaderPromise + | ElementLoaderExports | ClassConstructor; export type ApiLoaderProperty = | string | ApiLoaderPromise + | ApiLoaderExports | ClassConstructor; export type ElementAndApiLoaderProperty< ElementType extends HTMLElement = HTMLElement, @@ -65,5 +67,8 @@ export type ElementAndApiLoaderProperty< > = | string | ElementAndApiLoaderPromise + | ElementAndApiLoaderExports | ElementLoaderPromise - | ApiLoaderPromise; + | ElementLoaderExports + | ApiLoaderPromise + | ApiLoaderExports; From 47b63bc10cf12236ac826e9da5c9297307a35a53 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 14 Jan 2026 10:04:42 +0100 Subject: [PATCH 5/5] Add tests for loadManifest* functions in extension-api Introduces unit tests for loadManifestApi, loadManifestElement, and loadManifestPlainJs functions. These tests cover various scenarios including direct class constructors, dynamic and static imports, export prioritization, and edge cases for null and undefined inputs. --- .../load-manifest-api.function.test.ts | 110 +++++++++++++++++ .../load-manifest-element.function.test.ts | 111 ++++++++++++++++++ .../load-manifest-plain-js.function.test.ts | 63 ++++++++++ 3 files changed, 284 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-api.function.test.ts create mode 100644 src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-element.function.test.ts create mode 100644 src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-plain-js.function.test.ts diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-api.function.test.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-api.function.test.ts new file mode 100644 index 000000000000..4ae018f05221 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-api.function.test.ts @@ -0,0 +1,110 @@ +import type { UmbApi } from '../models/api.interface.js'; +import { loadManifestApi } from './load-manifest-api.function.js'; +import { expect } from '@open-wc/testing'; + +class UmbTestApiTrue implements UmbApi { + isValidClassInstance() { + return true; + } + destroy() {} +} + +class UmbTestApiFalse implements UmbApi { + isValidClassInstance() { + return false; + } + destroy() {} +} + +const jsModuleWithDefaultExport = { + default: UmbTestApiTrue, +}; + +const jsModuleWithApiExport = { + api: UmbTestApiTrue, +}; + +const jsModuleWithDefaultAndApiExport = { + default: UmbTestApiFalse, + api: UmbTestApiTrue, +}; + +describe('loadManifestApi', () => { + describe('class constructor', () => { + it('returns the class constructor when passed directly', async () => { + const result = await loadManifestApi(UmbTestApiTrue); + expect(result).to.equal(UmbTestApiTrue); + }); + }); + + describe('dynamic import (function)', () => { + it('returns the api class from default export', async () => { + const result = await loadManifestApi(() => Promise.resolve(jsModuleWithDefaultExport)); + expect(result).to.equal(UmbTestApiTrue); + }); + + it('returns the api class from api export', async () => { + const result = await loadManifestApi(() => Promise.resolve(jsModuleWithApiExport)); + expect(result).to.equal(UmbTestApiTrue); + }); + + it('prioritizes api export over default export', async () => { + const result = await loadManifestApi(() => Promise.resolve(jsModuleWithDefaultAndApiExport)); + expect(result).to.equal(UmbTestApiTrue); + }); + + it('returns undefined when loader returns null', async () => { + const result = await loadManifestApi(() => Promise.resolve(null as any)); + expect(result).to.be.undefined; + }); + + it('returns undefined when loader returns object without valid exports', async () => { + const result = await loadManifestApi(() => Promise.resolve({ other: 'value' } as any)); + expect(result).to.be.undefined; + }); + + it('returns undefined when export is not a function', async () => { + const result = await loadManifestApi(() => Promise.resolve({ default: 'not-a-function' } as any)); + expect(result).to.be.undefined; + }); + }); + + describe('static import (object)', () => { + it('returns the api class from default export', async () => { + const result = await loadManifestApi(jsModuleWithDefaultExport); + expect(result).to.equal(UmbTestApiTrue); + }); + + it('returns the api class from api export', async () => { + const result = await loadManifestApi(jsModuleWithApiExport); + expect(result).to.equal(UmbTestApiTrue); + }); + + it('prioritizes api export over default export', async () => { + const result = await loadManifestApi(jsModuleWithDefaultAndApiExport); + expect(result).to.equal(UmbTestApiTrue); + }); + + it('returns undefined when object has no valid exports', async () => { + const result = await loadManifestApi({ other: 'value' } as any); + expect(result).to.be.undefined; + }); + + it('returns undefined when export is not a function', async () => { + const result = await loadManifestApi({ default: 'not-a-function' } as any); + expect(result).to.be.undefined; + }); + }); + + describe('edge cases', () => { + it('returns undefined when passed null', async () => { + const result = await loadManifestApi(null as any); + expect(result).to.be.undefined; + }); + + it('returns undefined when passed undefined', async () => { + const result = await loadManifestApi(undefined as any); + expect(result).to.be.undefined; + }); + }); +}); diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-element.function.test.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-element.function.test.ts new file mode 100644 index 000000000000..75953c6961ab --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-element.function.test.ts @@ -0,0 +1,111 @@ +import { loadManifestElement } from './load-manifest-element.function.js'; +import { expect } from '@open-wc/testing'; +import { customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; + +@customElement('umb-test-element-true') +class UmbTestElementTrue extends UmbLitElement { + isValidClassInstance() { + return true; + } +} + +@customElement('umb-test-element-false') +class UmbTestElementFalse extends UmbLitElement { + isValidClassInstance() { + return false; + } +} + +const jsModuleWithDefaultExport = { + default: UmbTestElementTrue, +}; + +const jsModuleWithElementExport = { + element: UmbTestElementTrue, +}; + +const jsModuleWithDefaultAndElementExport = { + default: UmbTestElementFalse, + element: UmbTestElementTrue, +}; + +describe('loadManifestElement', () => { + describe('class constructor', () => { + it('returns the class constructor when passed directly', async () => { + const result = await loadManifestElement(UmbTestElementTrue); + expect(result).to.equal(UmbTestElementTrue); + }); + }); + + describe('dynamic import (function)', () => { + it('returns the element class from default export', async () => { + const result = await loadManifestElement(() => Promise.resolve(jsModuleWithDefaultExport)); + expect(result).to.equal(UmbTestElementTrue); + }); + + it('returns the element class from element export', async () => { + const result = await loadManifestElement(() => Promise.resolve(jsModuleWithElementExport)); + expect(result).to.equal(UmbTestElementTrue); + }); + + it('prioritizes element export over default export', async () => { + const result = await loadManifestElement(() => Promise.resolve(jsModuleWithDefaultAndElementExport)); + expect(result).to.equal(UmbTestElementTrue); + }); + + it('returns undefined when loader returns null', async () => { + const result = await loadManifestElement(() => Promise.resolve(null as any)); + expect(result).to.be.undefined; + }); + + it('returns undefined when loader returns object without valid exports', async () => { + const result = await loadManifestElement(() => Promise.resolve({ other: 'value' } as any)); + expect(result).to.be.undefined; + }); + + it('returns undefined when export is not a function', async () => { + const result = await loadManifestElement(() => Promise.resolve({ default: 'not-a-function' } as any)); + expect(result).to.be.undefined; + }); + }); + + describe('static import (object)', () => { + it('returns the element class from default export', async () => { + const result = await loadManifestElement(jsModuleWithDefaultExport); + expect(result).to.equal(UmbTestElementTrue); + }); + + it('returns the element class from element export', async () => { + const result = await loadManifestElement(jsModuleWithElementExport); + expect(result).to.equal(UmbTestElementTrue); + }); + + it('prioritizes element export over default export', async () => { + const result = await loadManifestElement(jsModuleWithDefaultAndElementExport); + expect(result).to.equal(UmbTestElementTrue); + }); + + it('returns undefined when object has no valid exports', async () => { + const result = await loadManifestElement({ other: 'value' } as any); + expect(result).to.be.undefined; + }); + + it('returns undefined when export is not a function', async () => { + const result = await loadManifestElement({ default: 'not-a-function' } as any); + expect(result).to.be.undefined; + }); + }); + + describe('edge cases', () => { + it('returns undefined when passed null', async () => { + const result = await loadManifestElement(null as any); + expect(result).to.be.undefined; + }); + + it('returns undefined when passed undefined', async () => { + const result = await loadManifestElement(undefined as any); + expect(result).to.be.undefined; + }); + }); +}); diff --git a/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-plain-js.function.test.ts b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-plain-js.function.test.ts new file mode 100644 index 000000000000..4c378ec3a330 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/libs/extension-api/functions/load-manifest-plain-js.function.test.ts @@ -0,0 +1,63 @@ +import { loadManifestPlainJs } from './load-manifest-plain-js.function.js'; +import { expect } from '@open-wc/testing'; + +interface TestModule { + value: string; + getValue(): string; +} + +const testModuleObject: TestModule = { + value: 'test-value', + getValue() { + return this.value; + }, +}; + +const jsModuleWithTestExport = { + value: 'dynamic-import-value', + getValue() { + return this.value; + }, +}; + +describe('loadManifestPlainJs', () => { + describe('dynamic import (function)', () => { + it('returns the module when loader returns an object', async () => { + const result = await loadManifestPlainJs(() => Promise.resolve(jsModuleWithTestExport)); + expect(result).to.not.be.undefined; + expect(result?.value).to.equal('dynamic-import-value'); + expect(result?.getValue()).to.equal('dynamic-import-value'); + }); + + it('returns undefined when loader returns null', async () => { + const result = await loadManifestPlainJs(() => Promise.resolve(null as unknown as TestModule)); + expect(result).to.be.undefined; + }); + + it('returns undefined when loader returns a primitive', async () => { + const result = await loadManifestPlainJs(() => Promise.resolve('string' as unknown as TestModule)); + expect(result).to.be.undefined; + }); + }); + + describe('static import (object)', () => { + it('returns the module object directly when passed an object', async () => { + const result = await loadManifestPlainJs(testModuleObject); + expect(result).to.not.be.undefined; + expect(result?.value).to.equal('test-value'); + expect(result?.getValue()).to.equal('test-value'); + }); + + it('returns undefined when passed null', async () => { + const result = await loadManifestPlainJs(null as unknown as TestModule); + expect(result).to.be.undefined; + }); + }); + + describe('edge cases', () => { + it('returns undefined when passed undefined', async () => { + const result = await loadManifestPlainJs(undefined as unknown as TestModule); + expect(result).to.be.undefined; + }); + }); +});