docs
  1. SCAYLE Resource Center
  2. Add-on Guide
  3. Getting Started
  4. Reference Architecture

Reference Architecture

Add-on Ecosystem

Add-ons allow developers to extend the functionality of the SCAYLE Panel.

What Defines SCAYLE

Before you begin planning and developing your app, it is important to first understand how add-ons work within the SCAYLE ecosystem of tenants, companies, and shops.

Tenants, companies, and shops in SCAYLE.

Tenants

A tenant has spaces like preview, test, and live that SCAYLE users access.

Example: Tenant ACME Ltd with the spaces (Prev, Live, Test).

  • acme-prev
  • acme-live
  • acme-test

Companies

Companies are legal entities that operate on a tenant-specific SCAYLE instance. The primary reason for this entity separation is to protect customer and order data.

Example:

  • Company ACME EUROPE
  • Company ACME US

Company IDs

Each company has an ID which can be retrieved during the authentication process of a logged-in SCAYLE Panel user (see the retrieveCurrentUser endpoint).

Company Setup

The company setup is created by SCAYLE based on the needs of the individual client. For more questions or information contact your SCAYLE Account Manager.

Shops

In SCAYLE there are global/parent shops with multiple country-level shops. Country shops are the customer-facing sites and are often based on language or region. APIs only operate on the country-level. Each shop has a unique URL and id.

Example:

  • acme.de id: 10
  • east.acme.com id: 20
  • west.acme.com id: 30

Relation Between Merchants and Shops

A merchant refers to various sources that supply products. These might include:

  • retailer
  • distributer
  • local shops
  • warehouses
  • logistics centres

One or more merchants can be connected to each shop.

Tech Stack

Front End

Any FE implementation that works with Single Spa. We recommend using VueJs 3 to be able to use our Component Library.

Back End

You can choose any language for the Add-on backend.

Hosting an Add-on

Add-ons are hosted separately from the SCAYLE Panel through any hosting provider. We recommend choosing a provider that is close to the SCAYLE data center (in Ireland EU).

The only constraint is the domain. SCAYLE Panel needs to add an extra CORS header to enable the domains.

Add-on Architecture

The SCAYLE Panel is a multi-tenant system and add-ons should be built as such. In other words, based on the subdomain of the panel, the Add-on loads different configurations for tenants in the same space.

Add-ons can be seen as satellite systems that can run standalone but connect to the SCAYLE Panel.

Add-ons can be seen as satellites that can run standalone but connect to the SCAYLE Panel.

A single tenant can have up to three spaces (such as "live", "test" and "prev"). Depending on the number of tenants and throughput, we suggest a best practice setup outlined in the table below.

SpaceEqual toContains
LiveProductionlatest stable release (e.g., 1.23.3)
TestStagingLatest stable release as on live (e.g., 1.23.3)
PrevIntegrationNext release (e.g., 1.24.0)

Single vs Multi-Company

If you need to store Personal Identifiable Information (PII) (see GDPR), we recommend separating your databases based on the company_id.

Single Company

Single company with one database.

Multiple Companies

Multiple companies with separate databases.

Demo Add-on Setup

Get Started with the demo Add-on using Vite.

Vite Demo Add-on

To get you up-and-running with Add-on development, we’ve provided a demo using Vite for bundler configuration.

The demo is built with:

  • Tailwind CSS
  • Vue 3
  • Vitest (test framework)
  • Vite
  • TypeScript

Setup

1. Clone the repository:

git clone https://github.com/scayle/demo-add-on-vite.git

2. Choose a domain

The demo uses demo-add-on.cloud-panel.aboutyou.test.

3. Install dependencies and create a valid SSL certificate:

npm ci
npm run generate:ssl

4. Add domain to your etc/hosts file

For the provided demo domain, you would add 127.0.0.1 demo-add-on.cloud-panel.aboutyou.test.

5. Configure the Domain

Configure in your .env file in the root directory CONFIG_SERVER_HOST.

6. Configure the identifier

Configure the identifier for your Add-on in your .env via PANEL_ADDON_IDENTIFIER.

The Identifier may contain alpha-numeric characters as well as dashes and underscores (no spaces). Max length is 20 characters.

Additional setup considerations

  • It is also recommended to install Volta in order to use the correct versions of node.js and npm.
  • To isolate the Add-on styling we can use Shadow DOM. This can be enabled in the .env as PANEL_USE_SHADOW_DOM=true.

Commands

The first 2 commands are required to view the Add-on within the SCAYLE Panel.

  • Build the production app: npm run build
  • Build the hot-reloading app: npm run dev
  • Run unit tests: npm run unit
  • Check types: npm run typecheck

SCAYLE Panel Configuration

