create-interview-cockpit 0.10.0 → 0.12.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.
@@ -5,7 +5,65 @@ export type FrontendLabType = FrontendLabWorkspace["type"];
5
5
  // ── Default file contents ────────────────────────────────────────────────────
6
6
 
7
7
  const REACT_DEFAULT_FILES: Record<string, string> = {
8
- "App.tsx": `import { useState } from "react";
8
+ "package.json": `{
9
+ "name": "react-lab",
10
+ "private": true,
11
+ "version": "0.0.0",
12
+ "type": "module",
13
+ "scripts": {
14
+ "dev": "vite",
15
+ "build": "tsc -b && vite build",
16
+ "preview": "vite preview"
17
+ },
18
+ "dependencies": {
19
+ "react": "^18.3.1",
20
+ "react-dom": "^18.3.1"
21
+ },
22
+ "devDependencies": {
23
+ "@types/react": "^18.3.1",
24
+ "@types/react-dom": "^18.3.1",
25
+ "@vitejs/plugin-react": "^4.3.4",
26
+ "typescript": "^5.6.2",
27
+ "vite": "^6.0.3"
28
+ }
29
+ }
30
+ `,
31
+ "vite.config.ts": `import { defineConfig } from "vite";
32
+ import react from "@vitejs/plugin-react";
33
+
34
+ export default defineConfig({
35
+ plugins: [react()],
36
+ server: {
37
+ headers: {
38
+ "X-Frame-Options": "ALLOWALL",
39
+ },
40
+ },
41
+ });
42
+ `,
43
+ "index.html": `<!DOCTYPE html>
44
+ <html lang="en">
45
+ <head>
46
+ <meta charset="UTF-8" />
47
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
48
+ <title>React Lab</title>
49
+ </head>
50
+ <body>
51
+ <div id="root"></div>
52
+ <script type="module" src="/src/main.tsx"></script>
53
+ </body>
54
+ </html>
55
+ `,
56
+ "src/main.tsx": `import React from "react";
57
+ import ReactDOM from "react-dom/client";
58
+ import App from "./App";
59
+
60
+ ReactDOM.createRoot(document.getElementById("root")!).render(
61
+ <React.StrictMode>
62
+ <App />
63
+ </React.StrictMode>,
64
+ );
65
+ `,
66
+ "src/App.tsx": `import { useState } from "react";
9
67
  import { Counter } from "./Counter";
10
68
  import type { User } from "./types";
11
69
 
@@ -30,7 +88,7 @@ export default function App() {
30
88
  );
31
89
  }
32
90
  `,
33
- "Counter.tsx": `import { useState, useCallback } from "react";
91
+ "src/Counter.tsx": `import { useState, useCallback } from "react";
34
92
  import type { CounterProps } from "./types";
35
93
 
36
94
  // Stateful child component — receives props from App
@@ -88,7 +146,7 @@ export function Counter({ initialCount = 0, onCountChange }: CounterProps) {
88
146
  );
89
147
  }
90
148
  `,
91
- "types.ts": `// Type definitions — shared across components
149
+ "src/types.ts": `// Type definitions — shared across components
92
150
 
