Skip to content

Commit efc2f8d

Browse files
workaround e2e flakes, consume all mf modules
1 parent b4f11c1 commit efc2f8d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+1466
-257
lines changed

examples/epic-stack/app/routes/_auth+/login.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import { type SEOHandle } from '@nasa-gcn/remix-seo'
44
import { data, Form, Link, useSearchParams } from 'react-router'
55
import { HoneypotInputs } from 'remix-utils/honeypot/react'
66
import { z } from 'zod'
7-
import { GeneralErrorBoundary } from '#app/components/error-boundary.tsx'
8-
import { CheckboxField, ErrorList, Field } from '#app/components/forms.tsx'
9-
import { Spacer } from '#app/components/spacer.tsx'
10-
import { StatusButton } from '#app/components/ui/status-button.tsx'
7+
import { GeneralErrorBoundary } from 'remote/components/error-boundary'
8+
import { CheckboxField, ErrorList, Field } from 'remote/components/forms'
9+
import { Spacer } from 'remote/components/spacer'
10+
import { StatusButton } from 'remote/components/ui/status-button'
1111
import { login, requireAnonymous } from '#app/utils/auth.server.ts'
1212
import {
1313
ProviderConnectionForm,

examples/federation/epic-stack-remote/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
"build:remix": "rsbuild build",
1616
"build:server": "tsx ./other/build-server.ts",
1717
"predev": "npm run build:icons --silent",
18-
"dev": "cross-env NODE_ENV=development MOCKS=true PORT=3001 tsx ./server/dev-server.js",
18+
"dev": "cross-env NODE_ENV=development MOCKS=true PORT=3001 NODE_OPTIONS=--experimental-vm-modules tsx ./server/dev-server.js",
1919
"prisma:studio": "prisma studio",
2020
"format": "prettier --write .",
2121
"lint": "eslint .",
22-
"setup": "prisma generate && prisma migrate reset && playwright install && pnpm run build",
23-
"start": "cross-env NODE_ENV=production node .",
24-
"start:mocks": "cross-env NODE_ENV=production MOCKS=true tsx .",
22+
"setup": "prisma generate && prisma migrate reset --force && playwright install && pnpm run build",
23+
"start": "cross-env NODE_ENV=production NODE_OPTIONS=--experimental-vm-modules node .",
24+
"start:mocks": "cross-env NODE_ENV=production MOCKS=true NODE_OPTIONS=--experimental-vm-modules tsx .",
2525
"test": "vitest",
2626
"coverage": "vitest run --coverage",
2727
"test:e2e": "npm run test:e2e:dev --silent",

examples/federation/epic-stack-remote/playwright.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { defineConfig, devices } from '@playwright/test'
22
import 'dotenv/config'
33

4-
const PORT = process.env.PORT || '3000'
4+
const PORT = process.env.PORT || '3001'
55

66
export default defineConfig({
77
testDir: './tests/e2e',

examples/federation/epic-stack-remote/rsbuild.config.ts

Lines changed: 78 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,79 @@ import type { Compiler } from '@rspack/core'
66

77
import 'react-router'
88

9-
class RuntimePlugin {
10-
apply(compiler: Compiler) {
11-
const { RuntimeGlobals } = compiler.webpack;
12-
compiler.hooks.compilation.tap('CustomPlugin', compilation => {
13-
compiler.options.devtool = false
14-
compilation.hooks.runtimeModule.tap(
15-
'CustomPlugin',
16-
(module: any, chunk: any) => {
17-
if(module.name === "module_chunk_loading") {
18-
const interceptor =`
19-
console.log('test');
20-
var getModuleUrl = function() {
21-
console.log('Getting module URL', import.meta.url);
22-
return import.meta.url;
23-
};
24-
25-
var importInterceptor = function(path) {
26-
var currentUrl = getModuleUrl();
27-
console.log('Importing:', path);
28-
console.log('From module:', currentUrl);
29-
return import(path);
30-
};\n
31-
`
32-
module.source.source = interceptor + module.source.source.replace(/import\(/g, 'importInterceptor(');
33-
}
34-
}
35-
);
36-
});
37-
}
9+
10+
// Common shared dependencies for Module Federation
11+
const sharedDependencies = {
12+
'react-router': {
13+
singleton: true,
14+
},
15+
'react-router/': {
16+
singleton: true,
17+
},
18+
react: {
19+
singleton: true,
20+
},
21+
'react/': {
22+
singleton: true,
23+
},
24+
'react-dom': {
25+
singleton: true,
26+
},
27+
'react-dom/': {
28+
singleton: true,
29+
},
30+
}
31+
32+
// Common exposed components
33+
const exposedComponents = {
34+
'./components/search-bar': './app/components/search-bar',
35+
'./components/user-dropdown': './app/components/user-dropdown',
36+
'./components/spacer': './app/components/spacer',
37+
'./components/toaster': './app/components/toaster',
38+
'./components/error-boundary': './app/components/error-boundary',
39+
'./components/floating-toolbar': './app/components/floating-toolbar',
40+
'./components/forms': './app/components/forms',
41+
'./components/progress-bar': './app/components/progress-bar',
42+
'./components/ui/tooltip': './app/components/ui/tooltip',
43+
'./components/ui/status-button': './app/components/ui/status-button',
44+
'./components/ui/textarea': './app/components/ui/textarea',
45+
'./components/ui/sonner': './app/components/ui/sonner',
46+
'./components/ui/label': './app/components/ui/label',
47+
'./components/ui/input': './app/components/ui/input',
48+
'./components/ui/input-otp': './app/components/ui/input-otp',
49+
'./components/ui/dropdown-menu': './app/components/ui/dropdown-menu',
50+
'./components/ui/icon': './app/components/ui/icon',
51+
'./components/ui/button': './app/components/ui/button',
52+
'./components/ui/checkbox': './app/components/ui/checkbox',
53+
"./utils/connections": "./app/utils/connections",
54+
}
55+
56+
// Common Module Federation configuration
57+
const commonFederationConfig = {
58+
name: 'remote',
59+
shareStrategy: "loaded-first" as const,
60+
runtime: undefined,
61+
exposes: exposedComponents,
62+
shared: sharedDependencies
63+
}
64+
65+
// Web-specific federation config
66+
const webFederationConfig = {
67+
...commonFederationConfig,
68+
library: {
69+
type: 'module'
70+
},
71+
}
72+
73+
// Node-specific federation config
74+
const nodeFederationConfig = {
75+
...commonFederationConfig,
76+
library: {
77+
type: 'commonjs-module'
78+
},
79+
runtimePlugins: [
80+
'@module-federation/node/runtimePlugin'
81+
],
3882
}
3983

4084
export default defineConfig({
@@ -58,116 +102,20 @@ export default defineConfig({
58102
tools: {
59103
rspack: {
60104
plugins: [
61-
new ModuleFederationPlugin({
62-
name: 'remote',
63-
library: {
64-
type: 'module'
65-
},
66-
shareStrategy: "loaded-first",
67-
runtime: false,
68-
exposes: {
69-
'./components/search-bar': './app/components/search-bar',
70-
'./components/user-dropdown': './app/components/user-dropdown',
71-
'./components/spacer': './app/components/spacer',
72-
'./components/toaster': './app/components/toaster',
73-
'./components/error-boundary': './app/components/error-boundary',
74-
'./components/floating-toolbar': './app/components/floating-toolbar',
75-
'./components/forms': './app/components/forms',
76-
'./components/progress-bar': './app/components/progress-bar',
77-
'./components/ui/tooltip': './app/components/ui/tooltip',
78-
'./components/ui/status-button': './app/components/ui/status-button',
79-
'./components/ui/textarea': './app/components/ui/textarea',
80-
'./components/ui/sonner': './app/components/ui/sonner',
81-
'./components/ui/label': './app/components/ui/label',
82-
'./components/ui/input': './app/components/ui/input',
83-
'./components/ui/input-otp': './app/components/ui/input-otp',
84-
'./components/ui/dropdown-menu': './app/components/ui/dropdown-menu',
85-
'./components/ui/icon': './app/components/ui/icon',
86-
'./components/ui/button': './app/components/ui/button',
87-
'./components/ui/checkbox': './app/components/ui/checkbox',
88-
},
89-
shared: {
90-
'react-router': {
91-
singleton: true,
92-
},
93-
'react-router/': {
94-
singleton: true,
95-
},
96-
react: {
97-
singleton: true,
98-
},
99-
'react/': {
100-
singleton: true,
101-
},
102-
'react-dom': {
103-
singleton: true,
104-
},
105-
'react-dom/': {
106-
singleton: true,
107-
},
108-
}
109-
})
105+
new ModuleFederationPlugin(webFederationConfig)
110106
]
111107
}
112108
},
113109
plugins: []
114110
},
115111
node: {
112+
output: {
113+
assetPrefix: 'http://localhost:3001/',
114+
},
116115
tools: {
117116
rspack: {
118117
plugins: [
119-
new ModuleFederationPlugin({
120-
name: 'remote',
121-
library: {
122-
type: 'commonjs-module'
123-
},
124-
dts: false,
125-
runtimePlugins: [
126-
'@module-federation/node/runtimePlugin'
127-
],
128-
runtime: false,
129-
exposes: {
130-
'./components/search-bar': './app/components/search-bar',
131-
'./components/user-dropdown': './app/components/user-dropdown',
132-
'./components/spacer': './app/components/spacer',
133-
'./components/toaster': './app/components/toaster',
134-
'./components/error-boundary': './app/components/error-boundary',
135-
'./components/floating-toolbar': './app/components/floating-toolbar',
136-
'./components/forms': './app/components/forms',
137-
'./components/progress-bar': './app/components/progress-bar',
138-
'./components/ui/tooltip': './app/components/ui/tooltip',
139-
'./components/ui/status-button': './app/components/ui/status-button',
140-
'./components/ui/textarea': './app/components/ui/textarea',
141-
'./components/ui/sonner': './app/components/ui/sonner',
142-
'./components/ui/label': './app/components/ui/label',
143-
'./components/ui/input': './app/components/ui/input',
144-
'./components/ui/input-otp': './app/components/ui/input-otp',
145-
'./components/ui/dropdown-menu': './app/components/ui/dropdown-menu',
146-
'./components/ui/icon': './app/components/ui/icon',
147-
'./components/ui/button': './app/components/ui/button',
148-
'./components/ui/checkbox': './app/components/ui/checkbox',
149-
},
150-
shared: {
151-
'react-router': {
152-
singleton: true,
153-
},
154-
'react-router/': {
155-
singleton: true,
156-
},
157-
react: {
158-
singleton: true,
159-
},
160-
'react/': {
161-
singleton: true,
162-
},
163-
'react-dom': {
164-
singleton: true,
165-
},
166-
'react-dom/': {
167-
singleton: true,
168-
},
169-
}
170-
})
118+
new ModuleFederationPlugin(nodeFederationConfig)
171119
]
172120
}
173121
},

examples/federation/epic-stack-remote/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"extends": ["@epic-web/config/typescript"],
44
"compilerOptions": {
55
// TODO: Probably should move this into epic-web/config
6-
"types": ["@react-router/node", "vite/client"],
6+
"types": ["@react-router/node"],
77
"rootDirs": [".", "./.react-router/types"],
88
"paths": {
99
"#app/*": ["./app/*"],
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
/// <reference types="vite/client" />
21
/// <reference types="@remix-run/node" />
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { captureException } from '@sentry/react'
2+
import { useEffect, type ReactElement } from 'react'
3+
import {
4+
type ErrorResponse,
5+
isRouteErrorResponse,
6+
useParams,
7+
useRouteError,
8+
} from 'react-router'
9+
import { getErrorMessage } from '#app/utils/misc'
10+
11+
type StatusHandler = (info: {
12+
error: ErrorResponse
13+
params: Record<string, string | undefined>
14+
}) => ReactElement | null
15+
16+
export function GeneralErrorBoundary({
17+
defaultStatusHandler = ({ error }) => (
18+
<p>
19+
{error.status} {error.data}
20+
</p>
21+
),
22+
statusHandlers,
23+
unexpectedErrorHandler = (error) => <p>{getErrorMessage(error)}</p>,
24+
}: {
25+
defaultStatusHandler?: StatusHandler
26+
statusHandlers?: Record<number, StatusHandler>
27+
unexpectedErrorHandler?: (error: unknown) => ReactElement | null
28+
}) {
29+
const error = useRouteError()
30+
const params = useParams()
31+
const isResponse = isRouteErrorResponse(error)
32+
33+
if (typeof document !== 'undefined') {
34+
console.error(error)
35+
}
36+
37+
useEffect(() => {
38+
if (isResponse) return
39+
40+
captureException(error)
41+
}, [error, isResponse])
42+
43+
return (
44+
<div className="container flex items-center justify-center p-20 text-h2">
45+
{isResponse
46+
? (statusHandlers?.[error.status] ?? defaultStatusHandler)({
47+
error,
48+
params,
49+
})
50+
: unexpectedErrorHandler(error)}
51+
</div>
52+
)
53+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const floatingToolbarClassName =
2+
'absolute bottom-3 left-3 right-3 flex items-center gap-2 rounded-lg bg-muted/80 p-4 pl-5 shadow-xl shadow-accent backdrop-blur-sm md:gap-4 md:pl-7 justify-end'

0 commit comments

Comments
 (0)