Skip to content

Embedding Components

Andre Kless edited this page Feb 11, 2026 · 37 revisions

Overview

Embedding in CCM is based on a strict separation between component definitions and component instances. A component describes what can be instantiated, while an instance represents a concrete app created from a component and a specific configuration.

The ccmjs framework provides three closely related API functions for embedding:

  • ccm.component() - Registering a Component Definition
  • ccm.instance() - Creating a Component Instance
  • ccm.start() - Creating and Running an Instance

They build upon each other and differ mainly in how much control they give over instantiation and execution.

ccm.component()

Registers a CCM component definition and prepares it for instantiation.

ccm.component() operates purely on the definition level. It does not create application instances but prepares a reusable component factory bound to a specific framework version.

Syntax

ccm.component( component [, config ] )  Promise<Component>

Parameters

Parameter Type Description
component Object | string Component definition object, or URL to a component file.
config Object (optional) Priority configuration that overrides or extends the component’s default configuration. This configuration is merged into the component’s default configuration and used as the base for all instances created from this component.

Return Value

Returns a Promise that resolves to a clone of the registered component object.

The returned component object provides the following methods, which can be used to create instances manually:

component.instance( config [, area ] )  Promise<Instance>
component.start( config [, area ] )  Promise<Instance>

Example

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Registering a Component Definition</title>
    <script src="./ccm.js"></script>
    <script type="module">
      // Load and register the component once
      const component = await ccm.component("./ccm.hello.js", {
        name: "Mika", // merged into the component's default instance configuration
      });

      // Create and start multiple instances with different configurations
      component.start({ name: "Jane" }, document.getElementById("div1"));
      component.start({ name: "John" }, document.getElementById("div2"));
      component.start({}, document.getElementById("div3"));
    </script>
  </head>
  <body>
    <div id="div1"></div>
    <div id="div2"></div>
    <div id="div3"></div>
  </body>
</html>

The web page then contains:

Hello Jane
Hello John
Hello Mika

Description

ccm.component() is the lowest-level entry point for embedding. It loads a component definition, resolves the required ccmjs framework version, and registers the component internally. If the component depends on a different ccmjs version than the currently active one, the required version is automatically loaded and the operation is delegated to that framework instance. As a result, every component is always bound to its own compatible ccmjs version.

The component definition is registered once per version and stored in a private internal registry. External JavaScript code only receives a cloned copy of the component object. This ensures that once a component version is registered, it cannot be modified accidentally or intentionally by other scripts.

The optional config parameter is merged with the component’s default configuration and serves as the base configuration for all instances created from this component.

Notes

  • Calling ccm.component() multiple times for the same component and version does not reload or re-register the component.
  • The returned component object is not an instance.
  • No DOM elements are created until component.instance() or component.start() is called.

ccm.instance()

Creates and initializes a concrete instance from a registered CCM component.

Syntax

ccm.instance( component [, config ] [, area ] )  Promise<Instance>
Parameter Type Description
component Object | string Component definition object, component index, or URL to a component file. If the component is not yet registered, it is registered implicitly via ccm.component().
config Object (optional) Instance-specific configuration. This configuration is merged with the component’s prepared default configuration and fully integrated into the instance.
area Element (optional) DOM element into which the instance will be embedded. If omitted, an on-the-fly <div> is created as the instance host and can be inserted into the DOM later via instance.host.

Return Value

Returns a Promise that resolves to the created CCM instance.

The returned instance exposes the following relevant properties:

  • instance.host – The host DOM element of the instance
  • instance.root – The Shadow DOM root (if enabled)
  • instance.element – The content element rendered by the component
  • instance.parent – The parent instance (if embedded as a dependency)
  • instance.children – Child instances created via declarative dependencies

The instance reference is returned exclusively to the caller. No other global access path exists, ensuring encapsulation and preventing unintended external interaction.

Example

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Creating a Component Instance</title>
    <script src="./ccm.js"></script>
    <script type="module">
      const instance = await ccm.instance('./ccm.hello.js', {
        name: 'Mika'
      });

      // Insert the instance into the DOM manually
      document.body.appendChild(instance.host);

      // Start the instance explicitly
      await instance.start();
    </script>
  </head>
  <body></body>
</html>

Description

ccm.instance() creates a concrete instance from a component and a specific configuration. If the component has not been registered yet, it is registered automatically using ccm.component().

During instance creation, the configuration is fully prepared and merged, and all declarative dependencies are resolved at runtime. This may include loading additional components, creating nested instances, or embedding complete apps. As a result, instance creation can trigger the construction of an entire dependency tree.