93
151
  export interface User {
94
152
  name: string;
@@ -216,10 +274,12 @@ This lab uses real webpack 5 + webpack-dev-server + Module Federation.
216
274
 
217
275
  ## What is here
218
276
 
219
- - package.json runs three apps together: a host plus two remotes
277
+ - the root package.json orchestrates three npm workspaces: a host plus two remotes
278
+ - each app owns its own dependencies in its local package.json
220
279
  - apps/host consumes federated modules from the remotes
221
280
  - apps/profile exposes a profile widget
222
281
  - apps/checkout exposes a checkout widget
282
+ - react-router-dom is declared in each app package.json so routing experiments work across apps
223
283
 
224
284
  ## Good experiments
225
285
 
@@ -231,33 +291,29 @@ This lab uses real webpack 5 + webpack-dev-server + Module Federation.
231
291
  ## Notes
232
292
 
233
293
  - Ports are injected by the lab runner through environment variables.
234
- - If you change package.json, restart the webpack lab so dependencies/scripts are re-read.
294
+ - Each webpack config builds its shared dependency list from that app's runtime dependencies.
295
+ - If you add or remove dependencies in an app package.json, restart the webpack lab so npm workspaces reinstall them.
235
296
  `,
236
297
  "package.json": `{
237
298
  "name": "webpack-module-federation-lab",
238
299
  "private": true,
300
+ "workspaces": [
301
+ "apps/host",
302
+ "apps/profile",
303
+ "apps/checkout"
304
+ ],
239
305
  "scripts": {
240
- "dev": "concurrently -k -n host,profile,checkout -c cyan,magenta,yellow 'npm --prefix apps/host run dev' 'npm --prefix apps/profile run dev' 'npm --prefix apps/checkout run dev'",
241
- "dev:host": "npm --prefix apps/host run dev",
242
- "dev:profile": "npm --prefix apps/profile run dev",
243
- "dev:checkout": "npm --prefix apps/checkout run dev",
244
- "build": "npm --prefix apps/host run build && npm --prefix apps/profile run build && npm --prefix apps/checkout run build",
245
- "build:host": "npm --prefix apps/host run build",
246
- "build:profile": "npm --prefix apps/profile run build",
247
- "build:checkout": "npm --prefix apps/checkout run build"
248
- },
249
- "dependencies": {
250
- "react": "^19.0.0",
251
- "react-dom": "^19.0.0"
306
+ "dev": "concurrently -k -n host,profile,checkout -c cyan,magenta,yellow 'npm run dev --workspace=@mf-lab/host' 'npm run dev --workspace=@mf-lab/profile' 'npm run dev --workspace=@mf-lab/checkout'",
307
+ "dev:host": "npm run dev --workspace=@mf-lab/host",
308
+ "dev:profile": "npm run dev --workspace=@mf-lab/profile",
309
+ "dev:checkout": "npm run dev --workspace=@mf-lab/checkout",
310
+ "build": "npm run build --workspace=@mf-lab/host && npm run build --workspace=@mf-lab/profile && npm run build --workspace=@mf-lab/checkout",
311
+ "build:host": "npm run build --workspace=@mf-lab/host",
312
+ "build:profile": "npm run build --workspace=@mf-lab/profile",
313
+ "build:checkout": "npm run build --workspace=@mf-lab/checkout"
252
314
  },
253
315
  "devDependencies": {
254
- "concurrently": "^9.2.1",
255
- "esbuild": "^0.28.0",
256
- "esbuild-loader": "^4.4.3",
257
- "html-webpack-plugin": "^5.6.7",
258
- "webpack": "^5.106.2",
259
- "webpack-cli": "^7.0.2",
260
- "webpack-dev-server": "^5.2.3"
316
+ "concurrently": "^9.2.1"
261
317
  }
262
318
  }
263
319
  `,
@@ -279,6 +335,19 @@ This lab uses real webpack 5 + webpack-dev-server + Module Federation.
279
335
  "scripts": {
280
336
  "dev": "webpack serve --config webpack.config.js",
281
337
  "build": "webpack --config webpack.config.js"
338
+ },
339
+ "dependencies": {
340
+ "react": "^19.0.0",
341
+ "react-dom": "^19.0.0",
342
+ "react-router-dom": "^7.6.1"
343
+ },
344
+ "devDependencies": {
345
+ "esbuild": "^0.28.0",
346
+ "esbuild-loader": "^4.4.3",
347
+ "html-webpack-plugin": "^5.6.7",
348
+ "webpack": "^5.106.2",
349
+ "webpack-cli": "^7.0.2",
350
+ "webpack-dev-server": "^5.2.3"
282
351
  }
283
352
  }
284
353
  `,
@@ -503,6 +572,44 @@ export function makeInspectableLazy(remoteKey, componentLoad, debugLoad) {
503
572
  }
504
573
  };
505
574
  }
575
+ `,
576
+ "apps/shared/buildSharedConfig.js": `function createSharedConfig(packageJson) {
577
+ const dependencyVersions =
578
+ packageJson && typeof packageJson === "object"
579
+ ? packageJson.dependencies || {}
580
+ : {};
581
+
582
+ const sharedOverrides = {
583
+ react: {
584
+ singleton: true,
585
+ requiredVersion: dependencyVersions.react || false,
586
+ },
587
+ "react-dom": {
588
+ singleton: true,
589
+ requiredVersion: dependencyVersions["react-dom"] || false,
590
+ },
591
+ "react-router-dom": {
592
+ singleton: true,
593
+ requiredVersion: dependencyVersions["react-router-dom"] || false,
594
+ },
595
+ };
596
+
597
+ // Add package names here if you want them installed but NOT shared.
598
+ const unsharedPackages = new Set([]);
599
+
600
+ return Object.fromEntries(
601
+ Object.keys(dependencyVersions)
602
+ .filter((packageName) => !unsharedPackages.has(packageName))
603
+ .map((packageName) => [
604
+ packageName,
605
+ sharedOverrides[packageName] ?? {
606
+ requiredVersion: dependencyVersions[packageName] || false,
607
+ },
608
+ ]),
609
+ );
610
+ }
611
+
612
+ module.exports = { createSharedConfig };
506
613
  `,
507
614
  "apps/host/src/index.jsx": `import("./bootstrap");
