@trackunit/iris-app-sdk-vite 1.6.3 → 1.7.0
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 +11 -0
- package/README.md +28 -5
- package/package.json +1 -1
- package/src/executors/serve/executor.js +12 -0
- package/src/executors/serve/schema.json +5 -0
- package/src/executors/utils/defaultViteConfig.js +16 -0
- package/src/executors/utils/findProductionReactConflicts.js +67 -0
- package/src/executors/utils/getTrackunitIrisAppVitePlugins.js +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## 1.7.0 (2026-10-02)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- **iris-app-sdk-vite:** add productionReact option to serve [GLU-2569] ([#26121](https://github.com/Trackunit/manager/pull/26121), [#26118](https://github.com/Trackunit/manager/issues/26118))
|
|
6
|
+
|
|
7
|
+
### ❤️ Thank You
|
|
8
|
+
|
|
9
|
+
- Cursor @cursoragent
|
|
10
|
+
- Mikkel Thorbjørn Andersen @mta-trackunit
|
|
11
|
+
|
|
1
12
|
## 1.6.3 (2026-10-01)
|
|
2
13
|
|
|
3
14
|
### 🧱 Updated Dependencies
|
package/README.md
CHANGED
|
@@ -61,11 +61,12 @@ Add to your `project.json`:
|
|
|
61
61
|
|
|
62
62
|
### Serve Options
|
|
63
63
|
|
|
64
|
-
| Option
|
|
65
|
-
|
|
|
66
|
-
| `port`
|
|
67
|
-
| `host`
|
|
68
|
-
| `viteConfig`
|
|
64
|
+
| Option | Type | Required | Description |
|
|
65
|
+
| ----------------- | ------- | -------- | --------------------------------------------------------------------------------------------- |
|
|
66
|
+
| `port` | number | No | Dev server port (default: auto-select from 22220-22229) |
|
|
67
|
+
| `host` | string | No | Dev server host (default: "localhost") |
|
|
68
|
+
| `viteConfig` | string | No | Path to optional custom vite config file for overrides |
|
|
69
|
+
| `productionReact` | boolean | No | Serve React's production runtime instead of the development one (default: `false`). See below |
|
|
69
70
|
|
|
70
71
|
### Build Options
|
|
71
72
|
|
|
@@ -199,6 +200,28 @@ react-devtools
|
|
|
199
200
|
|
|
200
201
|
React-devtools will automatically connect to whichever Iris App is running in your browser.
|
|
201
202
|
|
|
203
|
+
### Reproducing production-only React behaviour
|
|
204
|
+
|
|
205
|
+
Deployed Iris apps are meant to run React's **production** runtime, while `serve` uses React's development runtime by default (warnings, `jsxDEV`, React Fast Refresh). To reproduce an issue that only shows up in a deployed app, serve with React's production runtime:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
nx run your-app:serve --productionReact
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
What changes with `--productionReact`:
|
|
212
|
+
|
|
213
|
+
- React and your JSX are compiled for production: no `jsxDEV`, no development warnings or prop logging, and React errors are reported as coded messages (see https://react.dev/errors).
|
|
214
|
+
- React Fast Refresh is turned off, so changes reload the page instead of hot-swapping components.
|
|
215
|
+
- Pre-bundled dependencies are re-optimized when you switch the option on or off, so you never get the other mode's React from Vite's cache.
|
|
216
|
+
|
|
217
|
+
What stays the same: the dev server routes, the manifest and the iframe redirect still work, and `process.env.NODE_ENV` of the Nx process is not changed. React DevTools still connects, with the reduced feature set of any production React build.
|
|
218
|
+
|
|
219
|
+
Things to know:
|
|
220
|
+
|
|
221
|
+
- The compile-time `define` for `process.env.NODE_ENV` (which affects your app code and pre-bundled dependencies) switches to `"production"`. Vite's `mode` stays `development`, so `import.meta.env.DEV`, `import.meta.env.PROD` and `import.meta.env.MODE` keep their development values, and the Nx process environment is unchanged.
|
|
222
|
+
- If your `vite.config.ts` extends the default config, keep its `define`, `oxc` and `optimizeDeps` settings and do not add a React plugin. Otherwise the option does not take effect (or the app fails with `jsxDEV is not a function`); `serve` prints a warning when it detects this.
|
|
223
|
+
- The option only affects `serve`. It never changes the output of `build`.
|
|
224
|
+
|
|
202
225
|
### Common Issues
|
|
203
226
|
|
|
204
227
|
**"Failed to resolve dependency" warnings**
|
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@ const op = tslib_1.__importStar(require("rxjs/operators"));
|
|
|
12
12
|
require("win-ca");
|
|
13
13
|
const checkCustomFields_1 = require("../utils/checkCustomFields");
|
|
14
14
|
const defaultViteConfig_1 = require("../utils/defaultViteConfig");
|
|
15
|
+
const findProductionReactConflicts_1 = require("../utils/findProductionReactConflicts");
|
|
15
16
|
const manifestLoader_1 = require("../utils/manifestLoader");
|
|
16
17
|
const resolveSdkVersion_1 = require("../utils/resolveSdkVersion");
|
|
17
18
|
/**
|
|
@@ -25,6 +26,7 @@ async function* serveExecutor(options, context) {
|
|
|
25
26
|
await (0, iris_app_build_utilities_1.checkPackageVersion)(false);
|
|
26
27
|
const settings = (0, iris_app_1.getSettings)();
|
|
27
28
|
const skipTypeChecking = options.skipTypeChecking ?? false;
|
|
29
|
+
const productionReact = options.productionReact ?? false;
|
|
28
30
|
// eslint-disable-next-line @trackunit/no-typescript-assertion, @typescript-eslint/no-non-null-assertion
|
|
29
31
|
const projectRoot = context.projectsConfigurations.projects[context.projectName].root;
|
|
30
32
|
const projectRootDir = (0, path_1.join)(context.root, projectRoot);
|
|
@@ -69,6 +71,7 @@ async function* serveExecutor(options, context) {
|
|
|
69
71
|
const defaultConfig = await (0, defaultViteConfig_1.getDefaultViteConfig)("development", context.root, projectRootDir, irisAppManifest, context, {
|
|
70
72
|
serversidePortMap: serversideResult.portMap,
|
|
71
73
|
skipTypeChecking,
|
|
74
|
+
productionReact,
|
|
72
75
|
// Surface a persistent rebuild reminder when the in-sync check was skipped.
|
|
73
76
|
degraded: checkCustomFieldsResult === "unverified",
|
|
74
77
|
});
|
|
@@ -83,6 +86,15 @@ async function* serveExecutor(options, context) {
|
|
|
83
86
|
}
|
|
84
87
|
finalConfig = customConfig;
|
|
85
88
|
}
|
|
89
|
+
if (productionReact) {
|
|
90
|
+
const conflicts = (0, findProductionReactConflicts_1.findProductionReactConflicts)(finalConfig);
|
|
91
|
+
if (conflicts.length > 0) {
|
|
92
|
+
const conflictLines = conflicts.map(conflict => ` - ${conflict}`).join("\n");
|
|
93
|
+
// eslint-disable-next-line no-console
|
|
94
|
+
console.warn(`\n ⚠️ --productionReact may not take effect, because the final Vite config (including your vite.config.ts) is inconsistent with it:\n${conflictLines}\n` +
|
|
95
|
+
" Keep the default config's `define`, `oxc` and `optimizeDeps` settings when you extend it, and do not add a React plugin.\n");
|
|
96
|
+
}
|
|
97
|
+
}
|
|
86
98
|
// Apply port override if specified
|
|
87
99
|
if (options.port !== undefined) {
|
|
88
100
|
finalConfig.server = { ...finalConfig.server, port: options.port };
|
|
@@ -28,6 +28,11 @@
|
|
|
28
28
|
"type": "boolean",
|
|
29
29
|
"description": "Skip TypeScript type checking during dev server",
|
|
30
30
|
"default": false
|
|
31
|
+
},
|
|
32
|
+
"productionReact": {
|
|
33
|
+
"type": "boolean",
|
|
34
|
+
"description": "Serve React's production runtime (no jsxDEV, no React dev warnings, no Fast Refresh) to reproduce issues that only show up in deployed apps. Only affects local serving; it never changes the output of build.",
|
|
35
|
+
"default": false
|
|
31
36
|
}
|
|
32
37
|
},
|
|
33
38
|
"required": []
|
|
@@ -28,6 +28,7 @@ const virtualEntryPlugin = {
|
|
|
28
28
|
return null;
|
|
29
29
|
},
|
|
30
30
|
};
|
|
31
|
+
const PRODUCTION_NODE_ENV = JSON.stringify("production");
|
|
31
32
|
/**
|
|
32
33
|
* Gets the default Vite config for Iris Apps.
|
|
33
34
|
*
|
|
@@ -39,6 +40,7 @@ const virtualEntryPlugin = {
|
|
|
39
40
|
* @returns {Promise<UserConfig>} the default Vite config
|
|
40
41
|
*/
|
|
41
42
|
const getDefaultViteConfig = async (mode, nxRootDir, appDir, irisAppManifest, context, options = {}) => {
|
|
43
|
+
const productionReact = options.productionReact ?? false;
|
|
42
44
|
// Dynamic import - safe after enableTsConfigPath() is called
|
|
43
45
|
const irisAppPlugins = await (0, getTrackunitIrisAppVitePlugins_1.getTrackunitIrisAppVitePlugins)({
|
|
44
46
|
appDir,
|
|
@@ -51,6 +53,7 @@ const getDefaultViteConfig = async (mode, nxRootDir, appDir, irisAppManifest, co
|
|
|
51
53
|
serversidePortMap: options.serversidePortMap,
|
|
52
54
|
skipTypeChecking: options.skipTypeChecking,
|
|
53
55
|
degraded: options.degraded,
|
|
56
|
+
productionReact,
|
|
54
57
|
});
|
|
55
58
|
return {
|
|
56
59
|
root: appDir,
|
|
@@ -63,9 +66,22 @@ const getDefaultViteConfig = async (mode, nxRootDir, appDir, irisAppManifest, co
|
|
|
63
66
|
rolldownOptions: {
|
|
64
67
|
transform: {
|
|
65
68
|
target: "esnext",
|
|
69
|
+
// Pre-bundled React picks its runtime from this define. Vite also hashes it into the
|
|
70
|
+
// dependency cache key, so toggling the option re-optimizes instead of reusing the
|
|
71
|
+
// other mode's React.
|
|
72
|
+
...(productionReact ? { define: { "process.env.NODE_ENV": PRODUCTION_NODE_ENV } } : {}),
|
|
66
73
|
},
|
|
67
74
|
},
|
|
68
75
|
},
|
|
76
|
+
...(productionReact
|
|
77
|
+
? {
|
|
78
|
+
// `NODE_ENV` for the app's own code, without touching `process.env` (the custom-field
|
|
79
|
+
// escape-hatch guard reads the real environment).
|
|
80
|
+
define: { "process.env.NODE_ENV": PRODUCTION_NODE_ENV },
|
|
81
|
+
// Replaces the React plugin's serve transform, which always emits `jsxDEV`.
|
|
82
|
+
oxc: { jsx: { runtime: "automatic", development: false } },
|
|
83
|
+
}
|
|
84
|
+
: {}),
|
|
69
85
|
build: {
|
|
70
86
|
outDir: `dist/apps/${context.projectName}`,
|
|
71
87
|
emptyOutDir: true,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findProductionReactConflicts = void 0;
|
|
4
|
+
const NODE_ENV_DEFINE_KEY = "process.env.NODE_ENV";
|
|
5
|
+
const PRODUCTION_DEFINE_VALUE = '"production"';
|
|
6
|
+
const flattenPluginNames = (plugins) => (plugins ?? []).flatMap((plugin) => {
|
|
7
|
+
if (Array.isArray(plugin)) {
|
|
8
|
+
return flattenPluginNames(plugin);
|
|
9
|
+
}
|
|
10
|
+
if (typeof plugin === "object" && plugin !== null && "name" in plugin && typeof plugin.name === "string") {
|
|
11
|
+
return [plugin.name];
|
|
12
|
+
}
|
|
13
|
+
return [];
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* Finds what in the final Vite config (default config merged with the app's own `vite.config.ts`)
|
|
17
|
+
* defeats `serve --productionReact`. The option relies on three settings from the default config
|
|
18
|
+
* and on the React plugin being absent; an app config that replaces or adds them silently serves
|
|
19
|
+
* React's development runtime again, or crashes because `jsxDEV` is undefined in production React.
|
|
20
|
+
*
|
|
21
|
+
* @param config - the final Vite config that is handed to the dev server
|
|
22
|
+
* @returns {Array<string>} one human readable problem per conflict, empty when the config is consistent
|
|
23
|
+
*/
|
|
24
|
+
const findProductionReactConflicts = (config) => {
|
|
25
|
+
const conflicts = [];
|
|
26
|
+
const defineValue = config.define?.[NODE_ENV_DEFINE_KEY];
|
|
27
|
+
if (defineValue === undefined) {
|
|
28
|
+
conflicts.push(`\`define["${NODE_ENV_DEFINE_KEY}"]\` is missing, so the app's own code keeps React's development runtime`);
|
|
29
|
+
}
|
|
30
|
+
else if (defineValue !== PRODUCTION_DEFINE_VALUE) {
|
|
31
|
+
conflicts.push(`\`define["${NODE_ENV_DEFINE_KEY}"]\` is ${JSON.stringify(defineValue)} instead of ${JSON.stringify(PRODUCTION_DEFINE_VALUE)}, so the app's own code keeps React's development runtime`);
|
|
32
|
+
}
|
|
33
|
+
const optimizerDefineValue = config.optimizeDeps?.rolldownOptions?.transform?.define?.[NODE_ENV_DEFINE_KEY];
|
|
34
|
+
if (optimizerDefineValue === undefined) {
|
|
35
|
+
conflicts.push(`\`optimizeDeps.rolldownOptions.transform.define["${NODE_ENV_DEFINE_KEY}"]\` is missing, so pre-bundled dependencies (React itself) keep the development runtime`);
|
|
36
|
+
}
|
|
37
|
+
else if (optimizerDefineValue !== PRODUCTION_DEFINE_VALUE) {
|
|
38
|
+
conflicts.push(`\`optimizeDeps.rolldownOptions.transform.define["${NODE_ENV_DEFINE_KEY}"]\` is ${JSON.stringify(optimizerDefineValue)} instead of ${JSON.stringify(PRODUCTION_DEFINE_VALUE)}, so pre-bundled dependencies (React itself) keep the development runtime`);
|
|
39
|
+
}
|
|
40
|
+
if (config.oxc === undefined || config.oxc === false) {
|
|
41
|
+
conflicts.push("`oxc.jsx` is missing, so JSX is not compiled with the production runtime");
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
const jsxConfig = typeof config.oxc === "object" ? config.oxc.jsx : undefined;
|
|
45
|
+
if (jsxConfig === undefined) {
|
|
46
|
+
conflicts.push("`oxc.jsx` is missing, so JSX is not compiled with the production runtime");
|
|
47
|
+
}
|
|
48
|
+
else if (typeof jsxConfig === "string") {
|
|
49
|
+
conflicts.push(`\`oxc.jsx\` is set to ${JSON.stringify(jsxConfig)} instead of an object with \`runtime: "automatic"\` and \`development: false\``);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
if (jsxConfig.runtime !== "automatic") {
|
|
53
|
+
conflicts.push(`\`oxc.jsx.runtime\` must be \`"automatic"\` for production React, got ${JSON.stringify(jsxConfig.runtime ?? "undefined")}`);
|
|
54
|
+
}
|
|
55
|
+
if (jsxConfig.development !== false) {
|
|
56
|
+
conflicts.push(`\`oxc.jsx.development\` must be \`false\` for production React (got ${JSON.stringify(jsxConfig.development ?? "undefined")})`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const reactPlugins = flattenPluginNames(config.plugins).filter(name => name.startsWith("vite:react"));
|
|
61
|
+
if (reactPlugins.length > 0) {
|
|
62
|
+
conflicts.push(`a React plugin is registered (${reactPlugins.join(", ")}); its serve transform emits \`jsxDEV\`, which is undefined in React's production runtime`);
|
|
63
|
+
}
|
|
64
|
+
return conflicts;
|
|
65
|
+
};
|
|
66
|
+
exports.findProductionReactConflicts = findProductionReactConflicts;
|
|
67
|
+
//# sourceMappingURL=findProductionReactConflicts.js.map
|
|
@@ -677,7 +677,9 @@ async function getTrackunitIrisAppVitePlugins(options) {
|
|
|
677
677
|
// React Fast Refresh — enables HMR for React components so file changes
|
|
678
678
|
// are hot-swapped without a full page reload. Safe with Module Federation
|
|
679
679
|
// because Iris apps bundle their own React (iframe isolation).
|
|
680
|
-
|
|
680
|
+
// Skipped when serving with React's production runtime: its serve transform is
|
|
681
|
+
// development-only (`jsxDEV`, which is `undefined` in React's production build).
|
|
682
|
+
!options.productionReact && (0, plugin_react_swc_1.default)({}),
|
|
681
683
|
(0, tailwindSourcesPlugin_1.createTailwindSourcesPlugin)({ rootDir: workspaceRoot }),
|
|
682
684
|
hasExposes && disableHostAutoInitPlugin,
|
|
683
685
|
federationPlugin,
|