Skip to content

Commit a0bb4d0

Browse files
committed
fix(many): fix having the same DOM ids if there are multiple instances of InstUI, e.g. module federation
Its fixed by moving the instance counter to the global object. Also the generation pattern is altered slightly to work nicely with older versions of InstUI which do not use this new global object Fixes INSTUI-4484
1 parent b6f5f65 commit a0bb4d0

File tree

17 files changed

+119
-59
lines changed

17 files changed

+119
-59
lines changed

docs/guides/accessing-the-dom.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Accessing the DOM
33
category: Guides
4-
order: 4
4+
order: 5
55
---
66

77
## Accessing the DOM

docs/guides/color-system.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Color System
33
category: Guides
4-
order: 5
4+
order: 6
55
---
66

77
## Colors

docs/guides/focus-management.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Focus Management
33
category: Guides
4-
order: 3
4+
order: 4
55
---
66

77
## The Focus Management Problem

docs/guides/module-federation.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
title: Module federation
3+
category: Guides
4+
order: 1
5+
---
6+
7+
# Module federation
8+
9+
InstUI supports [module federation](https://module-federation.io/) with some caveats. In a host app-guest app scenario, you have 2 choices depending on the version of InstUI you are using:
10+
11+
### (Recommended) Host and guest app are using larger version than InstUI v10.14:
12+
13+
- Both apps should use `canvasThemeLocal` or `canvasHighContrastThemeLocal` from the `@instructure/ui-themes` package when using themes. This means that `InstUISettingsProvider`'s theme prop cannot be left unset because it will default to `canvas`.
14+
- Apps cannot use `canvas.use()`, `canvasHighContrast.use()`, these do not exist in the local themes.
15+
16+
### Host app is using InstUI v10.14 or earlier:
17+
18+
- Guest app needs to use **larger** version than InstUI v10.14
19+
- Host app needs to import the `canvas`/`canvasHighContrast` theme before loading the guest app
20+
- Guest app must use `canvasThemeLocal` or `canvasHighContrastThemeLocal`. Guest app's `InstUISettingsProvider`'s `theme` prop cannot be left unset because it will default to `canvas`
21+
- Guest app cannot use `canvas.use()`, `canvasHighContrast.use()`, these do not exist in the local themes.
22+
23+
> Overrides specified in global themes are not applied to local themes.
24+
25+
You can check out a sample application on [Github](https://github.com/matyasf/module-federation-instui)

docs/guides/server-side-rendering.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Server side rendering (SSR)
33
category: Guides
4-
order: 1
4+
order: 2
55
---
66

77
# SSR with Next.js
@@ -45,8 +45,7 @@ npm install @instructure/emotion @instructure/ui-react-utils
4545
4646
- in your Next.js application create - if it does not already exist - a file named `_app.js` inside the `pages` directory. This is a special file in Next.js because it allows you to override/control component initialization. Read more about it in the [Next.js docs](https://nextjs.org/docs/advanced-features/custom-app).
4747
48-
- then configure the `_app.js` so your component tree is wrapped with an `InstUISettingsProvider`
49-
- the important step is to call `generateInstanceCounterMap` on every request, so the server side instance tracking and browser side instance tracking stays in sync with each other:
48+
- then configure the `_app.js` so your component tree is wrapped with an `InstUISettingsProvider` component. This component is responsible for keeping track of component instances and generating deterministic `ids` for components.
5049
5150
```js
5251
---
@@ -66,8 +65,7 @@ export default function MyApp(props) {
6665
<Head>
6766
<meta name="viewport" content="initial-scale=1, width=device-width" />
6867
</Head>
69-
{/* This is the important step */}
70-
<InstUISettingsProvider instanceCounterMap={generateInstanceCounterMap()}>
68+
<InstUISettingsProvider>
7169
<Component {...pageProps} />
7270
</InstUISettingsProvider>
7371
</>

docs/guides/typography-system.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Typography
33
category: Guides
4-
order: 8
4+
order: 9
55
---
66

77
# Typography

docs/guides/using-theme-overrides.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: Using theme overrides
33
category: Guides
4-
order: 2
4+
order: 3
55
---
66

77
## Using theme overrides

packages/emotion/src/InstUISettingsProvider/index.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ type InstUIProviderProps = {
4444
theme?: ThemeOrOverride
4545

4646
/**
47-
* A [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) which keeps track of specific InstUI components. (generally this is used for deterministic id generation for [SSR](/#server-side-rendering))
47+
* @deprecated the `instanceCounterMap` prop is deprecated. You don't need to supply the
48+
* `instanceCounterMap` to the component. It handles it internally.
49+
*
50+
* A [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) which keeps track of
51+
* specific InstUI components. (generally this is used for deterministic id generation for [SSR](/#server-side-rendering))
4852
*/
4953
instanceCounterMap?: DeterministicIdProviderValue
5054
} & (
@@ -57,14 +61,16 @@ type InstUIProviderProps = {
5761
*/
5862
dir: 'ltr' | 'rtl'
5963
/**
60-
* Deprecated property, remove in 10.0.0
64+
* @deprecated remove in v11
65+
* The element type to render as
6166
*/
6267
as?: AsElementType
6368
}
6469
| {
6570
dir?: never
6671
/**
67-
* Deprecated property, remove in 10.0.0
72+
* @deprecated remove in v11
73+
* The element type to render as
6874
*/
6975
as?: never
7076
}

packages/ui-react-utils/src/DeterministicIdContext/DeterministicIdContext.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,31 @@
2222
* SOFTWARE.
2323
*/
2424
import React from 'react'
25-
import { generateInstanceCounterMap } from './generateInstanceCounterMap'
25+
import type { DeterministicIdProviderValue } from './DeterministicIdContextProvider'
26+
27+
declare global {
28+
// eslint-disable-next-line no-var
29+
var __INSTUI_GLOBAL_INSTANCE_COUNTER__: Map<string, number>
30+
}
31+
const instUIInstanceCounter = '__INSTUI_GLOBAL_INSTANCE_COUNTER__'
32+
33+
/**
34+
* Returns a global (window-level) instance counter map.
35+
* This needs to be global so that IDs are unique across application instances,
36+
* e.g. in module federation applications are loaded as a .js blob, this method
37+
* makes sure that there are no duplicate IDs across instances.
38+
*/
39+
function generateInstanceCounterMap(): DeterministicIdProviderValue {
40+
if (globalThis[instUIInstanceCounter]) {
41+
return globalThis[instUIInstanceCounter]
42+
}
43+
const map = new Map<string, number>()
44+
globalThis[instUIInstanceCounter] = map
45+
return map
46+
}
2647

2748
const defaultDeterministicIDMap = generateInstanceCounterMap()
49+
2850
const DeterministicIdContext = React.createContext(defaultDeterministicIDMap)
2951

3052
export { DeterministicIdContext, defaultDeterministicIDMap }

packages/ui-react-utils/src/DeterministicIdContext/DeterministicIdContextProvider.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ import {
2929

3030
type DeterministicIdProviderValue = Map<string, number>
3131
type DeterministicIdProviderProps = React.PropsWithChildren<{
32+
/**
33+
* @deprecated the `instanceCounterMap` prop is deprecated. You don't need to
34+
* supply the `instanceCounterMap` to the component. It handles it internally.
35+
* If you use this make sure that it generates unique IDs across app instances
36+
* like in module federation.
37+
*/
3238
instanceCounterMap?: DeterministicIdProviderValue
3339
}>
3440

@@ -44,7 +50,6 @@ type DeterministicIdProviderProps = React.PropsWithChildren<{
4450
* This is utility component for wrapping components with `DeterministicIdContext.Provider`
4551
* See detailed documentation about how to use it: [InstUISettingsProvider](/#InstUISettingsProvider)
4652
*/
47-
4853
const DeterministicIdContextProvider = ({
4954
children,
5055
instanceCounterMap = defaultDeterministicIDMap

0 commit comments

Comments
 (0)