508
615
  `,
@@ -607,14 +714,13 @@ export default function App() {
607
714
  const webpack = require("webpack");
608
715
  const HtmlWebpackPlugin = require("html-webpack-plugin");
609
716
  const { ModuleFederationPlugin } = webpack.container;
717
+ const packageJson = require("./package.json");
718
+ const { createSharedConfig } = require("../shared/buildSharedConfig");
610
719
 
611
720
  const hostPort = Number(process.env.HOST_PORT || 3100);
612
721
  const profilePort = Number(process.env.PROFILE_PORT || 3101);
613
722
  const checkoutPort = Number(process.env.CHECKOUT_PORT || 3102);
614
- const sharedConfig = {
615
- react: { singleton: true, requiredVersion: false },
616
- "react-dom": { singleton: true, requiredVersion: false },
617
- };
723
+ const sharedConfig = createSharedConfig(packageJson);
618
724
  const remoteConfig = {
619
725
  profile: "profile@http://localhost:" + profilePort + "/remoteEntry.js",
620
726
  checkout: "checkout@http://localhost:" + checkoutPort + "/remoteEntry.js",
@@ -695,6 +801,19 @@ module.exports = {
695
801
  "scripts": {
696
802
  "dev": "webpack serve --config webpack.config.js",
697
803
  "build": "webpack --config webpack.config.js"
804
+ },
805
+ "dependencies": {
806
+ "react": "^19.0.0",
807
+ "react-dom": "^19.0.0",
808
+ "react-router-dom": "^7.6.1"
809
+ },
810
+ "devDependencies": {
811
+ "esbuild": "^0.28.0",
812
+ "esbuild-loader": "^4.4.3",
813
+ "html-webpack-plugin": "^5.6.7",
814
+ "webpack": "^5.106.2",
815
+ "webpack-cli": "^7.0.2",
816
+ "webpack-dev-server": "^5.2.3"
698
817
  }
699
818
  }
700
819
  `,