To use the Add-on it must first be added to the Add-ons Store in the SCAYLE Panel.

  1. Sign-in to the SCAYLE Panel and create a new Add-on (Add-ons > New).
  2. Identifier must match PANEL_ADDON_IDENTIFIER in the .env file.
  3. If your Add-on is not on a server, run locally: npm run dev
  4. Add the App URL appended by /manifest.js. For example, to run the demo Add-on with local host, the App URL would be https://demo-add-on.cloud-panel.aboutyou.test:8082/manifest.js
  5. Complete the remaining fields and click Save.

Your Add-on will now be available to open from the Add-ons Store.

Add-on configuration in the SCAYLE Panel.

Server Configuration

Access to the Add-on Javascript file needs to be generated by setting the Access-Control-Allow-Origin Header on NGINX level.

Since the Add-on files are served on a different domain, we need to specify an exception to the browser CORS rules. The wildcard is generally used because there are many SCAYLE Panel domains we need to allow. Alternatively, you could add an exception for each SCAYLE Panel domain (acme.cloud-panel.cloud-panel.scayle.cloud, amce.staging.cloud-panel.cloud-panel.scayle.cloud, etc.).

# Basic NGINX Example (wildcard)
location ~ \.js$ {
    add_header Access-Control-Allow-Origin * always;
}

HTTPS

Because SCAYLE Panel is served over HTTPS, the Add-ons must also be served over HTTPS to avoid mixed-content errors.

Caching

Clients should be forced to always load the manifest file without a cache. This prevents users needing to be informed to clear their browser cache.

# Basic NGINX example
location = /js/your-application-file.js {
    add_header Last-Modified $date_gmt;
    add_header Cache-Control 'no-cache';
    if_modified_since off;
    expires off;
    etag off;
}

Best Practices

Chunking & Caching

Since there may be many add-ons on a system, we want to ensure their performance cost is as small as possible. The manifest is loaded for each page of every Add-on and should be as small as possible. An efficient caching strategy should be used to avoid unnecessary requests and smoothly handle deployments without requiring a hard-refresh by the user. To accomplish this, we recommended following the patterns below.

This assumes that you are using Webpack or another module bundler to compile your frontend assets.

Your Add-on should consist of a manifest file, plus other JavaScript assets which are dynamically loaded and split into chunks. Let's say you have an Add-on that consists of the following:

// my-app.js
import Vue from 'vue';
import singleSpaVue from 'single-spa-vue';

const App = {
    template: `
        <div class="h-full">
            <HelloWorld />
        </div>
    `,
    components: {
        HelloWorld: () => import('./HelloWorld.vue')
    }
};

const vueLifecycles = singleSpaVue({
    Vue,
    appOptions: {
        router,
        render: h => h(App),
        el: '#app',
    },
});

export const { bootstrap, mount, unmount } = vueLifecycles;
// my-add-on.js
export default function(registerApplication) {
    registerApplication({
        app: () => import('./my-app.js'),
    });
}

Since my-app.js and HelloWorld.vue both use Dynamic Imports, Webpack can split the code into separate chunks. The compiled output might look something like this:

dist/
    my-add-on.js
    chunks/
        my-app.js
        HelloWorld.js

As your Add-on grows, the majority of the code should end up in chunks, leaving the manifest as an entry point. (The manifest is requested on every page load with the chunks only requested as needed.)

Caching

We then want to aggressively cache the chunks. Unless the content changes, we don't want to make a round trip to the server for a file that already exists on the client.

A solution is to include a hash of the chunk's content in the filename of the chunk. We can then set a very long (a year or more) cache expiration on the chunk. This can be configured through Webpack's output.chunkFilename option.

output: {
  filename: 'js/[name].js',
  chunkFilename: 'js/chunks/[name].[chunkhash].js',
}

The output might look something like this:

dist/
    my-add-on.js
    chunks/
        my-app.5c2484.js
        HelloWorld.c5f829.js

Webpack will also handle updating any other files which depend on the chunk so they reference the correct filename. With this setup, chunks will be cached as long as their content does not change. Any changes that affect the chunk will cause the chunk to have a new filename and automatically invalidate any caches.

It's also possible to include a hash in the query instead of the filename itself by setting a chunkFilename value like js/chunks/[name].chunk.js?v=[chunkhash] This has some advantages such as always replacing the existing file, but this is not recommended. Some caches might ignore the query string while others might refuse to cache any resources with a query string. Neither of those outcomes are what we want.

Handling the manifest file

With the code chunks taken care of, we just need to handle the manifest file. Since the manifest references the immutable file names, everything else will go smoothly as long as we fetch the current manifest file.

Option A: Cache-Control header

