@trackunit/iris-app-sdk-vite 0.4.3 → 0.4.5
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/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## 0.4.5 (2026-04-30)
|
|
2
|
+
|
|
3
|
+
### 🧱 Updated Dependencies
|
|
4
|
+
|
|
5
|
+
- Updated iris-app-build-utilities to 1.16.4
|
|
6
|
+
- Updated iris-app-api to 1.18.3
|
|
7
|
+
- Updated iris-app to 1.19.4
|
|
8
|
+
|
|
9
|
+
## 0.4.4 (2026-04-30)
|
|
10
|
+
|
|
11
|
+
This was a version bump only for iris-app-sdk-vite to align it with other projects, there were no code changes.
|
|
12
|
+
|
|
1
13
|
## 0.4.3 (2026-04-30)
|
|
2
14
|
|
|
3
15
|
### 🧱 Updated Dependencies
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/iris-app-sdk-vite",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
5
5
|
"repository": "https://github.com/Trackunit/manager",
|
|
6
6
|
"executors": "./executors.json",
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"rxjs": "7.8.1",
|
|
12
12
|
"win-ca": "^3.5.1",
|
|
13
|
-
"@trackunit/iris-app-build-utilities": "1.16.
|
|
14
|
-
"@trackunit/iris-app-api": "1.18.
|
|
13
|
+
"@trackunit/iris-app-build-utilities": "1.16.4",
|
|
14
|
+
"@trackunit/iris-app-api": "1.18.3",
|
|
15
15
|
"tslib": "^2.6.2",
|
|
16
16
|
"vite": "7.3.1",
|
|
17
17
|
"@module-federation/vite": "1.11.0",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"@tailwindcss/postcss": "^4.1.18",
|
|
23
23
|
"@vitejs/plugin-react-swc": "^4.2.3",
|
|
24
24
|
"@nx/devkit": "22.6.5",
|
|
25
|
-
"@trackunit/iris-app": "1.19.
|
|
25
|
+
"@trackunit/iris-app": "1.19.4",
|
|
26
26
|
"cross-keychain": "^1.1.0",
|
|
27
27
|
"jwt-decode": "^3.1.2"
|
|
28
28
|
},
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.wrapResponseTiming = exports.shortenPath = exports.timeAsync = exports.time = exports.logEvent = exports.logTiming = void 0;
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Dev-server timing instrumentation
|
|
6
|
+
//
|
|
7
|
+
// Lightweight, zero-dependency timing logs for the Iris-app dev server.
|
|
8
|
+
// Disabled by default; opt in with IRIS_TIMING=true. Logs to stdout with the
|
|
9
|
+
// "[iris-timing]" prefix so they're easy to grep / filter.
|
|
10
|
+
//
|
|
11
|
+
// Each log line looks like:
|
|
12
|
+
// [iris-timing] T+<elapsed_since_module_load> <duration> <label>
|
|
13
|
+
//
|
|
14
|
+
// Durations >1000ms are highlighted red, >200ms yellow, so the slow stuff
|
|
15
|
+
// jumps out in a typical terminal. Per-request lines below the
|
|
16
|
+
// TRANSFORM_LOG_THRESHOLD_MS are dropped to keep the output usable.
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Read lazily so tests / users can flip IRIS_TIMING at runtime.
|
|
19
|
+
// Disabled by default; set IRIS_TIMING=true to enable.
|
|
20
|
+
const isTimingEnabled = () => process.env.IRIS_TIMING === "true";
|
|
21
|
+
const TRANSFORM_LOG_THRESHOLD_MS = 50;
|
|
22
|
+
const TIMING_MODULE_START = performance.now();
|
|
23
|
+
const padMs = (ms) => `${ms.toFixed(0).padStart(6, " ")}ms`;
|
|
24
|
+
const colorize = (ms, text) => {
|
|
25
|
+
if (ms > 1000)
|
|
26
|
+
return `\x1b[31m${text}\x1b[0m`; // red
|
|
27
|
+
if (ms > 200)
|
|
28
|
+
return `\x1b[33m${text}\x1b[0m`; // yellow
|
|
29
|
+
return text;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Logs a single timing line: elapsed-since-module-load, duration, and label.
|
|
33
|
+
* Slow durations are colorized so they stand out in the terminal.
|
|
34
|
+
*/
|
|
35
|
+
const logTiming = (durationMs, label) => {
|
|
36
|
+
if (!isTimingEnabled())
|
|
37
|
+
return;
|
|
38
|
+
const elapsed = padMs(performance.now() - TIMING_MODULE_START);
|
|
39
|
+
const dur = colorize(durationMs, padMs(durationMs));
|
|
40
|
+
// eslint-disable-next-line no-console
|
|
41
|
+
console.log(`\x1b[36m[iris-timing]\x1b[0m T+${elapsed} ${dur} ${label}`);
|
|
42
|
+
};
|
|
43
|
+
exports.logTiming = logTiming;
|
|
44
|
+
/**
|
|
45
|
+
* Logs a non-timed event (no duration column), e.g. "server: listening".
|
|
46
|
+
* Useful for marking moments in the dev-server lifecycle.
|
|
47
|
+
*/
|
|
48
|
+
const logEvent = (label) => {
|
|
49
|
+
if (!isTimingEnabled())
|
|
50
|
+
return;
|
|
51
|
+
const elapsed = padMs(performance.now() - TIMING_MODULE_START);
|
|
52
|
+
// eslint-disable-next-line no-console
|
|
53
|
+
console.log(`\x1b[36m[iris-timing]\x1b[0m T+${elapsed} ${label}`);
|
|
54
|
+
};
|
|
55
|
+
exports.logEvent = logEvent;
|
|
56
|
+
/** Wraps a synchronous function and logs its duration. */
|
|
57
|
+
const time = (label, fn) => {
|
|
58
|
+
if (!isTimingEnabled())
|
|
59
|
+
return fn();
|
|
60
|
+
const start = performance.now();
|
|
61
|
+
try {
|
|
62
|
+
return fn();
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
(0, exports.logTiming)(performance.now() - start, label);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
exports.time = time;
|
|
69
|
+
/** Wraps an async function and logs its duration. */
|
|
70
|
+
const timeAsync = async (label, fn) => {
|
|
71
|
+
if (!isTimingEnabled())
|
|
72
|
+
return fn();
|
|
73
|
+
const start = performance.now();
|
|
74
|
+
try {
|
|
75
|
+
return await fn();
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
(0, exports.logTiming)(performance.now() - start, label);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
exports.timeAsync = timeAsync;
|
|
82
|
+
/**
|
|
83
|
+
* Shortens long absolute paths to a workspace-relative form for log lines.
|
|
84
|
+
* "/@fs/Users/mta/.../manager1/libs/css/core/src/lib/core.css"
|
|
85
|
+
* -> "libs/css/core/src/lib/core.css"
|
|
86
|
+
*/
|
|
87
|
+
const shortenPath = (urlOrPath) => {
|
|
88
|
+
const idx = urlOrPath.indexOf("/manager1/");
|
|
89
|
+
if (idx >= 0)
|
|
90
|
+
return urlOrPath.substring(idx + "/manager1/".length);
|
|
91
|
+
const fsPrefix = "/@fs/";
|
|
92
|
+
if (urlOrPath.startsWith(fsPrefix))
|
|
93
|
+
return urlOrPath.substring(fsPrefix.length);
|
|
94
|
+
return urlOrPath;
|
|
95
|
+
};
|
|
96
|
+
exports.shortenPath = shortenPath;
|
|
97
|
+
/**
|
|
98
|
+
* Logs the total server-side response time per request via the `finish` event,
|
|
99
|
+
* which fires once the response has been fully written. This corresponds to
|
|
100
|
+
* the `wait` time you see in browser network panels.
|
|
101
|
+
*
|
|
102
|
+
* Combined with the per-handler timings inside our own middleware, it lets us
|
|
103
|
+
* see whether slowness is in our code or in downstream Vite work (transforms,
|
|
104
|
+
* optimizeDeps, etc).
|
|
105
|
+
*/
|
|
106
|
+
const wrapResponseTiming = (req, res) => {
|
|
107
|
+
if (!isTimingEnabled())
|
|
108
|
+
return;
|
|
109
|
+
const start = performance.now();
|
|
110
|
+
res.on("finish", () => {
|
|
111
|
+
const durationMs = performance.now() - start;
|
|
112
|
+
if (durationMs >= TRANSFORM_LOG_THRESHOLD_MS) {
|
|
113
|
+
const url = req.url ?? "(no url)";
|
|
114
|
+
(0, exports.logTiming)(durationMs, `${req.method ?? "GET"} ${(0, exports.shortenPath)(url)}`);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
exports.wrapResponseTiming = wrapResponseTiming;
|
|
119
|
+
//# sourceMappingURL=devServerTiming.js.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createExtensionLoaderCache = void 0;
|
|
4
|
+
const devServerTiming_1 = require("./devServerTiming");
|
|
5
|
+
const DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
6
|
+
/**
|
|
7
|
+
* Creates an in-memory cache for the /extensionloader.js script.
|
|
8
|
+
*
|
|
9
|
+
* The /extensionloader.js handler proxies to https://iris.trackunit.app
|
|
10
|
+
* (or http://localhost:3000 when LOCAL=true). Without caching, every page
|
|
11
|
+
* load triggers a fresh network fetch -- and during a single cold load Vite
|
|
12
|
+
* typically reloads the page 2-3 times (because of optimizeDeps cascades),
|
|
13
|
+
* so the upstream is hit 3+ times. HAR profiling showed individual fetches
|
|
14
|
+
* costing 200ms-5s+ depending on CDN health, and one trace recorded a single
|
|
15
|
+
* ~5.2s fetch as the dominant gap in the dev waterfall.
|
|
16
|
+
*
|
|
17
|
+
* The script changes very rarely, so we cache it in memory with a TTL and
|
|
18
|
+
* dedupe concurrent fetches.
|
|
19
|
+
*/
|
|
20
|
+
const createExtensionLoaderCache = ({ getUrl, ttlMs = DEFAULT_TTL_MS, shouldBypassCache, }) => {
|
|
21
|
+
let cached = null;
|
|
22
|
+
let inflight = null;
|
|
23
|
+
const getCachedExtensionLoaderBody = async () => {
|
|
24
|
+
const url = getUrl();
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
const bypass = shouldBypassCache?.() === true;
|
|
27
|
+
if (!bypass && cached !== null && cached.url === url && now - cached.fetchedAt < ttlMs) {
|
|
28
|
+
return cached.body;
|
|
29
|
+
}
|
|
30
|
+
if (inflight !== null) {
|
|
31
|
+
const settled = await inflight;
|
|
32
|
+
return settled.body;
|
|
33
|
+
}
|
|
34
|
+
const fetchPromise = (async () => {
|
|
35
|
+
(0, devServerTiming_1.logEvent)(`extensionloader cache: cold load (${url})`);
|
|
36
|
+
const body = await (0, devServerTiming_1.timeAsync)(`fetch ${url}`, async () => {
|
|
37
|
+
const resp = await fetch(url);
|
|
38
|
+
if (!resp.ok) {
|
|
39
|
+
throw new Error(`upstream returned ${resp.status} ${resp.statusText}`);
|
|
40
|
+
}
|
|
41
|
+
return resp.text();
|
|
42
|
+
});
|
|
43
|
+
cached = { url, body, fetchedAt: Date.now() };
|
|
44
|
+
return { url, body };
|
|
45
|
+
})();
|
|
46
|
+
inflight = fetchPromise;
|
|
47
|
+
// Clear the inflight pointer once the fetch settles. Use then(cleanup, cleanup)
|
|
48
|
+
// instead of finally() so a rejection isn't re-thrown from this side-channel;
|
|
49
|
+
// the caller still observes the rejection via the awaited `fetchPromise`.
|
|
50
|
+
const clearInflightIfStillCurrent = () => {
|
|
51
|
+
if (inflight === fetchPromise) {
|
|
52
|
+
inflight = null;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
fetchPromise.then(clearInflightIfStillCurrent, clearInflightIfStillCurrent);
|
|
56
|
+
const fetched = await fetchPromise;
|
|
57
|
+
return fetched.body;
|
|
58
|
+
};
|
|
59
|
+
return { getCachedExtensionLoaderBody };
|
|
60
|
+
};
|
|
61
|
+
exports.createExtensionLoaderCache = createExtensionLoaderCache;
|
|
62
|
+
//# sourceMappingURL=extensionLoaderCache.js.map
|
|
@@ -11,6 +11,9 @@ const path = tslib_1.__importStar(require("node:path"));
|
|
|
11
11
|
const vite_2 = require("vite");
|
|
12
12
|
const vite_plugin_checker_1 = tslib_1.__importDefault(require("vite-plugin-checker"));
|
|
13
13
|
const vite_plugin_css_injected_by_js_1 = tslib_1.__importDefault(require("vite-plugin-css-injected-by-js"));
|
|
14
|
+
const devServerTiming_1 = require("./devServerTiming");
|
|
15
|
+
const extensionLoaderCache_1 = require("./extensionLoaderCache");
|
|
16
|
+
const manifestCache_1 = require("./manifestCache");
|
|
14
17
|
// Only import enableTsConfigPath statically - other utilities are dynamically imported
|
|
15
18
|
// AFTER tsconfig paths are registered to avoid oxc analysis warnings
|
|
16
19
|
// MIME types for serving extension assets during development
|
|
@@ -57,7 +60,7 @@ const registerTsConfigPaths = (workspaceRoot, appDir) => {
|
|
|
57
60
|
* Loads the Iris app manifest from the app directory.
|
|
58
61
|
* Uses require() with cache clearing since Node.js doesn't support query strings in import paths.
|
|
59
62
|
*/
|
|
60
|
-
const loadManifest = (workspaceRoot, appDir) => {
|
|
63
|
+
const loadManifest = (workspaceRoot, appDir) => (0, devServerTiming_1.time)("loadManifest (re-import iris-app-manifest.ts)", () => {
|
|
61
64
|
// Register tsconfig paths before loading the manifest
|
|
62
65
|
registerTsConfigPaths(workspaceRoot, appDir);
|
|
63
66
|
const manifestPath = path.join(appDir, "iris-app-manifest.ts");
|
|
@@ -72,7 +75,7 @@ const loadManifest = (workspaceRoot, appDir) => {
|
|
|
72
75
|
const manifestModule = require(manifestPath);
|
|
73
76
|
manifestModule.default.moduleFormat = "esm";
|
|
74
77
|
return manifestModule.default;
|
|
75
|
-
};
|
|
78
|
+
});
|
|
76
79
|
/**
|
|
77
80
|
* Converts the shared dependencies from webpack format to Vite module federation format.
|
|
78
81
|
* Handles both array and object variants of the Shared type.
|
|
@@ -138,7 +141,7 @@ const generateIndexHtml = ({ manifest, packageJson, options, }) => {
|
|
|
138
141
|
</script>`
|
|
139
142
|
: "";
|
|
140
143
|
const devtools = options.config.mode === "development"
|
|
141
|
-
? `<script nonce="{{nonce}}" src="http://localhost:8097"></script>
|
|
144
|
+
? `<script nonce="{{nonce}}" defer src="http://localhost:8097"></script>
|
|
142
145
|
${iframeRedirect}
|
|
143
146
|
<script nonce="{{nonce}}">
|
|
144
147
|
if (typeof global === 'undefined') { window.global = window; }
|
|
@@ -159,18 +162,20 @@ const generateIndexHtml = ({ manifest, packageJson, options, }) => {
|
|
|
159
162
|
* Returns an array of plugins including Module Federation configuration.
|
|
160
163
|
*/
|
|
161
164
|
async function getTrackunitIrisAppVitePlugins(options) {
|
|
165
|
+
const pluginConstructionStart = performance.now();
|
|
166
|
+
(0, devServerTiming_1.logEvent)("plugin construction: started");
|
|
162
167
|
const resolvedAppDir = path.resolve(options.appDir);
|
|
163
168
|
const workspaceRoot = options.workspaceRoot ?? path.resolve(options.appDir, "../..");
|
|
164
169
|
// Load manifest upfront for federation config
|
|
165
170
|
const initialManifest = loadManifest(workspaceRoot, resolvedAppDir);
|
|
166
171
|
// Get build utilities (tsconfig paths now registered via loadManifest)
|
|
167
172
|
// Get an available port in the Iris app port range
|
|
168
|
-
const port = await (0, iris_app_build_utilities_1.getAvailablePort)(22220, 22229);
|
|
173
|
+
const port = await (0, devServerTiming_1.timeAsync)("getAvailablePort", () => (0, iris_app_build_utilities_1.getAvailablePort)(22220, 22229));
|
|
169
174
|
// Get module federation configuration from manifest
|
|
170
|
-
const webpackExposes = await (0, iris_app_build_utilities_1.getExposedExtensions)({
|
|
175
|
+
const webpackExposes = await (0, devServerTiming_1.timeAsync)(`getExposedExtensions (${initialManifest.extensions.length} extensions)`, () => (0, iris_app_build_utilities_1.getExposedExtensions)({
|
|
171
176
|
nxRootDir: workspaceRoot,
|
|
172
177
|
manifest: initialManifest,
|
|
173
|
-
});
|
|
178
|
+
}));
|
|
174
179
|
const shared = (0, iris_app_build_utilities_1.getSharedDependencies)({ manifest: initialManifest });
|
|
175
180
|
const viteExposes = convertExposesToViteFormat(webpackExposes);
|
|
176
181
|
const viteShared = convertSharedToViteFormat(shared);
|
|
@@ -246,7 +251,6 @@ async function getTrackunitIrisAppVitePlugins(options) {
|
|
|
246
251
|
};
|
|
247
252
|
// Create the iris app plugin
|
|
248
253
|
let config;
|
|
249
|
-
let manifest = initialManifest;
|
|
250
254
|
const reloadManifest = () => {
|
|
251
255
|
return loadManifest(workspaceRoot, resolvedAppDir);
|
|
252
256
|
};
|
|
@@ -259,6 +263,23 @@ async function getTrackunitIrisAppVitePlugins(options) {
|
|
|
259
263
|
manifestCopy.extensions = (0, iris_app_build_utilities_1.updateExtensions)([...manifestData.extensions]);
|
|
260
264
|
return (0, iris_app_build_utilities_1.injectFullDescriptionImages)(manifestCopy, resolvedAppDir);
|
|
261
265
|
};
|
|
266
|
+
const manifestCache = (0, manifestCache_1.createManifestCache)({
|
|
267
|
+
reloadManifest,
|
|
268
|
+
generateManifestJson,
|
|
269
|
+
initialManifest,
|
|
270
|
+
});
|
|
271
|
+
// The /extensionloader.js handler proxies to https://iris.trackunit.app
|
|
272
|
+
// (or http://localhost:3000 when LOCAL=true). The script changes very
|
|
273
|
+
// rarely, so we cache the body in memory with a TTL and dedupe concurrent
|
|
274
|
+
// fetches via createExtensionLoaderCache. Set IRIS_EXTENSIONLOADER_NO_CACHE=1
|
|
275
|
+
// to bypass the cache for debugging.
|
|
276
|
+
const getExtensionLoaderUrl = () => process.env.LOCAL === "true"
|
|
277
|
+
? "http://localhost:3000/extensionloader.js"
|
|
278
|
+
: "https://iris.trackunit.app/extensionloader.js";
|
|
279
|
+
const extensionLoaderCache = (0, extensionLoaderCache_1.createExtensionLoaderCache)({
|
|
280
|
+
getUrl: getExtensionLoaderUrl,
|
|
281
|
+
shouldBypassCache: () => process.env.IRIS_EXTENSIONLOADER_NO_CACHE === "1",
|
|
282
|
+
});
|
|
262
283
|
// No explicit type annotation to avoid excessive stack depth error with federation plugin's complex types
|
|
263
284
|
const irisAppPlugin = {
|
|
264
285
|
name: "trackunit-iris-app-vite-configuration",
|
|
@@ -285,7 +306,7 @@ async function getTrackunitIrisAppVitePlugins(options) {
|
|
|
285
306
|
},
|
|
286
307
|
},
|
|
287
308
|
// PostCSS configuration for Tailwind CSS v4
|
|
288
|
-
// Using @tailwindcss/postcss instead of @tailwindcss/vite for better monorepo support
|
|
309
|
+
// Using @tailwindcss/postcss instead of @tailwindcss/vite for better monorepo support.
|
|
289
310
|
css: {
|
|
290
311
|
postcss: {
|
|
291
312
|
plugins: [require("@tailwindcss/postcss")({})],
|
|
@@ -358,15 +379,37 @@ async function getTrackunitIrisAppVitePlugins(options) {
|
|
|
358
379
|
config = resolvedConfig;
|
|
359
380
|
},
|
|
360
381
|
buildStart() {
|
|
361
|
-
|
|
382
|
+
manifestCache.reloadManifestSync();
|
|
362
383
|
},
|
|
363
384
|
configureServer(server) {
|
|
385
|
+
// Watch iris-app-manifest.ts and invalidate the manifest cache on change so
|
|
386
|
+
// HMR picks up edits (e.g. adding/removing extensions). All other paths
|
|
387
|
+
// serve from the cache populated by the first request.
|
|
388
|
+
const manifestFilePath = path.join(resolvedAppDir, "iris-app-manifest.ts");
|
|
389
|
+
server.watcher.add(manifestFilePath);
|
|
390
|
+
server.watcher.on("change", changedPath => {
|
|
391
|
+
if (path.normalize(changedPath) === path.normalize(manifestFilePath)) {
|
|
392
|
+
manifestCache.invalidateManifestCache();
|
|
393
|
+
(0, devServerTiming_1.logEvent)("manifest cache: invalidated (iris-app-manifest.ts changed)");
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
// Request timing middleware - MUST be registered before any other middleware
|
|
397
|
+
// so we observe the full server-side response time. Logs to stdout when
|
|
398
|
+
// the response takes longer than TRANSFORM_LOG_THRESHOLD_MS. Enable with
|
|
399
|
+
// IRIS_TIMING=true.
|
|
400
|
+
server.middlewares.use((req, res, next) => {
|
|
401
|
+
(0, devServerTiming_1.wrapResponseTiming)(req, res);
|
|
402
|
+
next();
|
|
403
|
+
});
|
|
364
404
|
// Route /invoke requests to local serverside extensions when available.
|
|
365
405
|
server.middlewares.use("/invoke", (0, iris_app_build_utilities_1.createInvokeProxyMiddleware)(options.serversidePortMap));
|
|
366
406
|
// Serve extension assets from source during development (/{extensionId}/... → {sourceRoot}/...)
|
|
367
407
|
server.middlewares.use((req, res, next) => {
|
|
368
408
|
const url = req.url?.split("?")[0] ?? "/";
|
|
369
|
-
|
|
409
|
+
// Use the in-memory manifest if available; fall back to initialManifest
|
|
410
|
+
// (loaded once at plugin construction) so this synchronous middleware
|
|
411
|
+
// never blocks on a slow manifest reload.
|
|
412
|
+
const filePath = resolveExtensionAsset(url, manifestCache.getManifestSync() ?? initialManifest, workspaceRoot);
|
|
370
413
|
if (filePath) {
|
|
371
414
|
res.setHeader("Content-Type", getMimeType(filePath));
|
|
372
415
|
res.end(fs.readFileSync(filePath));
|
|
@@ -415,8 +458,7 @@ async function getTrackunitIrisAppVitePlugins(options) {
|
|
|
415
458
|
if (req.url === "/manifest.json") {
|
|
416
459
|
void (async () => {
|
|
417
460
|
try {
|
|
418
|
-
|
|
419
|
-
const manifestJson = generateManifestJson(manifest);
|
|
461
|
+
const manifestJson = await manifestCache.getCachedManifestJson();
|
|
420
462
|
res.setHeader("Content-Type", "application/json");
|
|
421
463
|
res.end(JSON.stringify(manifestJson, null, 2));
|
|
422
464
|
}
|
|
@@ -444,14 +486,15 @@ async function getTrackunitIrisAppVitePlugins(options) {
|
|
|
444
486
|
}
|
|
445
487
|
}
|
|
446
488
|
// Serve manifestAndToken during development (async route - handle response ourselves)
|
|
489
|
+
// Calls getCachedManifestJson directly instead of HTTP-fetching /manifest.json
|
|
490
|
+
// to avoid a redundant manifest reload on every request (the host calls this
|
|
491
|
+
// endpoint multiple times during initial app boot).
|
|
447
492
|
if (req.url?.startsWith("/manifestAndToken")) {
|
|
448
493
|
void (async () => {
|
|
449
494
|
try {
|
|
450
|
-
const
|
|
451
|
-
const resp = await fetch(`http://${host}/manifest.json`);
|
|
452
|
-
const body = await resp.json();
|
|
495
|
+
const manifestJson = await manifestCache.getCachedManifestJson();
|
|
453
496
|
res.setHeader("Content-Type", "application/json");
|
|
454
|
-
res.end(JSON.stringify({ manifest:
|
|
497
|
+
res.end(JSON.stringify({ manifest: manifestJson }));
|
|
455
498
|
}
|
|
456
499
|
catch (e) {
|
|
457
500
|
// eslint-disable-next-line no-console
|
|
@@ -462,17 +505,13 @@ async function getTrackunitIrisAppVitePlugins(options) {
|
|
|
462
505
|
})();
|
|
463
506
|
return; // Don't call next() - we're handling this route
|
|
464
507
|
}
|
|
465
|
-
// Serve extensionloader.js during development
|
|
508
|
+
// Serve extensionloader.js during development.
|
|
509
|
+
// To test extensionloader locally set LOCAL=true in front of the serve command.
|
|
510
|
+
// The fetched body is cached in memory; see createExtensionLoaderCache.
|
|
466
511
|
if (req.url?.startsWith("/extensionloader.js")) {
|
|
467
512
|
void (async () => {
|
|
468
513
|
try {
|
|
469
|
-
|
|
470
|
-
let loaderUrl = "https://iris.trackunit.app/extensionloader.js";
|
|
471
|
-
if (process.env.LOCAL === "true") {
|
|
472
|
-
loaderUrl = "http://localhost:3000/extensionloader.js";
|
|
473
|
-
}
|
|
474
|
-
const resp = await fetch(loaderUrl);
|
|
475
|
-
const body = await resp.text();
|
|
514
|
+
const body = await extensionLoaderCache.getCachedExtensionLoaderBody();
|
|
476
515
|
res.setHeader("Content-Type", "application/javascript");
|
|
477
516
|
res.end(body);
|
|
478
517
|
}
|
|
@@ -494,11 +533,11 @@ async function getTrackunitIrisAppVitePlugins(options) {
|
|
|
494
533
|
if (isRootOrSpaRoute) {
|
|
495
534
|
void (async () => {
|
|
496
535
|
try {
|
|
497
|
-
|
|
536
|
+
const currentManifest = await manifestCache.getCachedManifest();
|
|
498
537
|
const packageJsonPath = path.join(resolvedAppDir, "package.json");
|
|
499
538
|
const packageJson = fs.readFileSync(packageJsonPath, "utf8");
|
|
500
|
-
const html = generateIndexHtml({ manifest, packageJson, options });
|
|
501
|
-
const transformedHtml = await server.transformIndexHtml(req.url ?? "/", html);
|
|
539
|
+
const html = generateIndexHtml({ manifest: currentManifest, packageJson, options });
|
|
540
|
+
const transformedHtml = await (0, devServerTiming_1.timeAsync)(`server.transformIndexHtml(${(0, devServerTiming_1.shortenPath)(url)})`, () => server.transformIndexHtml(req.url ?? "/", html));
|
|
502
541
|
res.setHeader("Content-Type", "text/html");
|
|
503
542
|
res.end(transformedHtml);
|
|
504
543
|
}
|
|
@@ -516,15 +555,14 @@ async function getTrackunitIrisAppVitePlugins(options) {
|
|
|
516
555
|
});
|
|
517
556
|
// Log success message when server starts
|
|
518
557
|
server.httpServer?.once("listening", () => {
|
|
558
|
+
(0, devServerTiming_1.logEvent)("server: listening");
|
|
519
559
|
setTimeout(() => {
|
|
520
560
|
(0, iris_app_build_utilities_1.logInfo)(`\n✨ Iris App is now started, check it out ✨\nhttps://new.manager.trackunit.com/goto/iris-app-dev\n`);
|
|
521
561
|
}, 100);
|
|
522
562
|
});
|
|
523
563
|
},
|
|
524
564
|
generateBundle() {
|
|
525
|
-
|
|
526
|
-
manifest = reloadManifest();
|
|
527
|
-
}
|
|
565
|
+
const manifest = manifestCache.getManifestSync() ?? manifestCache.reloadManifestSync();
|
|
528
566
|
const packageJsonPath = path.join(resolvedAppDir, "package.json");
|
|
529
567
|
const packageJson = fs.readFileSync(packageJsonPath, "utf8");
|
|
530
568
|
// Generate and emit manifest.json
|
|
@@ -549,7 +587,7 @@ async function getTrackunitIrisAppVitePlugins(options) {
|
|
|
549
587
|
},
|
|
550
588
|
closeBundle() {
|
|
551
589
|
if (config.command === "build") {
|
|
552
|
-
const manifestForCopy =
|
|
590
|
+
const manifestForCopy = manifestCache.getManifestSync() ?? manifestCache.reloadManifestSync();
|
|
553
591
|
const copyPatterns = (0, iris_app_build_utilities_1.getCopyPatterns)({
|
|
554
592
|
nxRootDir: workspaceRoot,
|
|
555
593
|
appDir: resolvedAppDir,
|
|
@@ -604,7 +642,7 @@ async function getTrackunitIrisAppVitePlugins(options) {
|
|
|
604
642
|
//
|
|
605
643
|
// NOTE: Tailwind CSS is configured via PostCSS (in baseConfig.css.postcss above)
|
|
606
644
|
// NOT using @tailwindcss/vite because it doesn't respect tailwind.config.js content paths
|
|
607
|
-
// PostCSS approach matches Rspack and properly uses
|
|
645
|
+
// PostCSS approach matches Rspack and properly uses Tailwind v4 config files.
|
|
608
646
|
const plugins = [
|
|
609
647
|
(0, nx_tsconfig_paths_plugin_1.nxViteTsPaths)(),
|
|
610
648
|
// React Fast Refresh — enables HMR for React components so file changes
|
|
@@ -626,6 +664,7 @@ async function getTrackunitIrisAppVitePlugins(options) {
|
|
|
626
664
|
}),
|
|
627
665
|
bundleAnalyzerPlugin,
|
|
628
666
|
];
|
|
667
|
+
(0, devServerTiming_1.logTiming)(performance.now() - pluginConstructionStart, "plugin construction: complete");
|
|
629
668
|
return plugins.filter(Boolean);
|
|
630
669
|
}
|
|
631
670
|
//# sourceMappingURL=getTrackunitIrisAppVitePlugins.js.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createManifestCache = void 0;
|
|
4
|
+
const devServerTiming_1 = require("./devServerTiming");
|
|
5
|
+
/**
|
|
6
|
+
* Creates an in-memory cache for the Iris-app manifest.
|
|
7
|
+
*
|
|
8
|
+
* The cache deduplicates concurrent /manifest.json and /manifestAndToken
|
|
9
|
+
* requests so they don't re-resolve the manifest + all 25 extension entries
|
|
10
|
+
* on every call. Cold-load profiling showed each request taking ~3s; with the
|
|
11
|
+
* host firing several parallel calls during boot, that stacked into >6s of
|
|
12
|
+
* latency before any extension code could load.
|
|
13
|
+
*
|
|
14
|
+
* The cache is invalidated when iris-app-manifest.ts changes; callers wire
|
|
15
|
+
* that up through the dev server's file watcher.
|
|
16
|
+
*/
|
|
17
|
+
const createManifestCache = ({ reloadManifest, generateManifestJson, initialManifest, }) => {
|
|
18
|
+
let manifest = initialManifest;
|
|
19
|
+
let manifestJsonCache = null;
|
|
20
|
+
// Shared promise for an in-flight reload, used to dedupe concurrent requests
|
|
21
|
+
// that arrive while the cache is empty.
|
|
22
|
+
let inflightManifestReload = null;
|
|
23
|
+
const ensureManifestLoaded = () => {
|
|
24
|
+
if (manifest !== undefined && manifestJsonCache !== null) {
|
|
25
|
+
return Promise.resolve(manifestJsonCache);
|
|
26
|
+
}
|
|
27
|
+
if (inflightManifestReload !== null) {
|
|
28
|
+
(0, devServerTiming_1.logEvent)("manifest cache: joining in-flight reload (deduped)");
|
|
29
|
+
return inflightManifestReload;
|
|
30
|
+
}
|
|
31
|
+
(0, devServerTiming_1.logEvent)("manifest cache: cold load (cache empty)");
|
|
32
|
+
const reloadPromise = (async () => {
|
|
33
|
+
const loadedManifest = manifest ?? reloadManifest();
|
|
34
|
+
manifest = loadedManifest;
|
|
35
|
+
manifestJsonCache = (0, devServerTiming_1.time)("generateManifestJson", () => generateManifestJson(loadedManifest));
|
|
36
|
+
return manifestJsonCache;
|
|
37
|
+
})();
|
|
38
|
+
inflightManifestReload = reloadPromise;
|
|
39
|
+
// Clear the inflight pointer once the reload settles so post-invalidate
|
|
40
|
+
// reads start a fresh load instead of joining this resolved/rejected
|
|
41
|
+
// promise. The identity check guards against `invalidateManifestCache`
|
|
42
|
+
// having already nulled it out (or replaced it with a newer reload). We
|
|
43
|
+
// attach the cleanup with then(cleanup, cleanup) instead of finally() so
|
|
44
|
+
// a rejection isn't re-thrown from this side-channel; the caller still
|
|
45
|
+
// observes the rejection via the returned `reloadPromise`.
|
|
46
|
+
const clearInflightIfStillCurrent = () => {
|
|
47
|
+
if (inflightManifestReload === reloadPromise) {
|
|
48
|
+
inflightManifestReload = null;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
reloadPromise.then(clearInflightIfStillCurrent, clearInflightIfStillCurrent);
|
|
52
|
+
return reloadPromise;
|
|
53
|
+
};
|
|
54
|
+
const getCachedManifestJson = () => ensureManifestLoaded();
|
|
55
|
+
const getCachedManifest = async () => {
|
|
56
|
+
await ensureManifestLoaded();
|
|
57
|
+
if (manifest === undefined) {
|
|
58
|
+
throw new Error("Manifest unexpectedly undefined after reload");
|
|
59
|
+
}
|
|
60
|
+
return manifest;
|
|
61
|
+
};
|
|
62
|
+
const getManifestSync = () => manifest;
|
|
63
|
+
const reloadManifestSync = () => {
|
|
64
|
+
const fresh = reloadManifest();
|
|
65
|
+
manifest = fresh;
|
|
66
|
+
return fresh;
|
|
67
|
+
};
|
|
68
|
+
const invalidateManifestCache = () => {
|
|
69
|
+
manifestJsonCache = null;
|
|
70
|
+
manifest = undefined;
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
getCachedManifestJson,
|
|
74
|
+
getCachedManifest,
|
|
75
|
+
getManifestSync,
|
|
76
|
+
reloadManifestSync,
|
|
77
|
+
invalidateManifestCache,
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
exports.createManifestCache = createManifestCache;
|
|
81
|
+
//# sourceMappingURL=manifestCache.js.map
|