Semantic Versioning and Component Services

Component versioning follows the Semantic Versioning (SemVer) principles, as they pertain to Component Services.

In Semantic Versioning each level of the x.y.z version number hierarchy communicates something specific about the software and it’s relationship with other software.

Table 1. x.y.z Semantic Versioning
# Name Core communicated meaning

x

Major

Backwards-incompatible changes.

y

Minor

Backwards-compatible changes.

z

Patch

Backwards-compatible bug fixes.

Why Increment Component Versions?

Versioning is a Component Services requirement.

Once a component is changed, redeploying it under the same version is not possible.

Consequently, all component code modifications — whether it’s an added feature or a fixed bug — require a version increment.

As well, versioning offers security, allowing users in DXP environments to revert to earlier versions if necessary.

Versioning also means the version currently in use is always known.

Finally, versioning enables version comparison, providing insight into the specific changes made between compared versions.

Semantic Versioning in the Component Service

Semantic versioning aims to communicate the nature and extent of changes in a new version.

In the context of Component Service, we tailor SemVer to the scale and context of individual components.

Versioning reflects the evolution of these components, from minor tweaks and bug fixes to significant feature additions and breaking changes.

To illustrate, consider a version number, 1.5.3.

This notation signifies:

  • this is the first major version of the component;

  • which has undergone five minor updates;

  • and three patches.

This numbering tracks the component’s progress and, it is hoped, also signals its maturity and stability to users.

Incrementing a version number

The part of a version number incremented depends on the scale of the change introduced.

version numbers major minor patch
Table 2. Version number changes.
Incremented # Reasons for incrementing this number

Major

  • A significant change to the component’s structure

  • A introduced incompatiblity with the previous version.

  • Major functionality changes, such as

    • adding a required parameter;

    • removing a required parameter; or

    • changing how required parameters are accepted.

Minor

  • Adding a new but optional parameter.

  • Making stylistic changes that do not introduce new functionality (assuming that this new functionality does not otherwise alter the code’s operation).

  • Enhancing accessibility or structure.

  • Improving existing parameters.

Patch

  • Minor changes that do not add new features.

  • Fixing errors and typographical problems in code.

Resetting minor and patch values

In terms of the presented version number, a major update does two things.

It, obviously enough, increments the major value.

It also resets the minor and patch values back to zero.

For example, if a component is at version 3.2.5 and the next release is a major update, the new version number must be 4.0.0.

A minor update changes the presented version number in equivalent fashion, but only with regards the patch value.

For example, if a component is at version 5.12.423 and the next release is a minor update, the new version number must be 5.13.0.

Patch updates only increment the patch value in a version number.

Not incrementing a version number

Versioning is primarily about communicating with end users.

Consequently, not all work done on a component service should change any part of the version number.

For example, none of the the following work should change the component service’s version:

  • adding inline comments to code;

  • correcting typographical mistakes or errors in documentation;

  • incorporating tests in JEST or Cypress.

  • adding presentational CSS.

None of these actions impact the component’s operation. Nor do they affect the component’s functionality. They are not, therefore, reasons to increment any part of the component’s version number.

Version number changes are directly linked to code modification.

structural CSS vs presentational CSS

The section immediately above, Not incrementing a version number, notes that adding presentational CSS to a component is not, of itself, a reason to increment a version number.

Defining presentational here is important. Consider the following:

<h2>${text}</h2>

.h2 {
	font-weight: 900;
	color: red;
}

If the following changes are made —

<h2 class="hello-world">${text}</h2>

.h2 {
	font-weight: 900;
	color: black;
}

.hello-world {
	color: red;
}

— the addition of the hello-world CSS class requires a version number increment.

The addition of a presentational property specific to the new CSS class does not.

Where component version number changes are made

A component’s version is set and updated in the manifest.json file.

In the example below the component’s version is the value of the version field.

{
  "$schema": "http://localhost:3000/schemas/v1.json",
  "name": "testcomp",
  "namespace": "namespace",
  "description": "Component description",
  "displayName": "Test Component",
  "version": "0.1.0",
  "type": "edge",
  "mainFunction": "main",
  ...
}
Redeploying without incrementing the version number is not possible

As noted in Why Increment Component Versions? above, deploying the same version twice is not possible.

Attempting to redeploy a component with the same version number results in an error.

% FEATURE_EDGE_COMPONENTS=true dxp-next cmp deploy ./components/_<component-name>/
ERROR: Cannot upload component version, edge-dxp-comp-lib/<component-name>/ <x.y.z> already exists

