← All articlesUmbraco

From AngularJS to Lit and Vite: rebuilding my Umbraco packages

Lessons from rebuilding God Mode and Audit Log Viewer for the modern Umbraco backoffice, with Lit components, extension manifests and a Vite library build.

Bringing an AngularJS package into modern Umbraco involves more than replacing a few directives. The backoffice extension model has changed, and the most useful starting point is deciding which parts of the package deserve to survive the move.

When rebuilding God Mode and Audit Log Viewer for Umbraco 17, I kept the purpose of each tool: help developers inspect a site and help administrators understand its history. The interface and its connection to Umbraco needed a fresh implementation.

Three tools with different jobs

Web Components provide the browser’s component model. Lit helps render those components and update them when their data changes. Vite builds the TypeScript and other source files into JavaScript that the backoffice can load.

Umbraco supports native Web Components; Lit is its recommended approach rather than a requirement to use a particular framework. Its Lit foundation guide explains rendering, reactive properties and event handling.

For someone coming from AngularJS, these are useful starting points:

AngularJS approach Modern extension approach
Controller and $scope A component with explicit state and methods
ng-repeat and bindings Lit templates, expressions and array mapping
Angular services TypeScript services, Umbraco contexts and controllers
HTML view registered in package.manifest Extension manifests and JavaScript modules
A backoffice API controller called by the old client An authorised Management API and a compatible client

These are conceptual counterparts. A controller’s responsibilities may belong in several components and services after the rewrite.

Keep the first component small

God Mode’s browsers need to load a collection, show its state and let the user inspect an item. Before adding that complexity, I would make sure a small component can render and respond to input:

import { LitElement, html, customElement, state }
  from '@umbraco-cms/backoffice/external/lit';
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';

@customElement('example-package-browser')
export class ExamplePackageBrowser extends UmbElementMixin(LitElement) {
  @state() private _search = '';

  override render() {
    return html`
      <label>
        Find an item
        <input .value=${this._search}
          @input=${(event: Event) => {
            this._search = (event.target as HTMLInputElement).value;
          }} />
      </label>
      <p>Searching for: ${this._search}</p>
    `;
  }
}

export default ExamplePackageBrowser;

This illustrative component uses the legacy TypeScript decorator configuration used in my packages: experimentalDecorators: true and useDefineForClassFields: false. It still needs an extension manifest to appear in Umbraco.

Use Umbraco’s UI components for the finished interface. Also allow for loading, empty and failed states: a table containing nothing must not look the same as a request that failed.

Build a library the backoffice can load

My God Mode client uses a Vite library build. This is a shortened version of its configuration:

import { defineConfig } from 'vite';

export default defineConfig({
  base: '/App_Plugins/DiploGodMode/',
  build: {
    lib: {
      entry: 'src/index.ts',
      formats: ['es'],
      fileName: () => 'index.js',
    },
    outDir: '../wwwroot/App_Plugins/DiploGodMode',
    emptyOutDir: true,
    sourcemap: true,
    rollupOptions: { external: [/^@umbraco\//] },
  },
});

The output directory belongs exclusively to generated assets: emptyOutDir clears it. Keep any hand-maintained files elsewhere and copy them during packaging if necessary. Externalising Umbraco imports lets the backoffice provide its own modules rather than bundling another copy into the package.

Install a backoffice npm package compatible with the CMS version you are targeting. Umbraco’s Vite package setup guide covers the wider setup. A Vite development server is useful while working, but the NuGet package must contain the production assets.

Separate registration from implementation

Both packages have a small entry point which registers their manifests:

import type { UmbEntryPointOnInit }
  from '@umbraco-cms/backoffice/extension-api';
import { allManifests } from './manifests';

export const onInit: UmbEntryPointOnInit = (_host, registry) => {
  registry.registerMany(allManifests);
};

An umbraco-package.json backoffice entry point loads that module. The manifests describe the extensions; lazy module imports load their components. In God Mode, this keeps the growing collection of browser views separate from the registration code.

I also keep API calls behind a shared client helper. Components should not each reinvent authentication, response handling and error messages. In God Mode that helper uses Umbraco’s HTTP client. Server-side authorisation still matters even when a menu item is hidden from an unauthorised user.

Test the installed package

My final check is the packaged experience: install it into a clean compatible site, check the browser’s network requests, exercise permissions and reopen the backoffice after deployment. Missing chunks, incorrect asset paths and stale files can all hide behind a working development setup.

The public God Mode source and Audit Log Viewer source show the larger implementations. For a site upgrade involving your own extensions, see my Umbraco 13 to 17 migration notes.