@kaena1/ecomm-contained-utils 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +681 -0
- package/dist/client.d.ts +94 -0
- package/dist/client.js +10 -0
- package/dist/components/ImportMapScripts.js +33 -0
- package/dist/components/RemoteLinks.js +8 -0
- package/dist/components/RemoteRenderer.js +26 -0
- package/dist/hooks/useRemoteComponent.js +61 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.js +18 -0
- package/dist/package.json.js +10 -0
- package/dist/server.d.ts +66 -0
- package/dist/server.js +11 -0
- package/dist/services/ImportMapService.js +40 -0
- package/dist/services/fetchManifest.js +26 -0
- package/dist/services/moduleLoader.js +120 -0
- package/package.json +75 -0
package/README.md
ADDED
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
# ecomm-contained-utils
|
|
2
|
+
|
|
3
|
+
Shared utilities for loading and managing remote React components in Mint Mobile microfrontends.
|
|
4
|
+
|
|
5
|
+
## Package Overview
|
|
6
|
+
|
|
7
|
+
This package provides the essential infrastructure for dynamically loading remote React components (contained components) from external URLs into Next.js applications. It handles module loading, dependency resolution via import maps, and component rendering.
|
|
8
|
+
|
|
9
|
+
### Key Features
|
|
10
|
+
|
|
11
|
+
- **Dynamic Component Loading**: Load React components from remote URLs at runtime
|
|
12
|
+
- **Import Map Management**: Automatic dependency resolution for React and React DOM
|
|
13
|
+
- **Type-Safe**: Full TypeScript support with generated type definitions
|
|
14
|
+
- **Optimized for CDN**: Preserves CDN cache and TTL through native ES modules
|
|
15
|
+
- **Caching**: Built-in module caching to avoid redundant network requests
|
|
16
|
+
|
|
17
|
+
## Package Structure
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
packages/ecomm-contained-utils/
|
|
21
|
+
├── src/
|
|
22
|
+
│ ├── components/ # React components
|
|
23
|
+
│ │ ├── ImportMapScripts.tsx # Injects import map for dependency resolution
|
|
24
|
+
│ │ ├── RemoteLinks.tsx # Preloads remote component resources
|
|
25
|
+
│ │ └── RemoteRenderer.tsx # Renders remote components ('use client')
|
|
26
|
+
│ ├── hooks/ # React hooks
|
|
27
|
+
│ │ └── useRemoteComponent.ts # Hook for loading remote components ('use client')
|
|
28
|
+
│ ├── services/ # Core services
|
|
29
|
+
│ │ ├── fetchManifest.ts # Fetches component manifests
|
|
30
|
+
│ │ ├── ImportMapService.ts # Generates import map JSON
|
|
31
|
+
│ │ └── moduleLoader.ts # ES module loader service
|
|
32
|
+
│ ├── types/ # TypeScript definitions
|
|
33
|
+
│ ├── index.ts # Main entry point (exports everything)
|
|
34
|
+
│ ├── server.ts # Server-safe exports for App Router
|
|
35
|
+
│ └── client.ts # Client-only exports for App Router
|
|
36
|
+
├── dist/ # Built files (generated)
|
|
37
|
+
├── vite.config.ts # Build configuration
|
|
38
|
+
├── package.json
|
|
39
|
+
├── release.config.cjs # Semantic Release configuration
|
|
40
|
+
└── README.md
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Entry Points
|
|
44
|
+
|
|
45
|
+
The package provides three entry points for different use cases:
|
|
46
|
+
|
|
47
|
+
| Entry Point | Import Path | Use Case |
|
|
48
|
+
|-------------|-------------|----------|
|
|
49
|
+
| Main | `ecomm-contained-utils` | Everything (Pages Router, or when you need all exports) |
|
|
50
|
+
| Server | `ecomm-contained-utils/server` | Server Components (App Router RSC) |
|
|
51
|
+
| Client | `ecomm-contained-utils/client` | Client Components (App Router) |
|
|
52
|
+
|
|
53
|
+
**Server exports:** `fetchManifest`, `ImportMapScripts`, `RemoteLinks`, `generateImportMap`, `generateImportMapJson`, all types
|
|
54
|
+
|
|
55
|
+
**Client exports:** `RemoteRenderer`, `useRemoteComponent`, `moduleLoader`, `ModuleLoader`
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
### In Consumer Applications (e.g., mm-mf-features)
|
|
60
|
+
|
|
61
|
+
#### Option 1: Install Latest from POC (Recommended)
|
|
62
|
+
|
|
63
|
+
Always get the latest version from the POC branch:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm install ecomm-contained-utils@f-poc-EC-3301
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
This installs the latest version tagged with `f-poc-EC-3301`. You don't need to track specific version numbers.
|
|
70
|
+
|
|
71
|
+
#### Option 2: Install Specific Version
|
|
72
|
+
|
|
73
|
+
If you need a specific version:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm install ecomm-contained-utils@1.25.0-f-poc-EC-3301.5
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### Updating to Latest
|
|
80
|
+
|
|
81
|
+
To update to the latest POC version:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Update and save to package.json
|
|
85
|
+
npm install ecomm-contained-utils@f-poc-EC-3301 --save
|
|
86
|
+
|
|
87
|
+
# Or use npm update (if version is already in package.json with the tag)
|
|
88
|
+
npm update ecomm-contained-utils
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### For Local Development
|
|
92
|
+
|
|
93
|
+
**⚠️ Important: Use npm pack instead of npm link**
|
|
94
|
+
|
|
95
|
+
Due to React's requirement of a single instance, `npm link` causes "Invalid hook call" errors. Use `npm pack` instead:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# In the ecomm-contained-utils package
|
|
99
|
+
cd packages/ecomm-contained-utils
|
|
100
|
+
npm run build
|
|
101
|
+
npm pack
|
|
102
|
+
# This generates: ecomm-contained-utils-X.X.X.tgz
|
|
103
|
+
|
|
104
|
+
# In the consumer application (mm-mf-features)
|
|
105
|
+
npm install /absolute/path/to/ecomm-contained-utils-X.X.X.tgz
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Usage
|
|
109
|
+
|
|
110
|
+
### 1. Setup in Next.js Application
|
|
111
|
+
|
|
112
|
+
The setup differs slightly between Next.js Pages Router and App Router.
|
|
113
|
+
|
|
114
|
+
#### Pages Router Setup
|
|
115
|
+
|
|
116
|
+
##### Add Import Map Scripts to `_document.tsx`
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { Html, Head, Main, NextScript } from 'next/document';
|
|
120
|
+
import { ImportMapScripts } from 'ecomm-contained-utils';
|
|
121
|
+
|
|
122
|
+
export default function Document() {
|
|
123
|
+
return (
|
|
124
|
+
<Html lang="en-US">
|
|
125
|
+
<Head>
|
|
126
|
+
{/* Required: Enables import map for dependency resolution */}
|
|
127
|
+
<ImportMapScripts />
|
|
128
|
+
</Head>
|
|
129
|
+
<body>
|
|
130
|
+
<Main />
|
|
131
|
+
<NextScript />
|
|
132
|
+
</body>
|
|
133
|
+
</Html>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
##### Fetch Remote Manifest in `_app.tsx`
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import type { AppProps, AppContext } from 'next/app';
|
|
142
|
+
import {
|
|
143
|
+
RemoteRenderer,
|
|
144
|
+
RemoteLinks,
|
|
145
|
+
fetchManifest,
|
|
146
|
+
} from 'ecomm-contained-utils';
|
|
147
|
+
|
|
148
|
+
App.getInitialProps = async (ctx: AppContext) => {
|
|
149
|
+
const manifestUrl = `https://assets-qa.mintmobile.com/contained-components/manifest-poc/f-poc-live-update/manifest.json?_ts=${Date.now()}`;
|
|
150
|
+
|
|
151
|
+
let remoteManifest;
|
|
152
|
+
try {
|
|
153
|
+
remoteManifest = await fetchManifest(manifestUrl);
|
|
154
|
+
} catch (e) {
|
|
155
|
+
console.error('Failed to fetch manifest:', e);
|
|
156
|
+
remoteManifest = null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const app = await (await import('next/app')).default.getInitialProps(ctx);
|
|
160
|
+
return {
|
|
161
|
+
...app,
|
|
162
|
+
pageProps: {
|
|
163
|
+
...app.pageProps,
|
|
164
|
+
remoteManifest,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export default function App({ Component, pageProps }: AppProps) {
|
|
170
|
+
const remoteManifest = pageProps?.remoteManifest;
|
|
171
|
+
const layoutManifest = remoteManifest?.components?.MintLayout;
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<>
|
|
175
|
+
<Head>
|
|
176
|
+
{/* Preload component resources */}
|
|
177
|
+
{layoutManifest && <RemoteLinks manifest={layoutManifest} />}
|
|
178
|
+
</Head>
|
|
179
|
+
|
|
180
|
+
{/* Render remote component as wrapper */}
|
|
181
|
+
{layoutManifest ? (
|
|
182
|
+
<RemoteRenderer
|
|
183
|
+
manifest={layoutManifest}
|
|
184
|
+
componentName="MintLayout"
|
|
185
|
+
autoLoad={true}
|
|
186
|
+
componentProps={{
|
|
187
|
+
config: { NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL },
|
|
188
|
+
}}
|
|
189
|
+
>
|
|
190
|
+
<Component {...pageProps} />
|
|
191
|
+
</RemoteRenderer>
|
|
192
|
+
) : (
|
|
193
|
+
<Component {...pageProps} />
|
|
194
|
+
)}
|
|
195
|
+
</>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
#### App Router Setup
|
|
201
|
+
|
|
202
|
+
> **Note:** App Router is fully supported. The package provides separate entry points for server and client code:
|
|
203
|
+
> - `ecomm-contained-utils/server` - Server-safe exports (fetchManifest, ImportMapScripts, RemoteLinks, types)
|
|
204
|
+
> - `ecomm-contained-utils/client` - Client-only exports (RemoteRenderer, useRemoteComponent, moduleLoader)
|
|
205
|
+
|
|
206
|
+
##### Add Import Map Scripts to `app/layout.tsx`
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
// Use /server for server components
|
|
210
|
+
import {
|
|
211
|
+
ImportMapScripts,
|
|
212
|
+
RemoteLinks,
|
|
213
|
+
fetchManifest
|
|
214
|
+
} from 'ecomm-contained-utils/server';
|
|
215
|
+
|
|
216
|
+
// Use /client for client components (in separate 'use client' files)
|
|
217
|
+
// import { RemoteRenderer } from 'ecomm-contained-utils/client';
|
|
218
|
+
|
|
219
|
+
// Fetch manifest at build time
|
|
220
|
+
async function getRemoteManifest() {
|
|
221
|
+
try {
|
|
222
|
+
const manifestUrl = `https://assets-qa.mintmobile.com/contained-components/manifest-poc/f-poc-live-update/manifest.json?_ts=${Date.now()}`;
|
|
223
|
+
return await fetchManifest(manifestUrl);
|
|
224
|
+
} catch (e) {
|
|
225
|
+
console.error('Failed to fetch manifest:', e);
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export default async function RootLayout({
|
|
231
|
+
children,
|
|
232
|
+
}: {
|
|
233
|
+
children: React.ReactNode;
|
|
234
|
+
}) {
|
|
235
|
+
const remoteManifest = await getRemoteManifest();
|
|
236
|
+
const layoutManifest = remoteManifest?.components?.MintLayout;
|
|
237
|
+
|
|
238
|
+
return (
|
|
239
|
+
<html lang="en-US">
|
|
240
|
+
<head>
|
|
241
|
+
{/* Required: Enables import map for dependency resolution */}
|
|
242
|
+
<ImportMapScripts />
|
|
243
|
+
|
|
244
|
+
{/* Preload component resources */}
|
|
245
|
+
{layoutManifest && <RemoteLinks manifest={layoutManifest} />}
|
|
246
|
+
</head>
|
|
247
|
+
<body>
|
|
248
|
+
{layoutManifest ? (
|
|
249
|
+
<RemoteRenderer
|
|
250
|
+
manifest={layoutManifest}
|
|
251
|
+
componentName="MintLayout"
|
|
252
|
+
autoLoad={true}
|
|
253
|
+
componentProps={{
|
|
254
|
+
config: { NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL },
|
|
255
|
+
}}
|
|
256
|
+
>
|
|
257
|
+
{children}
|
|
258
|
+
</RemoteRenderer>
|
|
259
|
+
) : (
|
|
260
|
+
children
|
|
261
|
+
)}
|
|
262
|
+
</body>
|
|
263
|
+
</html>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### 2. Using Remote Components in Pages
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
import { RemoteRenderer } from 'ecomm-contained-utils';
|
|
272
|
+
|
|
273
|
+
export default function MyPage({ remoteManifest }) {
|
|
274
|
+
const pocComponentManifest = remoteManifest?.components?.PocComponent;
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<div>
|
|
278
|
+
<h1>My Page</h1>
|
|
279
|
+
|
|
280
|
+
{pocComponentManifest && (
|
|
281
|
+
<RemoteRenderer
|
|
282
|
+
manifest={pocComponentManifest}
|
|
283
|
+
componentName="PocComponent"
|
|
284
|
+
autoLoad={true}
|
|
285
|
+
componentProps={{
|
|
286
|
+
list: [
|
|
287
|
+
{ id: 1, name: 'Item 1' },
|
|
288
|
+
{ id: 2, name: 'Item 2' },
|
|
289
|
+
],
|
|
290
|
+
}}
|
|
291
|
+
/>
|
|
292
|
+
)}
|
|
293
|
+
</div>
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## API Reference
|
|
299
|
+
|
|
300
|
+
### Components
|
|
301
|
+
|
|
302
|
+
#### `<ImportMapScripts />`
|
|
303
|
+
|
|
304
|
+
Injects the import map script tags required for ES module dependency resolution.
|
|
305
|
+
|
|
306
|
+
**Props:** None
|
|
307
|
+
|
|
308
|
+
**Usage:**
|
|
309
|
+
|
|
310
|
+
```tsx
|
|
311
|
+
<Head>
|
|
312
|
+
<ImportMapScripts />
|
|
313
|
+
</Head>
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
#### `<RemoteLinks manifest={ComponentManifest} />`
|
|
319
|
+
|
|
320
|
+
Preloads CSS and JS resources for a remote component.
|
|
321
|
+
|
|
322
|
+
**Props:**
|
|
323
|
+
|
|
324
|
+
- `manifest`: `ComponentManifest` - Component manifest with `bundleUrl` and optional `styleUrl`
|
|
325
|
+
|
|
326
|
+
**Usage:**
|
|
327
|
+
|
|
328
|
+
```tsx
|
|
329
|
+
<RemoteLinks manifest={pocComponentManifest} />
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
#### `<RemoteRenderer />`
|
|
335
|
+
|
|
336
|
+
Renders a remote React component.
|
|
337
|
+
|
|
338
|
+
**Props:**
|
|
339
|
+
|
|
340
|
+
- `manifest`: `ComponentManifest` - Component manifest
|
|
341
|
+
- `componentName`: `string` - Name of the component to extract from the module
|
|
342
|
+
- `componentProps?`: `Record<string, unknown>` - Props to pass to the remote component
|
|
343
|
+
- `autoLoad?`: `boolean` - Whether to auto-load on mount (default: `true`)
|
|
344
|
+
- `placeholder?`: `ReactNode` - Content to show while loading
|
|
345
|
+
- `errorFallback?`: `(error: string) => ReactNode` - Custom error UI
|
|
346
|
+
- `children?`: `ReactNode` - Children to pass to the remote component
|
|
347
|
+
|
|
348
|
+
**Usage:**
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
<RemoteRenderer
|
|
352
|
+
manifest={manifest}
|
|
353
|
+
componentName="MyComponent"
|
|
354
|
+
autoLoad={true}
|
|
355
|
+
componentProps={{ title: 'Hello' }}
|
|
356
|
+
placeholder={<div>Loading...</div>}
|
|
357
|
+
errorFallback={(error) => <div>Error: {error}</div>}
|
|
358
|
+
>
|
|
359
|
+
<ChildComponent />
|
|
360
|
+
</RemoteRenderer>
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
### Hooks
|
|
366
|
+
|
|
367
|
+
#### `useRemoteComponent(options)`
|
|
368
|
+
|
|
369
|
+
Hook for loading remote components with manual control.
|
|
370
|
+
|
|
371
|
+
**Parameters:**
|
|
372
|
+
|
|
373
|
+
- `options.manifest`: `ComponentManifest` - Component manifest
|
|
374
|
+
- `options.componentName`: `string` - Component name
|
|
375
|
+
- `options.autoLoad?`: `boolean` - Auto-load on mount (default: `true`)
|
|
376
|
+
|
|
377
|
+
**Returns:**
|
|
378
|
+
|
|
379
|
+
```typescript
|
|
380
|
+
{
|
|
381
|
+
component: React.ComponentType | null,
|
|
382
|
+
manifest: ComponentManifest | null,
|
|
383
|
+
loading: boolean,
|
|
384
|
+
error: string | null,
|
|
385
|
+
loaded: boolean,
|
|
386
|
+
loadComponent: () => Promise<void>,
|
|
387
|
+
reload: () => Promise<void>,
|
|
388
|
+
clearError: () => void
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**Usage:**
|
|
393
|
+
|
|
394
|
+
```tsx
|
|
395
|
+
const {
|
|
396
|
+
component: Component,
|
|
397
|
+
loading,
|
|
398
|
+
error,
|
|
399
|
+
} = useRemoteComponent({
|
|
400
|
+
manifest: pocManifest,
|
|
401
|
+
componentName: 'PocComponent',
|
|
402
|
+
autoLoad: true,
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
if (loading) return <div>Loading...</div>;
|
|
406
|
+
if (error) return <div>Error: {error}</div>;
|
|
407
|
+
if (!Component) return null;
|
|
408
|
+
|
|
409
|
+
return <Component {...props} />;
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
### Services
|
|
415
|
+
|
|
416
|
+
#### `fetchManifest(manifestUrl: string): Promise<RemoteManifest>`
|
|
417
|
+
|
|
418
|
+
Fetches and validates a remote component manifest.
|
|
419
|
+
|
|
420
|
+
**Parameters:**
|
|
421
|
+
|
|
422
|
+
- `manifestUrl`: `string` - URL to the manifest JSON file
|
|
423
|
+
|
|
424
|
+
**Returns:** `Promise<RemoteManifest>`
|
|
425
|
+
|
|
426
|
+
**Usage:**
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
const manifest = await fetchManifest(
|
|
430
|
+
'https://assets-qa.mintmobile.com/.../manifest.json',
|
|
431
|
+
);
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
## Build & Development
|
|
437
|
+
|
|
438
|
+
### Build the Package
|
|
439
|
+
|
|
440
|
+
```bash
|
|
441
|
+
# Production build (minified)
|
|
442
|
+
npm run build
|
|
443
|
+
|
|
444
|
+
# Development build (unminified, preserves modules)
|
|
445
|
+
npm run build:debug
|
|
446
|
+
|
|
447
|
+
# Watch mode (rebuilds on changes)
|
|
448
|
+
npm run dev
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Type Checking & Linting
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
# Type check
|
|
455
|
+
npm run typecheck
|
|
456
|
+
|
|
457
|
+
# Lint
|
|
458
|
+
npm run lint
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## Publishing
|
|
462
|
+
|
|
463
|
+
### Current State (Manual Publishing)
|
|
464
|
+
|
|
465
|
+
**⚠️ Automatic npm publishing via semantic-release is currently disabled** because the NPM_TOKEN is not configured in Bitbucket Pipelines.
|
|
466
|
+
|
|
467
|
+
#### Manual Publishing Process
|
|
468
|
+
|
|
469
|
+
1. **Update version in `package.json`:**
|
|
470
|
+
|
|
471
|
+
```json
|
|
472
|
+
{
|
|
473
|
+
"version": "1.25.0-f-poc-EC-3301.5"
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
2. **Build the package:**
|
|
478
|
+
|
|
479
|
+
```bash
|
|
480
|
+
npm run build
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
3. **Publish to npm:**
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
npm publish --tag f-poc-live-update
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
4. **Install in consumer apps:**
|
|
490
|
+
|
|
491
|
+
```bash
|
|
492
|
+
# Specific version
|
|
493
|
+
npm install ecomm-contained-utils@1.25.0-f-poc-EC-3301.5
|
|
494
|
+
|
|
495
|
+
# Or use the tag for latest POC version
|
|
496
|
+
npm install ecomm-contained-utils@f-poc-live-update
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### Enabling Automatic Publishing (Future)
|
|
500
|
+
|
|
501
|
+
To enable automatic npm publishing via semantic-release:
|
|
502
|
+
|
|
503
|
+
1. **Create an Automation token**
|
|
504
|
+
2. **Add token to Bitbucket:**
|
|
505
|
+
3. **Uncomment `@semantic-release/npm` in `release.config.cjs`:**
|
|
506
|
+
|
|
507
|
+
```javascript
|
|
508
|
+
'@semantic-release/npm', // ← Uncomment this line
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
4. **Commit and push changes** - semantic-release will now automatically publish on merge.
|
|
512
|
+
|
|
513
|
+
## Versioning & Branching
|
|
514
|
+
|
|
515
|
+
### Semantic Release Configuration
|
|
516
|
+
|
|
517
|
+
The package uses [semantic-release](https://github.com/semantic-release/semantic-release) for automated versioning based on commit messages.
|
|
518
|
+
|
|
519
|
+
#### Supported Branches
|
|
520
|
+
|
|
521
|
+
| Branch Pattern | Prerelease Tag | Use Case |
|
|
522
|
+
| -------------- | -------------- | -------------------- |
|
|
523
|
+
| `master` | None | Production releases |
|
|
524
|
+
| `develop` | `true` | QA/Staging releases |
|
|
525
|
+
| `f-poc-*` | Branch name | POC/Feature releases |
|
|
526
|
+
|
|
527
|
+
#### Commit Message Format
|
|
528
|
+
|
|
529
|
+
Follow [Conventional Commits](https://www.conventionalcommits.org/):
|
|
530
|
+
|
|
531
|
+
```
|
|
532
|
+
<type>(<scope>): <description>
|
|
533
|
+
|
|
534
|
+
[optional body]
|
|
535
|
+
|
|
536
|
+
[optional footer]
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
**Examples:**
|
|
540
|
+
|
|
541
|
+
```bash
|
|
542
|
+
feat(ecomm-contained-utils): add new remote component loader
|
|
543
|
+
fix(ecomm-contained-utils): resolve React dependency conflicts
|
|
544
|
+
docs: update README with usage examples
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
#### Release Rules
|
|
548
|
+
|
|
549
|
+
| Commit Type | Scope | Release Type |
|
|
550
|
+
| ----------- | -------------- | ------------- |
|
|
551
|
+
| `feat` | `ecomm-contained-utils` | Minor (1.x.0) |
|
|
552
|
+
| `fix` | `ecomm-contained-utils` | Patch (1.0.x) |
|
|
553
|
+
| `feat` | Any | Minor |
|
|
554
|
+
| `fix` | Any | Patch |
|
|
555
|
+
|
|
556
|
+
### POC Branch Strategy
|
|
557
|
+
|
|
558
|
+
For the POC, we use the branch pattern `f-poc-*` with a specific prerelease identifier.
|
|
559
|
+
|
|
560
|
+
**Current setup:**
|
|
561
|
+
|
|
562
|
+
```javascript
|
|
563
|
+
{
|
|
564
|
+
name: 'f-poc-*',
|
|
565
|
+
prerelease: '${name}', // Uses full branch name as tag
|
|
566
|
+
}
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
This means:
|
|
570
|
+
|
|
571
|
+
- Branch `f-poc-EC-3301` → publishes as `1.25.0-f-poc-EC-3301.1`
|
|
572
|
+
- Branch `f-poc-live-update` → publishes as `1.25.0-f-poc-live-update.1`
|
|
573
|
+
|
|
574
|
+
## How It Works
|
|
575
|
+
|
|
576
|
+
### Architecture Overview
|
|
577
|
+
|
|
578
|
+
```
|
|
579
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
580
|
+
│ Consumer App (Next.js) │
|
|
581
|
+
│ ┌────────────────────────────────────────────────────────┐ │
|
|
582
|
+
│ │ _document.tsx: <ImportMapScripts /> │ │
|
|
583
|
+
│ │ └─> Injects import map for React dependency resolution│ │
|
|
584
|
+
│ └────────────────────────────────────────────────────────┘ │
|
|
585
|
+
│ ┌────────────────────────────────────────────────────────┐ │
|
|
586
|
+
│ │ _app.tsx: │ │
|
|
587
|
+
│ │ 1. fetchManifest() - Gets component URLs │ │
|
|
588
|
+
│ │ 2. <RemoteLinks /> - Preloads resources │ │
|
|
589
|
+
│ │ 3. <RemoteRenderer /> - Loads & renders component │ │
|
|
590
|
+
│ └────────────────────────────────────────────────────────┘ │
|
|
591
|
+
└─────────────────────────────────────────────────────────────┘
|
|
592
|
+
↓
|
|
593
|
+
┌─────────────────────────────────┐
|
|
594
|
+
│ ModuleLoader (ecomm-contained-utils) │
|
|
595
|
+
│ 1. Checks cache │
|
|
596
|
+
│ 2. Loads ES module via script │
|
|
597
|
+
│ 3. Extracts component │
|
|
598
|
+
│ 4. Returns React component │
|
|
599
|
+
└─────────────────────────────────┘
|
|
600
|
+
↓
|
|
601
|
+
┌─────────────────────────────────┐
|
|
602
|
+
│ Remote Component (S3/CDN) │
|
|
603
|
+
│ • MintLayout │
|
|
604
|
+
│ • PocComponent │
|
|
605
|
+
│ • TestComponent │
|
|
606
|
+
│ etc. │
|
|
607
|
+
└─────────────────────────────────┘
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
### Component Manifest Format
|
|
611
|
+
|
|
612
|
+
Remote components are described by a manifest JSON file:
|
|
613
|
+
|
|
614
|
+
```json
|
|
615
|
+
{
|
|
616
|
+
"components": {
|
|
617
|
+
"MintLayout": {
|
|
618
|
+
"bundleUrl": "https://.../MintLayout.abc123.es.js",
|
|
619
|
+
"styleUrl": "https://.../MintLayout.def456.css"
|
|
620
|
+
},
|
|
621
|
+
"PocComponent": {
|
|
622
|
+
"bundleUrl": "https://.../PocComponent.xyz789.es.js",
|
|
623
|
+
"styleUrl": "https://.../PocComponent.uvw012.css"
|
|
624
|
+
}
|
|
625
|
+
},
|
|
626
|
+
"version": "0.1.0",
|
|
627
|
+
"generatedAt": "2026-01-20T12:00:00.000Z"
|
|
628
|
+
}
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
### Import Map Resolution
|
|
632
|
+
|
|
633
|
+
The `ImportMapScripts` component injects an import map that resolves bare specifiers to use the **host app's React instance**:
|
|
634
|
+
|
|
635
|
+
```html
|
|
636
|
+
<script type="importmap-shim">
|
|
637
|
+
{
|
|
638
|
+
"imports": {
|
|
639
|
+
"react": "data:text/javascript,...(uses window.React)",
|
|
640
|
+
"react/jsx-runtime": "data:text/javascript,...(uses window.React.createElement)",
|
|
641
|
+
"react-dom": "https://esm.sh/react-dom@18.3.1",
|
|
642
|
+
"react-dom/client": "https://esm.sh/react-dom@18.3.1/client"
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
</script>
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
**Key design:** Both `react` and `react/jsx-runtime` are mapped to use `window.React`, ensuring remote components use the exact same React instance as the host app. This prevents "multiple copies of React" errors.
|
|
649
|
+
|
|
650
|
+
### Module Loading Flow
|
|
651
|
+
|
|
652
|
+
1. **Component Manifest** is fetched via `fetchManifest()`
|
|
653
|
+
2. **ModuleLoader** dynamically imports the ES module from `bundleUrl`
|
|
654
|
+
3. **Component extraction** finds the named export or default export
|
|
655
|
+
4. **React component** is returned and rendered via `<RemoteRenderer>`
|
|
656
|
+
|
|
657
|
+
## CI/CD Pipeline
|
|
658
|
+
|
|
659
|
+
### Bitbucket Pipelines Configuration
|
|
660
|
+
|
|
661
|
+
The package is built and deployed via Bitbucket Pipelines (`bitbucket-pipelines.yml` in repo root).
|
|
662
|
+
|
|
663
|
+
#### Pipeline Stages
|
|
664
|
+
|
|
665
|
+
**For `f-poc-*` branches:**
|
|
666
|
+
|
|
667
|
+
```yaml
|
|
668
|
+
- stage: <EcommContainedUtils> - POC
|
|
669
|
+
condition:
|
|
670
|
+
changesets:
|
|
671
|
+
includePaths:
|
|
672
|
+
- packages/ecomm-contained-utils/**
|
|
673
|
+
steps:
|
|
674
|
+
- Lint & Build
|
|
675
|
+
- Deploy (currently disabled, manual publishing)
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
**Triggers:**
|
|
679
|
+
|
|
680
|
+
- Runs when files in `packages/ecomm-contained-utils/**` are modified
|
|
681
|
+
- Pushes to any branch matching `f-poc-*` pattern
|