For each instance, a dedicated DOM structure is created. This includes a host element (instance.host), a Shadow DOM (instance.root, open by default), and a content element (instance.element) that the component renders into. Shadow DOM usage can be controlled via the config.root property ("open", "closed", or "none"). The resulting structure is conceptually as follows:

instance.host
└─ #shadow-root (open)
   └─ instance.element

Each instance participates in a well-defined lifecycle that spans the entire dependency tree. After an instance is created, optional init() methods are invoked top-down, starting from the root instance and continuing recursively through all dependent child instances. This phase is intended for early setup work such as preparing state or wiring internal references. This top-down execution ensures that parent instances can adjust configuration or state before child instances depend on it.

Once all instances have been initialized, optional ready() methods are executed bottom-up, beginning with the deepest child instances and propagating back to the root. At this point, all dependencies are fully available and the instance hierarchy is stable.

As a rule of thumb:

  • init() is used for dynamic pre-configuration.
  • ready() is used for the first meaningful actions that require all dependencies to be present.

The following simplified example illustrates this lifecycle for a linear dependency chain of three instances:

A
└─ B
   └─ C

The resulting execution order is:

init():  A → B → C
ready(): C → B → A
// Component A
this.init  = async () => console.log("init A");
this.ready = async () => console.log("ready A");

// Component B
this.init  = async () => console.log("init B");
this.ready = async () => console.log("ready B");

// Component C
this.init  = async () => console.log("init C");
this.ready = async () => console.log("ready C");

// Embedding with declarative dependencies
ccm.instance("A", {
  child: [ "ccm.instance", "B", {
    child: [ "ccm.instance", "C" ]
  }]
});

When multiple dependencies exist on the same level, the relative execution order between sibling instances is intentionally undefined.

After the lifecycle initialization has completed, the instance is fully prepared but not yet running.

At this point, it is important to distinguish between ready() and start().

The ready() method is part of the initialization lifecycle and is guaranteed to run at most once for a given instance. It finalizes preparation but does not represent the execution of application logic.

In contrast, start() marks the execution phase of an instance. It is responsible for rendering, activating behavior, or otherwise running the application logic and may be called multiple times during the lifetime of an instance, for example when an app is restarted or re-rendered.

For instances created via ccm.start(), this final step happens automatically. For instances created via ccm.instance(), calling start() is controlled explicitly by the caller.

ccm.start()

Creates, initializes, and immediately starts a CCM component instance.

ccm.start() does not introduce additional lifecycle behavior. It is strictly equivalent to calling ccm.instance() followed by instance.start().

Syntax

ccm.start( component [, config ] [, area ] )  Promise<Instance>

The syntax and parameters are identical to ccm.instance().

Return Value

Returns a Promise that resolves to the created and started CCM instance.

The returned instance is identical to one created via ccm.instance() and exposes the same properties and guarantees.

Example

// Fully automatic: create, initialize, and run
ccm.start('./ccm.hello.js', { name: 'Mika' }, document.body);

Equivalent manual control using ccm.instance():

const instance = await ccm.instance('./ccm.hello.js', { name: 'Mika' });
await instance.start();

Description

ccm.start() is the highest-level and most convenient entry point for embedding components.

Both ccm.instance() and ccm.start() perform the same steps up to lifecycle initialization:

  1. Component registration (if necessary)
  2. Configuration preparation and merging
  3. Runtime dependency resolution
  4. Instance creation
  5. Lifecycle execution
    1. init() is invoked top-down across the dependency tree
    2. ready() is invoked bottom-up once all instances are initialized

After the ready() phase, the instance is fully prepared but not yet running. Execution of application logic begins only when the instance’s start() method is called. ccm.start() performs this final step automatically and always returns a running instance. When using ccm.instance(), starting the instance is an explicit decision made by the caller.

Notes

  • ccm.start() is ideal for standalone apps and simple embeddings.
  • It offers the least manual control, but the most concise usage.

Choosing the Right Entry Point

Use ccm.component() when you want to register a component definition explicitly and prepare it for later instantiation, without creating any instances yet.

Use ccm.instance() when you want to create an instance and explicitly control when (and if) it is started.

Use ccm.start() when embedding a standalone app and you want instance creation, lifecycle initialization, and execution to happen automatically in a single step.

All three functions are fully compatible and interoperable. They differ only in their level of abstraction and in how much control they give over instantiation and execution.

The following behavior applies uniformly to all three entry points: If a component depends on a different ccmjs version than the currently active one, the call is transparently delegated to the appropriate framework instance, and the resulting component or instance is forwarded back to the caller.

Clone this wiki locally