@kaena1/ecomm-remote-utils 1.0.0-f-EC-4239.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/README.md ADDED
@@ -0,0 +1,833 @@
1
+ # ecomm-remote-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-remote-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
+ │ ├── lib/ # Business logic
29
+ │ │ ├── api/server/ # Server-side API functions
30
+ │ │ │ └── getMintLayoutProps/ # Fetches MintLayout props from ecomm API
31
+ │ │ └── mappers/ # Data mappers
32
+ │ │ └── mapMintLayoutResponseToProps.ts
33
+ │ ├── services/ # Core services
34
+ │ │ ├── fetchManifest.ts # Fetches component manifests
35
+ │ │ ├── ImportMapService.ts # Generates import map JSON
36
+ │ │ └── moduleLoader.ts # ES module loader service
37
+ │ ├── types/ # TypeScript definitions
38
+ │ ├── server.ts # Server-safe exports for App Router
39
+ │ ├── client.ts # Client-only exports for App Router
40
+ │ └── types.ts # Type-only exports entry point
41
+ ├── dist/ # Built files (generated)
42
+ ├── vite.config.ts # Build configuration
43
+ ├── package.json
44
+ ├── release.config.cjs # Semantic Release configuration
45
+ └── README.md
46
+ ```
47
+
48
+ ### Entry Points
49
+
50
+ The package provides three entry points for different use cases:
51
+
52
+ | Entry Point | Import Path | Use Case |
53
+ | ----------- | ----------------------------------- | ---------------------------------------------------- |
54
+ | Server | `@kaena1/ecomm-remote-utils/server` | Server Components (App Router RSC, Pages Router SSR) |
55
+ | Client | `@kaena1/ecomm-remote-utils/client` | Client Components (App Router, Pages Router CSR) |
56
+ | Types | `@kaena1/ecomm-remote-utils/types` | Type-only imports |
57
+
58
+ **Server exports:** `fetchManifest`, `getRemoteManifest`, `ImportMapScripts`, `RemoteLinks`, `generateImportMap`, `generateImportMapJson`, `getMintLayoutProps`, `mapMintLayoutResponseToProps`
59
+
60
+ **Client exports:** `RemoteRenderer`, `useRemoteComponent`, `moduleLoader`, `ModuleLoader`, `MintLayout`
61
+
62
+ **Types exports:** `ComponentManifest`, `RemoteManifest`, `MintLayoutProps`, `MintLayoutPropsAPIResponse`, WP REST API types
63
+
64
+ ## Installation
65
+
66
+ ### In Consumer Applications (e.g., mm-mf-features)
67
+
68
+ #### Install Latest Stable
69
+
70
+ ```bash
71
+ npm install @kaena1/ecomm-remote-utils
72
+ ```
73
+
74
+ #### Install Latest from a POC Branch
75
+
76
+ Each `f-poc-*` branch publishes a prerelease automatically. Use the branch name as the tag:
77
+
78
+ ```bash
79
+ npm install @kaena1/ecomm-remote-utils@f-poc-ec-3644
80
+ ```
81
+
82
+ You don't need to track specific version numbers — the tag always points to the latest prerelease from that branch.
83
+
84
+ #### Install Latest Beta (post-POC, from develop)
85
+
86
+ ```bash
87
+ npm install @kaena1/ecomm-remote-utils@beta
88
+ ```
89
+
90
+ #### Install Specific Version
91
+
92
+ ```bash
93
+ npm install @kaena1/ecomm-remote-utils@1.0.5
94
+ ```
95
+
96
+ #### Updating to Latest
97
+
98
+ ```bash
99
+ npm update @kaena1/ecomm-remote-utils
100
+ ```
101
+
102
+ #### For Local Development
103
+
104
+ **⚠️ Important: Use npm pack instead of npm link**
105
+
106
+ Due to React's requirement of a single instance, `npm link` causes "Invalid hook call" errors. Use `npm pack` instead:
107
+
108
+ ```bash
109
+ # In the ecomm-remote-utils package
110
+ cd packages/ecomm-remote-utils
111
+ npm run build
112
+ npm pack
113
+ # This generates: kaena1-ecomm-remote-utils-X.X.X.tgz
114
+
115
+ # In the consumer application (mm-mf-features)
116
+ npm install /absolute/path/to/kaena1-ecomm-remote-utils-X.X.X.tgz
117
+ ```
118
+
119
+ ## Usage
120
+
121
+ ### 1. Setup in Next.js Application
122
+
123
+ The setup differs slightly between Next.js Pages Router and App Router.
124
+
125
+ #### Pages Router Setup
126
+
127
+ > **Recommended approach:** Use [`withRemoteComponentsGSSP`](#withremotecomponentsgsspenvvars-pagegssP-getserversideprops) in each page's `getServerSideProps`. The `getInitialProps` pattern below is kept for reference but `withRemoteComponentsGSSP` is the mandatory pattern for new pages.
128
+
129
+ ##### Add Import Map Scripts to `_document.tsx`
130
+
131
+ ```typescript
132
+ import { Html, Head, Main, NextScript } from 'next/document';
133
+ import { ImportMapScripts } from '@kaena1/ecomm-remote-utils/server';
134
+
135
+ export default function Document() {
136
+ return (
137
+ <Html lang="en-US">
138
+ <Head>
139
+ {/* Required: Enables import map for dependency resolution */}
140
+ <ImportMapScripts />
141
+ </Head>
142
+ <body>
143
+ <Main />
144
+ <NextScript />
145
+ </body>
146
+ </Html>
147
+ );
148
+ }
149
+ ```
150
+
151
+ ##### Fetch Remote Manifest in `_app.tsx`
152
+
153
+ ```typescript
154
+ import type { AppProps, AppContext } from 'next/app';
155
+ import { RemoteLinks, getRemoteManifest } from '@kaena1/ecomm-remote-utils/server';
156
+ import { RemoteRenderer } from '@kaena1/ecomm-remote-utils/client';
157
+
158
+ App.getInitialProps = async (ctx: AppContext) => {
159
+ let remoteManifest;
160
+ try {
161
+ remoteManifest = await getRemoteManifest(process.env.MANIFEST_URL);
162
+ } catch (e) {
163
+ console.error('Failed to fetch manifest:', e);
164
+ remoteManifest = null;
165
+ }
166
+
167
+ const app = await (await import('next/app')).default.getInitialProps(ctx);
168
+ return {
169
+ ...app,
170
+ pageProps: {
171
+ ...app.pageProps,
172
+ remoteManifest,
173
+ },
174
+ };
175
+ };
176
+
177
+ export default function App({ Component, pageProps }: AppProps) {
178
+ const remoteManifest = pageProps?.remoteManifest;
179
+ const layoutManifest = remoteManifest?.components?.MintLayout;
180
+
181
+ return (
182
+ <>
183
+ <Head>
184
+ {/* Preload component resources */}
185
+ {layoutManifest && <RemoteLinks manifest={layoutManifest} />}
186
+ </Head>
187
+
188
+ {/* Render remote component as wrapper */}
189
+ {layoutManifest ? (
190
+ <RemoteRenderer
191
+ manifest={layoutManifest}
192
+ componentName="MintLayout"
193
+ autoLoad={true}
194
+ componentProps={{
195
+ config: { NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL },
196
+ }}
197
+ >
198
+ <Component {...pageProps} />
199
+ </RemoteRenderer>
200
+ ) : (
201
+ <Component {...pageProps} />
202
+ )}
203
+ </>
204
+ );
205
+ }
206
+ ```
207
+
208
+ #### App Router Setup
209
+
210
+ > **Note:** App Router is fully supported. The package provides separate entry points for server and client code:
211
+ >
212
+ > - `@kaena1/ecomm-remote-utils/server` - Server-safe exports (fetchManifest, getRemoteManifest, ImportMapScripts, RemoteLinks, getMintLayoutProps, mapMintLayoutResponseToProps)
213
+ > - `@kaena1/ecomm-remote-utils/client` - Client-only exports (RemoteRenderer, useRemoteComponent, moduleLoader, MintLayout)
214
+ > - `@kaena1/ecomm-remote-utils/types` - Type-only exports
215
+
216
+ > **MintLayout styles:** Importing `@kaena1/ecomm-remote-utils/client` now auto-injects package-owned MintLayout CSS at runtime in the browser. Consumers should not manually import `dist/style.css`.
217
+
218
+ ##### Add Import Map Scripts to `app/layout.tsx`
219
+
220
+ ```typescript
221
+ // Use /server for server components
222
+ import {
223
+ ImportMapScripts,
224
+ RemoteLinks,
225
+ getRemoteManifest,
226
+ } from '@kaena1/ecomm-remote-utils/server';
227
+
228
+ // Use /client for client components (in separate 'use client' files)
229
+ // import { RemoteRenderer } from '@kaena1/ecomm-remote-utils/client';
230
+
231
+ export default async function RootLayout({
232
+ children,
233
+ }: {
234
+ children: React.ReactNode;
235
+ }) {
236
+ const remoteManifest = await getRemoteManifest(process.env.MANIFEST_URL);
237
+ const layoutManifest = remoteManifest?.components?.MintLayout;
238
+
239
+ return (
240
+ <html lang="en-US">
241
+ <head>
242
+ {/* Required: Enables import map for dependency resolution */}
243
+ <ImportMapScripts />
244
+
245
+ {/* Preload component resources */}
246
+ {layoutManifest && <RemoteLinks manifest={layoutManifest} />}
247
+ </head>
248
+ <body>
249
+ {layoutManifest ? (
250
+ <RemoteRenderer
251
+ manifest={layoutManifest}
252
+ componentName="MintLayout"
253
+ autoLoad={true}
254
+ componentProps={{
255
+ config: { NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL },
256
+ }}
257
+ >
258
+ {children}
259
+ </RemoteRenderer>
260
+ ) : (
261
+ children
262
+ )}
263
+ </body>
264
+ </html>
265
+ );
266
+ }
267
+ ```
268
+
269
+ ### 2. Using Remote Components in Pages
270
+
271
+ ```typescript
272
+ import { RemoteRenderer } from '@kaena1/ecomm-remote-utils/client';
273
+
274
+ export default function MyPage({ remoteManifest }) {
275
+ const pocComponentManifest = remoteManifest?.components?.PocComponent;
276
+
277
+ return (
278
+ <div>
279
+ <h1>My Page</h1>
280
+
281
+ {pocComponentManifest && (
282
+ <RemoteRenderer
283
+ manifest={pocComponentManifest}
284
+ componentName="PocComponent"
285
+ autoLoad={true}
286
+ componentProps={{
287
+ list: [
288
+ { id: 1, name: 'Item 1' },
289
+ { id: 2, name: 'Item 2' },
290
+ ],
291
+ }}
292
+ />
293
+ )}
294
+ </div>
295
+ );
296
+ }
297
+ ```
298
+
299
+ ## API Reference
300
+
301
+ ### Components
302
+
303
+ #### `<ImportMapScripts />`
304
+
305
+ Injects the import map script tags required for ES module dependency resolution.
306
+
307
+ **Props:** None
308
+
309
+ **Usage:**
310
+
311
+ ```tsx
312
+ <Head>
313
+ <ImportMapScripts />
314
+ </Head>
315
+ ```
316
+
317
+ ---
318
+
319
+ #### `<RemoteLinks manifest={ComponentManifest} />`
320
+
321
+ Preloads CSS and JS resources for a remote component.
322
+
323
+ **Props:**
324
+
325
+ - `manifest`: `ComponentManifest` - Component manifest with `bundleUrl` and optional `styleUrl`
326
+
327
+ **Usage:**
328
+
329
+ ```tsx
330
+ <RemoteLinks manifest={pocComponentManifest} />
331
+ ```
332
+
333
+ ---
334
+
335
+ #### `<RemoteRenderer />`
336
+
337
+ Renders a remote React component.
338
+
339
+ **Props:**
340
+
341
+ - `manifest`: `ComponentManifest` - Component manifest
342
+ - `componentName`: `string` - Name of the component to extract from the module
343
+ - `componentProps?`: `Record<string, unknown>` - Props to pass to the remote component
344
+ - `autoLoad?`: `boolean` - Whether to auto-load on mount (default: `true`)
345
+ - `placeholder?`: `ReactNode` - Content to show while loading
346
+ - `errorFallback?`: `(error: string) => ReactNode` - Custom error UI
347
+ - `children?`: `ReactNode` - Children to pass to the remote component
348
+
349
+ **Usage:**
350
+
351
+ ```tsx
352
+ <RemoteRenderer
353
+ manifest={manifest}
354
+ componentName="MyComponent"
355
+ autoLoad={true}
356
+ componentProps={{ title: 'Hello' }}
357
+ placeholder={<div>Loading...</div>}
358
+ errorFallback={(error) => <div>Error: {error}</div>}
359
+ >
360
+ <ChildComponent />
361
+ </RemoteRenderer>
362
+ ```
363
+
364
+ ---
365
+
366
+ ### Hooks
367
+
368
+ #### `useRemoteComponent(options)`
369
+
370
+ Hook for loading remote components with manual control.
371
+
372
+ **Parameters:**
373
+
374
+ - `options.manifest`: `ComponentManifest` - Component manifest
375
+ - `options.componentName`: `string` - Component name
376
+ - `options.autoLoad?`: `boolean` - Auto-load on mount (default: `true`)
377
+
378
+ **Returns:**
379
+
380
+ ```typescript
381
+ {
382
+ component: React.ComponentType | null,
383
+ manifest: ComponentManifest | null,
384
+ loading: boolean,
385
+ error: string | null,
386
+ loaded: boolean,
387
+ loadComponent: () => Promise<void>,
388
+ reload: () => Promise<void>,
389
+ clearError: () => void
390
+ }
391
+ ```
392
+
393
+ **Usage:**
394
+
395
+ ```tsx
396
+ const {
397
+ component: Component,
398
+ loading,
399
+ error,
400
+ } = useRemoteComponent({
401
+ manifest: pocManifest,
402
+ componentName: 'PocComponent',
403
+ autoLoad: true,
404
+ });
405
+
406
+ if (loading) return <div>Loading...</div>;
407
+ if (error) return <div>Error: {error}</div>;
408
+ if (!Component) return null;
409
+
410
+ return <Component {...props} />;
411
+ ```
412
+
413
+ ---
414
+
415
+ ### Data fetching (server-side)
416
+
417
+ #### `getRemoteManifest(environment, cookieStore): Promise<RemoteManifest>`
418
+
419
+ Server-side function that fetches the remote component manifest. Encapsulates URL construction (cache-busting timestamp) and the fetch call.
420
+
421
+ **Parameters:**
422
+
423
+ - `environment`: `Environment` - Environment variable value set in the consumer app's repository. Can be `production` or `develop`. Defaults to `production`.
424
+ - `cookieBranch`: `string | undefined` - f-branch can be set as a cookie with the name `cont-comp`. If it's not set or none is provided, it will default to `f-poc-live-update` for develop, and `master` for production.
425
+
426
+ **Returns:** `Promise<RemoteManifest>`
427
+
428
+ **Usage (NextJS):**
429
+
430
+ ```typescript
431
+ import { getRemoteManifest } from '@kaena1/ecomm-remote-utils/server';
432
+ import { cookies } from 'next/headers';
433
+
434
+ const cookieStore = await cookies();
435
+ const branchCookie = cookieStore.get('cont-comp')?.value;
436
+ const remoteManifest = await getRemoteManifest(
437
+ process.env.NEXT_PUBLIC_ENVIRONMENT,
438
+ branchCookie,
439
+ );
440
+ ```
441
+
442
+ ---
443
+
444
+ #### `getMintLayoutProps(NEXT_PUBLIC_SITE_URL, ECOMM_API_URL, ECOMM_API_STANDALONE_DEVICE_CREDS): Promise<MintLayoutProps>`
445
+
446
+ Server-side function that fetches MintLayout props from the ecomm API and maps them to the expected format.
447
+
448
+ **Parameters:**
449
+
450
+ - `NEXT_PUBLIC_SITE_URL`: `string` - The public site URL
451
+ - `ECOMM_API_URL`: `string` - The ecomm API base URL
452
+ - `ECOMM_API_STANDALONE_DEVICE_CREDS`: `string` - Credentials in `username|password` format
453
+
454
+ **Returns:** `Promise<MintLayoutProps>`
455
+
456
+ **Usage:**
457
+
458
+ ```typescript
459
+ import { getMintLayoutProps } from '@kaena1/ecomm-remote-utils/server';
460
+
461
+ const mintLayoutProps = await getMintLayoutProps(
462
+ process.env.NEXT_PUBLIC_SITE_URL,
463
+ process.env.ECOMM_API_URL,
464
+ process.env.ECOMM_API_STANDALONE_DEVICE_CREDS,
465
+ );
466
+ ```
467
+
468
+ ---
469
+
470
+ ### Utils
471
+
472
+ #### `getRemoteComponentsData(envVars): Promise<{remoteManifest: RemoteManifest, mintLayoutProps: MintLayoutProps}>`
473
+
474
+ Server-side util function that handles all logic for app router NextJS apps.
475
+ **IMPORTANT**: it is **mandatory** to use this in the app router. This **WON'T WORK** in the pages router.
476
+
477
+ **Parameters:**
478
+
479
+ - `envVars`: `RemoteComponentsEnvVars` - Environment variables object containing:
480
+ - `NEXT_PUBLIC_ENVIRONMENT`: `string` - The environment (e.g., 'production', 'develop')
481
+ - `NEXT_PUBLIC_SITE_URL`: `string` - The public site URL
482
+ - `ECOMM_API_URL`: `string` - The ecomm API base URL
483
+ - `ECOMM_API_STANDALONE_DEVICE_CREDS`: `string` - API credentials
484
+
485
+ **Returns:** `Promise<{ remoteManifest: RemoteManifest, mintLayoutProps: MintLayoutProps }>`
486
+
487
+ **Usage:**
488
+
489
+ ```typescript
490
+ import { getRemoteComponentsData } from '@kaena1/ecomm-remote-utils/server';
491
+
492
+ const { remoteManifest, mintLayoutProps } = await getRemoteComponentsData({
493
+ NEXT_PUBLIC_ENVIRONMENT: process.env.NEXT_PUBLIC_ENVIRONMENT,
494
+ NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL,
495
+ ECOMM_API_URL: process.env.ECOMM_API_URL,
496
+ ECOMM_API_STANDALONE_DEVICE_CREDS:
497
+ process.env.ECOMM_API_STANDALONE_DEVICE_CREDS,
498
+ });
499
+ ```
500
+
501
+ ---
502
+
503
+ #### `withRemoteComponentsGSSP(envVars, pageGSSP?): GetServerSideProps`
504
+
505
+ A Higher-Order Function (HOF) for Next.js Pages Router that composes a `getServerSideProps` function to automatically fetch remote component data (manifest and layout props).
506
+ **IMPORTANT**: It is **mandatory** to use this in the pages router. It handles fetching the manifest and layout props, merging them with your page's props.
507
+
508
+ **Parameters:**
509
+
510
+ - `envVars`: `RemoteComponentsEnvVars` - Environment variables object (same as `getRemoteComponentsData`).
511
+ - `pageGSSP`: `GetServerSideProps` (optional) - Your page's existing `getServerSideProps` function. The results will be merged.
512
+
513
+ **Returns:** A new `GetServerSideProps` function.
514
+
515
+ **Usage:**
516
+
517
+ ```typescript
518
+ // pages/index.tsx
519
+ import { withRemoteComponentsGSSP } from '@kaena1/ecomm-remote-utils/server';
520
+
521
+ async function pageGSSP() {
522
+ // Your existing logic (optional)
523
+ }
524
+
525
+ export const getServerSideProps = withRemoteComponentsGSSP(
526
+ {
527
+ NEXT_PUBLIC_ENVIRONMENT: process.env.NEXT_PUBLIC_ENVIRONMENT,
528
+ NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL,
529
+ ECOMM_API_URL: process.env.ECOMM_API_URL,
530
+ ECOMM_API_STANDALONE_DEVICE_CREDS:
531
+ process.env.ECOMM_API_STANDALONE_DEVICE_CREDS,
532
+ },
533
+ pageGSSP,
534
+ );
535
+
536
+ export default function Page({ remoteManifest, mintLayoutProps, customProp }) {
537
+ // remoteManifest and mintLayoutProps are automatically injected
538
+ // ...
539
+ }
540
+ ```
541
+
542
+ ---
543
+
544
+ ## Build & Development
545
+
546
+ ### Build the Package
547
+
548
+ ```bash
549
+ # Production build (minified)
550
+ npm run build
551
+
552
+ # Development build (unminified, preserves modules)
553
+ npm run build:debug
554
+
555
+ # Watch mode (rebuilds on changes)
556
+ npm run dev
557
+ ```
558
+
559
+ ### Type Checking & Linting
560
+
561
+ ```bash
562
+ # Type check
563
+ npm run typecheck
564
+
565
+ # Lint
566
+ npm run lint
567
+ ```
568
+
569
+ ## Publishing
570
+
571
+ ### Automatic Publishing (Active)
572
+
573
+ Publishing is fully automated via semantic-release and Bitbucket Pipelines. No manual steps needed.
574
+
575
+ | Branch | npm tag | Example version |
576
+ | --------- | ------------------------------------- | ----------------------- |
577
+ | `master` | `latest` | `1.0.5` |
578
+ | `develop` | `beta` _(post-POC)_ | `1.0.5-beta.1` |
579
+ | `f-poc-*` | Branch name _(temporary, during POC)_ | `1.0.5-f-poc-ec-3644.1` |
580
+
581
+ A release is triggered automatically when a push includes a `feat` or `fix` commit. The pipeline handles building, versioning, publishing to npm, and committing the updated `package.json` back to the branch.
582
+
583
+ #### Installing a prerelease (POC)
584
+
585
+ ```bash
586
+ # Latest from a specific POC branch
587
+ npm install @kaena1/ecomm-remote-utils@f-poc-ec-3644
588
+
589
+ # Specific version
590
+ npm install @kaena1/ecomm-remote-utils@1.0.5-f-poc-ec-3644.1
591
+ ```
592
+
593
+ > **⚠️ Pin prereleases exactly — no `^` or `~`**
594
+ >
595
+ > Semver range operators behave unexpectedly with prerelease versions. `^1.1.0-f-poc-live-update.8` resolves to any `1.1.0-<tag>` where `<tag>` sorts higher lexicographically, which can install a completely different branch.
596
+ >
597
+ > Always use an exact version in `package.json`:
598
+ >
599
+ > ```json
600
+ > "@kaena1/ecomm-remote-utils": "1.1.0-f-poc-live-update.8"
601
+ > ```
602
+
603
+ ## Versioning & Branching
604
+
605
+ ### Semantic Release Configuration
606
+
607
+ The package uses [semantic-release](https://github.com/semantic-release/semantic-release) for automated versioning based on commit messages.
608
+
609
+ #### Supported Branches
610
+
611
+ | Branch Pattern | Prerelease Tag | Use Case |
612
+ | -------------- | -------------- | -------------------- |
613
+ | `master` | None | Production releases |
614
+ | `develop` | `true` | QA/Staging releases |
615
+ | `f-poc-*` | Branch name | POC/Feature releases |
616
+
617
+ #### Commit Message Format
618
+
619
+ Follow [Conventional Commits](https://www.conventionalcommits.org/):
620
+
621
+ ```
622
+ <type>(<scope>): <description>
623
+
624
+ [optional body]
625
+
626
+ [optional footer]
627
+ ```
628
+
629
+ **Examples:**
630
+
631
+ ```bash
632
+ feat(ecomm-remote-utils): add new remote component loader
633
+ fix(ecomm-remote-utils): resolve React dependency conflicts
634
+ docs: update README with usage examples
635
+ ```
636
+
637
+ #### Release Rules
638
+
639
+ Uses the default [Conventional Commits](https://www.conventionalcommits.org/) release rules:
640
+
641
+ | Commit Type | Release Type |
642
+ | ----------- | ------------- |
643
+ | `feat` | Minor (1.x.0) |
644
+ | `fix` | Patch (1.0.x) |
645
+ | `chore` | None |
646
+
647
+ ### POC Branch Strategy
648
+
649
+ For the POC, we use the branch pattern `f-poc-*` with a specific prerelease identifier.
650
+
651
+ **Current setup:**
652
+
653
+ ```javascript
654
+ {
655
+ name: 'f-poc-*',
656
+ prerelease: '${name}', // Uses full branch name as tag
657
+ }
658
+ ```
659
+
660
+ This means:
661
+
662
+ - Branch `f-poc-EC-3570` → publishes as `1.0.3-f-poc-EC-3570.1`
663
+ - Branch `f-poc-live-update` → publishes as `1.0.3-f-poc-live-update.1`
664
+
665
+ ## How It Works
666
+
667
+ ### Architecture Overview
668
+
669
+ ```
670
+ ┌─────────────────────────────────────────────────────────────┐
671
+ │ Consumer App (Next.js) │
672
+ │ ┌────────────────────────────────────────────────────────┐ │
673
+ │ │ _document.tsx: <ImportMapScripts /> │ │
674
+ │ │ └─> Injects import map for React dependency resolution│ │
675
+ │ └────────────────────────────────────────────────────────┘ │
676
+ │ ┌────────────────────────────────────────────────────────┐ │
677
+ │ │ _app.tsx: │ │
678
+ │ │ 1. fetchManifest() - Gets component URLs │ │
679
+ │ │ 2. <RemoteLinks /> - Preloads resources │ │
680
+ │ │ 3. <RemoteRenderer /> - Loads & renders component │ │
681
+ │ └────────────────────────────────────────────────────────┘ │
682
+ └─────────────────────────────────────────────────────────────┘
683
+
684
+ ┌─────────────────────────────────┐
685
+ │ ModuleLoader (ecomm-remote-utils) │
686
+ │ 1. Checks cache │
687
+ │ 2. Loads ES module via script │
688
+ │ 3. Extracts component │
689
+ │ 4. Returns React component │
690
+ └─────────────────────────────────┘
691
+
692
+ ┌─────────────────────────────────┐
693
+ │ Remote Component (S3/CDN) │
694
+ │ • MintLayout │
695
+ │ • PocComponent │
696
+ │ • TestComponent │
697
+ │ etc. │
698
+ └─────────────────────────────────┘
699
+ ```
700
+
701
+ ### Component Manifest Format
702
+
703
+ Remote components are described by a manifest JSON file:
704
+
705
+ ```json
706
+ {
707
+ "components": {
708
+ "MintLayout": {
709
+ "bundleUrl": "https://.../MintLayout.abc123.es.js",
710
+ "styleUrl": "https://.../MintLayout.def456.css"
711
+ },
712
+ "PocComponent": {
713
+ "bundleUrl": "https://.../PocComponent.xyz789.es.js",
714
+ "styleUrl": "https://.../PocComponent.uvw012.css"
715
+ }
716
+ },
717
+ "version": "0.1.0",
718
+ "generatedAt": "2026-01-20T12:00:00.000Z"
719
+ }
720
+ ```
721
+
722
+ ### Import Map Resolution
723
+
724
+ The `ImportMapScripts` component injects an import map that resolves bare specifiers to use the **host app's React instance**:
725
+
726
+ ```html
727
+ <script type="importmap-shim">
728
+ {
729
+ "imports": {
730
+ "react": "data:text/javascript,...(uses window.React)",
731
+ "react/jsx-runtime": "data:text/javascript,...(uses window.React.createElement)",
732
+ "react-dom": "https://esm.sh/react-dom@18.3.1",
733
+ "react-dom/client": "https://esm.sh/react-dom@18.3.1/client"
734
+ }
735
+ }
736
+ </script>
737
+ ```
738
+
739
+ **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.
740
+
741
+ ### Module Loading Flow
742
+
743
+ 1. **Component Manifest** is fetched via `fetchManifest()`
744
+ 2. **ModuleLoader** dynamically imports the ES module from `bundleUrl`
745
+ 3. **Component extraction** finds the named export or default export
746
+ 4. **React component** is returned and rendered via `<RemoteRenderer>`
747
+
748
+ ## CI/CD Pipeline
749
+
750
+ ### Bitbucket Pipelines Configuration
751
+
752
+ The package is built and deployed via Bitbucket Pipelines (`bitbucket-pipelines.yml` in repo root).
753
+
754
+ #### Pipeline Stages
755
+
756
+ **For `f-poc-*` branches (temporary, during POC):**
757
+
758
+ ```yaml
759
+ - stage: <EcommContainedUtils> - POC
760
+ condition:
761
+ changesets:
762
+ includePaths:
763
+ - packages/ecomm-remote-utils/**
764
+ steps:
765
+ - Lint & Build
766
+ - Deploy (prerelease published to npm with branch name as tag)
767
+ ```
768
+
769
+ **For `develop` branch (post-POC):**
770
+
771
+ ```yaml
772
+ - stage: <EcommContainedUtils> - QA
773
+ condition:
774
+ changesets:
775
+ includePaths:
776
+ - packages/ecomm-remote-utils/**
777
+ steps:
778
+ - Lint & Build
779
+ - Deploy (beta prerelease published to npm)
780
+ ```
781
+
782
+ **For `master` branch:**
783
+
784
+ ```yaml
785
+ - stage: <EcommContainedUtils> - Production
786
+ condition:
787
+ changesets:
788
+ includePaths:
789
+ - packages/ecomm-remote-utils/**
790
+ steps:
791
+ - Lint & Build
792
+ - Deploy (stable release published to npm)
793
+ ```
794
+
795
+ **Triggers:**
796
+
797
+ - Runs when files in `packages/ecomm-remote-utils/**` are modified
798
+
799
+ ## Environment Variables
800
+
801
+ All env vars are read in the consumer app, not in this package. These are passed into the utility functions as arguments.
802
+
803
+ | Variable | Used by | Description |
804
+ | ----------------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
805
+ | `NEXT_PUBLIC_ENVIRONMENT` | `getRemoteManifest`, `getRemoteComponentsData`, `withRemoteComponentsGSSP` | Controls which S3 bucket the manifest is fetched from. `production` → `assets.mintmobile.com`, anything else → `assets-qa.mintmobile.com` |
806
+ | `NEXT_PUBLIC_SITE_URL` | `getMintLayoutProps`, `getRemoteComponentsData` | Public-facing site URL, forwarded to MintLayout's `config` prop |
807
+ | `ECOMM_API_URL` | `getMintLayoutProps`, `getRemoteComponentsData` | Base URL of the ecomm API |
808
+ | `ECOMM_API_STANDALONE_DEVICE_CREDS` | `getMintLayoutProps`, `getRemoteComponentsData` | API credentials in `username\|password` format |
809
+
810
+ The `cont-comp` **cookie** (not an env var) can be set in the browser to override which branch's manifest is loaded. `getRemoteManifest` reads it when passed via `cookieBranch`. If absent, defaults to `f-poc-live-update` (develop) or `master` (production).
811
+
812
+ ## Troubleshooting
813
+
814
+ **`Invalid hook call` / "You may have more than one copy of React"**
815
+ Caused by `npm link` creating two separate React instances. Use `npm pack` instead — see [For Local Development](#for-local-development).
816
+
817
+ **Component renders nothing / stays on placeholder indefinitely**
818
+ Check the browser console for a module load error. Common causes:
819
+
820
+ - `bundleUrl` in the manifest is unreachable (wrong environment, pipeline hasn't run)
821
+ - Import map is missing — confirm `<ImportMapScripts />` is rendered before the component loads
822
+
823
+ **`getRemoteManifest` returns `null` or throws in production**
824
+ The manifest fetch failed server-side. The consumer app should handle `null` gracefully and render a fallback (e.g. render `children` without the layout wrapper). `withRemoteComponentsGSSP` and `getRemoteComponentsData` do this automatically.
825
+
826
+ **`RemoteRenderer` throws "Cannot use import statement" or similar ESM error**
827
+ `es-module-shims` is not loaded. Confirm `<ImportMapScripts />` is in `<head>` before any contained component scripts are executed.
828
+
829
+ **TypeScript: "Module has no exported member" for a remote component**
830
+ The `urlImports.d.ts` type declarations in the consumer app are out of date. Re-run `getMintLayoutTypes` (or update the file manually) to pull the latest type definitions.
831
+
832
+ **Wrong branch loaded in QA**
833
+ Check the `cont-comp` cookie value in the browser. It is case-sensitive and must match the branch name exactly (e.g. `f-poc-EC-3808`, not `f-poc-ec-3808`). Clear or update the cookie to switch branches.