@@ -758,12 +877,11 @@ export default function ProfileCard() {
758
877
  const webpack = require("webpack");
759
878
  const HtmlWebpackPlugin = require("html-webpack-plugin");
760
879
  const { ModuleFederationPlugin } = webpack.container;
880
+ const packageJson = require("./package.json");
881
+ const { createSharedConfig } = require("../shared/buildSharedConfig");
761
882
 
762
883
  const profilePort = Number(process.env.PROFILE_PORT || 3101);
763
- const sharedConfig = {
764
- react: { singleton: true, requiredVersion: false },
765
- "react-dom": { singleton: true, requiredVersion: false },
766
- };
884
+ const sharedConfig = createSharedConfig(packageJson);
767
885
  const exposeConfig = {
768
886
  "./ProfileCard": path.resolve(__dirname, "./src/ProfileCard.jsx"),
769
887
  "./InspectorBridge": path.resolve(__dirname, "./src/inspectorBridge.js"),
@@ -803,6 +921,7 @@ module.exports = {
803
921
  },
804
922
  devServer: {
805
923
  port: profilePort,
924
+ historyApiFallback: true,
806
925
  hot: true,
807
926
  headers: {
808
927
  "Access-Control-Allow-Origin": "*",
@@ -844,6 +963,19 @@ module.exports = {
844
963
  "scripts": {
845
964
  "dev": "webpack serve --config webpack.config.js",
846
965
  "build": "webpack --config webpack.config.js"
966
+ },
967
+ "dependencies": {
968
+ "react": "^19.0.0",
969
+ "react-dom": "^19.0.0",
970
+ "react-router-dom": "^7.6.1"
971
+ },
972
+ "devDependencies": {
973
+ "esbuild": "^0.28.0",
974
+ "esbuild-loader": "^4.4.3",
975
+ "html-webpack-plugin": "^5.6.7",
976
+ "webpack": "^5.106.2",
977
+ "webpack-cli": "^7.0.2",
978
+ "webpack-dev-server": "^5.2.3"
847
979
  }
848
980
  }
849
981
  `,
@@ -915,12 +1047,11 @@ export default function CheckoutPanel() {
915
1047
  const webpack = require("webpack");
916
1048
  const HtmlWebpackPlugin = require("html-webpack-plugin");
917
1049
  const { ModuleFederationPlugin } = webpack.container;
1050
+ const packageJson = require("./package.json");
1051
+ const { createSharedConfig } = require("../shared/buildSharedConfig");
918
1052
 
919
1053
  const checkoutPort = Number(process.env.CHECKOUT_PORT || 3102);
920
- const sharedConfig = {
921
- react: { singleton: true, requiredVersion: false },
922
- "react-dom": { singleton: true, requiredVersion: false },
923
- };
1054
+ const sharedConfig = createSharedConfig(packageJson);
924
1055
  const exposeConfig = {
925
1056
  "./CheckoutPanel": path.resolve(__dirname, "./src/CheckoutPanel.jsx"),
926
1057
  "./InspectorBridge": path.resolve(__dirname, "./src/inspectorBridge.js"),
@@ -960,6 +1091,7 @@ module.exports = {
960
1091
  },
961
1092
  devServer: {
962
1093
  port: checkoutPort,
1094
+ historyApiFallback: true,
963
1095
  hot: true,
964
1096
  headers: {
965
1097
  "Access-Control-Allow-Origin": "*",
@@ -991,7 +1123,7 @@ export const DEFAULT_REACT_LAB: FrontendLabWorkspace = {
991
1123
  version: 1,
992
1124
  label: "React Lab",
993
1125
  type: "react",
994
- activeFile: "App.tsx",
1126
+ activeFile: "src/App.tsx",
995
1127
  files: REACT_DEFAULT_FILES,
996
1128
  };
997
1129
 
@@ -1101,9 +1233,11 @@ export function getEntryFile(workspace: FrontendLabWorkspace): string {
1101
1233
  ? "apps/host/src/App.jsx"
1102
1234
  : Object.keys(workspace.files)[0];
1103
1235
  }
1104
- return workspace.files["App.tsx"]
1105
- ? "App.tsx"
1106
- : Object.keys(workspace.files)[0];
1236
+ return workspace.files["main.tsx"]
1237
+ ? "main.tsx"
1238
+ : workspace.files["App.tsx"]
1239
+ ? "App.tsx"
1240
+ : Object.keys(workspace.files)[0];
1107
1241
  }
1108
1242
 
1109
1243
  /** Preferred display order for the file tree. */
@@ -1154,7 +1288,8 @@ export function resolveNextjsEntry(
1154
1288
  *
1155
1289
  * Approach: loads React 18 UMD + Babel standalone from CDN, runs a
1156
1290
  * custom module system built on top of Babel's CJS transform plugin,
1157
- * then renders the default export from `entryFile`.
1291
+ * then either runs a bootstrap entry such as main.tsx or renders the
1292
+ * default export from `entryFile`.
1158
1293
  *
1159
1294
  * CDN URLs are version-pinned so the preview is reproducible.
1160
1295
  */
@@ -1168,9 +1303,6 @@ export function generatePreviewHTML(
1168
1303
  const entryJSON = JSON.stringify(entryFile);
1169
1304
  const sandboxJSON = JSON.stringify(sandboxUrl ?? "");
1170
1305
  const isNextjsJSON = isNextjs ? "true" : "false";
1171
- // _i breaks up the 'import' keyword so Vite/Babel doesn't misparse
1172
- // the template literal below as containing real module import declarations
1173
- const _i = "import";
1174
1306
 
1175
1307
  return `<!DOCTYPE html>
1176
1308
  <html>
@@ -1178,6 +1310,8 @@ export function generatePreviewHTML(
1178
1310
  <meta charset="utf-8">
1179
1311
  <meta name="viewport" content="width=device-width, initial-scale=1">
1180
1312
  <script>window.__F__=${filesJSON};window.__E__=${entryJSON};window.SANDBOX_URL=${sandboxJSON};window.__NX__=${isNextjsJSON};</script>
1313
+ <script src="https://unpkg.com/react@18.3.1/umd/react.production.min.js"></script>
1314
+ <script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.production.min.js"></script>
1181
1315
  <script src="https://unpkg.com/@babel/standalone@7.26.10/babel.min.js"></script>
1182
1316
  <style>
1183
1317
  *{box-sizing:border-box}
@@ -1188,11 +1322,7 @@ body{margin:0;background:#fff;font-family:system-ui,sans-serif}
1188
1322
  <body>
1189
1323
  <div id="root"></div>
1190
1324
  <div id="__err"></div>
1191
- <script type="module">
1192
- ${_i} React from 'https://esm.sh/react@19.1.0';
1193
- ${_i} * as ReactDOM from 'https://esm.sh/react-dom@19.1.0/client?deps=react@19.1.0';
1194
- window.React = React;
1195
- window.ReactDOM = ReactDOM;
1325
+ <script>
1196
1326
  (function(){
1197
1327
  var files=window.__F__,entry=window.__E__,reg={};
1198
1328
  function norm(from,id){
@@ -1255,27 +1385,36 @@ window.ReactDOM = ReactDOM;
1255
1385
  window.addEventListener('unhandledrejection',function(e){showErr(e.reason&&e.reason.message?e.reason.message:String(e.reason));});
1256
1386
  try{
1257
1387
  order.forEach(loadMod);
1258
- var em=reg[entry];
1259
- if(!em)throw new Error('Entry not found: '+entry);
1260
- var Comp=em.exports.default;
1261
- if(typeof Comp!=='function')throw new Error('No default export (function/component) in '+entry);
1262
1388
  // Expose a navigate helper so in-preview code can trigger URL bar changes:
1263
1389
  // window.__nxNavigate('/dashboard')
1264
1390
  window.__nxNavigate=function(to){try{parent.postMessage({type:'rlab-nav',to:to},'*');}catch(e){}};
1265
- var pageEl=React.createElement(Comp,null);
1266
- // In Next.js mode: wrap the page in app/layout.tsx if it exists
1267
- if(window.__NX__){
1268
- var lk=null;
1269
- for(var _le of['app/layout.tsx','app/layout.ts','app/layout.jsx','app/layout.js']){
1270
- if(reg[_le]){lk=_le;break;}
1391
+ var em=reg[entry];
1392
+ if(!em)throw new Error('Entry not found: '+entry);
1393
+ var isBootstrapEntry=/(^|\\/)(main|index)\\.(tsx|ts|jsx|js)$/.test(entry);
1394
+ if(isBootstrapEntry){
1395
+ if(typeof em.exports.mount==='function'){
1396
+ var mountRoot=document.getElementById('root');
1397
+ if(!mountRoot)throw new Error('Root element #root not found');
1398
+ em.exports.mount(mountRoot);
1271
1399
  }
1272
- if(lk&&typeof reg[lk].exports.default==='function'){
1273
- pageEl=React.createElement(reg[lk].exports.default,null,pageEl);
1400
+ }else{
1401
+ var Comp=em.exports.default;
1402
+ if(typeof Comp!=='function')throw new Error('No default export (function/component) in '+entry);
1403
+ var pageEl=React.createElement(Comp,null);
1404
+ // In Next.js mode: wrap the page in app/layout.tsx if it exists
1405
+ if(window.__NX__){
1406
+ var lk=null;
1407
+ for(var _le of['app/layout.tsx','app/layout.ts','app/layout.jsx','app/layout.js']){
1408
+ if(reg[_le]){lk=_le;break;}
1409
+ }
1410
+ if(lk&&typeof reg[lk].exports.default==='function'){
1411
+ pageEl=React.createElement(reg[lk].exports.default,null,pageEl);
1412
+ }
1274
1413
  }
1414
+ ReactDOM.createRoot(document.getElementById('root')).render(
1415
+ React.createElement(React.StrictMode,null,pageEl)
1416
+ );
1275
1417
  }
1276
- ReactDOM.createRoot(document.getElementById('root')).render(
1277
- React.createElement(React.StrictMode,null,pageEl)
1278
- );
1279
1418
  try{parent.postMessage({type:'rlab-ready'},'*');}catch(e){}
1280
1419
  }catch(err){showErr(err.message+(err.stack?'\\n\\n'+err.stack:''));}
1281
1420
  })();
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.8.0"
2
+ "version": "0.10.0"
3
3
  }