Giter Club home page Giter Club logo

microsoft / redux-micro-frontend Goto Github PK

View Code? Open in Web Editor NEW
340.0 15.0 61.0 264 KB

This is a library for using Redux to manage state for self-contained apps in a Micro-Frontend architecture. Each self-contained isolated app can have its own isolated and decoupled Redux store. The componentized stores interact with a global store for enabling cross-application communication.

License: MIT License

TypeScript 94.08% JavaScript 5.92%
redux microfrontends microfrontend statemanagement react-redux

redux-micro-frontend's Introduction

Redux Micro-Frontend

한국어🇰🇷

Version Deprecation Warning

1.1.0 - If you are using this version, please upgrade to latest immediately. This version has been deprecated due to a pipeline issue.

Pipeline Status

Build Status

CodeQL

Build-and-Publish

npm

Overview

This library can be used for using Redux in a Micro Frontend based architecture. Micro Frontends is an architectural pattern for breaking up a monolith Frontend application into manageable, decoupled and smaller applications. Each application is a self-contained and isolated unit. Generally, a common shell/platform application is used to host these small units to provide a common experience for the end-users.

Redux is one of the most popular libraries for predictable state management. However, the general practice in using Redux is to have a single store, thereby having a single state object. This approach would mean that all the Micro Frontends would have a shared state. This is a violation of the Micro Frontend based architecture since each App is supposed to be a self-contained unit having its store.

To provide a level of isolation some developers use combineReducer() to write a separate reducer for each Micro Frontend and then combine them into one big reducer. Although it would solve some problems this would still imply that a single state object is shared across all the apps. In the absence of sufficient precautions, apps might accidentally override each other's state.

In a Micro Frontend architecture, an individual application should not be able to modify the state of other apps. However, they should be able to see the state of other apps. Along the same line for enabling cross-application communication, they should also be able to send events/actions to other Stores and also get notified of changes in other apps' state. This library aims to attain that sweet spot between providing isolation and cross-application communication.

Concept

A concept of Global Store is introduced which is a virtual amalgamation of multiple Redux Stores. Strictly speaking, the Global Store is not an actual store, rather it's a collection of multiple isolated Redux Stores. Each physical Redux Store here refers to the isolated store that each app uses. Micro frontends having access to the Global Store would be able to perform all operations that are allowed on an individual Redux Store including getState(), dispatch() and subscribe().

Each Micro Frontend would have the capability to have its own Redux Store. Each app would create and register their Redux Store with the Global Store. The Global Store then uses these individual stores to project a Global State which is a combination of the state from all the other Stores. All the Micro Frontends would have access to the Global Store and would be able to see the state from the other Micro Frontends but won't be able to modify them. Actions dispatched by an app remains confined within the store registered by the app and is not dispatched to the other stores, thereby providing componentization and isolation.

Read more

Global Actions

A concept of Global Action is available which allows other apps to dispatch actions to stores registered by other Micro Frontends. Each Micro Frontend has the capability to register a set of global actions along with the store. These set of global actions can be dispatched in this Micro Frontend's store by other Micro Frontends. This enables cross-application communication. Global Store

Cross-state callbacks

Cross-application communication can also be achieved by subscribing to change notifications in other Micro Frontend's state. Since each micro-frontend has read-only permission to other states, they can also attach callbacks for listening to state changes. The callbacks can be attached either at an individual store level or at a global level (this would mean that state change in any store would invoke the callback).

Problems of a single shared state

  • Accidental override of state of other apps (in case duplicate actions are dispatched by multiple apps)
  • Apps would have to be aware of other Micro Frontends
  • Shared middlewares. Since only a single store is maintained, all the Micro Frontends would have to share the same middlewares. So in situations where one app wants to use redux-saga and other wants to use redux-thunk is not possible.

Installation

npm install redux-micro-frontend --save

Quick Guide

Get an instance of Global Store

