6M Technical Reference

6M version 2.1.0

This document describes the rules by which TUI 6M components are to be developed. If you don’t know what 6M is, read the 6M Intro. If you are developing a 6M component for the first time, you may find the 6M tutorial handy.

Project structure

A 6M component consists of a frontend and a middle layer (optional, but usually necessary). The frontend consists of static assets, whereas the middle layer is a stateless Docker service.

The frontend and the middle layer can be maintained within one repository (mono-repo, i.e. two applications in one Git repository) or two different repositories (multi-repo, one repo per project). In any case, a 6M repo must contain a README.md file and the build Dockerfile (see below) for the parts it contains.

README.md file

A README.md file must be present in the root of the repository. The file is not intended for users of the component; that’s what the asset documentation is for. The README.md file adresses developers and must contain an overview of the technology (e.g. frameworks, architecture, design patterns) used for the frontend as well as the middle layer. Additionaly, depending on the nature of the component, it may contain information such as:

  • Hints on serving frontend assets and/or running the middle layer in development mode without requiring a build,
  • Requirements and recommendations on running the middle layer in production (e.g. environment variables, memory requirements),
  • A roadmap for the component,
  • Contribution guidelines,
  • Branching/release strategy.

The frontend

Web component

From the frontend perspective, a 6M component is a web component, which provides a custom element.

The names of the custom element must be prefixed with tui- and must be unique. If you’re unsure if a name is already taken, look into the components catalog.

Tag and attribute naming

The initial configuration of the custom element must happen through HTML attributes. In order to allow the element to be used multiple times within the same context, it must support a scope attribute. If the scope attribute is set, all event publishers and subscribers registered by the component must respect the scope.

The following naming rules for attributes apply:

  • Do not use global attributes/properties such as id, class, style, is, hidden or slot for element-specific features.
  • Do not use the data- prefix for attributes on custom elements. They are not needed and make code less readable.
  • The locale attribute must only be used for setting the component’s locale (see below).
  • The scope attribute must only be used for setting the component’s event scope (see below).

Inner/private web components

If your 6M component ships additional custom elements, e.g. to build its inner parts, remember to prefix the names of these inner elements with the main component’s tag name followed by two dashes. For example, if your component’s tag is tui-image-gallery, and you define a button as an inner element, this element’s tag name should be something like tui-image-gallery--button.

Attribute changes at runtime

At the moment, it is not mandatory that the component reacts to attribute changes which occur after their initialisation. But this may become a requirement in the future, therefore it is recommended to implement this feature, if possible.

Skeletons

The component must provide at least one skeleton placeholder for each of its custom elements. Skeletons are neccessary to show an early preview of the layout and reduce flickering on the screen during loading/rendering of a website.

In practice, the owner of a website will use a custom element together with one of its skeletons like this:

<tui-image-gallery hotel="ABC1234">
<!-- BEGIN SKELETON -->
<div style="background: #eee; padding: 30px; display: flex;">
<div style="background: #ddd; width: 110px; height: 110px; border-radius: 50%"></div>
<div style="flex: 1 0; margin-left: 30px">
<div style="background: #ddd; height: 30px; width: 100%; margin-bottom: 10px;"></div>
<div style="background: #ddd; height: 30px; width: 100%; margin-bottom: 10px;"></div>
<div style="background: #ddd; height: 30px;"></div>
</div>
</div>
<!-- END SKELETON -->
</tui-image-gallery>

The skeleton must be styled with inline CSS to have the exact size of the real element. It should also show the shapes of its primary content elements.

Each Skeleton template must be put into a separate HTML file.