Beginning Development

At the earliest development stages it is appropriate to start with version 0.1.0.

For example, consider a proposed component that, when released, will present Hello World inside a <div></div> .

An initial project is created that includes:

  • the files needed for any component to function; and

  • the planned output — the Hello World — included in the appropriate file.

A version of 0.1.0 signals that this component is in the initial stages of development and not ready for production use.

It also, however, represents more than just a concept. This is a first iteration that can be tested and further developed.

Reaching Milestone 1.0.0

Incrementing a component to version 1.0.0 signals to users and other developers that the component is stable and reliable for use in production applications.

Therefore, a component is only ready to be incremented to version 1.0.0 when said component is complete in implementation and ready for production.

Guidelines for determining when a component is complete in implementation and ready for production include the following:

Stability

When the component operates without known bugs affecting its core functionality.

Feature completeness

The component includes all planned features outlined in whatever planning and design documentation was prepared for the component.

(OPTIONAL) Documentation

the README file is complete and edited and all fields in the manifest are accurately described.

Testing

The component has been tested across various use cases and environments.

Feedback

Upgrading to version 1.0.0 should include reviewing feedback from code reviews and stability testing in various environments to ensure it meets expectations.

Deployment

The development mode user interface allows for previewing component inputs and outputs in a fashion similar to those in Matrix proper.

Testing a component in a real environment, using production assets, is, however, encouraged.

As is deploying an in-progress component at virtually any development stage (assuming some working functionality).

In particular, publishing component versions that are not fully functional is entirely possible.

Deploying something like "Hello World" version 0.1.0 demonstrates progress and verifies that all the component’s files are correctly linked and built.

component preview with version listing screenshot

Examples

Versioning component examples at different stages of development are included below.

Example 1: an Accordion component

Consider an Accordion component, beginning with the initial creation of all required files through to a fully operational and ready-for-production component.

0.1.0

HTML structure added, but the component is not functional yet.

0.2.0

Scripts that add some functionality added. The component is not, however, production-ready.

0.3.0

Component styling and adjusting the component HTML structure. The look and presentation are complete, but the component is still not ready for deployment.

1.0.0

A functioning and stable version, ready for production use. All key features work correctly.

1.1.0

Addition of a new feature: the ability to configure expansion animations.

1.1.1

A minor bug found in the operational version is fixed.

Example 2: a long-operational News Listing component

A News Listing component has been operational in production at version 2.10.3 for some time.

An error is found in a manifest field description and is corrected.

The code-related error is a patch-level change.

The version number should be incremented to 2.10.4

Example 3: Adding a new and optional parameter

A UserProfile component, at version 1.4.5, and already used on the production environment across multiple pages.

This component includes the following parameters: username and email.

The 1.4.5 component structure is as follows:

export default {
  async main({ username, email }) {
    return `
      <section class="profile-section profile-section--light">
        <h2>${username}</h2>
        ${email ? `<a href="mailto:${email}">${email}</a>` : ''}
      </section>
    `;
  },
};

A planned update introduces a feature that allows for end-user selecting between dark and light themes.

This will be implemented by adding an optional colorTheme parameter. This parameter accepts two values: light or dark, with a default of light.

After this feature is added, the component’s structure is as follows:

export default {
  async main({ username, email, colorTheme = 'light' }) {
    return `
      <section class="profile-section profile-section--${colorTheme === 'light' ? 'light' : 'dark'}">
        <h2>${username}</h2>
        ${email ? `<a href="mailto:${email}">${email}</a>` : ''}
      </section>
    `;
  },
};

The colorTheme parameter is new but optional. This is a backwards-compatible with the previous version.

The version number should be incremented to 1.5.0.

Example 4: Removing a parameter and replacing it with a new parameter

Still working with the example UserProfile component, updated to version 1.5.0 in Example 3: Adding a new and optional parameter above.

As a consequence of evolving privacy requirements, the email parameter must be removed from the component and replaced with a new parameter, userId.

After implementing this change, the component’s structure will look as follows:

export default {
  async main({ username, userId, colorTheme = 'light' }) {
    return `
      <section class="profile-section profile-section--${colorTheme === 'light' ? 'light' : 'dark'}">
        <h2>${username}</h2>
        <p>${userId}</p>
      </section>
    `;
  },
};

This change replaces the email parameter with a userId parameter, which is a backwards-incompatible change.

The version number should be incremented to 2.0.0.