import { GlobalStore } from 'redux-micro-frontend';
...
this.globalStore = GlobalStore.Get();

Create/Register Store

let appStore = createStore(AppReducer); // Redux Store
this.globalStore.RegisterStore("App1", appStore);
this.globalStore.RegisterGlobalActions("App1", ["Action-1", "Action-2"]); // These actions can be dispatched by other apps to this store

Dispatch Action

let action = {
    type: 'Action-1',
    payload: 'Some data'
}
this.globalStore.DispatchAction("App1", action); // This will dispatch the action to current app's store as well other stores who might have registered 'Action-1' as a global action

Subscribe to State

// State change in any of the apps
this.globalStore.Subscribe("App1", localStateChanged);

// State change in the current app
this.globalStore.SubscribeToGlobalState("App1", globalStateChanged);

// State change in app2's state
this.globalStore.SubscribeToPartnerState("App1", "App2", app2StateChanged);

...

localStateChanged(localState) {
    // Do something with the new state
}

globalStateChanged(stateChanged) {
        // The global state has a separate attribute for all the apps registered in the store
        let app1State = globalState.App1;
        let app2State = globalState.App2; 
}

app2StateChanged(app2State) {
    // Do something with the new state of app 2
}

Sample App

Location: https://github.com/microsoft/redux-micro-frontend/tree/main/sample

Instruction for running Sample App

  1. Go to sample/counterApp and run npm i and then npm run start
  2. Go to sample/todoApp and run npm i and then npm run start
  3. Go to sample/shell and run npm i and then npm run start
  4. Browse http://localhost:6001

Documentation

See Github wiki

Appendix

Trademarks

This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft's Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies.

redux-micro-frontend's People

Contributors

datajoy-eric avatar deepakparamesh avatar enk0de avatar harunsmrkovic avatar iswar7892 avatar jaapaurelio avatar microsoft-github-operations[bot] avatar microsoftopensource avatar patrickcode avatar rotty3000 avatar siva-i avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

redux-micro-frontend's Issues

Option to set instanceName to avoid confusion between multiple stores in redux extension

Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

Describe the solution you'd like
A clear and concise description of what you want to happen.

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

Using redux-micro-frontend for shared history

At the moment, my organisation is looking into the whole instance of microfrontends.

We have several applications (React + Redux + React Router) that we have developed - and are developing - separately. However, we are interested in having them integrated as microfrontends in another application (the 'container'), which would contain common functionality like the navigation bars. Otherwise, the microfrontends would generally do their own thing. Users can navigate to Microfrontend App 1 at address /mfeapp1, Microfrontend App 2 at address /mfeapp2, and so on. (If the applications are running as microfrontends, we can engineer it so that the different apps are running on different paths.)

Each app has Redux, but there is no shared state between the apps... with one exception: router history. And this is one area where it would be a good idea to have shared state, so that both the container and the microfrontends know what URL the user has navigated to.

What I am looking for is some guidance on whether redux-micro-frontend is suitable for this situation. In the container app, we have:

const reducers = (history: History<unknown>) => combineReducers({
  router: connectRouter(history), // From connected-react-router
});

And the container store is:

  store = createReduxStore(reducers(history), compose(
    applyMiddleware(
      thunk,
      routerMiddleware(history),
    ),
    /* eslint-disable-next-line no-underscore-dangle */
    process.env.NODE_ENV === 'development' && window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : (f: unknown) => f,
  ));

Microfrontends may be interacting with the history

import { push } from 'connected-react-router';
/* ... */
dispatch(push(path)); // The user has pressed a button.

I looked at your sample app, but I noticed that both the microfrontends are running on the same page. This seems a different situation from where my organisations finds itself.

Error related to RXJS when using Webpack 5 Module Federation

** Context **
I am currently in the process of experimenting with a microfrontend architecture with Webpack 5 and the Module Federation plugin. I have integrate this library to communicate with microfrontends based on web-components.

