create-interview-cockpit 0.16.0 → 0.17.1

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.
@@ -342,21 +342,47 @@ This lab uses real webpack 5 + webpack-dev-server + Module Federation.
342
342
  "react-router-dom": "^7.6.1"
343
343
  },
344
344
  "devDependencies": {
345
+ "@types/react": "^19.0.0",
346
+ "@types/react-dom": "^19.0.0",
345
347
  "esbuild": "^0.28.0",
346
348
  "esbuild-loader": "^4.4.3",
347
349
  "html-webpack-plugin": "^5.6.7",
350
+ "typescript": "^5.6.0",
348
351
  "webpack": "^5.106.2",
349
352
  "webpack-cli": "^7.0.2",
350
353
  "webpack-dev-server": "^5.2.3"
351
354
  }
352
355
  }
353
356
  `,
354
- "apps/shared/mfInspector.js": `import React from "react";
357
+ "apps/host/tsconfig.json": `{
358
+ "compilerOptions": {
359
+ "target": "ES2020",
360
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
361
+ "module": "ESNext",
362
+ "moduleResolution": "bundler",
363
+ "jsx": "react-jsx",
364
+ "strict": true,
365
+ "esModuleInterop": true,
366
+ "allowSyntheticDefaultImports": true,
367
+ "skipLibCheck": true,
368
+ "noEmit": true
369
+ },
370
+ "include": ["src"]
371
+ }
372
+ `,
373
+ "apps/shared/mfInspector.ts": `import React from "react";
355
374
  import ReactDOM from "react-dom";
356
375
  // NOTE: default import (not namespace import) is intentional — namespace objects
357
376
  // are never reference-equal across MF consumers even when the singleton is shared,
358
377
  // so comparing the default export is the only way to prove same-instance.
359
378
 
379
+ // Webpack DefinePlugin / MF globals injected at build time
380
+ declare const __MF_INSPECTOR_APP__: unknown;
381
+ declare const __MF_INSPECTOR_SANDBOX_ID__: unknown;
382
+ declare const __MF_INSPECTOR_DECLARED_CONFIG__: unknown;
383
+ declare const __webpack_init_sharing__: ((scope: string) => Promise<void>) | undefined;
384
+ declare const __webpack_share_scopes__: Record<string, unknown> | undefined;
385
+
360
386
  const app =
361
387
  typeof __MF_INSPECTOR_APP__ === "string" ? __MF_INSPECTOR_APP__ : "unknown";
362
388
  const sandboxId =
@@ -374,7 +400,7 @@ const runtimeId =
374
400
 
375
401
  let attachedRouteListeners = false;
376
402
 
377
- function getRoute() {
403
+ function getRoute(): string {
378
404
  if (typeof window === "undefined" || !window.location) return "/";
379
405
  const route =
380
406
  window.location.pathname +
@@ -383,7 +409,7 @@ function getRoute() {
383
409
  return route || "/";
384
410
  }
385
411
 
386
- function cloneJsonSafe(value) {
412
+ function cloneJsonSafe(value: unknown): unknown {
387
413
  if (value == null) return value;
388
414
  try {
389
415
  return JSON.parse(JSON.stringify(value));
@@ -392,7 +418,7 @@ function cloneJsonSafe(value) {
392
418
  }
393
419
  }
394
420
 
395
- export async function ensureShareScopeReady() {
421
+ export async function ensureShareScopeReady(): Promise<void> {
396
422
  try {
397
423
  if (typeof __webpack_init_sharing__ === "function") {
398
424
  await __webpack_init_sharing__("default");
@@ -431,7 +457,7 @@ export function snapshotShareScopes() {
431
457
  }
432
458
  }
433
459
 
434
- export function emitInspectorEvent(kind, payload) {
460
+ export function emitInspectorEvent(kind: string, payload: unknown): void {
435
461
  if (!sandboxId || typeof window === "undefined" || !window.parent) return;
436
462
  window.parent.postMessage(
437
463
  {
@@ -465,7 +491,7 @@ export function emitDeclaredConfig() {
465
491
  });
466
492
  }
467
493
 
468
- export async function emitRuntimeBoot(label) {
494
+ export async function emitRuntimeBoot(label?: string): Promise<void> {
469
495
  attachRouteListeners();
470
496
  await ensureShareScopeReady();
471
497
  emitDeclaredConfig();
@@ -481,7 +507,7 @@ export async function emitRuntimeBoot(label) {
481
507
  });
482
508
  }
483
509
 
484
- export async function emitRouteSnapshot(label) {
510
+ export async function emitRouteSnapshot(label?: string): Promise<void> {
485
511
  await ensureShareScopeReady();
486
512
  emitInspectorEvent("route-change", {
487
513
  label: label || "route",
@@ -501,7 +527,11 @@ export function getRemoteInspectorBridge() {
501
527
  };
502
528
  }
503
529
 
504
- export function makeInspectableLazy(remoteKey, componentLoad, debugLoad) {
530
+ export function makeInspectableLazy<T>(
531
+ remoteKey: string,
532
+ componentLoad: () => Promise<T>,
533
+ debugLoad?: (() => Promise<Record<string, unknown>>) | null,
534
+ ): () => Promise<T> {
505
535
  return async () => {
506
536
  await ensureShareScopeReady();
507
537
  emitInspectorEvent("remote-load-start", {
@@ -573,7 +603,9 @@ export function makeInspectableLazy(remoteKey, componentLoad, debugLoad) {
573
603
  };
574
604
  }
575
605
  `,
