@fleetbase/ember-core 0.3.10 → 0.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/addon/exports/host-services.js +1 -0
- package/addon/exports/services.js +1 -0
- package/addon/services/app-cache.js +2 -1
- package/addon/services/crud.js +19 -0
- package/addon/services/current-user.js +50 -36
- package/addon/services/events.js +322 -0
- package/addon/services/resource-action.js +18 -0
- package/addon/services/universe.js +0 -1
- package/addon/utils/lookup-user-ip.js +221 -0
- package/app/services/events.js +1 -0
- package/package.json +1 -1
- package/BOOT_SEQUENCE_REFACTOR_GUIDE.md +0 -294
- package/UNIVERSE_REFACTOR_MIGRATION_GUIDE.md +0 -318
- package/UNIVERSE_REFACTOR_README.md +0 -220
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lookup user's IP address and geolocation information using geoiplookup.io
|
|
3
|
+
*
|
|
4
|
+
* This method calls the geoiplookup.io API directly from the browser to get
|
|
5
|
+
* accurate user location data, avoiding the issue of server-side IP lookup
|
|
6
|
+
* returning the server's location instead of the user's location.
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} options - Configuration options
|
|
9
|
+
* @param {number} options.timeout - Request timeout in milliseconds (default: 5000)
|
|
10
|
+
* @param {boolean} options.cache - Whether to cache the result (default: true)
|
|
11
|
+
* @returns {Promise<Object>} User's IP and geolocation data
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const whois = await lookupUserIp();
|
|
15
|
+
* console.log(whois.city); // "New York"
|
|
16
|
+
* console.log(whois.country_code); // "US"
|
|
17
|
+
*/
|
|
18
|
+
export default async function lookupUserIp(options = {}) {
|
|
19
|
+
const { timeout = 5000, cache = true } = options;
|
|
20
|
+
|
|
21
|
+
// Check cache first if enabled
|
|
22
|
+
if (cache) {
|
|
23
|
+
const cached = getCachedWhois();
|
|
24
|
+
if (cached) {
|
|
25
|
+
return cached;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Try multiple APIs with fallback
|
|
30
|
+
const apis = [
|
|
31
|
+
{
|
|
32
|
+
url: 'https://json.geoiplookup.io/',
|
|
33
|
+
normalize: normalizeGeoIPLookup,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
url: 'https://ipapi.co/json/',
|
|
37
|
+
normalize: normalizeIPApi,
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
for (const api of apis) {
|
|
42
|
+
try {
|
|
43
|
+
const controller = new AbortController();
|
|
44
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
45
|
+
|
|
46
|
+
const response = await fetch(api.url, {
|
|
47
|
+
signal: controller.signal,
|
|
48
|
+
headers: {
|
|
49
|
+
Accept: 'application/json',
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
clearTimeout(timeoutId);
|
|
54
|
+
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
console.warn(`[lookupUserIp] ${api.url} returned ${response.status}`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const data = await response.json();
|
|
61
|
+
const normalized = api.normalize(data);
|
|
62
|
+
|
|
63
|
+
// Cache the result if enabled
|
|
64
|
+
if (cache) {
|
|
65
|
+
cacheWhois(normalized);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return normalized;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.warn(`[lookupUserIp] ${api.url} failed:`, error.message);
|
|
71
|
+
// Continue to next API
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// All APIs failed, return fallback
|
|
76
|
+
console.error('[lookupUserIp] All IP lookup APIs failed, using fallback data');
|
|
77
|
+
return getFallbackWhois();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Normalize geoiplookup.io response to match Fleetbase whois format
|
|
82
|
+
*/
|
|
83
|
+
function normalizeGeoIPLookup(data) {
|
|
84
|
+
return {
|
|
85
|
+
ip: data.ip,
|
|
86
|
+
city: data.city,
|
|
87
|
+
region: data.region,
|
|
88
|
+
country_code: data.country_code,
|
|
89
|
+
country_name: data.country_name,
|
|
90
|
+
continent_code: data.continent_code,
|
|
91
|
+
continent_name: data.continent_name,
|
|
92
|
+
latitude: data.latitude,
|
|
93
|
+
longitude: data.longitude,
|
|
94
|
+
postal_code: data.postal_code,
|
|
95
|
+
timezone: data.timezone_name,
|
|
96
|
+
currency: {
|
|
97
|
+
code: data.currency_code,
|
|
98
|
+
name: data.currency_name,
|
|
99
|
+
},
|
|
100
|
+
languages: [
|
|
101
|
+
{
|
|
102
|
+
code: data.language_code,
|
|
103
|
+
name: data.language_name,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
isp: data.isp,
|
|
107
|
+
org: data.org,
|
|
108
|
+
asn: data.asn,
|
|
109
|
+
connection_type: data.connection_type,
|
|
110
|
+
// Metadata
|
|
111
|
+
_source: 'geoiplookup.io',
|
|
112
|
+
_timestamp: Date.now(),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Normalize ipapi.co response to match Fleetbase whois format
|
|
118
|
+
*/
|
|
119
|
+
function normalizeIPApi(data) {
|
|
120
|
+
return {
|
|
121
|
+
ip: data.ip,
|
|
122
|
+
city: data.city,
|
|
123
|
+
region: data.region,
|
|
124
|
+
country_code: data.country_code,
|
|
125
|
+
country_name: data.country_name,
|
|
126
|
+
continent_code: data.continent_code,
|
|
127
|
+
continent_name: null,
|
|
128
|
+
latitude: data.latitude,
|
|
129
|
+
longitude: data.longitude,
|
|
130
|
+
postal_code: data.postal,
|
|
131
|
+
timezone: data.timezone,
|
|
132
|
+
currency: {
|
|
133
|
+
code: data.currency,
|
|
134
|
+
name: data.currency_name,
|
|
135
|
+
},
|
|
136
|
+
languages: data.languages ? data.languages.split(',').map((lang) => ({ code: lang.trim(), name: lang.trim() })) : [],
|
|
137
|
+
isp: data.org,
|
|
138
|
+
org: data.org,
|
|
139
|
+
asn: data.asn,
|
|
140
|
+
connection_type: null,
|
|
141
|
+
// Metadata
|
|
142
|
+
_source: 'ipapi.co',
|
|
143
|
+
_timestamp: Date.now(),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get cached whois data from localStorage
|
|
149
|
+
*/
|
|
150
|
+
function getCachedWhois() {
|
|
151
|
+
try {
|
|
152
|
+
const cached = localStorage.getItem('fleetbase:whois');
|
|
153
|
+
if (!cached) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const data = JSON.parse(cached);
|
|
158
|
+
const age = Date.now() - data._timestamp;
|
|
159
|
+
const maxAge = 60 * 60 * 1000; // 1 hour
|
|
160
|
+
|
|
161
|
+
if (age > maxAge) {
|
|
162
|
+
localStorage.removeItem('fleetbase:whois');
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return data;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.error('[getCachedWhois] Error reading cache:', error);
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Cache whois data to localStorage
|
|
175
|
+
*/
|
|
176
|
+
function cacheWhois(data) {
|
|
177
|
+
try {
|
|
178
|
+
localStorage.setItem('fleetbase:whois', JSON.stringify(data));
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error('[cacheWhois] Error writing cache:', error);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get fallback whois data when all APIs fail
|
|
186
|
+
*/
|
|
187
|
+
function getFallbackWhois() {
|
|
188
|
+
// Try to get browser language and timezone as fallback
|
|
189
|
+
const browserLang = navigator.language || 'en-US';
|
|
190
|
+
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
ip: null,
|
|
194
|
+
city: null,
|
|
195
|
+
region: null,
|
|
196
|
+
country_code: null,
|
|
197
|
+
country_name: null,
|
|
198
|
+
continent_code: null,
|
|
199
|
+
continent_name: null,
|
|
200
|
+
latitude: null,
|
|
201
|
+
longitude: null,
|
|
202
|
+
postal_code: null,
|
|
203
|
+
timezone: timezone,
|
|
204
|
+
currency: {
|
|
205
|
+
code: null,
|
|
206
|
+
name: null,
|
|
207
|
+
},
|
|
208
|
+
languages: [
|
|
209
|
+
{
|
|
210
|
+
code: browserLang.split('-')[0],
|
|
211
|
+
name: browserLang,
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
isp: null,
|
|
215
|
+
org: null,
|
|
216
|
+
asn: null,
|
|
217
|
+
connection_type: null,
|
|
218
|
+
_source: 'fallback',
|
|
219
|
+
_timestamp: Date.now(),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from '@fleetbase/ember-core/services/events';
|
package/package.json
CHANGED
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
# Boot Sequence Refactor Guide
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
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.
|
|
6
|
-
|
|
7
|
-
## Understanding the Extension Loading Flow
|
|
8
|
-
|
|
9
|
-
The Fleetbase application has a three-tier extension loading system:
|
|
10
|
-
|
|
11
|
-
1. **pnpm Installation**: All extensions are installed via pnpm, making them available to the application
|
|
12
|
-
2. **System Configuration**: Extensions defined in `fleetbase.config.js` or `EXTENSIONS` environment variable are loaded globally
|
|
13
|
-
3. **User Permissions**: Individual users can install/uninstall extensions, which affects what loads for them specifically
|
|
14
|
-
|
|
15
|
-
Only extensions that are both installed AND enabled (via config or user permissions) will be initialized.
|
|
16
|
-
|
|
17
|
-
## The Goal
|
|
18
|
-
|
|
19
|
-
Stop loading all extension code at boot time. Instead:
|
|
20
|
-
- Load only the `extension.js` files (metadata registration)
|
|
21
|
-
- Keep engine bundles lazy-loaded (loaded on-demand when routes are visited)
|
|
22
|
-
- Preserve the `engines` property required by ember-engines for lazy loading
|
|
23
|
-
|
|
24
|
-
## Key Changes
|
|
25
|
-
|
|
26
|
-
1. **Keep `app.engines` property**: Required by ember-engines for lazy loading
|
|
27
|
-
2. **Create new `initialize-universe` instance initializer**: Loads `extension.js` files and registers metadata
|
|
28
|
-
3. **Remove `bootEngines` calls**: No more manual engine booting at startup
|
|
29
|
-
|
|
30
|
-
## Step-by-Step Guide
|
|
31
|
-
|
|
32
|
-
### Step 1: Update `app.js` to Preserve Engines Property
|
|
33
|
-
|
|
34
|
-
The `engines` property is **required** by ember-engines to enable lazy loading. Keep the existing structure but remove any `bootEngines` calls.
|
|
35
|
-
|
|
36
|
-
**Current `app.js` (fleetbase/console/app/app.js):**
|
|
37
|
-
|
|
38
|
-
```javascript
|
|
39
|
-
import Application from '@ember/application';
|
|
40
|
-
import Resolver from 'ember-resolver';
|
|
41
|
-
import loadInitializers from 'ember-load-initializers';
|
|
42
|
-
import config from '@fleetbase/console/config/environment';
|
|
43
|
-
import loadExtensions from '@fleetbase/ember-core/utils/load-extensions';
|
|
44
|
-
import mapEngines from '@fleetbase/ember-core/utils/map-engines';
|
|
45
|
-
import loadRuntimeConfig from '@fleetbase/console/utils/runtime-config';
|
|
46
|
-
import applyRouterFix from './utils/router-refresh-patch';
|
|
47
|
-
|
|
48
|
-
export default class App extends Application {
|
|
49
|
-
modulePrefix = config.modulePrefix;
|
|
50
|
-
podModulePrefix = config.podModulePrefix;
|
|
51
|
-
Resolver = Resolver;
|
|
52
|
-
extensions = [];
|
|
53
|
-
engines = {}; // ← KEEP THIS! Required by ember-engines
|
|
54
|
-
|
|
55
|
-
async ready() {
|
|
56
|
-
applyRouterFix(this);
|
|
57
|
-
const extensions = await loadExtensions();
|
|
58
|
-
|
|
59
|
-
this.extensions = extensions;
|
|
60
|
-
this.engines = mapEngines(extensions); // ← KEEP THIS! Maps extensions to engines
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
document.addEventListener('DOMContentLoaded', async () => {
|
|
65
|
-
await loadRuntimeConfig();
|
|
66
|
-
loadInitializers(App, config.modulePrefix);
|
|
67
|
-
|
|
68
|
-
let fleetbase = App.create();
|
|
69
|
-
fleetbase.deferReadiness();
|
|
70
|
-
fleetbase.boot();
|
|
71
|
-
});
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
**What to Keep:**
|
|
75
|
-
- ✅ `extensions` property - tracks which extensions are enabled
|
|
76
|
-
- ✅ `engines` property - required by ember-engines for lazy loading
|
|
77
|
-
- ✅ `loadExtensions()` - determines which extensions to load based on config + user permissions
|
|
78
|
-
- ✅ `mapEngines()` - creates the engines object required by ember-engines
|
|
79
|
-
|
|
80
|
-
**What Changes:**
|
|
81
|
-
- ❌ Remove any `bootEngines()` calls (if present in instance initializers)
|
|
82
|
-
- ❌ Remove `initialize-widgets.js` instance initializer (logic moves to `extension.js`)
|
|
83
|
-
|
|
84
|
-
### Step 2: Remove Old Instance Initializers
|
|
85
|
-
|
|
86
|
-
Delete the following instance initializers that perform eager engine loading:
|
|
87
|
-
|
|
88
|
-
**Files to Delete:**
|
|
89
|
-
- `app/instance-initializers/load-extensions.js` (if it calls `bootEngines`)
|
|
90
|
-
- `app/instance-initializers/initialize-widgets.js` (widgets now registered via `extension.js`)
|
|
91
|
-
|
|
92
|
-
### Step 3: Create New `initialize-universe` Initializer
|
|
93
|
-
|
|
94
|
-
Create a new instance initializer at `app/instance-initializers/initialize-universe.js`:
|
|
95
|
-
|
|
96
|
-
```javascript
|
|
97
|
-
import { getOwner } from '@ember/application';
|
|
98
|
-
import { scheduleOnce } from '@ember/runloop';
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Initializes the Universe by loading and executing extension.js files
|
|
102
|
-
* from all enabled extensions. This replaces the old bootEngines mechanism.
|
|
103
|
-
*
|
|
104
|
-
* Key differences from old approach:
|
|
105
|
-
* - Only loads extension.js files (small, metadata only)
|
|
106
|
-
* - Does NOT load engine bundles (those lazy-load when routes are visited)
|
|
107
|
-
* - Respects both system config and user permissions
|
|
108
|
-
*
|
|
109
|
-
* @param {ApplicationInstance} appInstance The application instance
|
|
110
|
-
*/
|
|
111
|
-
export function initialize(appInstance) {
|
|
112
|
-
const universe = appInstance.lookup('service:universe');
|
|
113
|
-
const owner = getOwner(appInstance);
|
|
114
|
-
const app = owner.application;
|
|
115
|
-
|
|
116
|
-
// Set application instance on universe
|
|
117
|
-
universe.applicationInstance = appInstance;
|
|
118
|
-
|
|
119
|
-
// Get the list of enabled extensions from the app
|
|
120
|
-
// This list already respects config + user permissions via loadExtensions()
|
|
121
|
-
const extensions = app.extensions || [];
|
|
122
|
-
|
|
123
|
-
// Load and execute extension.js from each enabled extension
|
|
124
|
-
extensions.forEach(extensionName => {
|
|
125
|
-
try {
|
|
126
|
-
// Dynamically require the extension.js file
|
|
127
|
-
// This is a small file with only metadata, not the full engine bundle
|
|
128
|
-
const setupExtension = require(`${extensionName}/extension`).default;
|
|
129
|
-
|
|
130
|
-
if (typeof setupExtension === 'function') {
|
|
131
|
-
// Execute the extension setup function
|
|
132
|
-
// This registers menus, widgets, hooks, etc. as metadata
|
|
133
|
-
setupExtension(appInstance, universe);
|
|
134
|
-
}
|
|
135
|
-
} catch (error) {
|
|
136
|
-
// Silently fail if extension.js doesn't exist
|
|
137
|
-
// Extensions can migrate gradually to the new pattern
|
|
138
|
-
// console.warn(`Could not load extension.js for ${extensionName}:`, error);
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// Execute any boot callbacks
|
|
143
|
-
scheduleOnce('afterRender', universe, 'executeBootCallbacks');
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
export default {
|
|
147
|
-
name: 'initialize-universe',
|
|
148
|
-
initialize
|
|
149
|
-
};
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### Step 4: Verify `router.js` Engine Mounting
|
|
153
|
-
|
|
154
|
-
Your `prebuild.js` script already handles mounting engines in `router.js`. Verify that engines are mounted like this:
|
|
155
|
-
|
|
156
|
-
```javascript
|
|
157
|
-
// This is generated by prebuild.js
|
|
158
|
-
this.mount('@fleetbase/fleetops-engine', { as: 'console.fleet-ops' });
|
|
159
|
-
this.mount('@fleetbase/customer-portal-engine', { as: 'console.customer-portal' });
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
**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.
|
|
163
|
-
|
|
164
|
-
### Step 5: Migrate Extensions to `extension.js` Pattern
|
|
165
|
-
|
|
166
|
-
For each extension, create an `addon/extension.js` file that registers metadata without importing components:
|
|
167
|
-
|
|
168
|
-
**Example: FleetOps `addon/extension.js`**
|
|
169
|
-
|
|
170
|
-
```javascript
|
|
171
|
-
import { MenuItem, MenuPanel, Widget, ExtensionComponent } from '@fleetbase/ember-core/contracts';
|
|
172
|
-
|
|
173
|
-
export default function (app, universe) {
|
|
174
|
-
// Register admin menu panel
|
|
175
|
-
universe.registerAdminMenuPanel(
|
|
176
|
-
'Fleet-Ops',
|
|
177
|
-
new MenuPanel({
|
|
178
|
-
title: 'Fleet-Ops',
|
|
179
|
-
icon: 'route',
|
|
180
|
-
items: [
|
|
181
|
-
new MenuItem({
|
|
182
|
-
title: 'Navigator App',
|
|
183
|
-
icon: 'location-arrow',
|
|
184
|
-
component: new ExtensionComponent('@fleetbase/fleetops-engine', 'components/admin/navigator-app')
|
|
185
|
-
}),
|
|
186
|
-
new MenuItem({
|
|
187
|
-
title: 'Avatar Management',
|
|
188
|
-
icon: 'images',
|
|
189
|
-
component: new ExtensionComponent('@fleetbase/fleetops-engine', 'components/admin/avatar-management')
|
|
190
|
-
})
|
|
191
|
-
]
|
|
192
|
-
})
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
// Register widgets
|
|
196
|
-
universe.registerDefaultWidget(
|
|
197
|
-
new Widget({
|
|
198
|
-
widgetId: 'fleet-ops-metrics',
|
|
199
|
-
name: 'Fleet-Ops Metrics',
|
|
200
|
-
description: 'Key metrics from Fleet-Ops',
|
|
201
|
-
icon: 'truck',
|
|
202
|
-
component: new ExtensionComponent('@fleetbase/fleetops-engine', 'components/widget/metrics'),
|
|
203
|
-
grid_options: { w: 12, h: 12, minW: 8, minH: 12 }
|
|
204
|
-
})
|
|
205
|
-
);
|
|
206
|
-
|
|
207
|
-
// Register hooks
|
|
208
|
-
universe.registerHook(
|
|
209
|
-
new Hook({
|
|
210
|
-
name: 'application:before-model',
|
|
211
|
-
handler: (session, router) => {
|
|
212
|
-
// Custom logic here
|
|
213
|
-
},
|
|
214
|
-
priority: 10
|
|
215
|
-
})
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
**Key Points:**
|
|
221
|
-
- ❌ NO `import MyComponent from './components/my-component'` - this would load the engine!
|
|
222
|
-
- ✅ Use `ExtensionComponent` with engine name + path for lazy loading
|
|
223
|
-
- ✅ Use contract classes (`MenuItem`, `Widget`, `Hook`) for type safety
|
|
224
|
-
|
|
225
|
-
See [UNIVERSE_REFACTOR_MIGRATION_GUIDE.md](./UNIVERSE_REFACTOR_MIGRATION_GUIDE.md) for detailed migration examples.
|
|
226
|
-
|
|
227
|
-
## How Lazy Loading Works with This Approach
|
|
228
|
-
|
|
229
|
-
1. **App Boot**: Application boots with `app.engines` property set
|
|
230
|
-
2. **`initialize-universe`**: Loads small `extension.js` files via `require()`
|
|
231
|
-
3. **Metadata Registration**: Extensions register menus, widgets, hooks (no component code loaded)
|
|
232
|
-
4. **User Navigation**: User navigates to `/console/fleet-ops`
|
|
233
|
-
5. **Ember-Engines**: Detects route is in a mounted engine, lazy-loads the engine bundle
|
|
234
|
-
6. **Component Resolution**: `<LazyEngineComponent>` resolves components from loaded engine
|
|
235
|
-
|
|
236
|
-
## Performance Impact
|
|
237
|
-
|
|
238
|
-
| Metric | Before (bootEngines) | After (Lazy Loading) |
|
|
239
|
-
|--------|---------------------|---------------------|
|
|
240
|
-
| Initial Load Time | 10-40 seconds | <1 second |
|
|
241
|
-
| Initial Bundle Size | Core + All Engines | Core + extension.js files |
|
|
242
|
-
| Engine Loading | All at boot | On-demand when route visited |
|
|
243
|
-
| Memory Usage | All engines in memory | Only visited engines in memory |
|
|
244
|
-
|
|
245
|
-
## Ember-Engines Requirements
|
|
246
|
-
|
|
247
|
-
According to [ember-engines documentation](https://github.com/ember-engines/ember-engines):
|
|
248
|
-
|
|
249
|
-
> **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.
|
|
250
|
-
|
|
251
|
-
**Required for lazy loading:**
|
|
252
|
-
1. ✅ `app.engines` property must be set (maps extension names to engine modules)
|
|
253
|
-
2. ✅ Engines must be mounted in `router.js` via `this.mount()`
|
|
254
|
-
3. ✅ Engine's `index.js` must have `lazyLoading: true` (default)
|
|
255
|
-
|
|
256
|
-
**What breaks lazy loading:**
|
|
257
|
-
1. ❌ Calling `owner.lookup('engine:my-engine')` at boot time
|
|
258
|
-
2. ❌ Importing components from engines in `extension.js`
|
|
259
|
-
3. ❌ Manual `bootEngines()` calls
|
|
260
|
-
|
|
261
|
-
## Troubleshooting
|
|
262
|
-
|
|
263
|
-
### Extension not loading
|
|
264
|
-
- Check that extension is in `app.extensions` array
|
|
265
|
-
- Verify `extension.js` file exists and exports a function
|
|
266
|
-
- Check browser console for errors
|
|
267
|
-
|
|
268
|
-
### Components not rendering
|
|
269
|
-
- Ensure `ExtensionComponent` has correct engine name and path
|
|
270
|
-
- Verify engine is mounted in `router.js`
|
|
271
|
-
- Check that `<LazyEngineComponent>` is used in templates
|
|
272
|
-
|
|
273
|
-
### Engines loading at boot
|
|
274
|
-
- Remove any `owner.lookup('engine:...')` calls from initializers
|
|
275
|
-
- Remove component imports from `extension.js`
|
|
276
|
-
- Verify no `bootEngines()` calls remain
|
|
277
|
-
|
|
278
|
-
## Migration Checklist
|
|
279
|
-
|
|
280
|
-
- [ ] Update `app.js` to keep `engines` property
|
|
281
|
-
- [ ] Remove old instance initializers (`load-extensions.js`, `initialize-widgets.js`)
|
|
282
|
-
- [ ] Create new `initialize-universe.js` instance initializer
|
|
283
|
-
- [ ] Verify `router.js` has `this.mount()` calls for all engines
|
|
284
|
-
- [ ] Create `extension.js` for each extension
|
|
285
|
-
- [ ] Replace component imports with `ExtensionComponent` definitions
|
|
286
|
-
- [ ] Test lazy loading in browser dev tools (Network tab)
|
|
287
|
-
- [ ] Verify initial bundle size reduction
|
|
288
|
-
- [ ] Test all extension functionality still works
|
|
289
|
-
|
|
290
|
-
## References
|
|
291
|
-
|
|
292
|
-
- [Ember Engines Guide](https://guides.emberjs.com/v5.6.0/applications/ember-engines/)
|
|
293
|
-
- [ember-engines GitHub](https://github.com/ember-engines/ember-engines)
|
|
294
|
-
- [Ember Engines RFC](https://github.com/emberjs/rfcs/blob/master/text/0010-engines.md)
|