Describe the bug
When i integrate the "redux-micro-frontend" library inside my shell or microfrontend separatly based on Angular, all it is fine. But when i unite the shell and microfrontends AND i use an othe library who use RXJS like angular material. An error occured :
image

At first I didn't notice the error because I had the Redux dev tools chrome extension enabled. This extension obviously made it possible to resolve this conflict between the versions of RXJS.

But when i threw my application in production and the clients try to use some features of the application, they notice some bugs with some components from Angular Material.

To Reproduce
Steps to reproduce the behavior:

  1. Use webpack 5 module federation
  2. Create a basic shell and a basic microfrontend
  3. Integrate @angular/material inside your shell or your microfrontend
  4. Integrate "redux-micro-frontend"
  5. You will note that when you try to do : "GlobalStore.Get()" the error above will appear.

Solution
So I investigated and noted that when the "redux-micro-frotnend" library was not integrated no problems occurred. And I quickly came to the conclusion that there was a conflict between the redux library used and rxjs in my application. To solve this problem, I was able to observe two solutions:

  • Grab redux and redux-devtools-extension inside peerDependencies (in package.json)
  • Upgrade the redux version to 4.1.1

I hope i was clear with my explications :)

Issue with start-component

I get following error while using npm run start start-component

$ npm run start start-component

> [email protected] start C:\Users\HOME\Documents\projects\redux-micro-frontend\sample\todoApp
> webpack serve "start-component"

`resolve/lib/core` is deprecated; please use `is-core-module` directly
[webpack-cli] Unknown argument: start-component
npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! [email protected] start: `webpack serve "start-component"`
npm ERR! Exit status 2
npm ERR!
npm ERR! Failed at the [email protected] start script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\HOME\AppData\Roaming\npm-cache\_logs\2020-12-03T13_24_20_148Z-debug.log

Support exposing derived state from an app that partner apps can consume.

The beauty of this library is that you can combine with multiple redux states and they can be able to lazy loaded and registered when appropriate. By being able to subscribe to a partner state, we get the ability to get the latest state from a partner app but the problem is that Redux ideally saved the smallest amount of data in the store, and use libraries like reselect can do computations and compute derived state. Making getting derived state from partner apps not possible.

If the GlobalStore was able to expose derived state APIs then the partner apps could listen to state changes, but also get derived state without knowing the structure of the partner state. Otherwise, listening to partner state changes would require another partner app to grab the state and massage the data as needed - while this does work, it forces apps to be coupled and require to understand the structure of each others state, which is not desired.

I've tried chunking the selectors from partner apps, and lazy loading; however, reselect isn't meant to lazy load selectors, so it is rather difficult. Furthermore, it makes it hard to truly decouple apps.

I have submitted a PR to implement this feature here: #39

Thank you so much for such a wonderful library! :)

Need control on the format of the event logs this library is adding

Is your feature request related to a problem? Please describe.
The event logs in the logger is not customizable for the consumer application.

Describe the solution you'd like
Currently we have the option to pass our own logger instance but it doesn't give me control to pass what should be the source or the even name for same.

How to apply redux-micro-frontend with nextjs

Your library is very well but i don't know how to apply it in my project
I am building a project by micro-front-end with nextjs. But in progress, we see an issue about managing redux of each shared component: Error: could not find react-redux context value; please ensure the component is wrapped in a <Provider>. I think , they can't able to see the state of other apps, need to set up to share state between components

Can you guide me how to apply your library in micro-front-end with nextjs?

InvokeGlobalListeners is not called when RegisterStore is used

Describe the bug
InvokeGlobalListeners is not called when RegisterStore is used

To Reproduce
Steps to reproduce the behavior:

  1. Register a manually created store using GlobalStore.RegisterStore
  2. subscribe to global state using globalStore.SubscribeToGlobalState(..)
  3. trigger any store actions
  4. global listeners are not triggered

