Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
116 commits
Select commit Hold shift + click to select a range
1e89385
allow properties to be set from initialization options for `ResourceA…
roncodes Oct 23, 2025
35bd843
feat: Complete UniverseService refactor with service decomposition an…
roncodes Nov 26, 2025
cb85630
bump version
roncodes Nov 26, 2025
0b931e9
fix: Add app directory re-exports for services and components
roncodes Nov 26, 2025
cc652b6
fix: Contract constructors accept full definitions and RegistryServic…
roncodes Nov 26, 2025
5c57b06
refactor: Use hash (#) separator instead of double dash (--) in Regis…
roncodes Nov 26, 2025
89fe4f0
fix: RegistryService respects Ember native types and boot guide prese…
roncodes Nov 26, 2025
fb0a0cf
fix: Add missing waitForBoot and widget service methods
roncodes Nov 26, 2025
f6ded6b
refactor: Update Widget contract and WidgetService for dashboard-spec…
roncodes Nov 26, 2025
ed85373
fix: Contract constructors - set properties before super() and use is…
roncodes Nov 26, 2025
3f092c4
fix: Implement setup() pattern to fix 'Must call super constructor' e…
roncodes Nov 26, 2025
aad9cef
refactor: Simplify contract setup pattern - call super.setup() directly
roncodes Nov 26, 2025
6b7beb1
feat: Add loadExtensions and setupExtensions methods to ExtensionManager
roncodes Nov 26, 2025
69b0e22
fix: Import utilities at top of ExtensionManager instead of using req…
roncodes Nov 26, 2025
273bf59
fix: Use extension.name in setupExtensions loop
roncodes Nov 26, 2025
81b41e5
feat: Use EXTENSION_LOADERS map instead of dynamic require()
roncodes Nov 26, 2025
c15d845
Fix: Call executeBootCallbacks to resolve waitForBoot promise
roncodes Nov 26, 2025
fa595b5
Remove LazyEngineComponent - moved to ember-ui
roncodes Nov 26, 2025
717a8cf
Fix: Support widgetId property for backward compatibility
roncodes Nov 26, 2025
87172b0
Refactor: Replace underscore-prefixed private methods with # syntax
roncodes Nov 26, 2025
a5b743d
Debug: Add comprehensive logging to widget registration and lookup
roncodes Nov 26, 2025
da5e627
Fix: Correct widget registry lookup to work with array storage
roncodes Nov 26, 2025
9ea6285
Fix: Store registration key with items for proper filtering
roncodes Nov 26, 2025
30c7aa6
use the new extension loader setup
roncodes Nov 28, 2025
bde61d2
feat: Add backward compatibility facade methods to UniverseService
roncodes Nov 28, 2025
b1afa86
feat: Add getService() method to UniverseService
roncodes Nov 28, 2025
40d2921
feat: Improve DX and add performance monitoring
roncodes Nov 28, 2025
4e6e338
fix: add missing virtualRouteRedirect and getViewFromTransition methods
roncodes Nov 28, 2025
45771b6
perf: optimize extensions.json loading with localStorage caching
roncodes Nov 28, 2025
18537d1
fix: correct adminMenuItems getter and add adminMenuPanels getter
roncodes Nov 28, 2025
06ef8b2
feat: add computed getters to MenuService for template access
roncodes Nov 28, 2025
36a65c6
fix: trigger reactivity when creating new registries
roncodes Nov 28, 2025
02983a7
fix: share registries Map across all engines via container
roncodes Nov 28, 2025
b41f259
refactor: use container as single source of truth, Map as index
roncodes Nov 28, 2025
7a9cdf9
refactor: simplify RegistryService - clean, performant, reactive
roncodes Nov 29, 2025
54fa9d7
feat: add application container registration methods to Universe and …
roncodes Nov 29, 2025
ead00fa
remove lazy-engine-comonent export
roncodes Nov 29, 2025
a587213
fix: critical fixes for RegistryService and ExtensionManager
roncodes Dec 1, 2025
14100f4
refactor: implement fully dynamic, Map-based registry system
roncodes Dec 1, 2025
b091d7d
fix: restore createRegistry/createRegistries and remove registerUtil
roncodes Dec 1, 2025
e63cb69
fix: refactor WidgetService to correctly use RegistryService
roncodes Dec 1, 2025
176059c
refactor: use singular list names and Ember's warn
roncodes Dec 1, 2025
8fc5dc0
fix: remove duplicate methods and fix MenuService implementation
roncodes Dec 1, 2025
52191f2
fix: correct ExtensionManager engine loading and prevent MenuService …
roncodes Dec 1, 2025
3b0d731
refactor: convert all private methods to hash prefix (#) in Extension…
roncodes Dec 1, 2025
91b6534
fix: restore correct #mountPathFromEngineName implementation
roncodes Dec 1, 2025
190badb
refactor: complete ExtensionManager implementation
roncodes Dec 1, 2025
42926db
fix: use owner.hasRegistration instead of router.hasRegistration
roncodes Dec 1, 2025
2cb86c1
fix: add Evented mixin to ExtensionManager for trigger support
roncodes Dec 1, 2025
1e090e9
fix: restore correct transitionMenuItem implementation
roncodes Dec 1, 2025
9d9a728
fix: set section and view properties on menu panel items for correct …
roncodes Dec 1, 2025
2d13b1e
fix: add ALL missing properties to MenuItem to match original _create…
roncodes Dec 1, 2025
3c9c2ce
debug: add logging to trace section parameter issue in menu panel items
roncodes Dec 1, 2025
5698467
debug: add logging to transitionMenuItem to see what menu item data i…
roncodes Dec 1, 2025
a781920
fix: update panel.items array with modified menu items that have sect…
roncodes Dec 1, 2025
01e74d2
fix: correct panel item slug/view structure to match original behavior
roncodes Dec 1, 2025
3c09b5e
feat: add event triggers for backward compatibility
roncodes Dec 1, 2025
dc4ecd1
fix: add Evented mixin to MenuService and use this.trigger
roncodes Dec 1, 2025
a214534
fix: add open property to MenuPanel with default value of true
roncodes Dec 1, 2025
368d59c
fix: correct registry name mismatch for header menu items
roncodes Dec 1, 2025
f064ebd
fix: add null checks before dasherize calls in MenuItem constructor
roncodes Dec 1, 2025
c0bcdfb
fix: restore backward compatibility for registerMenuItem
roncodes Dec 1, 2025
8da88ea
feat: Add extension.js hook patterns with onEngineLoaded support
roncodes Dec 1, 2025
d403089
fix: Wrap onClick handlers with menuItem and universe parameters
roncodes Dec 1, 2025
f45822c
fix: Apply finalView normalization consistently for all menu items
roncodes Dec 1, 2025
bfe34a3
fix: Add missing getApplicationInstance method to UniverseService
roncodes Dec 1, 2025
fbb0e51
fix: Add missing getServiceFromEngine method to UniverseService
roncodes Dec 1, 2025
cb0c08c
fix: Run onEngineLoaded hooks immediately if engine already loaded
roncodes Dec 1, 2025
d2d8dc3
feat: Patch buildChildEngineInstance to run hooks for router-loaded e…
roncodes Dec 1, 2025
0020dfd
engine events and hooks should run after engine is booted
roncodes Dec 2, 2025
a56ebcf
set cache on `loadEngines` fetch
roncodes Dec 2, 2025
1e495bb
fix: Restore loadInstalledExtensions for admin-configured and user-in…
roncodes Dec 2, 2025
8a075dd
fix service map injection on engine boot
roncodes Dec 2, 2025
618954d
added full fetch api options to `fleetbaseApiFetch` util
roncodes Dec 2, 2025
5263d3d
feat: Enhance ExtensionComponent to support component classes
roncodes Dec 2, 2025
5ee2430
refactor: Move registerRenderableComponent to RegistryService with Un…
roncodes Dec 2, 2025
f537269
fix: Remove trailing dot from engineInstance.mountPoint
roncodes Dec 2, 2025
61b49c2
named exports on "exports" namespace
roncodes Dec 2, 2025
64984db
fix: uncomment hostServices export in addon/exports/index.js
roncodes Dec 2, 2025
2242661
fix: correct app/exports to use addon/ path instead of circular refer…
roncodes Dec 2, 2025
c1f3168
fix: separate default and named exports in app/exports re-exports
roncodes Dec 2, 2025
2368836
feat: implement helper registration system for cross-engine helper sh…
roncodes Dec 2, 2025
24fe1bf
refactor: move TemplateHelper from addon/models to addon/contracts
roncodes Dec 2, 2025
e101c22
feat: make registerHelper async to ensure engine is loaded
roncodes Dec 3, 2025
079dc62
feat: implement UniverseRegistry singleton for cross-engine registry …
roncodes Dec 3, 2025
f75b20e
refactor: move UniverseRegistry from classes to contracts
roncodes Dec 3, 2025
8953545
fix: critical regression - set applicationInstance and window.Fleetbase
roncodes Dec 3, 2025
9e06b30
fix: convert to instance initializer and set window.Fleetbase correctly
roncodes Dec 3, 2025
8028b94
refactor: add setApplicationInstance to UniverseService and simplify
roncodes Dec 3, 2025
c8c56b8
refactor: move instance initializer to console application
roncodes Dec 3, 2025
91f1dd1
added a toString method for string representation of extension component
roncodes Dec 3, 2025
1416eac
fix debug logging and only cache extensions in production env
roncodes Dec 3, 2025
3079b46
feat: implement ExtensionBootState singleton for shared boot state
roncodes Dec 3, 2025
61eb5bb
feat: implement HookRegistry singleton and expand ExtensionBootState
roncodes Dec 3, 2025
be76d1a
doing some debugging on engine loading/patch
roncodes Dec 3, 2025
fff5f7a
fix: prioritize applicationInstance over getOwner in all services
roncodes Dec 3, 2025
0daa605
Merge: keep our getOwner fixes
roncodes Dec 3, 2025
b32f15c
fix: prevent multiple patches of buildChildEngineInstance
roncodes Dec 3, 2025
db94a20
fix: trigger engine.loaded events and hooks for router-loaded engines
roncodes Dec 3, 2025
86ae978
fix: use private getter for engineLoadedHooks in boot patch
roncodes Dec 3, 2025
a07ce17
fix: track router-loaded engines in loadedEngines Map
roncodes Dec 3, 2025
714acc0
feat: implement extension checking methods and populate registeredExt…
roncodes Dec 3, 2025
3e5a699
Fix: Replace undefined 'owner' with 'application' in constructEngineI…
roncodes Dec 3, 2025
42c3c5c
remove duplicate docblock above `constructEngineInstance`
roncodes Dec 3, 2025
9ed8cfb
Fix: getEngineInstance now uses loadedEngines Map instead of router._…
roncodes Dec 3, 2025
e293fff
Fix: Register all extensions before running setup hooks
roncodes Dec 3, 2025
7148e52
Add whenEngineLoaded utility method to simplify engine-dependent setup
roncodes Dec 3, 2025
4065c95
`getApplicationInstance` fallback to window.Fleetbase
roncodes Dec 3, 2025
65b55d9
Clean up excessive debug logs in ExtensionManager
roncodes Dec 4, 2025
5c7365c
Add back essential debug logs and replace console.warn with @ember/de…
roncodes Dec 4, 2025
ad0b954
fixed linter
roncodes Dec 4, 2025
9b61d87
Refactor: Rename universe sub-services to follow naming conventions
roncodes Dec 5, 2025
9069759
Fix: Update class names and create app exports for renamed services
roncodes Dec 5, 2025
7eedffd
Revert to -service naming with enhanced getService() resolution
roncodes Dec 5, 2025
719a0a9
fix core universe services
roncodes Dec 5, 2025
8bd074f
Merge pull request #67 from fleetbase/feature/universe-refactor
roncodes Dec 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
294 changes: 294 additions & 0 deletions BOOT_SEQUENCE_REFACTOR_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
# Boot Sequence Refactor Guide

## Overview

This guide provides the steps to refactor the application boot sequence to enable true lazy loading and move away from the old `bootEngines` mechanism that loads all extensions at startup.

## Understanding the Extension Loading Flow

The Fleetbase application has a three-tier extension loading system:

1. **pnpm Installation**: All extensions are installed via pnpm, making them available to the application
2. **System Configuration**: Extensions defined in `fleetbase.config.js` or `EXTENSIONS` environment variable are loaded globally
3. **User Permissions**: Individual users can install/uninstall extensions, which affects what loads for them specifically

Only extensions that are both installed AND enabled (via config or user permissions) will be initialized.

## The Goal

Stop loading all extension code at boot time. Instead:
- Load only the `extension.js` files (metadata registration)
- Keep engine bundles lazy-loaded (loaded on-demand when routes are visited)
- Preserve the `engines` property required by ember-engines for lazy loading

## Key Changes

1. **Keep `app.engines` property**: Required by ember-engines for lazy loading
2. **Create new `initialize-universe` instance initializer**: Loads `extension.js` files and registers metadata
3. **Remove `bootEngines` calls**: No more manual engine booting at startup

## Step-by-Step Guide

### Step 1: Update `app.js` to Preserve Engines Property

The `engines` property is **required** by ember-engines to enable lazy loading. Keep the existing structure but remove any `bootEngines` calls.

**Current `app.js` (fleetbase/console/app/app.js):**

```javascript
import Application from '@ember/application';
import Resolver from 'ember-resolver';
import loadInitializers from 'ember-load-initializers';
import config from '@fleetbase/console/config/environment';
import loadExtensions from '@fleetbase/ember-core/utils/load-extensions';
import mapEngines from '@fleetbase/ember-core/utils/map-engines';
import loadRuntimeConfig from '@fleetbase/console/utils/runtime-config';
import applyRouterFix from './utils/router-refresh-patch';