For the manifest file, make sure the header Cache-Control: no-cache is set. This will ensure that the client will always check with the server to make sure its cached version is not out of date. For this to work properly, Last-Modified or Etag should also be sent in the manifest response. These headers can be set through Nginx, or in PHP if you are forwarding requests through a controller.

Example (Option A)

With the Webpack build and caching headers properly configured, the app should work something like this:

On the first page load, the client will have to fetch the manifest and all required chunks. The browser cache will store copies of all the resources. For the chunks, it will consider them 'fresh' for a year. If they are requested within a year, the browser will return the content from the cache. For the manifest, the browser cache keeps track of its last-modified date. The next time the manifest is requested, it will first check with the server to see if its version is up to date, and if it is, it will return the content from the cache, otherwise it will get the full response from the server.

When the user reloads the page, and there have been no code changes, the browser will check with the server to see if there have been any updates to the manifest since it was last fetched. Since there have not been any deployments, the server will return a 304 response, and the browser will return the content from the cache. When the client requests the chunks, the browser considers them to be 'fresh' and immediately returns the content from the cache without contacting the server.

After a deploy which modifies my-app.js (and also my-add-on.js because it references my-app.js), the user does another reload. The browser once again checks with the server to see if the manifest is up to date, but this time there have been changes. The server sends the content of the latest manifest file along with a new Last-Modified header. The browser returns this content and updates its cache.

It then requests my-app.2c73d3.js. Since the cache has never seen this resource (the chunk was previously named my-app.5c2484.js), the browser gets the value from the server and updates the cache. Since the HelloWorld chunk has not been modified, it has the same filename and is found in the cache.

Option B: Dynamic Manifest Filename / Updating app_url

This is a little more complicated, but is an option when you are not able to control the headers for one reason or another. You can configure Webpack to also include a hash in the manifest filename as well as the chunk filenames.

output: {
  filename: 'js/[name].[contenthash].js',
  chunkFilename: 'js/chunks/[name].[chunkhash].js',
}

On every deployment, you must make sure to update the app_url for your Add-on in the SCAYLE Panel's add_on table. This will soon be possible to do via an API.

Alternatively, the filename can stay consistent, but after every deployment you update the app_url to add a version number in the query string.

This option is more efficient from a latency and bandwidth perspective (even the re-validation portion is skipped), but there's a higher risk of something getting out of sync and causing cache errors.

Add-on Styles & Design

Designing an Add-on that integrates seamlessly into a SCAYLE Panel makes the user experience (UX) more familiar and appealing and the user interface (UI) more intuitive. The more user-friendly, accessible, and elegant your Add-on is, the more likely you will create an Add-on that adds value to the existing core functionalities.

In this section, we’ll dive into what factors you need to consider as you design your Add-on, and the tools that can make designing it easier and faster.

Style Guide

For standards and best practices, consult the public Figma style guide & component library.

VueJs 3 Component Library

Our Component Library is a set of components for the SCAYLE Panel and Add-ons based on the SCAYLE Style Guide. We also recommend and support the usage of element-ui-plus for Vue.js 3 since our component library only enhances components that other third-party libraries don't offer.

To name only a few we have

  • Statistic Components such as Line- and Bar Charts
  • General Components such as Modals and Tabs
  • Form Components
  • Dashboard Components

Tailwind Presets

A base Tailwind preset for add-ons is available with the same look and feel as the SCAYLE Panel. It includes our core colors, fonts, and sizes, along with a few plugins based on the SCAYLE Style Guide.

Consult the Tailwind documentation for info about presets.

Icons

The SCAYLE Panel icon package already includes a vast number of icons that are used by the SCAYLE Panel to give the Add-on the same look and feel.

Additionally, this package explains how to create Vue3 Icon components (see examples below).

NameFilenameVue
2fa-restore./img/ic-2fa-restore.svg<Icon2faRestore class="icon" />
activity./img/ic-activity.svg<IconActivity class="icon" />
add./img/ic-add.svg<IconAdd class="icon" />
analytics./img/ic-analytics.svg<IconAnalytics class="icon" />
arrow-bottom-right./img/ic-arrow-bottom-right.svg<IconArrowBottomRight class="icon" />
arrow-circle-down./img/ic-arrow-circle-down.svg<IconArrowCircleDown class="icon" />
arrow-circle-left./img/ic-arrow-circle-left.svg<IconArrowCircleLeft class="icon" />

Example icons.

Style Isolation

The demo Add-on provides an option to mount the application under a shadow root so the Add-on will not be affected by any SCAYLE Panel styles and any styles that the Add-on loads will not affect the rest of the panel. To use this feature, install the single-spa-vue fork and add shadow: true to the single-spa-vue options. (The demo add-on has an example implementation of this.)