Upgrade from Monster 4 to 5

Monster 5 makes the CustomElement option lifecycle explicit. Code that reads or writes options while those options are still being defined now fails immediately instead of silently using a fallback or losing a write.

Who needs to migrate?

Migrate subclasses that call getOption(), setOption() or setOptions() while evaluating defaults or customization, including calls hidden in helpers. Applications that only configure existing components after construction are not affected by this breaking change.

The breaking contract

  • The option API is unavailable while defaults and customization are evaluated.
  • Early access throws the exported OptionsNotInitializedError. Its method and component properties identify the operation and class.
  • Monster 5 has no compatibility flag, injected legacy strategy, early-read fallback or queue for early writes.
  • Normal fallbacks still work after initialization. Valid values such as false, 0, an empty string and null remain valid.

Choose the phase that owns the data

PhaseAvailable configurationUse it for
defaults / customizationStatic declarations only; the option API is unavailable.Default values, templates, mappings and callbacks that run later.
initMethodSymbolInitialized defaults, customization and attributes already present during construction.One-time non-DOM state derived from constructor-time options.
assembleMethodSymbol, after superConnection-time attributes, data-monster-options, selected script configuration and rendered DOM.DOM work and values that may arrive after construction.
RuntimeThe complete option tree.Reactive changes through setOption() and setOptions().

One constructor caveat

initMethodSymbol runs from the base constructor. Derived class fields have not been initialized yet. Do not make the hook depend on those fields.

Replace circular defaults

This Monster 4 pattern asks the option tree for a value while that same tree is being built. It always depended on fallback behavior and throws in Monster 5.

// Before: unsupported in Monster 5
get defaults() {
    return {
        ...super.defaults,
        title: this.getOption("labels.title", "Details"),
    };
}

Declare the state directly and let updater-bound markup read it after assembly.

// After: declarative defaults
get defaults() {
    return {
        ...super.defaults,
        labels: {
            title: "Details",
        },
        templates: {
            main: "<h2 data-monster-replace='path:labels.title'></h2>",
        },
    };
}

Move derived work to the right hook

Use initMethodSymbol only when constructor-time configuration is sufficient. Always keep the parent call.

import {
    CustomElement,
    initMethodSymbol,
} from "@schukai/monster/source/dom/customelement.mjs";

class ReportPanel extends CustomElement {
    [initMethodSymbol]() {
        super[initMethodSymbol]();
        this.initialReportType = this.getOption("report.type", "summary");
        return this;
    }
}

Use assembleMethodSymbol when values may come from late attributes or script configuration, or when the work needs rendered DOM. Call super first.

import {
    CustomElement,
    assembleMethodSymbol,
} from "@schukai/monster/source/dom/customelement.mjs";

class ReportPanel extends CustomElement {
    [assembleMethodSymbol]() {
        super[assembleMethodSymbol]();
        this.configureResource(this.getOption("resourceConfig"));
        return this;
    }
}

Assembly does not rebuild an earlier template decision

The base template has rendered after the parent assembly call. Prefer static templates with updater bindings, or update the rendered component through its documented API. Do not cache IDs or context keys from constructor-time fallback values.

document.createElement() and late configuration

document.createElement() completes construction synchronously. The returned upgraded element can use the option API immediately, but attributes the caller adds later could not have influenced initMethodSymbol.

await customElements.whenDefined("report-panel");
const panel = document.createElement("report-panel");

// Safe: construction and option initialization have completed.
panel.setOption("report.type", "detailed");

// Also applied during connection, but it was not visible to initMethodSymbol.
panel.setAttribute("data-monster-option-report-scope", "quarter");
document.body.append(panel);

On first connection, Monster reads option attributes again, merges data-monster-options and selected script configuration, then renders and binds updaters. Reconnection reuses the assembled state; it does not reload those configuration sources.

Migration checklist

  1. Search both getters and every helper they call for all three option methods.
  2. Replace option-derived defaults with literal, static or class-level declarations.
  3. Move constructor-only derived state to initMethodSymbol.
  4. Move DOM and connection-time configuration work to assembleMethodSymbol.
  5. Call the matching parent hook before relying on Monster state.
  6. Test parsed HTML, document.createElement(), first connection and reconnection.
  7. Remove compatibility guards that catch OptionsNotInitializedError.

Related documentation

CustomElement tutorial

Build a subclass using the lifecycle contract from the start.

CustomElement API

Review exported errors, methods, hooks and their failure contracts.

The current width of the area is too small to display the content correctly.