export default class App extends Application {
modulePrefix = config.modulePrefix;
podModulePrefix = config.podModulePrefix;
Resolver = Resolver;
extensions = [];
engines = {}; // ← KEEP THIS! Required by ember-engines

async ready() {
applyRouterFix(this);
const extensions = await loadExtensions();

this.extensions = extensions;
this.engines = mapEngines(extensions); // ← KEEP THIS! Maps extensions to engines
}
}

document.addEventListener('DOMContentLoaded', async () => {
await loadRuntimeConfig();
loadInitializers(App, config.modulePrefix);

let fleetbase = App.create();
fleetbase.deferReadiness();
fleetbase.boot();
});
```

**What to Keep:**
- ✅ `extensions` property - tracks which extensions are enabled
- ✅ `engines` property - required by ember-engines for lazy loading
- ✅ `loadExtensions()` - determines which extensions to load based on config + user permissions
- ✅ `mapEngines()` - creates the engines object required by ember-engines

**What Changes:**
- ❌ Remove any `bootEngines()` calls (if present in instance initializers)
- ❌ Remove `initialize-widgets.js` instance initializer (logic moves to `extension.js`)

### Step 2: Remove Old Instance Initializers

Delete the following instance initializers that perform eager engine loading:

**Files to Delete:**
- `app/instance-initializers/load-extensions.js` (if it calls `bootEngines`)
- `app/instance-initializers/initialize-widgets.js` (widgets now registered via `extension.js`)

### Step 3: Create New `initialize-universe` Initializer

Create a new instance initializer at `app/instance-initializers/initialize-universe.js`:

```javascript
import { getOwner } from '@ember/application';
import { scheduleOnce } from '@ember/runloop';

