create-bluecopa-react-app 1.0.1 → 1.0.3
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/README.md +144 -53
- package/bin/create-bluecopa-react-app.js +4 -4
- package/package.json +2 -2
- package/templates/latest/Agent.md +288 -0
- package/templates/latest/README.md +31 -9
- package/templates/latest/package-lock.json +282 -134
- package/templates/latest/package.json +3 -7
- package/templates/latest/preview/index.html +330 -0
- package/templates/latest/vite.config.ts +18 -31
|
@@ -7,16 +7,12 @@
|
|
|
7
7
|
"scripts": {
|
|
8
8
|
"dev": "vite",
|
|
9
9
|
"build": "tsc && vite build",
|
|
10
|
-
"
|
|
10
|
+
"start": "npx serve dist -l 8080 --cors",
|
|
11
|
+
"preview": "npx serve ./preview -l 3001 --cors",
|
|
11
12
|
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 100",
|
|
12
13
|
"lint:fix": "eslint src --ext ts,tsx --fix",
|
|
13
14
|
"type-check": "tsc --noEmit",
|
|
14
15
|
"clean": "rm -rf dist node_modules package-lock.json",
|
|
15
|
-
"serve:federation": "npm run build:federation && npx serve dist -l 8080 --cors",
|
|
16
|
-
"serve:standalone": "npm run build:standalone && npx serve dist-standalone -l 8080 --cors",
|
|
17
|
-
"build:federation": "vite build",
|
|
18
|
-
"build:standalone": "VITE_BUILD_TARGET=standalone vite build --outDir dist-standalone && cp dist-standalone/standalone.html dist-standalone/index.html",
|
|
19
|
-
"build:dev": "vite build --mode development",
|
|
20
16
|
"start:railway": "ls -la && echo \"BUILD_TYPE: $BUILD_TYPE\" && echo \"Serving: dist${BUILD_TYPE:+-$BUILD_TYPE}\" && npx serve -s dist${BUILD_TYPE:+-$BUILD_TYPE} -l $PORT --cors"
|
|
21
17
|
},
|
|
22
18
|
"dependencies": {
|
|
@@ -56,6 +52,6 @@
|
|
|
56
52
|
"postcss": "^8.4.47",
|
|
57
53
|
"tailwindcss": "^3.4.17",
|
|
58
54
|
"typescript": "^5.6.0",
|
|
59
|
-
"vite": "^
|
|
55
|
+
"vite": "^7.1.5"
|
|
60
56
|
}
|
|
61
57
|
}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Bluecopa Preview</title>
|
|
8
|
+
<meta name="description" content="Preview wrapper for federation module" />
|
|
9
|
+
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div id="loading-indicator" class="loading">
|
|
13
|
+
Loading Bluecopa module...
|
|
14
|
+
</div>
|
|
15
|
+
<div id="error-container" style="display: none;"></div>
|
|
16
|
+
<div id="bluecopa-preview"></div>
|
|
17
|
+
|
|
18
|
+
<!-- SystemJS import map for shared dependencies (UMD for compatibility) -->
|
|
19
|
+
<script type="systemjs-importmap">
|
|
20
|
+
{
|
|
21
|
+
"imports": {
|
|
22
|
+
"react": "https://unpkg.com/react@18/umd/react.development.js",
|
|
23
|
+
"react-dom": "https://unpkg.com/react-dom@18/umd/react-dom.development.js",
|
|
24
|
+
"react-router-dom": "https://unpkg.com/react-router-dom@6/dist/umd/react-router-dom.development.js",
|
|
25
|
+
"single-spa": "https://unpkg.com/single-spa@5/dist/system/single-spa.min.js",
|
|
26
|
+
"single-spa-react": "https://unpkg.com/single-spa-react@4/dist/umd/single-spa-react.min.js"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<!-- SystemJS for loading System format bluecopa modules -->
|
|
32
|
+
<script src="https://unpkg.com/systemjs@6/dist/system.min.js"></script>
|
|
33
|
+
|
|
34
|
+
<script type="module">
|
|
35
|
+
// Configure SystemJS with import map after load
|
|
36
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
37
|
+
if (window.System) {
|
|
38
|
+
const importMap = document.querySelector('script[type="systemjs-importmap"]');
|
|
39
|
+
if (importMap) {
|
|
40
|
+
window.System.addImportMap(JSON.parse(importMap.textContent));
|
|
41
|
+
}
|
|
42
|
+
// Auto-load on page open
|
|
43
|
+
await loadFederationModule();
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
let currentModule = null;
|
|
47
|
+
let currentMountProps = null;
|
|
48
|
+
|
|
49
|
+
// Helper to load remoteEntry.js script (no wait needed, as System.import will handle)
|
|
50
|
+
async function loadRemoteEntryScript(moduleUrl) {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
// Remove any previous script
|
|
53
|
+
const existingScript = document.querySelector(`script[src='${moduleUrl}']`);
|
|
54
|
+
if (existingScript) existingScript.remove();
|
|
55
|
+
// Create new script
|
|
56
|
+
const script = document.createElement('script');
|
|
57
|
+
script.type = 'text/javascript';
|
|
58
|
+
script.src = moduleUrl;
|
|
59
|
+
script.async = true;
|
|
60
|
+
script.onload = () => resolve();
|
|
61
|
+
script.onerror = () => reject(new Error('Failed to load remoteEntry.js'));
|
|
62
|
+
document.head.appendChild(script);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Global function to load federation module
|
|
67
|
+
window.loadFederationModule = async function() {
|
|
68
|
+
// Hardcoded defaults
|
|
69
|
+
const moduleUrl = "http://localhost:8080/assets/remoteEntry.js";
|
|
70
|
+
const moduleName = "__copa_ext_app__react_route";
|
|
71
|
+
const exposeName = "./App";
|
|
72
|
+
const basePath = "/preview";
|
|
73
|
+
|
|
74
|
+
const loadingIndicator = document.getElementById('loading-indicator');
|
|
75
|
+
const errorContainer = document.getElementById('error-container');
|
|
76
|
+
const previewContainer = document.getElementById('bluecopa-preview');
|
|
77
|
+
|
|
78
|
+
// Show loading state
|
|
79
|
+
loadingIndicator.style.display = 'flex';
|
|
80
|
+
loadingIndicator.textContent = 'Loading bluecopa module...';
|
|
81
|
+
errorContainer.style.display = 'none';
|
|
82
|
+
previewContainer.innerHTML = '';
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
// Unload any existing module first
|
|
86
|
+
await unloadModule();
|
|
87
|
+
|
|
88
|
+
console.log('Loading federation module from:', moduleUrl);
|
|
89
|
+
|
|
90
|
+
// Load remoteEntry script
|
|
91
|
+
await loadRemoteEntryScript(moduleUrl);
|
|
92
|
+
|
|
93
|
+
// Clear System cache for fresh load
|
|
94
|
+
if (window.System && typeof window.System.delete === 'function') {
|
|
95
|
+
window.System.delete(moduleUrl);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Use SystemJS to import the container with timeout
|
|
99
|
+
if (typeof System !== 'undefined') {
|
|
100
|
+
const container = await Promise.race([
|
|
101
|
+
System.import(moduleUrl),
|
|
102
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Module load timeout')), 15000))
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
console.log('Loaded container:', container);
|
|
106
|
+
|
|
107
|
+
// Try to get the exposed module with multiple entry points
|
|
108
|
+
let appModule;
|
|
109
|
+
if (typeof container.get === 'function') {
|
|
110
|
+
const entryPoints = ['./App', './app', './index', './main', './src/App', './src/index'];
|
|
111
|
+
for (const entry of entryPoints) {
|
|
112
|
+
try {
|
|
113
|
+
const factory = await container.get(entry);
|
|
114
|
+
appModule = factory ? factory() : factory;
|
|
115
|
+
console.log(`Found module at ${entry}:`, appModule);
|
|
116
|
+
break;
|
|
117
|
+
} catch (e) {
|
|
118
|
+
console.log(`No module at ${entry}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!appModule) {
|
|
124
|
+
appModule = container;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
await mountModule(appModule, basePath, loadingIndicator, previewContainer, container);
|
|
128
|
+
} else {
|
|
129
|
+
throw new Error('SystemJS not available');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
} catch (error) {
|
|
133
|
+
// Show error visibly on the page
|
|
134
|
+
loadingIndicator.style.display = 'none';
|
|
135
|
+
errorContainer.style.display = 'block';
|
|
136
|
+
errorContainer.innerHTML = `
|
|
137
|
+
<div class="error">
|
|
138
|
+
<h2>Failed to Load Application</h2>
|
|
139
|
+
<p>${error.message}</p>
|
|
140
|
+
<ul>
|
|
141
|
+
<li>Make sure the federation build is running: <code>npm run preview</code></li>
|
|
142
|
+
<li>Verify the module URL points to the correct remoteEntry.js</li>
|
|
143
|
+
<li>Check that module name matches your vite.config.ts federation name</li>
|
|
144
|
+
<li>Open browser DevTools Console for detailed error information</li>
|
|
145
|
+
<li>Ensure CORS is enabled on your federation server</li>
|
|
146
|
+
</ul>
|
|
147
|
+
<p>Refresh the page to retry.</p>
|
|
148
|
+
</div>
|
|
149
|
+
`;
|
|
150
|
+
// Also log to console
|
|
151
|
+
console.error('bluecopa preview error:', error);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// Helper function to mount the module
|
|
156
|
+
async function mountModule(appModule, basePath, loadingIndicator, previewContainer, container) {
|
|
157
|
+
// Create a container for the module
|
|
158
|
+
const moduleContainer = document.createElement('div');
|
|
159
|
+
moduleContainer.id = 'single-spa-application:bluecopa-preview';
|
|
160
|
+
moduleContainer.style.width = '100%';
|
|
161
|
+
moduleContainer.style.minHeight = '500px';
|
|
162
|
+
previewContainer.appendChild(moduleContainer);
|
|
163
|
+
|
|
164
|
+
console.log('App module:', appModule);
|
|
165
|
+
console.log('App module type:', typeof appModule);
|
|
166
|
+
|
|
167
|
+
let mounted = false;
|
|
168
|
+
|
|
169
|
+
// Initialize container if needed
|
|
170
|
+
if (typeof container.init === 'function') {
|
|
171
|
+
try {
|
|
172
|
+
await container.init({});
|
|
173
|
+
} catch (e) {
|
|
174
|
+
console.warn('Container init failed:', e);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Try multiple mount patterns
|
|
179
|
+
const mountFunc = appModule?.mount ||
|
|
180
|
+
appModule?.default?.mount ||
|
|
181
|
+
appModule?.render ||
|
|
182
|
+
appModule?.default?.render ||
|
|
183
|
+
appModule?.init ||
|
|
184
|
+
appModule?.default?.init ||
|
|
185
|
+
appModule?.bootstrap ||
|
|
186
|
+
appModule?.default?.bootstrap ||
|
|
187
|
+
appModule?.start ||
|
|
188
|
+
appModule?.default?.start ||
|
|
189
|
+
(typeof appModule === 'function' ? appModule : null);
|
|
190
|
+
|
|
191
|
+
if (typeof mountFunc === 'function') {
|
|
192
|
+
console.log('Found mount function, mounting app...');
|
|
193
|
+
|
|
194
|
+
// Clear container
|
|
195
|
+
moduleContainer.innerHTML = '';
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
// Pattern 1: Single-spa config
|
|
199
|
+
const props = {
|
|
200
|
+
domElement: moduleContainer,
|
|
201
|
+
basename: basePath,
|
|
202
|
+
singleSpa: {
|
|
203
|
+
name: 'bluecopa-preview',
|
|
204
|
+
mountParcel: () => {},
|
|
205
|
+
getProps: () => ({})
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
await mountFunc(props);
|
|
209
|
+
mounted = true;
|
|
210
|
+
} catch (e1) {
|
|
211
|
+
console.log('Single-spa pattern failed:', e1);
|
|
212
|
+
try {
|
|
213
|
+
// Pattern 2: Direct element
|
|
214
|
+
await mountFunc(moduleContainer);
|
|
215
|
+
mounted = true;
|
|
216
|
+
} catch (e2) {
|
|
217
|
+
console.log('Direct element pattern failed:', e2);
|
|
218
|
+
try {
|
|
219
|
+
// Pattern 3: No args
|
|
220
|
+
await mountFunc();
|
|
221
|
+
mounted = true;
|
|
222
|
+
} catch (e3) {
|
|
223
|
+
console.error('All mount patterns failed:', { e1, e2, e3 });
|
|
224
|
+
throw new Error('Failed to mount application with any pattern');
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (mounted) {
|
|
230
|
+
currentModule = { unmount: async () => await unmountModule(appModule, moduleContainer), container };
|
|
231
|
+
currentMountProps = { domElement: moduleContainer, basename: basePath };
|
|
232
|
+
loadingIndicator.style.display = 'none';
|
|
233
|
+
console.log('Bluecopa module mounted successfully');
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Fallback: Treat as plain React component
|
|
239
|
+
if (typeof appModule === 'function') {
|
|
240
|
+
console.log('Mounting as plain React component');
|
|
241
|
+
const { default: React } = await import('react');
|
|
242
|
+
const { createRoot } = await import('react-dom/client');
|
|
243
|
+
const root = createRoot(moduleContainer);
|
|
244
|
+
root.render(React.createElement(appModule, { basename: basePath }));
|
|
245
|
+
currentModule = { unmount: () => root.unmount(), container };
|
|
246
|
+
loadingIndicator.style.display = 'none';
|
|
247
|
+
console.log('React component mounted successfully');
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
throw new Error('No valid mount function or React component found');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Helper to unmount
|
|
255
|
+
async function unmountModule(appModule, moduleContainer) {
|
|
256
|
+
// Try multiple unmount patterns
|
|
257
|
+
const unmountFunc = appModule?.unmount ||
|
|
258
|
+
appModule?.default?.unmount ||
|
|
259
|
+
appModule?.destroy ||
|
|
260
|
+
appModule?.default?.destroy ||
|
|
261
|
+
appModule?.cleanup ||
|
|
262
|
+
appModule?.default?.cleanup ||
|
|
263
|
+
appModule?.dispose ||
|
|
264
|
+
appModule?.default?.dispose;
|
|
265
|
+
|
|
266
|
+
if (typeof unmountFunc === 'function') {
|
|
267
|
+
try {
|
|
268
|
+
// Try with props if available
|
|
269
|
+
if (currentMountProps) {
|
|
270
|
+
await unmountFunc(currentMountProps);
|
|
271
|
+
} else {
|
|
272
|
+
await unmountFunc();
|
|
273
|
+
}
|
|
274
|
+
console.log('Module unmounted via function');
|
|
275
|
+
return;
|
|
276
|
+
} catch (e) {
|
|
277
|
+
console.warn('Unmount function failed:', e);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Fallback manual cleanup
|
|
282
|
+
if (moduleContainer) {
|
|
283
|
+
moduleContainer.innerHTML = '';
|
|
284
|
+
}
|
|
285
|
+
console.log('Module cleaned up manually');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Global function to unload module
|
|
289
|
+
window.unloadModule = async function() {
|
|
290
|
+
if (currentModule) {
|
|
291
|
+
try {
|
|
292
|
+
if (currentModule.container && typeof currentModule.container.delete === 'function') {
|
|
293
|
+
currentModule.container.delete(currentModule.container.url || ''); // Clear cache
|
|
294
|
+
}
|
|
295
|
+
await unmountModule(currentModule.appModule || currentModule, document.getElementById('bluecopa-preview'));
|
|
296
|
+
} catch (error) {
|
|
297
|
+
console.error('Error unmounting module:', error);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
currentModule = null;
|
|
302
|
+
currentMountProps = null;
|
|
303
|
+
|
|
304
|
+
const previewContainer = document.getElementById('bluecopa-preview');
|
|
305
|
+
previewContainer.innerHTML = '';
|
|
306
|
+
|
|
307
|
+
const loadingIndicator = document.getElementById('loading-indicator');
|
|
308
|
+
loadingIndicator.style.display = 'flex';
|
|
309
|
+
loadingIndicator.textContent = 'Ready to load bluecopa module...';
|
|
310
|
+
|
|
311
|
+
const errorContainer = document.getElementById('error-container');
|
|
312
|
+
errorContainer.style.display = 'none';
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// Global function to reload module
|
|
316
|
+
window.reloadModule = async function() {
|
|
317
|
+
await unloadModule();
|
|
318
|
+
// Clear any remaining cache
|
|
319
|
+
if (window.System && typeof window.System.clear === 'function') {
|
|
320
|
+
window.System.clear();
|
|
321
|
+
}
|
|
322
|
+
// Small delay to ensure cleanup
|
|
323
|
+
setTimeout(() => {
|
|
324
|
+
loadFederationModule();
|
|
325
|
+
}, 100);
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
</script>
|
|
329
|
+
</body>
|
|
330
|
+
</html>
|
|
@@ -5,8 +5,8 @@ import federation from "@originjs/vite-plugin-federation";
|
|
|
5
5
|
|
|
6
6
|
// https://vitejs.dev/config/
|
|
7
7
|
export default defineConfig(({ mode }) => {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
console.log("Vite mode:", mode);
|
|
9
|
+
|
|
10
10
|
return {
|
|
11
11
|
server: {
|
|
12
12
|
host: "::",
|
|
@@ -15,25 +15,19 @@ export default defineConfig(({ mode }) => {
|
|
|
15
15
|
headers: {
|
|
16
16
|
"Access-Control-Allow-Origin": "*",
|
|
17
17
|
"Access-Control-Allow-Methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
|
|
18
|
-
"Access-Control-Allow-Headers":
|
|
19
|
-
|
|
18
|
+
"Access-Control-Allow-Headers":
|
|
19
|
+
"Origin, X-Requested-With, Content-Type, Accept, Authorization, Cache-Control",
|
|
20
|
+
},
|
|
20
21
|
},
|
|
21
22
|
plugins: [
|
|
22
23
|
react(),
|
|
23
|
-
|
|
24
|
-
name:
|
|
25
|
-
filename:
|
|
24
|
+
federation({
|
|
25
|
+
name: "__copa_ext_app__react_route",
|
|
26
|
+
filename: "remoteEntry.js",
|
|
26
27
|
exposes: {
|
|
27
|
-
|
|
28
|
+
"./App": "./src/single-spa.tsx",
|
|
28
29
|
},
|
|
29
|
-
shared:
|
|
30
|
-
react: {
|
|
31
|
-
singleton: true,
|
|
32
|
-
},
|
|
33
|
-
'react-dom': {
|
|
34
|
-
singleton: true,
|
|
35
|
-
}
|
|
36
|
-
}
|
|
30
|
+
shared: ["react", "react-dom"],
|
|
37
31
|
}),
|
|
38
32
|
].filter(Boolean),
|
|
39
33
|
resolve: {
|
|
@@ -41,24 +35,17 @@ export default defineConfig(({ mode }) => {
|
|
|
41
35
|
"@": path.resolve(__dirname, "./src"),
|
|
42
36
|
},
|
|
43
37
|
},
|
|
44
|
-
build:
|
|
45
|
-
target:
|
|
38
|
+
build: {
|
|
39
|
+
target: "esnext",
|
|
46
40
|
minify: false,
|
|
47
41
|
cssCodeSplit: false,
|
|
48
42
|
rollupOptions: {
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
} : {
|
|
52
|
-
target: 'esnext',
|
|
53
|
-
minify: false,
|
|
54
|
-
cssCodeSplit: false,
|
|
55
|
-
rollupOptions: {
|
|
56
|
-
external: ['react', 'react-dom'],
|
|
43
|
+
external: ["react", "react-dom"],
|
|
57
44
|
output: {
|
|
58
|
-
format:
|
|
59
|
-
}
|
|
60
|
-
}
|
|
45
|
+
format: "system",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
61
48
|
},
|
|
62
|
-
base:
|
|
49
|
+
base: "/",
|
|
63
50
|
};
|
|
64
|
-
});
|
|
51
|
+
});
|