576
- "apps/shared/buildSharedConfig.js": `function createSharedConfig(packageJson) {
606
+ "apps/shared/buildSharedConfig.js": `
607
+ /** @param {{ dependencies?: Record<string, string> }} packageJson */
608
+ function createSharedConfig(packageJson) {
577
609
  const dependencyVersions =
578
610
  packageJson && typeof packageJson === "object"
579
611
  ? packageJson.dependencies || {}
@@ -611,14 +643,14 @@ export function makeInspectableLazy(remoteKey, componentLoad, debugLoad) {
611
643
 
612
644
  module.exports = { createSharedConfig };
613
645
  `,
614
- "apps/host/src/index.jsx": `import("./bootstrap");
646
+ "apps/host/src/index.tsx": `import("./bootstrap");
615
647
  `,
616
- "apps/host/src/bootstrap.jsx": `import React from "react";
648
+ "apps/host/src/bootstrap.tsx": `import React from "react";
617
649
  import { createRoot } from "react-dom/client";
618
650
  import App from "./App";
619
651
  import { emitRuntimeBoot } from "../../shared/mfInspector";
620
652
 
621
- const root = createRoot(document.getElementById("root"));
653
+ const root = createRoot(document.getElementById("root")!);
622
654
 
623
655
  root.render(
624
656
  <React.StrictMode>
@@ -628,7 +660,7 @@ root.render(
628
660
 
629
661
  void emitRuntimeBoot("host-bootstrap");
630
662
  `,
631
- "apps/host/src/App.jsx": `import React, { Suspense } from "react";
663
+ "apps/host/src/App.tsx": `import React, { Suspense } from "react";
632
664
  import { makeInspectableLazy } from "../../shared/mfInspector";
633
665
 
634
666
  const ProfileCard = React.lazy(
@@ -646,7 +678,7 @@ const CheckoutPanel = React.lazy(
646
678
  ),
647
679
  );
648
680
 
649
- function RemoteBoundary({ title, children }) {
681
+ function RemoteBoundary({ title, children }: { title: string; children: React.ReactNode }) {
650
682
  return (
651
683
  <div
652
684
  style={{
@@ -720,6 +752,7 @@ const { createSharedConfig } = require("../shared/buildSharedConfig");
720
752
  const hostPort = Number(process.env.HOST_PORT || 3100);
721
753
  const profilePort = Number(process.env.PROFILE_PORT || 3101);
722
754
  const checkoutPort = Number(process.env.CHECKOUT_PORT || 3102);
755
+ // TypeScript support via esbuild-loader
723
756
  const sharedConfig = createSharedConfig(packageJson);
724
757
  const remoteConfig = {
725
758
  profile: "profile@http://localhost:" + profilePort + "/remoteEntry.js",
@@ -733,24 +766,24 @@ const inspectorConfig = {
733
766
 
734
767
  module.exports = {
735
768
  mode: "development",
736
- entry: path.resolve(__dirname, "./src/index.jsx"),
769
+ entry: path.resolve(__dirname, "./src/index.tsx"),
737
770
  output: {
738
771
  path: path.resolve(__dirname, "./dist"),
739
772
  publicPath: "http://localhost:" + hostPort + "/",
740
773
  clean: true,
741
774
  },
742
775
  resolve: {
743
- extensions: [".js", ".jsx"],
776
+ extensions: [".js", ".jsx", ".ts", ".tsx"],
744
777
  },
745
778
  module: {
746
779
  rules: [
747
780
  {
748
- test: /\\.(js|jsx)$/,
781
+ test: /\\.(js|jsx|ts|tsx)$/,
749
782
  exclude: /node_modules/,
750
783
  use: {
751
784
  loader: "esbuild-loader",
752
785
  options: {
753
- loader: "jsx",
786
+ loader: "tsx",
754
787
  jsx: "automatic",
755
788
  target: "es2020",
756
789
  },
@@ -808,28 +841,47 @@ module.exports = {
808
841
  "react-router-dom": "^7.6.1"
809
842
  },
810
843
  "devDependencies": {
844
+ "@types/react": "^19.0.0",
845
+ "@types/react-dom": "^19.0.0",
811
846
  "esbuild": "^0.28.0",
812
847
  "esbuild-loader": "^4.4.3",
813
848
  "html-webpack-plugin": "^5.6.7",
849
+ "typescript": "^5.6.0",
814
850
  "webpack": "^5.106.2",
815
851
  "webpack-cli": "^7.0.2",
816
852
  "webpack-dev-server": "^5.2.3"
817
853
  }
818
854
  }
819
855
  `,
820
- "apps/profile/src/inspectorBridge.js": `import { getRemoteInspectorBridge } from "../../shared/mfInspector";
856
+ "apps/profile/tsconfig.json": `{
857
+ "compilerOptions": {
858
+ "target": "ES2020",
859
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
860
+ "module": "ESNext",
861
+ "moduleResolution": "bundler",
862
+ "jsx": "react-jsx",
863
+ "strict": true,
864
+ "esModuleInterop": true,
865
+ "allowSyntheticDefaultImports": true,
866
+ "skipLibCheck": true,
867
+ "noEmit": true
868
+ },
869
+ "include": ["src"]
870
+ }
871
+ `,
872
+ "apps/profile/src/inspectorBridge.ts": `import { getRemoteInspectorBridge } from "../../shared/mfInspector";
821
873
 
822
874
  export { getRemoteInspectorBridge };
823
875
  export default getRemoteInspectorBridge;
824
876
  `,
825
- "apps/profile/src/index.jsx": `import("./bootstrap");
877
+ "apps/profile/src/index.tsx": `import("./bootstrap");
826
878
  `,
827
- "apps/profile/src/bootstrap.jsx": `import React from "react";
879
+ "apps/profile/src/bootstrap.tsx": `import React from "react";
828
880
  import { createRoot } from "react-dom/client";
829
881
  import App from "./App";
830
882
  import { emitRuntimeBoot } from "../../shared/mfInspector";
831
883
 
832
- const root = createRoot(document.getElementById("root"));
884
+ const root = createRoot(document.getElementById("root")!);
833
885
 
834
886
  root.render(
835
887
  <React.StrictMode>
@@ -839,7 +891,7 @@ root.render(
839
891
 
840
892
  void emitRuntimeBoot("profile-bootstrap");
841
893
  `,
842
- "apps/profile/src/App.jsx": `import React from "react";
894
+ "apps/profile/src/App.tsx": `import React from "react";
843
895
  import ProfileCard from "./ProfileCard";
844
896
 
845
897
  export default function App() {
@@ -854,7 +906,7 @@ export default function App() {
854
906
  );
855
907
  }
856
908
  `,
857
- "apps/profile/src/ProfileCard.jsx": `import React from "react";
909
+ "apps/profile/src/ProfileCard.tsx": `import React from "react";
858
910
 
859
911
  export default function ProfileCard() {
860
912
  return (
@@ -883,8 +935,8 @@ const { createSharedConfig } = require("../shared/buildSharedConfig");
883
935
  const profilePort = Number(process.env.PROFILE_PORT || 3101);
884
936
  const sharedConfig = createSharedConfig(packageJson);
885
937
  const exposeConfig = {
886
- "./ProfileCard": path.resolve(__dirname, "./src/ProfileCard.jsx"),
887
- "./InspectorBridge": path.resolve(__dirname, "./src/inspectorBridge.js"),
938
+ "./ProfileCard": path.resolve(__dirname, "./src/ProfileCard.tsx"),
939
+ "./InspectorBridge": path.resolve(__dirname, "./src/inspectorBridge.ts"),
888
940
  };
889
941
  const inspectorConfig = {
890
942
  app: "profile",
@@ -894,24 +946,24 @@ const inspectorConfig = {
894
946
 
895
947
  module.exports = {
896
948
  mode: "development",
897
- entry: path.resolve(__dirname, "./src/index.jsx"),
949
+ entry: path.resolve(__dirname, "./src/index.tsx"),
898
950
  output: {
899
951
  path: path.resolve(__dirname, "./dist"),
900
952
  publicPath: "http://localhost:" + profilePort + "/",
901
953
  clean: true,
902
954
  },
903
955
  resolve: {
904
- extensions: [".js", ".jsx"],
956
+ extensions: [".js", ".jsx", ".ts", ".tsx"],
905
957
  },
906
958
  module: {
907
959
  rules: [
908
960
  {
909
- test: /\\.(js|jsx)$/,
961
+ test: /\\.(js|jsx|ts|tsx)$/,
910
962
  exclude: /node_modules/,
911
963
  use: {
912
964
  loader: "esbuild-loader",
913
965
  options: {
914
- loader: "jsx",
966
+ loader: "tsx",
915
967
  jsx: "automatic",
916
968
  target: "es2020",
917
969
  },
@@ -970,28 +1022,47 @@ module.exports = {
970
1022
  "react-router-dom": "^7.6.1"
971
1023
  },
972
1024
  "devDependencies": {
1025
+ "@types/react": "^19.0.0",
1026
+ "@types/react-dom": "^19.0.0",
973
1027
  "esbuild": "^0.28.0",
974
1028
  "esbuild-loader": "^4.4.3",
975
1029
  "html-webpack-plugin": "^5.6.7",
1030
+ "typescript": "^5.6.0",
976
1031
  "webpack": "^5.106.2",
977
1032
  "webpack-cli": "^7.0.2",
978
1033
  "webpack-dev-server": "^5.2.3"
979
1034
  }
980
1035
  }
981
1036
  `,
982
- "apps/checkout/src/inspectorBridge.js": `import { getRemoteInspectorBridge } from "../../shared/mfInspector";
1037
+ "apps/checkout/tsconfig.json": `{
1038
+ "compilerOptions": {
1039
+ "target": "ES2020",
1040
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
1041
+ "module": "ESNext",
1042
+ "moduleResolution": "bundler",
1043
+ "jsx": "react-jsx",
1044
+ "strict": true,
1045
+ "esModuleInterop": true,
1046
+ "allowSyntheticDefaultImports": true,
1047
+ "skipLibCheck": true,
1048
+ "noEmit": true
1049
+ },
1050
+ "include": ["src"]
1051
+ }
1052
+ `,
1053
+ "apps/checkout/src/inspectorBridge.ts": `import { getRemoteInspectorBridge } from "../../shared/mfInspector";
983
1054
 
984
1055
  export { getRemoteInspectorBridge };
985
1056
  export default getRemoteInspectorBridge;
986
1057
  `,
987
- "apps/checkout/src/index.jsx": `import("./bootstrap");
1058
+ "apps/checkout/src/index.tsx": `import("./bootstrap");
988
1059
  `,
989
- "apps/checkout/src/bootstrap.jsx": `import React from "react";
1060
+ "apps/checkout/src/bootstrap.tsx": `import React from "react";
990
1061
  import { createRoot } from "react-dom/client";
991
1062
  import App from "./App";
992
1063
  import { emitRuntimeBoot } from "../../shared/mfInspector";
993
1064
 
994
- const root = createRoot(document.getElementById("root"));
1065
+ const root = createRoot(document.getElementById("root")!);
995
1066
 
996
1067
  root.render(
997
1068
  <React.StrictMode>
@@ -1001,7 +1072,7 @@ root.render(
1001
1072
 
1002
1073
  void emitRuntimeBoot("checkout-bootstrap");
1003
1074
  `,
1004
- "apps/checkout/src/App.jsx": `import React from "react";
1075
+ "apps/checkout/src/App.tsx": `import React from "react";
1005
1076
  import CheckoutPanel from "./CheckoutPanel";
1006
1077
 
1007
1078
  export default function App() {
@@ -1016,7 +1087,7 @@ export default function App() {
1016
1087
  );
1017
1088
  }
1018
1089
  `,
1019
- "apps/checkout/src/CheckoutPanel.jsx": `import React from "react";
1090
+ "apps/checkout/src/CheckoutPanel.tsx": `import React from "react";
1020
1091
 
1021
1092
  export default function CheckoutPanel() {
1022
1093
  return (
@@ -1053,8 +1124,8 @@ const { createSharedConfig } = require("../shared/buildSharedConfig");
1053
1124
  const checkoutPort = Number(process.env.CHECKOUT_PORT || 3102);
1054
1125
  const sharedConfig = createSharedConfig(packageJson);
1055
1126
  const exposeConfig = {
1056
- "./CheckoutPanel": path.resolve(__dirname, "./src/CheckoutPanel.jsx"),
1057
- "./InspectorBridge": path.resolve(__dirname, "./src/inspectorBridge.js"),
1127
+ "./CheckoutPanel": path.resolve(__dirname, "./src/CheckoutPanel.tsx"),
1128
+ "./InspectorBridge": path.resolve(__dirname, "./src/inspectorBridge.ts"),
1058
1129
  };
1059
1130
  const inspectorConfig = {
1060
1131
  app: "checkout",
@@ -1064,24 +1135,24 @@ const inspectorConfig = {
1064
1135
 
1065
1136
  module.exports = {
1066
1137
  mode: "development",
1067
- entry: path.resolve(__dirname, "./src/index.jsx"),
1138
+ entry: path.resolve(__dirname, "./src/index.tsx"),
1068
1139
  output: {
1069
1140
  path: path.resolve(__dirname, "./dist"),
1070
1141
  publicPath: "http://localhost:" + checkoutPort + "/",
1071
1142
  clean: true,
1072
1143
  },
1073
1144
  resolve: {
1074
- extensions: [".js", ".jsx"],
1145
+ extensions: [".js", ".jsx", ".ts", ".tsx"],
1075
1146
  },
1076
1147
  module: {
1077
1148
  rules: [
1078
1149
  {
1079
- test: /\\.(js|jsx)$/,
1150
+ test: /\\.(js|jsx|ts|tsx)$/,
1080
1151
  exclude: /node_modules/,
1081
1152
  use: {
1082
1153
  loader: "esbuild-loader",
1083
1154
  options: {
1084
- loader: "jsx",
1155
+ loader: "tsx",
1085
1156
  jsx: "automatic",
1086
1157
  target: "es2020",
1087
1158
  },
@@ -1139,7 +1210,7 @@ export const DEFAULT_MODULE_FEDERATION_LAB: FrontendLabWorkspace = {
1139
1210
  version: 1,
1140
1211
  label: "Webpack Module Federation Lab",
1141
1212
  type: "module-federation",
1142
- activeFile: "apps/host/src/App.jsx",
1213
+ activeFile: "apps/host/src/App.tsx",
1143
1214
  files: MODULE_FEDERATION_DEFAULT_FILES,
1144
1215
  };
1145
1216
 
@@ -1208,14 +1279,33 @@ In this **isolated** pattern:
1208
1279
  "react-dom": "^19.0.0"
1209
1280
  },
1210
1281
  "devDependencies": {
1282
+ "@types/react": "^19.0.0",
1283
+ "@types/react-dom": "^19.0.0",
1211
1284
  "esbuild": "^0.28.0",
1212
1285
  "esbuild-loader": "^4.4.3",
1213
1286
  "html-webpack-plugin": "^5.6.7",
1287
+ "typescript": "^5.6.0",
1214
1288
  "webpack": "^5.106.2",
1215
1289
  "webpack-cli": "^7.0.2",
1216
1290
  "webpack-dev-server": "^5.2.3"
1217
1291
  }
1218
1292
  }
1293
+ `,
1294
+ "apps/host/tsconfig.json": `{
1295
+ "compilerOptions": {
1296
+ "target": "ES2020",
1297
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
1298
+ "module": "ESNext",
1299
+ "moduleResolution": "bundler",
1300
+ "jsx": "react-jsx",
1301
+ "strict": true,
1302
+ "esModuleInterop": true,
1303
+ "allowSyntheticDefaultImports": true,
1304
+ "skipLibCheck": true,
1305
+ "noEmit": true
1306
+ },
1307
+ "include": ["src"]
1308
+ }
1219
1309
  `,
1220
1310
  "apps/host/webpack.config.js": `const HtmlWebpackPlugin = require("html-webpack-plugin");
1221
1311
  const { ModuleFederationPlugin } = require("webpack").container;
@@ -1226,19 +1316,19 @@ const MFE_AUTH_PORT = parseInt(process.env.MFE_AUTH_PORT || "3002");
1226
1316
 
1227
1317
  module.exports = {
1228
1318
  mode: "development",
1229
- entry: "./src/index.js",
1319
+ entry: "./src/index.ts",
1230
1320
  output: {
1231
1321
  publicPath: "auto",
1232
1322
  },
1233
- resolve: { extensions: [".js", ".jsx"] },
1323
+ resolve: { extensions: [".js", ".jsx", ".ts", ".tsx"] },
1234
1324
  module: {
1235
1325
  rules: [
1236
1326
  {
1237
- test: /\\.(js|jsx)$/,
1327
+ test: /\\.(js|jsx|ts|tsx)$/,
1238
1328
  exclude: /node_modules/,
1239
1329
  use: {
1240
1330
  loader: "esbuild-loader",
1241
- options: { loader: "jsx", jsx: "automatic", target: "es2020" },
1331
+ options: { loader: "tsx", jsx: "automatic", target: "es2020" },
1242
1332
  },
1243
1333
  },
1244
1334
  ],
@@ -1288,16 +1378,16 @@ module.exports = {
1288
1378
  </body>
1289
1379
  </html>
1290
1380
  `,
1291
- "apps/host/src/index.js": `// Async boundary: required for Module Federation dynamic imports
1381
+ "apps/host/src/index.ts": `// Async boundary: required for Module Federation dynamic imports
1292
1382
  import("./bootstrap");
1293
1383
  `,
1294
- "apps/host/src/bootstrap.jsx": `import React from "react";
1384
+ "apps/host/src/bootstrap.tsx": `import React from "react";
1295
1385
  import { createRoot } from "react-dom/client";
1296
1386
  import App from "./App";
1297
1387
 
1298
- createRoot(document.getElementById("root")).render(<App />);
1388
+ createRoot(document.getElementById("root")!).render(<App />);
1299
1389
  `,
1300
- "apps/host/src/App.jsx": `import React from "react";
1390
+ "apps/host/src/App.tsx": `import React from "react";
1301
1391
  import MfeContainer from "./MfeContainer";
1302
1392
 
1303
1393
  export default function App() {
@@ -1316,18 +1406,30 @@ export default function App() {
1316
1406
  );
1317
1407
  }
1318
1408
  `,
1319
- "apps/host/src/MfeContainer.jsx": `import React, { useRef, useEffect } from "react";
1409
+ "apps/host/src/MfeContainer.tsx": `import React, { useRef, useEffect } from "react";
1410
+
1411
+ interface User {
1412
+ name?: string;
1413
+ role?: string;
1414
+ }
1415
+
1416
+ interface MfeMount {
1417
+ update?: (props: { user: User }) => void;
1418
+ unmount?: () => void;
1419
+ }
1320
1420
 
1321
1421
  // Lazy-load the remote's mount/unmount contract — not a React component.
1322
- const mfeAuthMountPromise = import("mfeAuth/mount");
1422
+ const mfeAuthMountPromise = import("mfeAuth/mount") as Promise<{
1423
+ mount: (el: HTMLElement, props: { user: User }) => MfeMount;
1424
+ }>;
1323
1425
 
1324
- export default function MfeContainer({ user }) {
1325
- const elRef = useRef(null);
1326
- const mountedRef = useRef(null); // holds { unmount } returned by remote
1426
+ export default function MfeContainer({ user }: { user: User }) {
1427
+ const elRef = useRef<HTMLDivElement>(null);
1428
+ const mountedRef = useRef<MfeMount | null>(null);
1327
1429
 
1328
1430
  useEffect(() => {
1329
1431
  let cancelled = false;
1330
- let cleanup = null;
1432
+ let cleanup: MfeMount | null = null;
1331
1433
 
1332
1434
  mfeAuthMountPromise.then(({ mount }) => {
1333
1435
  if (cancelled || !elRef.current) return;
@@ -1340,7 +1442,7 @@ export default function MfeContainer({ user }) {
1340
1442
  return () => {
1341
1443
  cancelled = true;
1342
1444
  // On cleanup, call the remote's own unmount so it can tear down its root.
1343
- cleanup?.unmount();
1445
+ cleanup?.unmount?.();
1344
1446
  };
1345
1447
  }, []); // mount once — treat like a portal
1346
1448
 
@@ -1379,14 +1481,33 @@ export default function MfeContainer({ user }) {
1379
1481
  "react-dom": "^19.0.0"
1380
1482
  },
1381
1483
  "devDependencies": {
1484
+ "@types/react": "^19.0.0",
1485
+ "@types/react-dom": "^19.0.0",
1382
1486
  "esbuild": "^0.28.0",
1383
1487
  "esbuild-loader": "^4.4.3",
1384
1488
  "html-webpack-plugin": "^5.6.7",
1489
+ "typescript": "^5.6.0",
1385
1490
  "webpack": "^5.106.2",
1386
1491
  "webpack-cli": "^7.0.2",
1387
1492
  "webpack-dev-server": "^5.2.3"
1388
1493
  }
1389
1494
  }
1495
+ `,
1496
+ "apps/mfe-auth/tsconfig.json": `{
1497
+ "compilerOptions": {
1498
+ "target": "ES2020",
1499
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
1500
+ "module": "ESNext",
1501
+ "moduleResolution": "bundler",
1502
+ "jsx": "react-jsx",
1503
+ "strict": true,
1504
+ "esModuleInterop": true,
1505
+ "allowSyntheticDefaultImports": true,
1506
+ "skipLibCheck": true,
1507
+ "noEmit": true
1508
+ },
1509
+ "include": ["src"]
1510
+ }
1390
1511
  `,
1391
1512
  "apps/mfe-auth/webpack.config.js": `const HtmlWebpackPlugin = require("html-webpack-plugin");
1392
1513
  const { ModuleFederationPlugin } = require("webpack").container;
@@ -1396,19 +1517,19 @@ const MFE_AUTH_PORT = parseInt(process.env.MFE_AUTH_PORT || "3002");
1396
1517
 
1397
1518
  module.exports = {
1398
1519
  mode: "development",
1399
- entry: "./src/index.js",
1520
+ entry: "./src/index.ts",
1400
1521
  output: {
1401
1522
  publicPath: "auto",
1402
1523
  },
1403
- resolve: { extensions: [".js", ".jsx"] },
1524
+ resolve: { extensions: [".js", ".jsx", ".ts", ".tsx"] },
1404
1525
  module: {
1405
1526
  rules: [
1406
1527
  {
1407
- test: /\\.(js|jsx)$/,
1528
+ test: /\\.(js|jsx|ts|tsx)$/,
1408
1529
  exclude: /node_modules/,
1409
1530
  use: {
1410
1531
  loader: "esbuild-loader",
1411
- options: { loader: "jsx", jsx: "automatic", target: "es2020" },
1532
+ options: { loader: "tsx", jsx: "automatic", target: "es2020" },
1412
1533
  },
1413
1534
  },
1414
1535
  ],
@@ -1420,7 +1541,7 @@ module.exports = {
1420
1541
  exposes: {
1421
1542
  // Key difference: we expose the MOUNT MODULE, not a React component.
1422
1543
  // The host never imports a JSX element from us directly.
1423
- "./mount": "./src/mount.jsx",
1544
+ "./mount": "./src/mount.tsx",
1424
1545
  },
1425
1546
  // This remote brings its OWN React copy.
1426
1547
  // No singleton sharing with the host here.
@@ -1443,21 +1564,26 @@ module.exports = {
1443
1564
  <body><div id="root"></div></body>
1444
1565
  </html>
1445
1566
  `,
1446
- "apps/mfe-auth/src/index.js": `import("./bootstrap");
1567
+ "apps/mfe-auth/src/index.ts": `import("./bootstrap");
1447
1568
  `,
1448
- "apps/mfe-auth/src/bootstrap.jsx": `// Standalone entry — only used when running mfe-auth on its own for development.
1569
+ "apps/mfe-auth/src/bootstrap.tsx": `// Standalone entry — only used when running mfe-auth on its own for development.
1449
1570
  import React from "react";
1450
1571
  import { createRoot } from "react-dom/client";
1451
1572
  import App from "./App";
1452
1573
 
1453
- createRoot(document.getElementById("root")).render(
1574
+ createRoot(document.getElementById("root")!).render(
1454
1575
  <App user={{ name: "Dev User", role: "developer" }} />
1455
1576
  );
1456
1577
  `,
1457
- "apps/mfe-auth/src/App.jsx": `import React from "react";
1578
+ "apps/mfe-auth/src/App.tsx": `import React from "react";
1579
+
1580
+ interface User {
1581
+ name?: string;
1582
+ role?: string;
1583
+ }
1458
1584
 
1459
1585
  // Standalone view — used for local development of the MFE in isolation.
1460
- export default function App({ user = {} }) {
1586
+ export default function App({ user = {} }: { user?: User }) {
1461
1587
  return (
1462
1588
  <div style={{ padding: "1rem", fontFamily: "system-ui, sans-serif", background: "#0f172a", color: "#e2e8f0", minHeight: "100vh" }}>
1463
1589
  <h2 style={{ color: "#a78bfa", fontSize: "0.9rem", margin: "0 0 0.5rem" }}>
@@ -1469,7 +1595,7 @@ export default function App({ user = {} }) {
1469
1595
  }
1470
1596
 
1471
1597
  // The actual React component that this MFE renders.
1472
- export function AuthWidget({ user = {} }) {
1598
+ export function AuthWidget({ user = {} }: { user?: User }) {
1473
1599
  return (
1474
1600
  <div style={{ padding: "0.75rem 1rem", background: "#1e293b", borderRadius: "6px", border: "1px solid #334155" }}>
1475
1601
  <p style={{ margin: 0, fontSize: "0.8rem", color: "#94a3b8" }}>Auth MFE — isolated React root</p>
@@ -1485,8 +1611,8 @@ export function AuthWidget({ user = {} }) {
1485
1611
  );
1486
1612
  }
1487
1613
  `,
1488
- "apps/mfe-auth/src/mount.jsx": `// ─────────────────────────────────────────────────────────────
1489
- // mount.jsx — the public contract exposed via Module Federation
1614
+ "apps/mfe-auth/src/mount.tsx": `// ─────────────────────────────────────────────────────────────
1615
+ // mount.tsx — the public contract exposed via Module Federation
1490
1616
  //
1491
1617
  // The host imports THIS file, not a React component.
1492
1618
  // It calls mount(domElement, props) to render, unmount(domElement) to tear down.
@@ -1500,17 +1626,23 @@ import React from "react";
1500
1626
  import { createRoot } from "react-dom/client";
1501
1627
  import { AuthWidget } from "./App";
1502
1628
 
1629
+ interface User {
1630
+ name?: string;
1631
+ role?: string;
1632
+ }
1633
+
1634
+ interface MfeHandle {
1635
+ update: (props: { user?: User }) => void;
1636
+ unmount: () => void;
1637
+ }
1638
+
1503
1639
  // Keep track of roots by element so we can update or unmount them.
1504
- const roots = new WeakMap();
1640
+ const roots = new WeakMap<HTMLElement, ReturnType<typeof createRoot>>();
1505
1641
 
1506
1642
  /**
1507
1643
  * Mount the MFE into the given DOM element.
1508
- *
1509
- * @param {HTMLElement} el - the host-provided DOM node
1510
- * @param {object} props - initial props from the host
1511
- * @returns {{ unmount: () => void, update: (props: object) => void }}
1512
1644
  */
1513
- export function mount(el, props = {}) {
1645
+ export function mount(el: HTMLElement, props: { user?: User } = {}): MfeHandle {
1514
1646
  const root = createRoot(el);
1515
1647
  roots.set(el, root);
1516
1648
 
@@ -1518,7 +1650,7 @@ export function mount(el, props = {}) {
1518
1650
 
1519
1651
  return {
1520
1652
  /** Call this to pass updated props from the host without remounting. */
1521
- update(newProps) {
1653
+ update(newProps: { user?: User }) {
1522
1654
  root.render(<AuthWidget {...newProps} />);
1523
1655
  },
1524
1656
  /** Tear down the React root when the host removes this MFE. */
@@ -1531,9 +1663,8 @@ export function mount(el, props = {}) {
1531
1663
 
1532
1664
  /**
1533
1665
  * Convenience function: unmount using only the element reference.
1534
- * Useful if the host didn't store the return value of mount().
1535
1666
  */
1536
- export function unmount(el) {
1667
+ export function unmount(el: HTMLElement): void {
1537
1668
  const root = roots.get(el);
1538
1669
  if (root) {
1539
1670
  root.unmount();
@@ -1547,7 +1678,7 @@ export const ISOLATED_MODULE_FEDERATION_LAB: FrontendLabWorkspace = {
1547
1678
  version: 1,
1548
1679
  label: "Webpack MF — Isolated Mount/Unmount",
1549
1680
  type: "module-federation",
1550
- activeFile: "apps/mfe-auth/src/mount.jsx",
1681
+ activeFile: "apps/mfe-auth/src/mount.tsx",
1551
1682
  files: MODULE_FEDERATION_ISOLATED_FILES,
1552
1683
  };
1553
1684
 
@@ -1613,10 +1744,12 @@ No \`@module-federation/nextjs-mf\` package is required.
1613
1744
  }
1614
1745
  }
1615
1746
  `,
1616
- "apps/shell/next.config.js": `/** @type {import('next').NextConfig} */
1617
- const nextConfig = {
1747
+ "apps/shell/next.config.ts": `import type { NextConfig } from 'next';
1748
+ // Use webpack's built-in ModuleFederationPlugin — no extra npm package.
1749
+ import { container } from 'webpack';
1750
+
1751
+ const nextConfig: NextConfig = {
1618
1752
  reactStrictMode: true,
1619
- // Use webpack's built-in ModuleFederationPlugin — no extra npm package needed.
1620
1753
  webpack(config, { webpack }) {
1621
1754
  config.plugins.push(
1622
1755
  new webpack.container.ModuleFederationPlugin({
@@ -1633,7 +1766,7 @@ const nextConfig = {
1633
1766
  },
1634
1767
  };
1635
1768
 
1636
- module.exports = nextConfig;
1769
+ export default nextConfig;
1637
1770
  `,
1638
1771
  "apps/shell/src/app/layout.tsx": `import type { Metadata } from 'next';
1639
1772
  import React from 'react';
@@ -1753,8 +1886,9 @@ export function RemoteSlot({ scope, module, remoteUrl, fallback }: Props) {
1753
1886
  }
1754
1887
  }
1755
1888
  `,
1756
- "apps/remote/next.config.js": `/** @type {import('next').NextConfig} */
1757
- const nextConfig = {
1889
+ "apps/remote/next.config.ts": `import type { NextConfig } from 'next';
1890
+
1891
+ const nextConfig: NextConfig = {
1758
1892
  reactStrictMode: true,
1759
1893
  webpack(config, { webpack }) {
1760
1894
  config.plugins.push(
@@ -1775,7 +1909,7 @@ const nextConfig = {
1775
1909
  },
1776
1910
  };
1777
1911
 
1778
- module.exports = nextConfig;
1912
+ export default nextConfig;
1779
1913
  `,
1780
1914
  "apps/remote/src/app/layout.tsx": `import type { Metadata } from 'next';
1781
1915
  import React from 'react';
@@ -1903,13 +2037,14 @@ Ports are assigned automatically by the lab runner (HOST_PORT for shell, REMOTE_
1903
2037
  }
1904
2038
  }
1905
2039
  `,
1906
- "apps/shell/next.config.js": `/** @type {import('next').NextConfig} */
1907
- const nextConfig = {
2040
+ "apps/shell/next.config.ts": `import type { NextConfig } from 'next';
2041
+ // No federation plugin — the shell loads remotes entirely at runtime.
2042
+
2043
+ const nextConfig: NextConfig = {
1908
2044
  reactStrictMode: true,
1909
- // No federation plugin — the shell loads remotes entirely at runtime.
1910
2045
  };
1911
2046
 
1912
- module.exports = nextConfig;
2047
+ export default nextConfig;
1913
2048
  `,
1914
2049
  "apps/shell/src/lib/loadRemoteModule.ts": `// Utility: load a webpack 5 Module Federation container at runtime.
1915
2050
  // Works in any browser environment — no webpack plugin needed on the shell side.
@@ -2014,14 +2149,33 @@ export default function Home() {
2014
2149
  "react-dom": "18.2.0"
2015
2150
  },
2016
2151
  "devDependencies": {
2152
+ "@types/react": "^18.0.0",
2153
+ "@types/react-dom": "^18.0.0",
2017
2154
  "esbuild": "^0.28.0",
2018
2155
  "esbuild-loader": "^4.4.3",
2019
2156
  "html-webpack-plugin": "^5.6.7",
2157
+ "typescript": "^5.6.0",
2020
2158
  "webpack": "^5.106.2",
2021
2159
  "webpack-cli": "^7.0.2",
2022
2160
  "webpack-dev-server": "^5.2.3"
2023
2161
  }
2024
2162
  }
2163
+ `,
2164
+ "apps/remote/tsconfig.json": `{
2165
+ "compilerOptions": {
2166
+ "target": "ES2020",
2167
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
2168
+ "module": "ESNext",
2169
+ "moduleResolution": "bundler",
2170
+ "jsx": "react-jsx",
2171
+ "strict": true,
2172
+ "esModuleInterop": true,
2173
+ "allowSyntheticDefaultImports": true,
2174
+ "skipLibCheck": true,
2175
+ "noEmit": true
2176
+ },
2177
+ "include": ["src"]
2178
+ }
2025
2179
  `,
2026
2180
  "apps/remote/webpack.config.js": `const path = require('path');
2027
2181
  const HtmlWebpackPlugin = require('html-webpack-plugin');
@@ -2031,7 +2185,7 @@ const REMOTE_PORT = Number(process.env.REMOTE_PORT || 3001);
2031
2185
 
2032
2186
  module.exports = {
2033
2187
  mode: 'development',
2034
- entry: './src/index.js',
2188
+ entry: './src/index.ts',
2035
2189
  output: {
2036
2190
  path: path.resolve(__dirname, 'dist'),
2037
2191
  publicPath: \`http://localhost:\${REMOTE_PORT}/\`,
@@ -2045,7 +2199,7 @@ module.exports = {
2045
2199
  exclude: /node_modules/,
2046
2200
  use: {
2047
2201
  loader: 'esbuild-loader',
2048
- options: { loader: 'jsx', jsx: 'automatic', target: 'es2020' },
2202
+ options: { loader: 'tsx', jsx: 'automatic', target: 'es2020' },
2049
2203
  },
2050
2204
  },
2051
2205
  ],
@@ -2055,7 +2209,7 @@ module.exports = {
2055
2209
  name: 'myRemote',
2056
2210
  filename: 'remoteEntry.js',
2057
2211
  exposes: {
2058
- './Widget': './src/Widget.jsx',
2212
+ './Widget': './src/Widget.tsx',
2059
2213
  },
2060
2214
  shared: {
2061
2215
  react: { singleton: true, requiredVersion: false },
@@ -2076,20 +2230,20 @@ module.exports = {
2076
2230
  <body><div id="root"></div></body>
2077
2231
  </html>
2078
2232
  `,
2079
- "apps/remote/src/index.js": `// Async boundary required for Module Federation
2233
+ "apps/remote/src/index.ts": `// Async boundary required for Module Federation
2080
2234
  import('./bootstrap');
2081
2235
  `,
2082
- "apps/remote/src/bootstrap.jsx": `import React from 'react';
2236
+ "apps/remote/src/bootstrap.tsx": `import React from 'react';
2083
2237
  import { createRoot } from 'react-dom/client';
2084
2238
  import Widget from './Widget';
2085
2239
 
2086
- createRoot(document.getElementById('root')).render(<Widget />);
2240
+ createRoot(document.getElementById('root')!).render(<Widget />);
2087
2241
  `,
2088
- "apps/remote/src/Widget.jsx": `import React from 'react';
2242
+ "apps/remote/src/Widget.tsx": `import React from 'react';
2089
2243
 
2090
2244
  // This component is exposed at ./Widget via ModuleFederationPlugin.
2091
2245
  // The shell loads it with loadRemoteModule('myRemote', './Widget', url).
2092
- export default function Widget() {
2246
+ export default function Widget(): React.ReactElement {
2093
2247
  return (
2094
2248
  <section
2095
2249
  style={{
@@ -2167,10 +2321,10 @@ Each zone deploys independently — the shell never rebuilds when Zone B changes
2167
2321
  }
2168
2322
  }
2169
2323
  `,
2170
- "apps/shell/next.config.js": `/** @type {import('next').NextConfig} */
2324
+ "apps/shell/next.config.ts": `import type { NextConfig } from 'next';
2171
2325
  // Shell acts as the routing layer.
2172
2326
  // /store and /store/* are proxied to Zone B running at REMOTE_PORT.
2173
- const nextConfig = {
2327
+ const nextConfig: NextConfig = {
2174
2328
  async rewrites() {
2175
2329
  const zoneB = 'http://localhost:' + process.env.REMOTE_PORT;
2176
2330
  return [
@@ -2179,7 +2333,7 @@ const nextConfig = {
2179
2333
  ];
2180
2334
  },
2181
2335
  };
2182
- module.exports = nextConfig;
2336
+ export default nextConfig;
2183
2337
  `,
2184
2338
  "apps/shell/src/app/layout.tsx": `import type { Metadata } from 'next';
2185
2339
  import React from 'react';
@@ -2229,13 +2383,13 @@ export default function Home() {
2229
2383
  }
2230
2384
  }
2231
2385
  `,
2232
- "apps/zone-b/next.config.js": `/** @type {import('next').NextConfig} */
2386
+ "apps/zone-b/next.config.ts": `import type { NextConfig } from 'next';
2233
2387
  // basePath makes Zone B serve its pages under /store,
2234
2388
  // matching the path prefix the shell forwards here.
2235
- const nextConfig = {
2389
+ const nextConfig: NextConfig = {
2236
2390
  basePath: '/store',
2237
2391
  };
2238
- module.exports = nextConfig;
2392
+ export default nextConfig;
2239
2393
  `,
2240
2394
  "apps/zone-b/src/app/layout.tsx": `import type { Metadata } from 'next';
2241
2395
  import React from 'react';
@@ -2272,7 +2426,7 @@ export const NEXTJS_MULTI_ZONES_LAB: FrontendLabWorkspace = {
2272
2426
  version: 1,
2273
2427
  label: "Next.js \u2014 Multi-Zones",
2274
2428
  type: "module-federation",
2275
- activeFile: "apps/shell/next.config.js",
2429
+ activeFile: "apps/shell/next.config.ts",
2276
2430
  files: NEXTJS_MULTI_ZONES_FILES,
2277
2431
  };
2278
2432
 
@@ -2322,10 +2476,11 @@ This is the recommended safe pattern for consuming federation in the App Router.
2322
2476
  }
2323
2477
  }
2324
2478
  `,
2325
- "apps/shell/next.config.js": `/** @type {import('next').NextConfig} */
2479
+ "apps/shell/next.config.ts": `import type { NextConfig } from 'next';
2326
2480
  // No webpack hooks needed — federation is handled entirely at runtime
2327
2481
  // inside FederatedWidget.tsx via @module-federation/enhanced/runtime.
2328
- module.exports = {};
2482
+ const nextConfig: NextConfig = {};
2483
+ export default nextConfig;
2329
2484
  `,
2330
2485
  "apps/shell/tsconfig.json": `{
2331
2486
  "compilerOptions": {
@@ -2430,23 +2585,40 @@ export default function FederatedWidget() {
2430
2585
  "react-dom": "^19.0.0"
2431
2586
  },
2432
2587
  "devDependencies": {
2588
+ "@types/react": "^19.0.0",
2589
+ "@types/react-dom": "^19.0.0",
2590
+ "esbuild": "^0.28.0",
2591
+ "esbuild-loader": "^4.4.3",
2592
+ "html-webpack-plugin": "^5",
2593
+ "typescript": "^5.6.0",
2433
2594
  "webpack": "^5",
2434
2595
  "webpack-cli": "^5",
2435
- "webpack-dev-server": "^5",
2436
- "html-webpack-plugin": "^5",
2437
- "@babel/core": "^7",
2438
- "@babel/preset-env": "^7",
2439
- "@babel/preset-react": "^7",
2440
- "babel-loader": "^9"
2596
+ "webpack-dev-server": "^5"
2441
2597
  }
2442
2598
  }
2599
+ `,
2600
+ "apps/mf-remote/tsconfig.json": `{
2601
+ "compilerOptions": {
2602
+ "target": "ES2020",
2603
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
2604
+ "module": "ESNext",
2605
+ "moduleResolution": "bundler",
2606
+ "jsx": "react-jsx",
2607
+ "strict": true,
2608
+ "esModuleInterop": true,
2609
+ "allowSyntheticDefaultImports": true,
2610
+ "skipLibCheck": true,
2611
+ "noEmit": true
2612
+ },
2613
+ "include": ["src"]
2614
+ }
2443
2615
  `,
2444
2616
  "apps/mf-remote/webpack.config.js": `const path = require('path');
2445
2617
  const webpack = require('webpack');
2446
2618
  const HtmlWebpackPlugin = require('html-webpack-plugin');
2447
2619
 
2448
2620
  module.exports = {
2449
- entry: './src/index.js',
2621
+ entry: './src/index.ts',
2450
2622
  mode: 'development',
2451
2623
  output: {
2452
2624
  path: path.resolve(__dirname, 'dist'),
@@ -2460,21 +2632,21 @@ module.exports = {
2460
2632
  module: {
2461
2633
  rules: [
2462
2634
  {
2463
- test: /\\.(js|jsx)$/,
2635
+ test: /\\.(js|jsx|ts|tsx)$/,
2464
2636
  exclude: /node_modules/,
2465
2637
  use: {
2466
- loader: 'babel-loader',
2467
- options: { presets: ['@babel/preset-env', '@babel/preset-react'] },
2638
+ loader: 'esbuild-loader',
2639
+ options: { loader: 'tsx', jsx: 'automatic', target: 'es2020' },
2468
2640
  },
2469
2641
  },
2470
2642
  ],
2471
2643
  },
2472
- resolve: { extensions: ['.js', '.jsx'] },
2644
+ resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx'] },
2473
2645
  plugins: [
2474
2646
  new webpack.container.ModuleFederationPlugin({
2475
2647
  name: 'mfRemote',
2476
2648
  filename: 'remoteEntry.js',
2477
- exposes: { './Widget': './src/exposes/Widget' },
2649
+ exposes: { './Widget': './src/exposes/Widget.tsx' },
2478
2650
  shared: {
2479
2651
  react: { singleton: true, eager: true, requiredVersion: '^19.0.0' },
2480
2652
  'react-dom': { singleton: true, eager: true, requiredVersion: '^19.0.0' },
@@ -2484,18 +2656,18 @@ module.exports = {
2484
2656
  ],
2485
2657
  };
2486
2658
  `,
2487
- "apps/mf-remote/src/index.js": `// Remote bootstrap page. MF consumers load ./Widget via remoteEntry.js.
2488
- document.getElementById('root').innerHTML =
2659
+ "apps/mf-remote/src/index.ts": `// Remote bootstrap page. MF consumers load ./Widget via remoteEntry.js.
2660
+ document.getElementById('root')!.innerHTML =
2489
2661
  '<div style="padding:2rem;font-family:system-ui">' +
2490
2662
  '<h2>mfRemote \u2014 running</h2>' +
2491
2663
  '<p>Exposes <code>mfRemote/Widget</code> via Module Federation.</p>' +
2492
2664
  '</div>';
2493
2665
  `,
2494
- "apps/mf-remote/src/exposes/Widget.jsx": `import React from 'react';
2666
+ "apps/mf-remote/src/exposes/Widget.tsx": `import React from 'react';
2495
2667
 
2496
2668
  // Exposed as mfRemote/Widget via ModuleFederationPlugin.
2497
2669
  // The shell loads this at runtime via @module-federation/enhanced/runtime.
2498
- export default function Widget() {
2670
+ export default function Widget(): React.ReactElement {
2499
2671
  return (
2500
2672
  <section style={{
2501
2673
  padding: '1rem', border: '1px solid #e2e8f0',
@@ -2563,14 +2735,33 @@ still work \u2014 here a plain webpack remote is used for simplicity.
2563
2735
  },
2564
2736
  "devDependencies": {
2565
2737
  "@rspack/core": "latest",
2566
- "@rspack/cli": "latest"
2738
+ "@rspack/cli": "latest",
2739
+ "@types/react": "^18.0.0",
2740
+ "@types/react-dom": "^18.0.0",
2741
+ "typescript": "^5.6.0"
2567
2742
  }
2568
2743
  }
2744
+ `,
2745
+ "apps/rspack-shell/tsconfig.json": `{
2746
+ "compilerOptions": {
2747
+ "target": "ES2020",
2748
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
2749
+ "module": "ESNext",
2750
+ "moduleResolution": "bundler",
2751
+ "jsx": "react-jsx",
2752
+ "strict": true,
2753
+ "esModuleInterop": true,
2754
+ "allowSyntheticDefaultImports": true,
2755
+ "skipLibCheck": true,
2756
+ "noEmit": true
2757
+ },
2758
+ "include": ["src"]
2759
+ }
2569
2760
  `,
2570
2761
  "apps/rspack-shell/rspack.config.js": `const rspack = require('@rspack/core');
2571
2762
 
2572
2763
  module.exports = {
2573
- entry: './src/index.jsx',
2764
+ entry: './src/index.tsx',
2574
2765
  mode: 'development',
2575
2766
  output: { publicPath: 'auto' },
2576
2767
  devServer: {
@@ -2578,16 +2769,17 @@ module.exports = {
2578
2769
  port: parseInt(process.env.HOST_PORT, 10) || 3000,
2579
2770
  hot: true,
2580
2771
  },
2581
- resolve: { extensions: ['.js', '.jsx'] },
2772
+ resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx'] },
2582
2773
  module: {
2583
2774
  rules: [
2584
2775
  {
2585
- test: /\\.(js|jsx)$/,
2776
+ test: /\\.(js|jsx|ts|tsx)$/,
2586
2777
  // Rspack ships a built-in SWC loader — no Babel or extra packages needed.
2778
+ // Use 'typescript' syntax to handle .ts/.tsx files.
2587
2779
  loader: 'builtin:swc-loader',
2588
2780
  options: {
2589
2781
  jsc: {
2590
- parser: { syntax: 'ecmascript', jsx: true },
2782
+ parser: { syntax: 'typescript', tsx: true },
2591
2783
  transform: { react: { runtime: 'automatic' } },
2592
2784
  },
2593
2785
  },
@@ -2627,19 +2819,30 @@ module.exports = {
2627
2819
  </body>
2628
2820
  </html>
2629
2821
  `,
2630
- "apps/rspack-shell/src/index.jsx": `import { createRoot } from 'react-dom/client';
2822
+ "apps/rspack-shell/src/mf.d.ts": `// Type declarations for Module Federation remotes.
2823
+ // Rspack resolves these at runtime — TypeScript needs a fallback declaration.
2824
+ declare module 'mfRemote/Widget' {
2825
+ import type { ComponentType } from 'react';
2826
+ const Widget: ComponentType;
2827
+ export default Widget;
2828
+ }
2829
+ `,
2830
+ "apps/rspack-shell/src/index.tsx": `import React from 'react';
2831
+ import { createRoot } from 'react-dom/client';
2631
2832
  import App from './App';
2632
2833
 
2633
- // Rspack's SWC loader handles JSX via builtin:swc-loader (no Babel needed).
2634
- createRoot(document.getElementById('root')).render(<App />);
2834
+ // Rspack's SWC loader handles TSX via builtin:swc-loader (no Babel needed).
2835
+ createRoot(document.getElementById('root')!).render(<App />);
2635
2836
  `,
2636
- "apps/rspack-shell/src/App.jsx": `import React, { Suspense, lazy } from 'react';
2837
+ "apps/rspack-shell/src/App.tsx": `import React, { Suspense, lazy } from 'react';
2838
+ import type { ComponentType } from 'react';
2637
2839
 
2638
2840
  // mfRemote is declared in rspack.config.js under remotes.
2639
2841
  // Rspack resolves this lazy import to the federated container at runtime.
2640
- const RemoteWidget = lazy(() => import('mfRemote/Widget'));
2842
+ // The type declaration is in src/mf.d.ts.
2843
+ const RemoteWidget = lazy<ComponentType>(() => import('mfRemote/Widget'));
2641
2844
 
2642
- export default function App() {
2845
+ export default function App(): React.ReactElement {
2643
2846
  return (
2644
2847
  <main style={{ padding: '2rem', fontFamily: 'system-ui, sans-serif' }}>
2645
2848
  <h1>Rspack Shell</h1>
@@ -2663,16 +2866,33 @@ export default function App() {
2663
2866
  "react-dom": "^18.0.0"
2664
2867
  },
2665
2868
  "devDependencies": {
2869
+ "@types/react": "^18.0.0",
2870
+ "@types/react-dom": "^18.0.0",
2871
+ "esbuild": "^0.28.0",
2872
+ "esbuild-loader": "^4.4.3",
2873
+ "html-webpack-plugin": "^5",
2874
+ "typescript": "^5.6.0",
2666
2875
  "webpack": "^5",
2667
2876
  "webpack-cli": "^5",
2668
- "webpack-dev-server": "^5",
2669
- "html-webpack-plugin": "^5",
2670
- "@babel/core": "^7",
2671
- "@babel/preset-env": "^7",
2672
- "@babel/preset-react": "^7",
2673
- "babel-loader": "^9"
2877
+ "webpack-dev-server": "^5"
2674
2878
  }
2675
2879
  }
2880
+ `,
2881
+ "apps/webpack-remote/tsconfig.json": `{
2882
+ "compilerOptions": {
2883
+ "target": "ES2020",
2884
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
2885
+ "module": "ESNext",
2886
+ "moduleResolution": "bundler",
2887
+ "jsx": "react-jsx",
2888
+ "strict": true,
2889
+ "esModuleInterop": true,
2890
+ "allowSyntheticDefaultImports": true,
2891
+ "skipLibCheck": true,
2892
+ "noEmit": true
2893
+ },
2894
+ "include": ["src"]
2895
+ }
2676
2896
  `,
2677
2897
  "apps/webpack-remote/webpack.config.js": `const path = require('path');
2678
2898
  const webpack = require('webpack');
@@ -2693,21 +2913,21 @@ module.exports = {
2693
2913
  module: {
2694
2914
  rules: [
2695
2915
  {
2696
- test: /\\.(js|jsx)$/,
2916
+ test: /\\.(js|jsx|ts|tsx)$/,
2697
2917
  exclude: /node_modules/,
2698
2918
  use: {
2699
- loader: 'babel-loader',
2700
- options: { presets: ['@babel/preset-env', '@babel/preset-react'] },
2919
+ loader: 'esbuild-loader',
2920
+ options: { loader: 'tsx', jsx: 'automatic', target: 'es2020' },
2701
2921
  },
2702
2922
  },
2703
2923
  ],
2704
2924
  },
2705
- resolve: { extensions: ['.js', '.jsx'] },
2925
+ resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx'] },
2706
2926
  plugins: [
2707
2927
  new webpack.container.ModuleFederationPlugin({
2708
2928
  name: 'mfRemote',
2709
2929
  filename: 'remoteEntry.js',
2710
- exposes: { './Widget': './src/exposes/Widget' },
2930
+ exposes: { './Widget': './src/exposes/Widget.tsx' },
2711
2931
  shared: {
2712
2932
  react: { singleton: true, eager: true, requiredVersion: '^18.0.0' },
2713
2933
  'react-dom': { singleton: true, eager: true, requiredVersion: '^18.0.0' },
@@ -2724,10 +2944,10 @@ document.getElementById('root').innerHTML =
2724
2944
  '<p>Exposes <code>mfRemote/Widget</code> via Module Federation.</p>' +
2725
2945
  '</div>';
2726
2946
  `,
2727
- "apps/webpack-remote/src/exposes/Widget.jsx": `import React from 'react';
2947
+ "apps/webpack-remote/src/exposes/Widget.tsx": `import React from 'react';
2728
2948
 
2729
2949
  // Exposed as mfRemote/Widget. The Rspack shell loads this via lazy import.
2730
- export default function Widget() {
2950
+ export default function Widget(): React.ReactElement {
2731
2951
  return (
2732
2952
  <section style={{
2733
2953
  padding: '1rem', border: '1px solid #e2e8f0',
@@ -2843,9 +3063,11 @@ export function getEntryFile(workspace: FrontendLabWorkspace): string {
2843
3063
  : Object.keys(workspace.files)[0];
2844
3064
  }
2845
3065
  if (workspace.type === "module-federation") {
2846
- return workspace.files["apps/host/src/App.jsx"]
2847
- ? "apps/host/src/App.jsx"
2848
- : Object.keys(workspace.files)[0];
3066
+ return workspace.files["apps/host/src/App.tsx"]
3067
+ ? "apps/host/src/App.tsx"
3068
+ : workspace.files["apps/host/src/App.jsx"]
3069
+ ? "apps/host/src/App.jsx"
3070
+ : Object.keys(workspace.files)[0];
2849
3071
  }
2850
3072
  return workspace.files["main.tsx"]
2851
3073
  ? "main.tsx"