/**
* Initializes the Universe by loading and executing extension.js files
* from all enabled extensions. This replaces the old bootEngines mechanism.
*
* Key differences from old approach:
* - Only loads extension.js files (small, metadata only)
* - Does NOT load engine bundles (those lazy-load when routes are visited)
* - Respects both system config and user permissions
*
* @param {ApplicationInstance} appInstance The application instance
*/
export function initialize(appInstance) {
const universe = appInstance.lookup('service:universe');
const owner = getOwner(appInstance);
const app = owner.application;

// Set application instance on universe
universe.applicationInstance = appInstance;

// Get the list of enabled extensions from the app
// This list already respects config + user permissions via loadExtensions()
const extensions = app.extensions || [];

// Load and execute extension.js from each enabled extension
extensions.forEach(extensionName => {
try {
// Dynamically require the extension.js file
// This is a small file with only metadata, not the full engine bundle
const setupExtension = require(`${extensionName}/extension`).default;

if (typeof setupExtension === 'function') {
// Execute the extension setup function
// This registers menus, widgets, hooks, etc. as metadata
setupExtension(appInstance, universe);
}
} catch (error) {
// Silently fail if extension.js doesn't exist
// Extensions can migrate gradually to the new pattern
// console.warn(`Could not load extension.js for ${extensionName}:`, error);
}
});

// Execute any boot callbacks
scheduleOnce('afterRender', universe, 'executeBootCallbacks');
}