Web components have a feature called [Slots](https://developers.google.com/web/fundamentals/web-components/shadowdom#slots). Slots can be *anonymous* or *named*. If you are using slots in your 6M component, please keep in mind that a skeleton would be inserted into anonymous slots. Therefore it is mandatory to only use named slots in 6M components.

Styling and generic elements

TUI has developed a design system based on generic web components, called UI Elements. You can and should use them in your web component to reduce the resource footprint of your 6M component. To learn more about available elements and their usage, have a look at the Element section on this website.

The component must also support branding through UI Styles. This means that it must support the color schemes of the different TUI brands and be aware of the brand attribute on the host element, i.e. styling via :host([brand=…]). Have a look at the UI Styles documentation to learn more.

The JavaScript

It is entirely up to you how the JavaScript looks on the inside, just take care of the following:

  • The component must run in all common browsers, mobile and desktop (yes, this includes IE 11).
  • There are already polyfills for web components v1, provided through UI Elements, which are based on the Stencil project.
  • Your code must not pollute the global (window) namespace, or at least take measures to avoid collisions.
  • It must not have any external or cross-dependencies, unless they are shipped as a part of the component. (Exceptions are UI Elements and the Cotton Ball.)
  • Your code must not mess with JavaScript internals (i.e. no “monkey-patching”).
  • While you are free to use any framework/libraries you want, you should prefer lightweight solutions where possible.
  • Think about performance and memory footprint when writing your code.

Please have a look at the unified build process requirements, too. There are several rules regarding the built JavaScript.

Event Handler (the “Cotton Ball”)

6M components talk to each other through events managed by a Publish/Subscribe event handler which we call the Cotton Ball. The Cotton Ball is the only “hard” dependency of a 6M component, and as such will always be present in the context of a website, so you can expect it to be there.

Cotton Ball API

The Cotton Ball has the following methods:

/**
* Emits an event.
*
* @param string componentName The components own name.
* @param string scope The scope, as passed through the `scope` attribute.
* @param string eventName The name of the event.
* @param object payload The event data/payload.
*
* @return void
*/

tuiCottonBall.publish(componentName, scope, eventName, payload)

/**
* Emits an event.
*
* @param string componentName The name of the component from which an event is expected.
* @param string scope The scope in which to listen.
* @param string eventName The name of the event to listen on.
* @param function callback Callback
*
* @return function Callback to unregister the event
*/

tuiCottonBall.subscribe(componentName, scope, eventName, callback)
Event scoping

6M has the concept of scoping: A 6M component can be put into one or more scopes, thus grouping it together with other components. The components HTML tag must support the scope attribute to which the maintainer of a host site can assign the scope(s).

When a component publishes, or subscribes to, an event, it must always pass the value of the scope attribute. Usually the value will be a string, but it may also be undefined if the host site maintainer does not assign a scope. The Cotton Ball will take care of distributing events into the right scopes.

Component and event names

The publish and subscribe methods have two name-related parameters: componentName and eventName. The componentName is the root namespace, the eventName is the specific name. Names are written in lowercase and may contain lowercase letters, dashes, underscores and numbers. Event names may additionaly contain dots for separating sub-namespaces.

const currentScope = this.getAttribute("scope") || "*"
tuiCottonBall.publish("image-gallery", currentScope, "init", {/* … */})
tuiCottonBall.publish("image-gallery", currentScope, "imgage-gallery.slider.open", {/* … */})

Magic constants

The 6M build and deployment process may put frontend assets and the middle layer in very different locations. Both locations will be injected into the script via “magic variables”, allowing the frontend to communicate with the middle layer and load additional assets, if necessary:

  • The __TUI_6M_ASSETS_URL__ string will be replaced with the URL to the assets.
  • The __TUI_6M_MIDDLELAYER_URL__ string will be replaced with the URL to the middle layer.

For an implementation example, have a look into the 6M Tutorial.

NOTE: Keep in mind that the URL may have a non-standard port number, and the host part may be either a simple host name, or a FQDN, or an IP address (IPv4/IPv6). Do not make any assumptions about the URL beyond RFC 1738/3986.

Avoiding collisions

Remember that your component runs in the same website context as other components. Wherever you occupy resources or assign names, remember to avoid collisions. Some areas to look out for are:

  • Custom Element tags
  • CSS class names (outside of Shadow DOM)
  • Cookie names
  • Local/Session Storage keys

Use appropriate measures, such as namespacing, to avoid collisions.

Localisation

All components must be multilingual by design, supporting the en-US locale as fallback as well as at least one additional locale (usually de-DE). Note that in JavaScript and HTML, the language and country parts are separated by a dash, not an underscore.

Locales are set through the locale attribute on the element. If the component doesn’t ship localisation for the requested locale, it should try to use a similar one, or en-US as fallback.

For example, if the locale attribute requests the de-AT locale, but the component only supports de-DE, it should switch to that one. If the locale attribute requests the pt-BR locale, and the component doesn’t support Portuguese at all, it should switch to the en-US locale.

You are free in your choice of localisation implementation, though you might want to have a look at the l10n library maintained by the TUI.com team. It is lightweight, can extract localized messages from your code and works well with web components.

UI locale vs. content locale

If the component is able to display content in a different locale than the UI (for example legal documents), it may offer the configuration this feature though an element attribute. Consider the following example:

<tui-document-viewer content-locale="fr-BE"></tui-document-viewer>

The 6m.json file

A 6M component must have a 6m.json file with 6M-specific meta information.

File location

The 6m.json must be publicly available at runtime from your assets server. In fact, making this file available and submitting its URL to the 6M components registry is the official way to publish the component.

The files referenced within 6m.json are expected to be available relative to the location of the 6m.json file.

JSON properties

The file must contain a JSON object with the following properties:

name

A human readable name of the component, such as “Image Gallery”. To avoid redundancy, the name should not contain “TUI” or “6M”.

file

The location of the main JavaScript file for embedding the component. This can be either a path, in which case it is considered relative to the 6m.json file. Or it can be a full URL. The file field is optional. If it doesn’t exist, the file is assumed to be named main.js, and to be in the same directory as the 6m.json file.

icon

The location of an image file (PNG or JPG) with a resolution of 300x300 pixels. Expect the image to be scaled down to much lower dimensions.

screenshots

An optional array of objects, where every object has the keys location and description. The location is the location of an image file (PNG or JPEG) showing a screenshot of the component. It is recommended to show the component with a width of 1024 pixels, though other sizes are allowed as well. The description should contain a caption for the image.

maintainer

A string containing an e-mail address to contact the maintainer of this component.

NOTE: If the component is maintained by a team, or maintainers are changing, it is recommemded to set up an e-mail alias and forward it to the responsible person(s). (Multiple maintainer contacts per component are not desired because a component user looking for support would not know who is the primary maintainer.)
description

A short description of 30 to 50 words, e.g. “A component for displaying images of hotels or other services. Can be used as part of other components or on the root level of a host page.” The description could, for example, be displayed in a list of known 6M components.

documentation

The location of a Markdown document containing extended documentation of the component, relative to the 6m.json file, e.g. docs/about.md. Consider your target audience to be website owners who are interested in embedding your component into their site. In-depth technical documentation is not required here, however, you are welcome to give hints on the integration. Also, you don’t need to document your attributes or events here, because this documentation will be generated automatically.

6m-version

The version of the specification the 6M component implements. The version number of this specification is found at the top of this document.

legacy

Some components have been developed in the pre-webcomponent era. They are not embedded as custom elements, but as div tags, where the component identifier is added as a class name and attributes have a data- prefix. If this is such a component, the value of legacy must be true. Otherwise it can be omitted or be set to false.

locales

Contains an array of supported locales, e.g. ["en-US", "de-DE"].

tag

Contains a string with the tag name of the custom element, e.g. tui-image-gallery.

attributes

An array of one or more objects, where every object has the following structure:

{
"name" : "hotel",
"description" : "An ID of a hotel.",
"documentation" : "docs/attributes/hotel.md",
"schema" : {
"type": "string",
"pattern": "^[A-Z]{3}[0-9]{5}$"
},
"required" : true
}
  • The name key contains the attribute’s name.
  • The description key contains a description of the attributes functionality.
  • The documentation field contains the location of a Markdown document with extended documentation. This field is OPTIONAL.
  • The schema key contains a JSON schema definition (draft-07 compatible).
  • The required key indicates if the attribute is mandatory.
NOTE: The `locale`, `scope` and `brand` attributes do not have to be documented.
events

The events key contains an object with two sub-keys, publish and subscribe. They each contain an array with one or more items, where each item is an object describing an event:

{
"name" : "image-gallery.slideshow.next",
"description" : "Triggers when the slideshow switches to the next element.",
"data" : [
{
"name" : "nextPos",
"description" : "The position of the next image in the set. Note that if the last image is reached, nextPos will be `0`, i.e. the first picture.",
"documentation" : "docs/events/image-gallery.slideshow.next.md",
"schema" : {
"type": "integer",
"minimum": 0,
"maximum": 999
}
}
]
}
  • The name key contains the event’s name.
  • The description key contains the event’s description.
  • The documentation field contains the location of a Markdown document with extended documentation. This field is OPTIONAL.
  • The data key describes the payload object of the event. It is an array, where each item is an object describing a property of the payload object:
    • The name key contains the property’s name.
    • The description key contains the field’s description.
    • The documentation field contains the location of a Markdown document with extended documentation. This field is OPTIONAL.
    • The type key contains the data type of the property’s value. Possible values are integer, float, string, array and object.
    • The schema key contains a JSON schema definition (draft-07 compatible).
NOTE: You only need to document events which are defined by the component itself. In situations where your component acts as a “client” of a different component, i.e. it publishes, or subscribes to, an event defined elsewhere, you don’t need to document that particular event.
skeletons

The skeletons field is an array containing at least one object. Each object contains a description key with a brief description of the skeleton, and a location key with the relative location of the skeleton file.

[
{
"description": "A minimal, non-animated skeleton.",
"location" : "skeletons/minimal.html"
},
{
"description": "An animated skeleton with more detail.",
"location" : "skeletons/animated.html"
}
]
examples

The examples field is optional, if set it is an array containing objects. Each object contains a description key with a brief description of the example, and a attributes key with a map of attributes to set on the example element.

[
{
"description": "Showing the images in the most crazy way possible.",
"attributes" : {
"some-attribute" : "some-value",
"some-other-attribute" : "some-other-value"
}
}
]

Examples help editors and product owners to see usage scenarios of a component. It is especially advised to have examples if your component has required attributes and will fail if embedded without them.

The Middle Layer

General considerations

The middle layer of a 6M component is a translation service into the world of backends. Common use cases for middle layers are:

  • Backend access which requires secret credentials,
  • Complex or aggregated backend calls,
  • Complex business logic which is too heavy to be delivered to, or executed in, the browser,
  • Business logic which must stay confidential,
  • Lambda-style functions, e.g. image processing.

In any case, the middle layer must be stateless, i.e. it must not do things like persisting data or managing sessions. Caching and serving imported data are edge-cases; make sure not to run into concurrency issues when multiple middle layers run in parallel.

6M Components without a Middle Layer

It is possible that a 6M component doesn’t need to talk to any backends at all, and therefore doesn’t need a Middle Layer. However, if there is backend communication, a dedicated Middle Layer must be used rather than talking to fat backends directly.

Technology

Developers of 6M components are free to implement and operate their Middle Layers in any technology (e.g. NodeJS, Golang, Java, Python, PHP) and with any API paradigm (e.g. REST or GraphQL) they want. It is however recommended to choose languages and frameworks which are best suited for a lightweight, stateless service.

Public API endpoints

The middle layer must expose the following endpoints at runtime:

The /health endpoint

The /health endpoint must inform the client about the overall status of the application. It must do so through HTTP status codes. If possible, additional information should be provided in the response body. For instance:

  • If everything is in order, the endpoint must return an HTTP 200 with the string OK in the body.
  • If the Middle Layer’s backend is unavailable, the endpoint should return an HTTP 502 or 504 status, and should provide additional information in the response body.

The /monitor endpoint

The /monitor endpoint should return a JSON object with extended status information for the 6M component. There is no common format for this yet, so feel free to add anything that seems relevant (without disclosing sensitive information). However, be prepared that there will be a specified format in the future.

Environment variables

If the component’s middle layer needs environment variables, it is very important to document them in the README.md file. It is recommended to add a shell-style code block with the supported variables along with sufficient inline documentation.

For example:

```shell # Log level; possible values are debug, info, warning, error. LOG_LEVEL=info

# The URL to the logging server. LOG_SERVER_URL=

# Auth token. Contact the backend team at backend@example.com to obtain one. BACKEND_AUTH_TOKEN= ```

If a value is assigned to a variable, it will be understood as a default, and the variable will be considered optional.

The maintainer of the runtime environment must make sure to pass those variables with valid values to the container. In a simple Docker setup, this would look something like

docker run -e "LOG_SERVER_URL=https://www.example.com/some-endpoint" middlelayer:latest

The build process

The build process will create static assets for the frontend and (if available) the middle layer image. After building, your 6M component is ready to be deployed to a runtime environment. Asset files are usually put onto a CDN, the middle layer is deployed to a Docker host and started there.

In order to simulate the build and runtime, we have created a reference environment where you can test building and running your component. Have a look at the 6m-env project and follow the instructions in the documentation.

Frontend build

The frontend build happens inside a Docker image. What you do during the image build is entirely up to you. There are only the following requirements:

  1. The Dockerfile must be located at build/Dockerfile.assets.
  2. The image creating process must perform all necessary steps to create your assets through RUN commands.
  3. The WORKDIR entry of the Dockerfile must specify the location where the generated assets are stored inside the image.
  4. The WORKDIR directory must also contain the 6m.json file as well as all files and directories (i.e. documentation and skeletons) referenced therein.
  5. If the location of the main component file is not specified in the 6m.json, it is expected to be named main.js, and it must not be in a subdirectory (i.e. it must be placed directly into WORKDIR).
  6. Files and directories whose name starts with a dot might be ignored by deployment processes, therefore avoid them.

Middle layer build

The middle layer must also be described by a Dockerfile:

  1. The Dockerfile must be located at build/Dockerfile.middlelayer.
  2. Multi-stage builds are supported, and actually recommended to produce lightweight runtime containers.
  3. The web service inside the Docker container must listen on port 80. HTTPS connections will be terminated outside the container, therefore listening on port 443 is not necessary.

Credentials and security

It is very important that NO CONFIDENTIAL INFORMATION is stored in component repositories. All confidential information required by the runtime container must be injected through environment variables.

You must always assume that the repository may become publicly available at any time. Different teams and business units, as well as external contractors, will have access to components. Make sure that secrets stay secret. Also, some components may become “real” Open Source code one day, and we wouldn’t want to publish our credentials on Github, right?

Optional: Extended Stack and Integration Repository

In some cases, it makes sense to provide an additional repository with your component, an integration repository. Such a case may be:

  • Your component needs additional services below the Middle Layer, which need to be orchestrated.
  • You have integration tests that interact with services outside of your 6M component.

An integration repository defines the runtime environment of the component in a particular combination of orchestration service and environment types.

  • Supported environment types are: dev, test, staging, preview, prod.
  • Orchestration services might be (these are just examples): Rancher1, Rancher2/Kubernetes, Amazon Webservices (EKS), Docker Compose/Swarm.

Your integration repository may support multiple orchestration services at the same time. Therefore you should the config files for each service into subdirectories.

As operation environments are beyond the scope of this specification, you should refer to the ops team of the target environment in order to decide on the structure of the integration repository.

A later version of this specification may, however, introduce rules for defining orchestration.