npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@bluealba-public/pae-ui-react-core

v1.2.1

Published

React utilities for PAE

Downloads

513

Readme

PAE - UI React Core

Quality Gate Status Bugs Code Smells Coverage Duplicated Lines (%) Lines of Code Reliability Rating Security Rating Technical Debt Maintainability Rating Vulnerabilities

Core PAE library for PAE microfrontend applications. It exposes PAE functionality in the form of react elements such as hooks, components, etc. which provide cross-cutting concerns such as Authentication (Impersonation), Authorization, Service Interaction, Monitoring, etc.

Usage

Initialize your Microfrontend

A PAE microfrontend must be created by calling the initiliazeMicroFrontend() function (not although you could directly create a SingleSPA application that will tied you to SPA and you won't be able to use all the functionality of this PAE library).

import React from "react";
import ReactDOMClient from "react-dom/client";
import { initializeMicroFrontend } from "@bluealba-public/pae-ui-react-core";
import Root from "./root.component";

export const { bootstrap, mount, unmount } = initializeMicroFrontend({
  React,
  ReactDOMClient,
  rootComponent: Root,
  errorBoundary(err) {
    console.error(err);
    return (
      <div>
        <h1>Something went wrong</h1>
        <p>{err.message}</p>
      </div>
    );
  }
});

The most important part here is that you specify your rootComponent of your app and a way to handle errors locally.

Utility Functions

navigateTo() utility function

Use this function to navigate to a different url. This function is a wrapper around the single-spa navigateToURL function

import { navigateTo } from '@bluealba-public/pae-ui-react-core';

navigateTo('/path/to/another/module');

Hooks

PAE provides the following hooks to access data and interact with PAE services.

useAuth

Retrieve the current user information as well as a method to check authorization access.

import { useAuth } from '@bluealba-public/pae-ui-react-core';

const TestComponent = () => {
    const { authUser, hasAccess } = useAuth();
    return (
        <div>
            <h1>{authUser.displayName}</h1> {/* e.g: John Doe */}
            <p>{authUser.username}</p> {/* e.g: johndoe */}
            <p>{authUser.familyName}</p> {/* e.g: Doe */}
            <p>{authUser.givenName}</p> {/* e.g: John */}
            <p>{authUser.initials}</p> {/* e.g: JD */}
            <p>{hasAccess('operation1', 'operation2')}</p>
        </div>
    );
};

useCatalog

Use this hook to get access to the current ecosystem applications and modules catalog

import { useCatalog } from '@bluealba-public/pae-ui-react-core';

const TestComponent = () => {
    const { catalog } = useCatalog();
    return (
        <div>
        {catalog.map((item) => (
            <div key={item.name}>
                <h1>{item.name}</h1>
                <p>{item.version}</p>
            </div>
        ))}
        </div>
    );
};

useServiceInvoker

Use this service to make a request to a PAE microservice. This way you don't need to know under which path it is exposed by the system (gateway). And you just need to know the service/module full name. It returns a function that is fully compatible with the fetch function

const invoker = useServiceInvoker("@acme/my-wheather-service");

return (
    <button onClick={() => invoker('/get-temperature', { method: 'GET' })}>Load</button>
)

Internally this hook uses the catalog to resolve the service full URL (there is also a useServiceBaseUrl hook)

useTrackEvent

Use this service to track Application Usage Events. This is the frontend API to PAE Habits Service.

const trackEvent = useTrackEvent();

const trackClickEvent = useCallback(async e => {
    await trackEvent("button-click-app2", {
      buttonText: e.target.innerText
    });
  },
  [trackEvent]
  );

return (
    <button onClick={trackClickEvent}>Click Me</button>
)

The trackEvent() function receives the name of the event and a custom payload that can be anything you want. PAE will automatically store this event with contextual information such as: the current User, the Module that triggered this event and its version and the associated Application (if the module has one), together with timestamps.

Alternatively you can also use useTrackEvents() (plural) hook to publish more than one event in a single call.

Authorization

In order to render a piece of JSX conditionally based on authorization there are two ways to do this.

  • using the useAuth() hook: which is more "manual" but powerful
  • using the <Authorized> component

useAuth()

With this hook you get the current user and a utility function hasAccess()

const MyComponent = () => {
  const { authUser, hasAccess } = useAuth();
  return (
      <div>
        {hasAccess('operation1', 'operation2') && (
            <div>access granted</div>
        )}
      </div>
  )      
}

<Authorized> component (recommended)

This component only renders the children content if the user has the specified operations

const MyComponent = () => {
  return (
      <Authorized operations={['create-entity', 'update-entity']}>
        <SectionContent>
          <p>You have access to this section</p>
        </SectionContent>
      </Authorized>
  )
}

If it has no access then nothing is rendered.

There's also an optional property forbiddenContent with which you can specify the content to be displayed in case the user has no grant on the operations.

This prop is overloaded and you can pass different content.

  • The name of a React component
  • A render function
  • A straight JSX content

For example a render function

// passing a React component
<Authorized operations={['create-entity', 'update-entity']} forbiddenContent={ForbiddenComponent}>
  <SectionContent>
    <p>You have access to this section</p>
  </SectionContent>
</Authorized>

// passing a render prop function
<Authorized operations={['create-entity', 'update-entity']} forbiddenContent={({ operations }) => <ForbiddenComponent operations={operations} />}>
  <SectionContent>
    <p>You have access to this section</p>
  </SectionContent>
</Authorized>

// passing content directly
<Authorized operations={['create-entity', 'update-entity']} forbiddenContent={<div>Section not allowed</div>}>
  <SectionContent>
    <p>You have access to this section</p>
  </SectionContent>
</Authorized>

Extension Points

They allow UI modules to declare points such as React components that can be later replaced by another value/component from another UI module dynamically.

Declaring an extension point

There are two ways for a MF to declare an extension point that can be later extended by other MFs. Internally they both the the same thing, it is just a matter of a different flavor of API.

  • <ExtensionPoint> component: useful to declare an extension point inline within a JSX
  • extensionPoint() HOC (high-order component): useful to make a whole component replaceable as an extension point.

Declaring an extension point inline with ExtensionPoint component

In any component’s JSX you can declare an extension point, like a “slot” to be extended by other MFs.

const MyComponent = () => {
  return (
    <div>
      Hello <ExtensionPoint name="Greeting" />
    </div>
  )
}

This will create an extension point that is scoped to the current module with the given name. For example my-module::Greeting

You can include children elements that will be the fallback/default content in case nobody extends this extension point

const MyComponent = () => {
  return (
    <div>
      Hello <ExtensionPoint name="Greeting"> World </ExtensionPoint>
    </div>
  )
}

Declaring a component as an extension point with the HOC

There’s another common case where you want a whole component to be “replaceable” completely as an extension point. In this case you can use the extensionPoint(component) HOC as a shortcut to void creating a wrapper component just for this.

For example the pae-shell-ui (or any MF) declares a React component to be an “extension point” by simply using a new pae-ui-react-core HOC wrapping the component that is the menu

const MenuApplicationSelector: React.FC = () => {
  // regular react component
}

// the displayName becomes the extension point name by inference (it can be set manual optionally in the hoc call)
MenuApplicationSelector.displayName = 'ApplicationSelector';

// here we are wrapping the component using the HOC which makes it an extension point
export default extensionPoint(MenuApplicationSelector);

If no-one is extending the point the the component will be rendered. If someone extends it then this component won’t be mounted at all, and it will be replaced by the extending component.

Extending an Extension Point

Then another MF can extend that point in two different ways:

  • ExtendExtensionPoint: Using a built-in component rendering the content inline
  • useExtendExtensionPoint: using a hook which allows more programmatic control

We will see both options below:

Using the ExtendExtensionPoint component

With a React Component provided by pae-ui-react-core

// somewhere in your MF DOM
<ExtendExtensionPoint module="@bluealba/pae-shell-ui" point="ApplicationSelector">
  Hello <strong>World</strong> From an Extension!
</ExtendExtensionPoint>

Using the useExtendExtensionPoint hook

Or more "manually" using the useExtendExtensionPoint hook

const Root = () => {
  // register the extension
  useExtendExtensionPoint('@bluealba/pae-shell-ui', 'ApplicationSelector', CustomApplicationMenu)
  
  return <div> your app content here </div>
}

In both cases the extension follows the regular React lifecycle ! Meaning that when the component that called the hook/included the extension component gets unmounted, then the extension point gets un-registered dynamically. Then this means that components are extended / disengaged dynamically.

Library Development

This section is for PAE platform developers or anyone that wants to make changes to this repository. This Library is one of the "client facing" API for PAE. Meaning that it is the API that exposes all PAE behavior to microfrontends for different customers and projects. Because of this we must be very careful with changes in terms of backward compatibility. This is the first layer of integration. Some projects might be using different versions of PAE and this library.

Therefore we should try to reduce the number of non-backwards compatible changes !!

Development Process and Releasing

To make modifications and have them automatically picked while working locally you should run

npm run build-and-publish:watch

Just make sure that you have the pae-orchestrator-service cloned as a sibling folder to this repo.

The following content describes what happens under the hood. The cycle goes like this:

  • Create feature branches to work in a feature
  • Eventually merge them into develop branch. This will build but not publish any package
  • Once you reached a point where you want to publish a new release. Bump the version to the target version you want to publish and create a PR to main
  • Once merged into main the pipeline will automatically build and publish the version to NPMjs public repository.

Now there are 2 more steps to follow in order to start using this published version (next sections)

Update PAE Orchestrator

In the PAE architecture the orchestrator is the one that automatically includes this library in runtime for all applications. It has its own locally cached bundle js of this library. To update it you must

  • Edit the build/libs/build-libs.mjs file in pae-orchestrator-service to update the "shared-libraries" entry with the version you just published
  • Run npm run build:libs in the orchestrator so that it fetches this new version and caches it
  • Merge to develop to create a new build/image of the orchestrator
  • Apply it to any environment / product you want

Projects Microfrontends

Every microfrontend includes this library as a devDependency so that Typescript can use it for type checking. Then once built this is not included, because the orchestrator includes it in runtime (think of it as a dynamically linked library). Nevertheless this means that in order to catch errors and be consistent each microfrontend dependency must match the same version of this lib as the orchestrator has for its specific product.

So once you release a new version of this lib and update the orchestrator to use it. When you bump the orchestrator version for a particular product, you should be going through all your microfrontends and update the devDependency to match the released version.