diff --git a/cms/0024-implement-the-new-backoffice.md b/cms/0024-implement-the-new-backoffice.md new file mode 100644 index 0000000..a86e8c6 --- /dev/null +++ b/cms/0024-implement-the-new-backoffice.md @@ -0,0 +1,428 @@ +# Implement The New Backoffice + +### Request for Comments (RFC) 0024: Implement The New Backoffice + +## Code of Conduct + +Please read and respect the [RFC Code of Conduct](https://github.com/umbraco/rfcs/blob/master/CODE_OF_CONDUCT.md). + +## Intended Audience + +The intended audience for this RFC is + +- Technical users +- Developers +- Extension developers +- Package authors + +## Summary + +To future-proof Umbraco’s backoffice, we intend to run a three-part process - each with an RFC: + +1. Standalone UI Component library (RFC accepted January 2021) +2. Defining backoffice extension API (RFC accepted December 2021) +3. Implement the new backoffice (This RFC) + +It can be helpful to watch this [umbracoCoffee episode](https://youtu.be/i0QfgRYj0zQ?t=1681) where Filip Bruun Bech-Larsen, CTO for Umbraco, introduces the concept. + +## Motivation + +Umbraco’s backoffice is a big reason why editors AND developers choose Umbraco. It is easy to use and flexible to customize to the specific needs of a project/client + +The current (second) generation backoffice is built using AngularJS. It was a great choice in 2013, and has served us well, but has long been considered outdated. Since December 31, 2021, AngularJS has reached its [end-of-life state](https://blog.angular.io/discontinued-long-term-support-for-angularjs-cc066b82e65a). + +Since 2012 we have learned a lot about building a complex Single Page Application (SPA). The current situation does not allow us (or makes it hard) to fix things we have done inherently wrong. The current state is holding development - and excitement - back. + +With the next generation of Umbraco’s backoffice, we want to bring several benefits to Umbraco, namely: + +- Speed up the maintenance and future development of the backoffice for Umbraco HQ and community developers alike +- The code will utilize TypeScript and accompanying types in the high-seat +- 3rd-party integrations will be considered as first-class citizens +- Make Umbraco more attractive for new contributors, by using modern technology and methodology - thereby also making it easier to contribute +- Simplify and speed up work for developers and package authors, by enhancing the ability to create great UI/UX +- Streamline the experience throughout the entire journey incl. 1st- and 3rd-party packages, Cloud, etc. +- Reuse work across our products for less development but also for improvements to be distributed +- Maintain market leadership in a customizable editing experience (tailored to clients) + +## Detailed Design + +### Building the application + +#### Single-Page Application + +As with the old backoffice, the new backoffice will be built as a single-page application. In addition to being served solely in the browser, the new backoffice will also be built as a true standalone application meaning that it can be hosted anywhere since it only requires a browser and a file host to run. No server-side rendering will be required to run the backoffice application. + +#### Routing + +In single-page applications routing is usually a non-trivial area. We are looking into what routers are available at the moment. We need something for the application that can handle both top-level routing and deep-level nesting so that each section, content app, infinite editor, tab, and so on, can push a state to either the URL, the browser history, or both. It should also be possible for any frontend extension to register its own set of routes enabling anyone to deep-link into an extension subarea. + +#### Web Components + +We are going to use a standards-based way to build our custom UI as close to modern browser conventions and technology as possible. We have considered many options - libraries and frameworks alike - and have concluded that we would like to avoid falling into another technical grave in the years to come when what we choose now becomes obsolete. + +> _When you're working with the browser rather than against it, code, skills, and knowledge remain relevant for a longer time. Development becomes faster and debugging is easier because there are fewer layers of abstractions involved._ — [modern-web.dev](https://modern-web.dev/discover/about/) + +We have a firm belief that sticking with the browser standards will continue to evolve and benefit us in the years to come. Therefore we have chosen to build the new backoffice UI with [Web Components](https://developer.mozilla.org/en-US/docs/Web/Web_Components). + +JavaScript frameworks are all doing essentially the same thing: Producing custom elements with syntactic sugar, and also added benefits of having built-in routers, state machines, and stores. The concept of Web Components is created such that the browsers natively support all the things we love from libraries such as React and Vue including having custom elements, reactivity, and state. It allows us to create the most optimal setup and environment for the backoffice to ship modern, slim, chunked modules for the UI. It also allows us to choose whatever libraries we like for state management, routing, and observability. + +Building our software with Web Components - a native web standard - ensures that our software works and will keep working for at least a longer period than any external library will. The APIs will keep getting updated along with the browser itself, patching any security vulnerability along the way. + +##### Boilerplating + +Web Components require a lot of boilerplate in their current state, so to speed up development, we are going to work with a very efficient, [tiny library called Lit](https://lit.dev/). This library has helped us tremendously in building the [Umbraco UI Library](https://github.com/umbraco/Umbraco.UI) and is also now serving parts of the [Umbraco Cloud portal](https://umbraco.com/products/umbraco-cloud/). Choosing Lit also has the added benefit of being able to share tech and collaborate throughout Umbraco HQ. + +A component for the new backoffice can be built with only a few lines of code: + +```ts +import { html, css, LitElement } from "lit"; +import { customElement, property } from "lit/decorators.js"; + +@customElement("my-simple-greeting") +export class SimpleGreeting extends LitElement { + static styles = css` + p { + color: blue; + } + `; + + @property() + name = "Somebody"; + + render() { + return html`
Hello, ${this.name}!
`; + } +} +``` + +Incidentally, Web Components and Lit take care of most of the stuff we know from AngularJS, such as the concept of "components" and data binding. Running through a data set is fairly easy with Lit's template interpolation with native JavaScript, we can accomplish this in a much cleaner way than AngularJS ever did, where you had two-way data binding. Two-way data binding is, however, something that only AngularJS supported and is not something you would use today. Instead, the flow would be: input = data, output = events. + +```mermaid +flowchart +ParentComponent -- data in through attributes --> ChildComponent +ChildComponent -- events out through an emitter --> ParentComponent +``` + +Although we have chosen Lit and TypeScript to build the new backoffice, developers are free to use any framework and libraries of their choice to implement backoffice extensions. We will be building an extension API that is framework agnostic as stated in an [earlier RFC](https://github.com/umbraco/rfcs/blob/main/cms/0023-define-the-backoffice-extension-api.md#detailed-design). + +Please have a look at [Lit’s excellent playground](https://lit.dev/playground/) to learn more about it. + +#### Umbraco UI Library + +We have built a UI Library that will serve as a great base for the backoffice (and some of our other products). Using the Umbraco UI Library provides us with custom-made components needed for the backoffice UI. This means the code for composing UI will be as little as possible. Additionally, we can extend components of the UI Library to make special parts for the backoffice or extensions. + +#### Build setup + +A lot of tools come with the territory when building a modern web application. With the choice of Lit and TypeScript, we need something to help us compile/transpile TypeScript code, bundle everything together in small chunks, and remove any code that is not in use from libraries, etc. + +Many setups exist today with support for Lit and TypeScript. We especially like the premise of [esbuild](https://esbuild.github.io/) to transpile the code due to its swiftness and lightness. For tree shaking and splitting, we like what [Rollup](https://rollupjs.org/) has done in terms of supporting esbuild. However, managing the configuration of these and keeping them up-to-date is not a task that we want to spend too much time on. Therefore we have chosen to use [Vite.js](https://vitejs.dev/) to handle everything related to the development and build processes, including running the entire application in a development mode when building it as well as creating the production build. Vite uses Rollup underneath the surface and it also exposes the underlying configuration if we need to change something, but all-in-all it abstracts the whole process away to ensure that we are always running the most optimal setup. + +The development flow of the new backoffice will simply consist of cloning down the repository, installing the npm dependencies, running Vite, and a browser will launch immediately supporting live reload and debugging: + +```bash +git clone git@github.com:umbraco/Umbraco.CMS.Backoffice.git +cd Umbraco.CMS.Backoffice +npm install +npm run dev +``` + +### Extension API + +With the old backoffice, it was possible to expose the entire AngularJS API globally enabling extension developers to not only use built-in components but also create their own on the same runtime as the backoffice. In addition to AngularJS, we also ship the old backoffice with other globals such as Underscore, jQuery, SignalR, and many more. + +With modern web development, it is customary to run the code through both a minification and a tree-shaking process and even split the code into chunks to avoid loading the entire application initially. This process also puts all dependencies into their own scope, preventing them from leaking and/or being used outside by other bundles such as extensions loaded dynamically unless the dependencies are specifically exposed. + +Initially, we will not expose anything, because the combined bundled versions of our tools like Lit and others are rather large and do not necessarily support being exposed at all. This means that extensions will not be able to hook onto most of the underlying libraries of the backoffice. Instead, extensions will have to provide their dependencies and run them through a tree-shaking process with tools like Rollup. However, we are looking to see if some tools could benefit from being provided globally - provided that they come with a universal module that runs in the browser. We expect that the overhead of overlapping dependencies between the backoffice and extensions will be very small, but a lot of upcoming features such as [import maps](https://github.com/WICG/import-maps) will be interesting to look into to solve that particular drawback. + +The recommendation to extension developers is that they also choose Lit and TypeScript. To help them do all of that, we are planning to release a helper tool that will allow developers to start building extensions against the new extension API. The tool will bootstrap their development in such a way that they get up and running very quickly and can start writing code. + +There are several ways to go about building extensions for the new backoffice and this tool will cover a few ways of doing so, both for developers wanting to start with TypeScript and Lit, and for those wanting to start with HTML and vanilla JavaScript. The tool could come in the form of either a dotnet template (adding to [the existing templates](https://www.nuget.org/packages/Umbraco.Templates)) or an npm package, and could be invoked like this: + +**Dotnet:** + +```bash +dotnet new --install Umbraco.Templates +dotnet new umbracopackage --name MyPropertyEditor --type propertyeditor +``` + +**NPM:** + +```bash +npm create @umbraco/package --name MyPropertyEditor --type propertyeditor +``` + +#### Typings + +The new backoffice will ship with [TypeScript declaration files](https://www.typescriptlang.org/docs/handbook/declaration-files/templates/module-d-ts.html), either physically or in a [Definitely Typed](https://github.com/DefinitelyTyped/DefinitelyTyped) package, for all relevant components enabling TypeScript as a first-class citizen for Web API interaction with both request and response types, JavaScript runtime services through the Context API, Umbraco backoffice components and UI Library components. + +#### Context API + +To provide contextual shared logic we have established a system called Context API. This is an event-based protocol that components can use to retrieve context from any location in the DOM. A context is an instance of a class that is provided to a certain scope of the DOM. + +The Context API consists of two parts: A Context Provider and a Context Consumer. + +The following example shows how to provide context (in this instance it is a service) for other components to consume: + +```ts +import { UmbContextProviderMixin } from "@umbraco/context"; + +// Create a class that extends from the context PROVIDER mixin, which gives you access to the provideContext method +class UmbAppElement extends UmbContextProviderMixin(HTMLElement) { + constructor() { + super(); + + // Provide anything you like here and send it downstream in the DOM tree + this.provideContext("myContextAPIService", new MyContextAPIService()); + } +} +``` + +And the context service is then requested through the consumer, which has a handy mixin to extend the component from: + +```ts +import { UmbContextConsumerMixin } from "@umbraco/context"; + +// Create a class that extends from the context CONSUMER mixin, which gives you the consumeContext method +class UmbMyElement extends UmbContextConsumerMixin(HTMLElement) { + constructor() { + super(); + + // Request anything you like here such as the myContextAPIService that was provided above + this.consumeContext("myContextAPIService", (api) => { + const myContextApi = api; + + // Invoke a method on the provided service + myContextApi.fooBar(); + }); + } +} +``` + +#### Styling + +We recommend initiating Web Components with ShadowDOM, and this is always the case when using Lit. + +ShadowDOM encapsulates the insides of a Web Component which means no CSS classes will be available nor will any of the styling interfere with the backoffice. + +Style selectors can be much simpler and separated for each component. The styling (CSS) of a Web Component is provided inline in the JavaScript code by a static `styles` property: + +```ts +import { LitElement, css, html } from "lit"; + +export class MyStylingExampleElement extends LitElement { + static styles = css` + p { + color: darkgrey; + } + div { + border: 1px solid green; + color: green; + padding: 6px; + } + `; + + render() { + return html` +Hello world
+Hello world
+