Extension Lifecycle โ
The extension lifecycle in KIMU follows the standard Web Components model, with specific additions for framework integration.
Lifecycle Phases โ
1. Discovery โ
Extensions are discovered by the KimuExtensionManager through the extensions-manifest.json file.
typescript
// KimuExtensionManager loads the manifest
const extensions = await this.loadExtensionsManifest();
console.log('Extensions discovered:', extensions.length);2. Registration โ
The extension is registered as a custom element in the DOM.
typescript
// Automatically managed by the @KimuComponent decorator
@KimuComponent({
tag: 'my-extension',
name: 'My Extension'
})
export class MyExtension extends HTMLElement {
// Element is registered automatically
}3. Instantiation โ
When the element is added to the DOM, a new instance is created.
typescript
export class MyExtension extends HTMLElement {
constructor() {
super();
console.log('Extension instantiated');
// Basic initialization
this.attachShadow({ mode: 'open' });
this.setupInitialState();
}
private setupInitialState() {
// Initial configuration
this.setAttribute('data-kimu-extension', 'true');
}
}4. Connection โ
The element is connected to the DOM tree.
typescript
export class MyExtension extends HTMLElement {
connectedCallback() {
console.log('Extension connected to DOM');
// Operations to perform on connection
this.initializeExtension();
this.render();
this.setupEventListeners();
this.loadAssets();
}
private initializeExtension() {
// Extension-specific initialization
this.setupStyles();
this.loadConfiguration();
this.connectToStore();
}
}5. Attribute Changes โ
Reacts to changes in observed attributes.
typescript
export class MyExtension extends HTMLElement {
// Define observed attributes
static get observedAttributes() {
return ['theme', 'language', 'data-config'];
}
attributeChangedCallback(
name: string,
oldValue: string,
newValue: string
) {
console.log(`Attribute ${name} changed from ${oldValue} to ${newValue}`);
switch (name) {
case 'theme':
this.updateTheme(newValue);
break;
case 'language':
this.updateLanguage(newValue);
break;
case 'data-config':
this.updateConfiguration(newValue);
break;
}
}
private updateTheme(theme: string) {
this.classList.remove('theme-light', 'theme-dark');
this.classList.add(`theme-${theme}`);
}
}6. Adoption โ
The element is moved to a new document.
typescript
export class MyExtension extends HTMLElement {
adoptedCallback() {
console.log('Extension adopted to new document');
// Reconfigure for new context
this.reinitializeForNewDocument();
}
private reinitializeForNewDocument() {
// Operations needed for the new document
this.reapplyStyles();
this.reconnectEventListeners();
}
}7. Disconnection โ
The element is removed from the DOM.
typescript
export class MyExtension extends HTMLElement {
private eventListeners: Array<() => void> = [];
private intervals: number[] = [];
private subscriptions: Array<() => void> = [];
disconnectedCallback() {
console.log('Extension disconnected from DOM');
// Complete cleanup
this.cleanup();
}
private cleanup() {
// Remove event listeners
this.eventListeners.forEach(removeListener => removeListener());
this.eventListeners = [];
// Clear intervals and timeouts
this.intervals.forEach(id => clearInterval(id));
this.intervals = [];
// Cancel subscriptions
this.subscriptions.forEach(unsubscribe => unsubscribe());
this.subscriptions = [];
// Clean up specific resources
this.cleanupAssets();
this.disconnectFromStore();
}
}Lifecycle Hooks โ
Pre-Connection Hook โ
typescript
export class MyExtension extends HTMLElement {
private async preConnectionSetup() {
// Operations to complete before connection
await this.loadConfiguration();
await this.authenticateUser();
await this.preloadAssets();
}
async connectedCallback() {
await this.preConnectionSetup();
this.render();
}
}Post-Connection Hook โ
typescript
export class MyExtension extends HTMLElement {
connectedCallback() {
this.render();
// Post-connection operations
requestAnimationFrame(() => {
this.postConnectionSetup();
});
}
private postConnectionSetup() {
this.animateEntrance();
this.notifyOtherExtensions();
this.startPeriodicTasks();
}
}Error Handling โ
typescript
export class MyExtension extends HTMLElement {
connectedCallback() {
try {
this.initializeExtension();
} catch (error) {
this.handleError('Connection failed', error);
}
}
private handleError(context: string, error: Error) {
console.error(`[MyExtension] ${context}:`, error);
// Fallback UI
this.renderErrorState(error.message);
// Notify error system
this.dispatchEvent(new CustomEvent('kimu:extension-error', {
bubbles: true,
detail: { extension: 'my-extension', context, error }
}));
}
private renderErrorState(message: string) {
if (this.shadowRoot) {
this.shadowRoot.innerHTML = `
<div class="error-state">
<h3>โ ๏ธ Extension Error</h3>
<p>${message}</p>
<button onclick="location.reload()">Reload Page</button>
</div>
`;
}
}
}Asynchronous Management โ
Asynchronous Operations in Connection โ
typescript
export class MyExtension extends HTMLElement {
private isInitialized = false;
async connectedCallback() {
if (this.isInitialized) return;
try {
// Show loading state
this.renderLoadingState();
// Asynchronous operations
const [config, assets, data] = await Promise.all([
this.loadConfiguration(),
this.preloadAssets(),
this.fetchInitialData()
]);
// Initialize with loaded data
this.initialize(config, assets, data);
this.render();
this.isInitialized = true;
} catch (error) {
this.handleError('Async initialization failed', error);
}
}
private renderLoadingState() {
if (this.shadowRoot) {
this.shadowRoot.innerHTML = `
<div class="loading-state">
<div class="spinner"></div>
<p>Loading extension...</p>
</div>
`;
}
}
}Communication during Lifecycle โ
Lifecycle Events โ
typescript
export class MyExtension extends HTMLElement {
connectedCallback() {
// Notify connection
this.dispatchEvent(new CustomEvent('kimu:extension-connected', {
bubbles: true,
detail: {
tag: 'my-extension',
timestamp: Date.now()
}
}));
this.render();
}
disconnectedCallback() {
// Notify disconnection
this.dispatchEvent(new CustomEvent('kimu:extension-disconnected', {
bubbles: true,
detail: {
tag: 'my-extension',
timestamp: Date.now()
}
}));
this.cleanup();
}
}Coordination between Extensions โ
typescript
export class MyExtension extends HTMLElement {
connectedCallback() {
this.render();
// Listen to other extensions
document.addEventListener('kimu:extension-connected', (event) => {
const { tag } = event.detail;
if (tag === 'dependency-extension') {
this.handleDependencyReady();
}
});
}
private handleDependencyReady() {
console.log('Dependency extension is ready');
this.enableAdvancedFeatures();
}
}Lifecycle Debugging โ
Detailed Logging โ
typescript
export class MyExtension extends HTMLElement {
private debug = true;
private log(phase: string, data?: any) {
if (this.debug) {
console.log(`[MyExtension] ${phase}`, data || '');
}
}
constructor() {
super();
this.log('Constructor called');
}
connectedCallback() {
this.log('Connected to DOM');
this.render();
}
disconnectedCallback() {
this.log('Disconnected from DOM');
this.cleanup();
}
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
this.log('Attribute changed', { name, oldValue, newValue });
}
}Best Practices โ
- Gradual Initialization: Load only immediately necessary resources
- Error Handling: Always handle errors during initialization
- Complete Cleanup: Remove all listeners and resources on disconnection
- Performance: Use
requestAnimationFramefor rendering operations - Persistent State: Maintain state between disconnections if necessary
- Communication: Use custom events to coordinate with other extensions