Skip to content

πŸ“ ccmjs Conventions

Andre Kless edited this page Mar 28, 2026 · 40 revisions

The ccmjs framework intentionally keeps its core small and flexible. Instead of introducing additional APIs for many common patterns, ccmjs relies on conventions that define optional meanings for certain properties in configurations and instance API.

These conventions are not strictly required by the framework, but when followed they enable automatic behavior and interoperability between components. Many ccmjs ecosystem components rely on these conventions.

🧩 Component Filename

Components follow a strict filename convention to enable automatic parsing, versioning, and efficient component reuse.

The following format applies to component filenames:

ccm.<name>-<major>.<minor>.<patch>[.min].[m]js

The following rules apply to this format:

  • The filename must start with ccm.
  • <name> must:
    • start with a lowercase letter
    • contain only lowercase letters, digits, and underscores
    • not contain hyphens
  • <major>.<minor>.<patch> follows semantic versioning (three numeric segments)
  • .min is optional and indicates a minified build
  • .mjs is the standard file extension (.js is also supported for backward compatibility)

Valid:

ccm.quiz-1.0.0.mjs
ccm.user_profile-2.3.1.min.mjs
ccm.chat-0.1.0.js

Invalid:

ccm.quiz.mjs                (missing version)
ccm.my-component-1.0.0.mjs  (hyphen in name)
ccm.Quiz-1.0.0.mjs          (uppercase letter)

This convention allows ccmjs to:

  • Extract component name and version directly from the filename
  • Generate unique component identifiers
  • Avoid reloading already registered component versions
  • Ensure consistent caching behavior

The filename is therefore considered part of the component's public interface.

βš™οΈ Instance Configuration

Certain properties in instance configurations follow established conventions and are interpreted by ccmjs with specific semantics and processing behavior. This allows configuration to remain declarative while still enabling structured behavior.

ccm

This property allows overriding the framework version used by the component instance.

Example:

ccm.start("https://ccmjs.github.io/quiz/ccm.quiz.mjs", {
  ccm: "https://ccmjs.github.io/framework/ccm.js",
  feedback: true
});

config

A configuration may include a config property that references a base configuration.

This base configuration is resolved first and then merged with the local configuration. Properties defined locally override those from the base configuration.

Example:

{
  config: ["ccm.get", { name: "quiz_configs" }, "default"],  // Referenced base configuration
  feedback: false
}

Assume the referenced base configuration resolves to:

{
  feedback: true,
  questions: [
    "2+2?",
    "3+3?"
  ]
}

After resolving the base configuration and merging it with the local configuration, the final configuration becomes:

{
  feedback: false,
  questions: [
    "2+2?",
    "3+3?"
  ]
}

The local configuration overrides properties from the base configuration (feedback), while all other properties (questions) are inherited unchanged.

The merge is performed before dependency resolution and mapper execution.

A base configuration may itself contain another config property. In this case, the resolution process continues recursively until a configuration without a config property is reached.

This mechanism enables reusable and composable configurations, including:

  • Inheritance
  • Shared templates
  • External configuration sources

Because base configurations can be loaded via dependencies, they may originate from:

  • Local files
  • Remote datastores
  • Other components

The config property is not part of the final instance configuration, but is only used during configuration resolution.

ignore

This property can be used to exclude parts of a configuration from dependency resolution. Any value inside the ignore object is left unchanged, even if it contains a dependency.

Example:

{
  ignore: {
    hello: ["ccm.start", "https://ccmjs.github.io/hello/ccm.hello.mjs", { name: "Mika" }]
  }
}

After instance creation, the value remains unresolved:

this.ignore.hello
// ["ccm.start", "https://ccmjs.github.io/hello/ccm.hello.mjs", { name: "Mika" }]

This also applies when the value itself is a dependency (not wrapped in an object).

mapper

A configuration may include a mapper that transforms the configuration before it is applied to the instance.

This enables declarative adaptation of configuration structures, for example when connecting components with different schemas.

The mapper is applied after dependency resolution and before instance initialization. A mapper can be defined as an object that maps source paths to target paths:

ccm.start("ccm.statistics.mjs", {
  result: ["ccm.get", { name: "quiz_results" }, "quiz1"],
  mapper: {
    "result.score": "points",
    "result.max": "total"
  }
});

Resulting configuration:

{
  result: {...},
  points: 8,
  total: 10
}

Alternatively, the mapper can be a function:

mapper: config => ({
  ...config,
  percentage: Math.round(
    config.result.score / config.result.max * 100
  )
})

The mapper itself may also be provided via a dependency:

mapper: ["ccm.load", "./mapper.mjs#transform"]

onaction

A configuration may include an onaction callback that is invoked whenever an action or state transition occurs during instance execution.

This pattern allows components to remain decoupled while exposing their internal state transitions.

The callback receives an event object with the following properties:

  • instance β€” Reference to the component instance
  • type β€” Unique name of the action (e.g. "start", "next", "finish")
  • data β€” Optional data associated with the action

Additional properties may be provided depending on the component and the specific action.

Example:

onaction: event => {
  switch (event.type) {
    case "start":
      console.log("Started");
      break;
    case "next":
      console.log("Next step");
      break;
    case "finish":
      console.log("Result:", event.data);
      break;
  }
}

The available action types and additional event data depend on the implementation of the respective component.

Component developers can trigger actions by calling the configured callback and passing an event object.

Example (component implementation):

if (this.onaction) {
  this.onaction({
    instance: this,
    type: "finish",
    data: result
  });
}

The instance reference also provides access to the rendered DOM via event.instance.element. This allows external logic to extend or adapt the UI without modifying the component itself.

Component developers are encouraged to emit meaningful actions for all relevant state transitions.

root

The root property controls how the component's DOM is encapsulated and isolated.

Possible values:

Value Behavior
"open" (default) Creates an open Shadow DOM
"closed" Creates a closed Shadow DOM
false Disables Shadow DOM

If Shadow DOM is disabled, this.host directly contains this.element, without an intermediate Shadow Root.

user

The user property is a convention used for authentication in remote datastores.

If a component instance provides a user property, it is automatically discovered by child instances via the component hierarchy. This is used by ccm.store() when working with a remote datastore to attach authentication information to server requests. The user instance is resolved using:

ccm.helper.findInAncestors(this, "user")

This allows authentication to be defined once at a higher level and reused by all nested components.

A user instance is expected to provide the following methods:

  • isLoggedIn() β€” Returns whether a user is currently authenticated
  • login() β€” Initiates a login process
  • logout() β€” Logs out the current user
  • getAppState() β€” Returns an object containing at least a token property

The token is automatically attached to remote datastore requests:

params.token = this.user.getAppState().token

If authentication fails (e.g. HTTP 401/403), the datastore may attempt to re-authenticate automatically.

Note:

  • The user property is optional and only relevant when using remote datastores.
  • Authentication logic is fully encapsulated in the user component.
  • Other components remain independent of authentication details.

πŸ”Œ Instance API

Certain properties and methods on component instances follow established conventions and are provided by ccmjs with defined semantics and behavior. This ensures a consistent and predictable interaction model across all components.

config

Every ccmjs instance exposes the configuration used for its creation via the property this.config. It contains the fully resolved configuration as a JSON string.

console.log(this.config);

Example:

'{"feedback":true,"questions":[...]}'

The configuration is stored after dependency resolution and represents the exact state that was used to create the instance.

Since the value is serialized as JSON, JavaScript functions are not preserved. If function behavior should survive serialization (e.g. for sharing or restoring an app), it is recommended to include functions via declarative dependencies instead.

This snapshot can be used for debugging, reproducing application states, exporting configurations, or even sharing complete apps (e.g. via bookmarklets), since both the component and its configuration are known.

getAppState()

Interactive components typically expose a method getAppState().

This method returns the current application state resulting from user interaction. The returned object contains all user-dependent data that represents the logical state of the app.

In addition to application state, components may also maintain UI state. UI state is typically managed internally and is not necessarily part of getAppState().

Depending on the requirements, UI state may be:

  • kept in memory (default)
  • included in the app state (getAppState())
  • stored in a datastore (ccm.store())
  • persisted in browser storage (localStorage / sessionStorage)
  • encoded in routing state

ccmjs deliberately does not prescribe how UI state should be managed.

πŸ”’ Reserved Properties

The following property names are reserved by the framework and should not be used in component configurations because they are used internally by ccmjs instances.

  • children
  • component
  • element
  • host
  • init
  • instance
  • parent
  • ready
  • root
  • start

If these properties are present in a configuration, they are automatically removed by ccmjs to prevent conflicts with internal instance properties.

🧭 Convention Philosophy

Conventions in ccmjs follow three principles:

  1. Optional
    Components may ignore them completely.

  2. Composable
    Conventions allow independent components to interoperate.

  3. Minimal
    The core framework does not enforce app architecture.

Instead, conventions provide lightweight coordination mechanisms for component ecosystems.

ccmjs apps are composed by orchestrating independent components through shared datastores, lifecycle callbacks, and runtime DOM composition.

Clone this wiki locally