export default {
name: 'initialize-universe',
initialize
};
```

### Step 4: Verify `router.js` Engine Mounting

Your `prebuild.js` script already handles mounting engines in `router.js`. Verify that engines are mounted like this:

```javascript
// This is generated by prebuild.js
this.mount('@fleetbase/fleetops-engine', { as: 'console.fleet-ops' });
this.mount('@fleetbase/customer-portal-engine', { as: 'console.customer-portal' });
```

**Important**: The `this.mount()` calls are what enable ember-engines lazy loading. When a user navigates to a route, ember-engines automatically loads the engine bundle on-demand.

### Step 5: Migrate Extensions to `extension.js` Pattern

For each extension, create an `addon/extension.js` file that registers metadata without importing components:

**Example: FleetOps `addon/extension.js`**

```javascript
import { MenuItem, MenuPanel, Widget, ExtensionComponent } from '@fleetbase/ember-core/contracts';

export default function (app, universe) {
// Register admin menu panel
universe.registerAdminMenuPanel(
'Fleet-Ops',
new MenuPanel({
title: 'Fleet-Ops',
icon: 'route',
items: [
new MenuItem({
title: 'Navigator App',
icon: 'location-arrow',
component: new ExtensionComponent('@fleetbase/fleetops-engine', 'components/admin/navigator-app')
}),
new MenuItem({
title: 'Avatar Management',
icon: 'images',
component: new ExtensionComponent('@fleetbase/fleetops-engine', 'components/admin/avatar-management')
})
]
})
);

// Register widgets
universe.registerDefaultWidget(
new Widget({
widgetId: 'fleet-ops-metrics',
name: 'Fleet-Ops Metrics',
description: 'Key metrics from Fleet-Ops',
icon: 'truck',
component: new ExtensionComponent('@fleetbase/fleetops-engine', 'components/widget/metrics'),
grid_options: { w: 12, h: 12, minW: 8, minH: 12 }
})
);

// Register hooks
universe.registerHook(
new Hook({
name: 'application:before-model',
handler: (session, router) => {
// Custom logic here
},
priority: 10
})
);
}
```

**Key Points:**
- ❌ NO `import MyComponent from './components/my-component'` - this would load the engine!
- ✅ Use `ExtensionComponent` with engine name + path for lazy loading
- ✅ Use contract classes (`MenuItem`, `Widget`, `Hook`) for type safety

See [UNIVERSE_REFACTOR_MIGRATION_GUIDE.md](./UNIVERSE_REFACTOR_MIGRATION_GUIDE.md) for detailed migration examples.

## How Lazy Loading Works with This Approach

1. **App Boot**: Application boots with `app.engines` property set
2. **`initialize-universe`**: Loads small `extension.js` files via `require()`
3. **Metadata Registration**: Extensions register menus, widgets, hooks (no component code loaded)
4. **User Navigation**: User navigates to `/console/fleet-ops`
5. **Ember-Engines**: Detects route is in a mounted engine, lazy-loads the engine bundle
6. **Component Resolution**: `<LazyEngineComponent>` resolves components from loaded engine

## Performance Impact

| Metric | Before (bootEngines) | After (Lazy Loading) |
|--------|---------------------|---------------------|
| Initial Load Time | 10-40 seconds | <1 second |
| Initial Bundle Size | Core + All Engines | Core + extension.js files |
| Engine Loading | All at boot | On-demand when route visited |
| Memory Usage | All engines in memory | Only visited engines in memory |

## Ember-Engines Requirements

According to [ember-engines documentation](https://github.com/ember-engines/ember-engines):

> **Lazy loading** - An engine can allow its parent to boot with only its routing map loaded. The rest of the engine can be loaded only as required (i.e. when a route in an engine is visited). This allows applications to boot faster and limit their memory consumption.

**Required for lazy loading:**
1. ✅ `app.engines` property must be set (maps extension names to engine modules)
2. ✅ Engines must be mounted in `router.js` via `this.mount()`
3. ✅ Engine's `index.js` must have `lazyLoading: true` (default)

**What breaks lazy loading:**
1. ❌ Calling `owner.lookup('engine:my-engine')` at boot time
2. ❌ Importing components from engines in `extension.js`
3. ❌ Manual `bootEngines()` calls

## Troubleshooting

### Extension not loading
- Check that extension is in `app.extensions` array
- Verify `extension.js` file exists and exports a function
- Check browser console for errors

### Components not rendering
- Ensure `ExtensionComponent` has correct engine name and path
- Verify engine is mounted in `router.js`
- Check that `<LazyEngineComponent>` is used in templates

### Engines loading at boot
- Remove any `owner.lookup('engine:...')` calls from initializers
- Remove component imports from `extension.js`
- Verify no `bootEngines()` calls remain

## Migration Checklist

- [ ] Update `app.js` to keep `engines` property
- [ ] Remove old instance initializers (`load-extensions.js`, `initialize-widgets.js`)
- [ ] Create new `initialize-universe.js` instance initializer
- [ ] Verify `router.js` has `this.mount()` calls for all engines
- [ ] Create `extension.js` for each extension
- [ ] Replace component imports with `ExtensionComponent` definitions
- [ ] Test lazy loading in browser dev tools (Network tab)
- [ ] Verify initial bundle size reduction
- [ ] Test all extension functionality still works

## References

- [Ember Engines Guide](https://guides.emberjs.com/v5.6.0/applications/ember-engines/)
- [ember-engines GitHub](https://github.com/ember-engines/ember-engines)
- [Ember Engines RFC](https://github.com/emberjs/rfcs/blob/master/text/0010-engines.md)
Loading
Loading