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.
- package/package.json +1 -1
- package/template/client/package-lock.json +75 -2
- package/template/client/package.json +3 -1
- package/template/client/src/browserSecurityTemplates.ts +75 -72
- package/template/client/src/components/CodeRunnerModal.tsx +504 -24
- package/template/client/src/reactLab.ts +374 -152
- package/template/cockpit.json +1 -1
- package/template/server/src/index.ts +134 -47
|
@@ -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/
|
|
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(
|
|
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": `
|
|
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.
|
|
646
|
+
"apps/host/src/index.tsx": `import("./bootstrap");
|
|
615
647
|
`,
|
|
616
|
-
"apps/host/src/bootstrap.
|
|
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.
|
|
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.
|
|
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: "
|
|
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/
|
|
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.
|
|
877
|
+
"apps/profile/src/index.tsx": `import("./bootstrap");
|
|
826
878
|
`,
|
|
827
|
-
"apps/profile/src/bootstrap.
|
|
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.
|
|
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.
|
|
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.
|
|
887
|
-
"./InspectorBridge": path.resolve(__dirname, "./src/inspectorBridge.
|
|
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.
|
|
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: "
|
|
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/
|
|
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.
|
|
1058
|
+
"apps/checkout/src/index.tsx": `import("./bootstrap");
|
|
988
1059
|
`,
|
|
989
|
-
"apps/checkout/src/bootstrap.
|
|
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.
|
|
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.
|
|
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.
|
|
1057
|
-
"./InspectorBridge": path.resolve(__dirname, "./src/inspectorBridge.
|
|
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.
|
|
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: "
|
|
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.
|
|
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.
|
|
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: "
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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);
|
|
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.
|
|
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: "
|
|
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.
|
|
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.
|
|
1567
|
+
"apps/mfe-auth/src/index.ts": `import("./bootstrap");
|
|
1447
1568
|
`,
|
|
1448
|
-
"apps/mfe-auth/src/bootstrap.
|
|
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.
|
|
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.
|
|
1489
|
-
// mount.
|
|
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.
|
|
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.
|
|
1617
|
-
|
|
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
|
-
|
|
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.
|
|
1757
|
-
|
|
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
|
-
|
|
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.
|
|
1907
|
-
|
|
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
|
-
|
|
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.
|
|
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: '
|
|
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.
|
|
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.
|
|
2233
|
+
"apps/remote/src/index.ts": `// Async boundary required for Module Federation
|
|
2080
2234
|
import('./bootstrap');
|
|
2081
2235
|
`,
|
|
2082
|
-
"apps/remote/src/bootstrap.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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: '
|
|
2467
|
-
options: {
|
|
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.
|
|
2488
|
-
document.getElementById('root')
|
|
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.
|
|
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.
|
|
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: '
|
|
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/
|
|
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
|
|
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.
|
|
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
|
-
|
|
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: '
|
|
2700
|
-
options: {
|
|
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.
|
|
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.
|
|
2847
|
-
? "apps/host/src/App.
|
|
2848
|
-
:
|
|
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"
|