Tuesday 16 April 2024

React + Typescript_ Module federation _ Micro Front end -Standalone and integrated


Module Federation

The Module Federation is actually part of Webpack config. This config enables us to expose or receive different parts of the CRA to another CRA project. These separate project should not have dependencies between each other, so they can be developed and deployed individually.


Let’s first start by creating our Container project which exports other two app APP-1 and APP-2.


step:1

npx create-react-app container --template typescript


step:2

Container App

npm install html-webpack-plugin serve ts-loader webpack webpack-cli webpack-dev-server @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^5


step:3

index.tsx -> bootstrap.tsx

move content

step:4

index.tsx ->index.ts


import("./bootstrap");

export {};


step:5

App.tsx:


import { Box, Center, Flex, Heading, Spinner, Image, Link, Text } from '@chakra-ui/react';

import React from 'react';


const CounterAppOne = React.lazy(() => import('app1/MFE1WebpackAppName1'));    //// impoert('appName/exposeCompoent');

const CounterAppTwo = React.lazy(() => import('app2/MFE2WebpackAppName2'));


export const App=()=>{


return(

<>

<React.Suspense fallback={<>loading... </>}>

<CounterAppOne />

</React.Suspense >

<React.Suspense fallback={<>loading... </>}>

<CounterAppTwo />

</React.Suspense >

</>

)


step:6


webpack.config.js


const HtmlWebpackPlugin = require("html-webpack-plugin");

const webpack = require("webpack"); // only add this if you don't have yet

const { ModuleFederationPlugin } = webpack.container;

const deps = require("./package.json").dependencies;


const buildDate = new Date().toLocaleString();


module.exports = (env, argv) => {

  const isProduction = argv.mode === "production";

  console.log({ isProduction });

  return {

    entry: "./src/index.ts",

    mode: process.env.NODE_ENV || "development",

    devServer: {

      port: 3000,

      open: true,

    },

    resolve: {

      extensions: [".ts", ".tsx", ".js"],

    },

    module: {

      rules: [

        {

          test: /\.(js|jsx|tsx|ts)$/,

          loader: "ts-loader",

          exclude: /node_modules/,

        },

      ],

    },


    plugins: [

      new webpack.EnvironmentPlugin({ BUILD_DATE: buildDate }),

      new webpack.DefinePlugin({

        "process.env": JSON.stringify(process.env),

      }),

      new ModuleFederationPlugin({

        name: "container",

        remotes: {

          app1: "app1@http://localhost:3001/remoteEntry.js",

          app2: "app2@http://localhost:3002/remoteEntry.js",

        },

        shared: {

          ...deps,

          react: { singleton: true, eager: true, requiredVersion: deps.react },

          "react-dom": {

            singleton: true,

            eager: true,

            requiredVersion: deps["react-dom"],

          },

        },

      }),

      new HtmlWebpackPlugin({

        template: "./public/index.html",

      }),

    ],

  };

};


step:7


Update your package.json scripts as follows:


"scripts": {

    "start": "webpack serve --open",

    "build": "webpack --config webpack.prod.js",

    "serve": "serve dist -p 3000",

    "clean": "rm -rf dist"

}


step:8


Update your tsconfig as follows:

tsconfig.ts


{

  "compilerOptions": {

    "target": "es5",

    "lib": ["dom", "dom.iterable", "esnext"],

    "baseUrl": "./",

    "allowJs": true,

    "skipLibCheck": true,

    "esModuleInterop": true,

    "allowSyntheticDefaultImports": true,

    "strict": true,

    "forceConsistentCasingInFileNames": true,

    "noFallthroughCasesInSwitch": true,

    "module": "esnext",

    "moduleResolution": "node",

    "resolveJsonModule": true,

    "isolatedModules": true,

    "noEmit": false,

    "jsx": "react-jsx"

  },

  "include": ["src"]

}


step:9


Create a .env in the project root folder:


DEV_APP1="MFE1WebpackAppName1@http://localhost:3001/remoteEntry.js"

DEV_APP2="app2@http://localhost:3002/remoteEntry.js"


PROD_APP1="MFE1WebpackAppName1@http://YOUR_APPLICATION_PRODUCTION_URL_HERE/remoteEntry.js"

PROD_APP2="app2@http://YOUR_APPLICATION_PRODUCTION_URL_HERE/remoteEntry.js"



npm start ( need to run all mfe's  in standalone mode)


npm run build

then dist will create with mentioned remoteEntry.js files


npm run serve will provide the




MFE-1 ( app1)


step:1


npx create-react-app app-1 --template typescript

yarn add html-webpack-plugin serve ts-loader webpack webpack-cli webpack-dev-server @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^5



Just like we did in Container app we’ll setup bootstrap.tsx, index.ts and app.tsx.


bootstrap.tsx

import App from './App';

import React from 'react';

import ReactDOM from 'react-dom/client';

import { ChakraProvider } from "@chakra-ui/react";


const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)

root.render(

  <ChakraProvider>

    <App />

  </ChakraProvider>

);

Copy

And add those into index.ts


index.ts

import("./bootstrap");


App.tsx

import CounterAppOne from './components/CounterAppOne';


const App = () => (

  <Box margin="1.2rem">

    <Box>APP-1</Box>

    <Box>

      <CounterAppOne />

    </Box>

  </Box>

);


export default App;


step:4


webpack config.js



const HtmlWebpackPlugin = require("html-webpack-plugin");

const { ModuleFederationPlugin } = require("webpack").container;

const path = require("path");

const deps = require("./package.json").dependencies;


module.exports = {

  entry: "./src/index.ts",

  mode: "development",

  devServer: {

    port: 3001,

    open: true,

  },

  resolve: {

    extensions: [".ts", ".tsx", ".js"],

  },

  module: {

    rules: [

      {

        test: /\.(js|jsx|tsx|ts)$/,

        loader: "ts-loader",

        exclude: /node_modules/,

      },

    ],

  },

  plugins: [

    new ModuleFederationPlugin({

      name: "app1",

      filename: "remoteEntry.js",

      exposes: {

        // expose each component

        "./MFE1WebpackAppName1": "./src/components/CounterAppOne.tsx",  

(OR)

"./MFE1WebpackAppName1": "./src/App.tsx",

      },

      shared: {

        ...deps,

        react: { singleton: true, eager: true, requiredVersion: deps.react },

        "react-dom": {

          singleton: true,

          eager: true,

          requiredVersion: deps["react-dom"],

        },

      },

    }),

    new HtmlWebpackPlugin({

      template: "./public/index.html",

    }),

  ],

};


step:5


Update your package.json scripts as follows:


"scripts": {

    "start": "webpack serve --open",

    "build": "webpack --config webpack.prod.js",

    "serve": "serve dist -p 3001",

    "clean": "rm -rf dist"

}


step:6

tsconfig.ts



{

  "compilerOptions": {

    "target": "es5",

    "lib": ["dom", "dom.iterable", "esnext"],

    "baseUrl": "./",

    "allowJs": true,

    "skipLibCheck": true,

    "esModuleInterop": true,

    "allowSyntheticDefaultImports": true,

    "strict": true,

    "forceConsistentCasingInFileNames": true,

    "noFallthroughCasesInSwitch": true,

    "module": "esnext",

    "moduleResolution": "node",

    "resolveJsonModule": true,

    "isolatedModules": true,

    "noEmit": false,

    "jsx": "react-jsx"

  },

  "include": ["src"]

}



In the host app - 


remotes: {

          app1: 'app1@http://localhost:3001/remoteEntry.js',

        },

step:7

npm run build

then dist will create

npm run serve

Thursday 4 April 2024

JS- 10 JavaScript Sites Every Web Developer Should Know

2. https://javascript.info/

1

2
3
4







https://javascript.info/rest-parameters-spread


 1.https://developer.mozilla.org/

https://developer.mozilla.org/en-US/docs/Web/JavaScript


https://developer.mozilla.org/en-US/docs/Web/API


https://developer.mozilla.org/en-US/docs/Web/Tutorials



Sunday 28 January 2024

Git Flow - Git flow to organize their Git branches

Git flow is a Git branching strategy .

Git flow  is not a tool or software that can be downloaded. Developers can use Git flow to organize their Git branches and development process to ship releases more efficiently. 

Best practices for Source code management (SCM) 



Other 

GitHub Flow:

===========

More simple than Git flow, GitHub flow is great for smaller teams and web applications or products that don’t require supporting multiple versions. Because of its simplicity, the GitHub flow workflow allows for continuous delivery and continuous integration.


Of course, there are also related risks to consider. The lack of dedicated development branches, for example, makes this workflow more susceptible to bugs in production.


GitLab Flow:

===========

Also more simple than Git flow, GitLab flow is more organized and structured when compared to the GitHub flow workflow.


GitLab flow introduces environment branches, such as production and pre-production, or release branches, depending on your situation.


With slight modifications, GitLab flow can allow for versioned releases and continuous delivery.


Thursday 25 January 2024

React - custom -memo - cache api response

 import React, { useState, useEffect } from "react";

import axios from "axios";

import "./styles.css";


const memo = callback => {

  const cache = new Map();

  return (...args) => {

    const selector = JSON.stringify(args);

    if (cache.has(selector)) return cache.get(selector);

    const value = callback(...args);

    cache.set(selector, value);

    return value;

  };

};


const memoizedAxiosGet = memo(axios.get);


const SelectPokemonButton = ({ name, onClick }) => (

  <button onClick={() => onClick(name)}>{name}</button>

);


export default function App() {

  const [pokemon, setPokemon] = useState("");

  const [pokemonData, setPokemonData] = useState({});


  useEffect(() => {

    if (pokemon) {

      memoizedAxiosGet(`https://pokeapi.co/api/v2/pokemon/${pokemon}/`)

        .then(response => {

          setPokemonData(response);

        })

        .catch(error => {

          console.error("Error retrieving pokemon with name ", pokemon);

        });

    }

  }, [pokemon]);


  const onClickPokemon = name => {

    setPokemon(name);

  };


  return (

    <div className="App">

      <h1>Memoization request Demo</h1>

      <h3>Before clicking the buttons, open the devtools.</h3>

      <h3>

        Check that, wih every click on different pokemon, the network request is

        done

      </h3>

      <h3>

        But, if the pokemon is clicked for second time, the request wont be done

      </h3>

      <div />

      <SelectPokemonButton name="ditto" onClick={onClickPokemon} />

      <SelectPokemonButton name="pikachu" onClick={onClickPokemon} />

      <SelectPokemonButton name="charizard" onClick={onClickPokemon} />

      <div>Pokemon data:</div>

      <div>{JSON.stringify({ pokemonData })}</div>

    </div>

  );

}


Sunday 21 January 2024

React 2 way data binding with custom Hook

import * as React from 'react';

import { render } from 'react-dom';


// List of supported HTML elements

type ModelElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;


// Custom hook for two-way data binding

// We can provide the initial value and an onChange function

// if we need to do additional things in our component

function useModel<E extends ModelElement>(

  initial: string = null,

  onChange?: React.ChangeEventHandler<E>

) {

  const [value, setValue] = React.useState<string>(initial);

  const handler: React.ChangeEventHandler<E> = (e) => {

    // Store the current value and run the callback function if provided

    setValue(e.currentTarget.value);

    onChange && onChange(e);

  };


  const model = { value, onChange: handler };

  return { model, setModel: setValue };

}


function App() {

  // Use custom hook

  const { model, setModel } = useModel('John', (e) => {

    console.log('Model changed', e.currentTarget.value);

  });


  const reset = () => setModel('');


  return (

    <React.Fragment>

      {/* Spread the model on the input element */}

      <label htmlFor="name">Name: </label>

      <input id="name" {...model} />

      <div>Hello {model.value}</div>

      <button onClick={reset}>Reset</button>

    </React.Fragment>

  );

}


render(<App />, document.getElementById('root'));


Tuesday 2 January 2024

file Saver at UI - Angular convert JSON response to excel file to download.

file Saver - UI Angular

==========================


ng.component.ts

----------------


import * as fileSaver from 'file-saver';


  downloadTestExcelFile() {

    const data = "Excel File column to get Default column name;

    const blob = new Blob([data], {type: 'text/csv'});

    fileSaver.saveAs(blob, "Downloaded_Custom_file_name");

  }

  

  

  

  

  

ng.template.html

------------------

<a href="javascript:void(0)" id="downloadTestExcel_id" (click)="downloadTestExcelFile()" class="link">Click here to download FileSaver    Test File</a>

React Hooks - file-saver at UI -Convert JOSN to excel file format and download



file-saver in React Hooks

==========================


npm install file-saver


import React from "react";

import FileSaver from "file-saver";


export default function App() {

  function sayHello() {

    var blob = new Blob(["Hello, world!"], {

      type: "text/plain;charset=utf-8"

    });

    FileSaver.saveAs(blob, "hello world.txt");

  }

  

    const downloadTestExcelFile =()=> {

    const data = "Excel File column to get Default column name;

    const blob = new Blob([data], {type: 'text/csv'});

    fileSaver.saveAs(blob, "Downloaded_Custom_file_name");  /// empty excel file

  }


  return (

    <div className="App">

      <button onClick={sayHello}>Click me download txt file!</button>

  

   <button onClick={downloadExcel}>Click me download EXCEL file!</button>

    </div>

  );


Wednesday 9 August 2023

React- Ag Grid Server side Pagination Implmentation

 import React, { useEffect, useState } from 'react';


import { AgGridColumn, AgGridReact } from 'ag-grid-react';


import 'ag-grid-community/dist/styles/ag-grid.css';

import 'ag-grid-community/dist/styles/ag-theme-alpine.css';


import './App.css';


function App() {

  const [gridApi, setGridApi] = useState(null);

  const perPage = 3;


  const onGridReady = (params) => {

    setGridApi(params.api);

  };


  useEffect(() => {

    if (gridApi) {

      const dataSource = {

        getRows: (params) => {

          // Use startRow and endRow for sending pagination to Backend

          // params.startRow : Start Page

          // params.endRow : End Page


          const page = params.endRow / perPage;

          fetch(`https://reqres.in/api/users?per_page=${perPage}&page=${page}`)

            .then(resp => resp.json())

            .then(res => {

              params.successCallback(res.data, res.total);

            }).catch(err => {

              params.successCallback([], 0);

            });

        }

      }


      gridApi.setDatasource(dataSource);

    }

  }, [gridApi]);


  const avatarFormatter = ({ value }) => {

    return <img src={value} width="50px" height="50px" />

  }


  return (

    <div className="App">

      <h2>Server side pagination in the React AG Grid - <a href="https://www.cluemediator.com" target="_blank">Clue Mediator</a></h2>

      <div className="ag-theme-alpine ag-style">

        <AgGridReact

          pagination={true}

          rowModelType={'infinite'}

          paginationPageSize={perPage}

          cacheBlockSize={perPage}

          onGridReady={onGridReady}

          rowHeight={60}

          defaultColDef={{ flex: 1 }}

        >

          <AgGridColumn field="first_name" headerName="First Name" cellClass="vertical-middle" />

          <AgGridColumn field="last_name" headerName="Last Name" cellClass="vertical-middle" />

          <AgGridColumn field="email" headerName="Email" cellClass="vertical-middle" />

          <AgGridColumn field="avatar" headerName="Avatar" cellRendererFramework={avatarFormatter} cellClass="vertical-middle" />

        </AgGridReact>

      </div>

    </div>

  );

}


export default App;


Thursday 6 April 2023

React- nested API call asyn - await

useEffect(() => {

  const getData = async () => {

      try {

          const { data } = await fetchContext.authAxios.get('/myapi/' + auth.authState.id);

          const customerIdList = data.map(value => value.customerID);

          // this fetches list of all customer details in one go

          const customersDetails = (await fetchContext.authAxios.post('/mySecondApi/', {customerIdList})).data;

          // please make necessary changes to the api call

          const updatedData = data.map(value => {

                 // filtering the particular customer's detail and updating the data from first api call

                 const customerDetails = customersDetails.filter(c => c.customerID === value.customerID)[0];

                  

                  return {

                      ...value, // de-structuring

                      customerID: customerDetails

                      // as you asked customer data should replace the customerID field

                  }

              }

          );

          setData(updatedData); // this data would contain the other details of customer in it's customerID field, along with all other fields returned by your first api call

      } catch (err) {

          console.log(err);

      }

  };

  getData();

}, [fetchContext]); 

CSS - regex

 


css regx proeprty You could use the ^= ('starts with') and $= ('ends with')

attribute selectors. 

[class^="dh-"]{} ---> start with dh

[class$="-bl"]{} ---> end with dl


Wednesday 5 April 2023

Angular 13 - Micro front end architecture with angular element, custom element, web compoentsjs

 Angular Micro front end with angular element, custom element, web compoentsjs

=======================================================================

MicroApp: 

Step:1 ng new MicroApp angular 13.0.0

 

ng add @angular/elements

document-register-element 

npm i @webcomponents/custom-elements --save

@webcomponents/webcomponentsjs 

ng add ngx-build-plus polyfill.js 


import'document-register-element' 

import"@webcomponents/custom-elements/src/native-shim";

import "@webcomponents/custom-elements/custom-elements.min"; 


app.module.ts


complete the All the

development of Child/Micro app as entire app or particular component as ???

Particular module??? Note: For Module will be part of any component. So, The

idea will module will become component as mciroApp 

Step:2 MicroApp:> ng add @angular/elements will generate / Impact package.json -> 

"@angular/elements" 

"document-register-element": "1.8.1", --> manull add at devDependency

then do npm install polyfill.ts ->Automatically Import this packages into

polyfills.ts file: if not do it maually import 'document-register-element';


Step:3 Install some polyfills:

 MicroApp:> npm i @webcomponents/custom-elements--save add at pakcage.json 

 "@webcomponents/webcomponentsjs ": "^2.6.0", then npm install Import those packages into polyfills.ts file: import

"@webcomponents/custom-elements/src/native-shim"; 

import "@webcomponents/custom-elements/custom-elements.min"; 

Step:4 MicroApp:> ng add ngx-build-plus will generate / Impact package.json -> "ngx-build-plus":

"^9.0.6", "architect": { "build": { "builder": "ngx-build-plus:browser ....


$while building$ "builder": 

"ngx-build-plus:build", .... 

"budget": [ // to avoid

budget Error Delete this part in Micro UI angular.json , ->budget error occurs 

{

"type": "anyComponentStyle", 

"maximumWarning": "10mb", //from 6kb

"maximumError": "10mb" // from10kb } ] 

"serve": { "builder":"@angular-devkit/build-angular:dev-server", ... 

"test": { "builder":"@angular-devkit/build-angular:karma", 

Step:5 MicroApp:> 

app.module.ts import {BrowserModule } from '@angular/platform-browser';

 import { NgModule,Injector,CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';

 import {createCustomElement } from '@angular/elements';

 bootstrap: [], //webcompionentjs will handle it. while develop it will get filled with appComponent , but in build time it must be empty schemas:

[CUSTOM_ELEMENTS_SCHEMA] // entire app as microUI 


export class AppModule { const

elm = createCustomElement(AppComponent, {injector: this.injector});

console.log('cream-index-ui custom element:::',

customElements.get('cream-index-ui')); 

if (!customElements.get('micro-app1')) {

customElements.define('micro-app1', elm); } 

}


 Step:6 build the child app and

generate dist for master/parent app MicroApp:>ng build --configuration production --output-hashing none --single-bundle true --bundle-styles false --keep-styles false 


Notes: --output-hashing none ->will avoid hashing the file

names. --single-bundle true ->will bundle all the compiled files into a single

JS file. --bundle-styles false ->if we follow the same library css for all theprojects 


Step:6.1 Sometimes budget error will occur. so, Make the correction at

MicroApp angular.json "budgets": [ { "type": "initial", "maximumWarning": "2mb",

"maximumError": "5mb" }, { "type": "anyComponentStyle", "maximumWarning": "2mb",

//from 6kb "maximumError": "5mb" // from10kb } ] copy the dist folder into

master app src/app/assets/MicroApp --> which contains Main.js, index.html,3

other files 


Step:6.2 To do the auto copy -> npm install copyfiles -g // in the

machine/ laptop MciroApp:> npm run copfiles : copyfiles ./dist/*.*

MasterAppPath/src/app/assets ALternatively : ng build make output path from MciroApp 


Step:7 To generate mutliple micro app follow the step -> Step 1- 6

Another child app or MciroApp dist Step:8 MasterApp / ContainerApp / ng new

MasterApp Step: 9 ( Repeat Step 2 ) MasterApp:> will generate / Impact

package.json -> "document-register-element": "1.8.1", --> change the version

1.8.1 if not then npm install 


Step:10 MasterApp app.module.ts import { NgModule,

CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; bootstrap: [AppComponent],

schemas: [CUSTOM_ELEMENTS_SCHEMA] // which allows custom element as recognized

element in master App }) 

export class AppModule { } 

Step:11 MastetApp: Whichever the component need to import/use the in that component MasterApp -Requried


component.component.ts 

childAppPath ='http://localhost:4200/assets/MicroApp/main.js'; 

constructor(){ } 

ngOnInit() {

this.loadScript(this.childAppPath);

 }

 loadScript(scriptPath): void { 

 var alreadyLoaded = false; for (var scriptIndex in document.scripts) { //

console.log(scriptPath, scriptIndex, document.scripts[scriptIndex].src) 

if(!alreadyLoaded && scriptPath === document.scripts[scriptIndex].src) {

alreadyLoaded = true; break; }


 } 

 if (!alreadyLoaded) { 

 const content =document.getElementById('content'); 

 const script =document.createElement('script'); 

 script.src = scriptPath;

content.appendChild(script); } 


or load style and script seperatly


loadStyles(stylePath: string): void { 

var alreadyLoaded = false; 

for (var indexin document.styleSheets) { 

if (!alreadyLoaded && stylePath ===document.styleSheets[index].href)

 { alreadyLoaded = true; break; } 

 if(!alreadyLoaded) {

 var head = document.head; 

 var style =document.createElement("link");

 style.type = "text/css"; 

 style.rel ="stylesheet"; head.appendChild(style);

 } 

 } 

 } 

 loadScript(scriptPath: string,tagName: string): void {

 var alreadyLoaded = false; 

 for (var scriptIndex in document.scripts) { 

 if (!alreadyLoaded && scriptPath === document.scripts[scriptIndex].src) {

 alreadyLoaded = true; break; 

 } 

 } if(!alreadyLoaded) { 

 const content = document.getElementById('content');

 const script = document.createElement('script'); 

 script.src = scriptPath;

content.appendChild(script); 

}


Step:12 MasterApp -


<div id="content"><micro-app1></micro-app1></div>

//

<micro-app1>

  is the custom Tag element mentioned at MicroApp app.module.ts MasterApp:> ng

  serve ==> http://localhost:4200 default if chagne the default port Change the

  ChilAppPath/port

</micro-app1>


React + Typescript_ Module federation _ Micro Front end -Standalone and integrated

Module Federation The Module Federation is actually part of Webpack config. This config enables us to expose or receive different parts of t...