Expected behavior
global listeners are called for state changes in any store.

Re-open old issue with JEST testing

[Related]
#8

This is the message I get

Details:

    ~/Project/node_modules/redux-micro-frontend/index.js:1
    ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){export * from './src/actions';
                                                                                             ^^^^^^

    SyntaxError: Unexpected token 'export'

      1 | /* eslint-disable @typescript-eslint/no-explicit-any */
    > 2 | import { GlobalStore, IAction } from 'redux-micro-frontend'
        | ^
      3 |
      4 | export interface Action<T = any> {
      5 |   type: T

      at Runtime.createScriptFromCode (../../node_modules/jest-runtime/build/index.js:1350:14)
      at Object.<anonymous> (src/services/globalState/index.tsx:2:1)

Test Suites: 1 failed, 1 passed, 2 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        6.445 s
Ran all test suites.

package.json

{
  "name": "@tools",
  "version": "1.0.311",
  "main": "index.ts",
  "private": true,
  "babel": {
    "presets": [
      "@babel/preset-react"
    ]
  },
  "dependencies": {
    "@react-keycloak/web": "^3.4.0",
    "axios": "^0.21.1",
    "axios-hooks": "^2.3.0",
    "i18next": "^20.3.4",
    "i18next-browser-languagedetector": "^6.1.2",
    "i18next-resources-to-backend": "^1.0.0",
    "jsbi": "^3.1.6",
    "keycloak-js": "^12.0.3",
    "react": "^17.0.1",
    "react-error-boundary": "^3.1.1",
    "react-i18next": "^11.11.3",
    "react-use": "^17.2.4",
    "redux-micro-frontend": "^1.2.0",
    "rollbar": "^2.21.1",
    "ulid": "^2.3.0",
    "uuid": "^8.3.2"
  },
  "devDependencies": {
    "@testing-library/react-hooks": "^5.0.3",
    "@types/jest": "^26.0.20",
    "@types/node": "^16.11.6",
    "@types/uuid": "^8.3.1",
    "@typescript-eslint/eslint-plugin": "^4.29.0",
    "@typescript-eslint/eslint-plugin-tslint": "^5.0.0",
    "@typescript-eslint/parser": "^4.29.0",
    "axios-mock-adapter": "^1.19.0",
    "babel-jest": "^26.6.3",
    "eslint": "^7.32.0",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-import": "^2.24.2",
    "eslint-plugin-prettier": "^3.4.0",
    "eslint-plugin-react": "^7.24.0",
    "eslint-plugin-react-hooks": "^4.2.0",
    "eslint-webpack-plugin": "^3.0.1",
    "jest": "^26.6.3",
    "react-test-renderer": "^16.9.0",
    "ts-jest": "^26.5.1",
    "ts-node": "^9.1.1"
  },
  "scripts": {
    "test": "REF_URL=localhost:50000 jest",
    "lint": "eslint . --quiet",
    "clean": "rm -rf dist"
  }
}

jest.config.js

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  modulePathIgnorePatterns: ['<rootDir>/dist/'],
}

process.env['NUEVO_URL'] = 'http://localhost:50000'

Doesn't work with Jest

I'm trying to test my application using Jest lib and getting the following error:

image

I've tryed common solutions like babel-jest and other configurations mentioned on docs. but have no result. Can you check?

Security Issues in y18n

Describe the bug
Security dependabot alert

To Reproduce
Steps to reproduce the behavior:

  1. Go to any of the same projects
  2. Run npm i
  3. Run npm audit

Expected behavior
0 Securutiry Vulnerabilities

Additional context
Package - y18n

AppB is able to modify store in AppA that doesn't have registered global actions

Describe the bug
Hi everyone,

I'm playing arround with this library and I found out that I'm able to update state from AppA -> AppB even if AppB doesn't have registered global actions.

AppA:

            this.globalStore = GlobalStore.Get(false);
            this.globalStore.CreateStore('MFAppStore', MfReducer, []);
            this.globalStore.RegisterGlobalActions('MFAppStore', null);

AppB:

                this.globalStore = GlobalStore.Get(false);
                this.globalStore.CreateStore('GlobalAppStore', GlobalReducer, []);
                this.globalStore.RegisterGlobalActions('GlobalAppStore', [INCREMENT_GLOBAL, DECREMENT_GLOBAL]);

I'm able to dispatch action from AppB to AppA and modify the 'MFAppStore' even if there are not registered global actions.

In documentation ( readme ) it says:

this.globalStore.DispatchAction("App1", action); // This will dispatch the action to current app's store as well other stores who might have registered 'Action-1' as a global action

but it seems that it doesn't work as it is intended or I'm missing something?

Or it was always allowed?

Thanks

queued subscribe callbacks for missing stores

Is your feature request related to a problem? Please describe.
I get an error when subscribing to a missing store.

Describe the solution you'd like
Instead of getting an error when subscribing to a missing store, I'd rather the callback be queued up for the possible eventual arrival of the store (maybe never). Meanwhile, the unsubscribe would pull the callback out of the queue if no store ever arrived.
Of course if the store did eventually arrive the queued callback should be pulled out of the queue, applied to the store.

Describe alternatives you've considered
Not sure of any alternatives.

Adding custom logger is throwing unhandled exception

Describe the bug
When adding a custom logger to the existing loggers, an unhandled exception is getting thrown

To Reproduce
Add a custom logger to the Global Store
globalStore.SetLogger(actionLoggerAdaptor);

Expected behavior
Custom logger should get added

Screenshots
image

Desktop (please complete the following information):

  • OS: Windows
  • Browser Chrome

add option to get-or-set a store instance

Is your feature request related to a problem? Please describe.
I'm having a hard time integrating redux-developer tools. I can do it, but it's not elegant. In doing so I find that we have to forgo the get-or-set behaviour of the GlobalStore.

Ideas?
I started with GlobalStore.Get().CreateStore(...) which behaves as get-or-set like I wish however it does not allow me to use the enhancer parameter of Redux.createStore which means I cannot easily apply the redex developer tools plugin (which is wired via enhancer function) while preserving the get-or-set approach.

Describe the solution you'd like
I would like to apply the redux developer tools plugin to stores via the get-or-set methods of the globalStore.

Describe alternatives you've considered
I've directly create stores that use the plugin and wired them into the global store. This works just fine but I can't do the same with the GlobalStore.CreateStore

(it's quite possible I'm just not knowledgable enough to do it.)

PS: is there a forum or mail list for this project for usability questions?

How to get the global state change in one of the other app?

Describe the bug

(Not a Bug, it is a Question)
I have two apps built using redux and redux-micro-frontend, In this case how could I get the global state change happening in one place, to one of the other App?

Here is my code in the Main App component to subscribe the CounterApp

const globalStore = GlobalStore.Get(false);
const globalStateChanged = (stateChanged) => {
        const stateC = globalState.CounterApp;
        console.log({ stateChanged, stateC });
};
globalStore.SubscribeToGlobalState('CounterApp', globalStateChanged);

And in the other App(Counter app), a method is triggered by the button click. Below is the code for that. (we have the same reducer and actions file that is created from the samples projects under the library)

const incrementGlobal = () => {
    globalStore.DispatchAction("CounterApp", IncrementGlobalCounter());
};

But I don't see the globalStateChanged invocation and the console print in Main App method. What will be the issue here? Are we missing anything here, and if you could share the code for the right approach it would be great.

Verison 1.1.0 is broken

Describe the bug
Version 1.1.0 has typescript files

To Reproduce
Steps to reproduce the behavior:

  1. npm i [email protected]

Expected behavior
Only JavaScript and definition files should be present

Screenshots
image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.