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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@novx/portal

v0.4.3

Published

Portal library

Readme

Portal

Introduction

Portal implements a react portal framework supporting microfrontends vastly simplifying implementation efforts since a number of technical challenges every application has to solve are already part of the framework.

  • integration of a DI solution
  • centralized error handling ( including error boundaries )
  • session handling
  • i18n integration
  • meta-data based approach that allows for
    • filtering of available features according to authentication, authorization or other aspects ( e.g. feature flags )
    • automatic router configuration according to the metadata
    • dynamic navigation features that are based on the meta-data and custom rules
    • feature outlets that cover both local and federated components and allow for custom async preloading logic ( e.g. i18n loading )
  • custom application configurations with support for both client and server side logic

While the framework supports enterprise portals with dynamic microfrontends - and server side configuration mechanisms - as one extreme it also covers small local only applications without significant coding and rampup overhead, making it a one-size-fits-all framework.

The main idea for most of the mechanisms is that modules expose meta-data of "what is inside", by annotating available "features" ( named components used internally or part of the routing ) with special decorators that can be parsed and extracted.

@Feature({
  id: 'public-navigation',
  label: 'Navigation',
  visibility: ["public"],
  features: ["feature-a"],
  permissions: []
  tags: ['portal'],
  path: '/'
})
export class PublicNavigationFeature extends React.Component {
    ...
}

A parser - as part of the build - will locate those features and generate a manifest.json.

{
  "id": "shell",
  "label": "Shell",
  "version": "1.0.0",
  "moduleName": "ApplicationModule",
  "sourceFile": "apps/shell/src/main.tsx",
  "description": "Shell",
  "features": [
     {
      "id": "public-navigation",
      "label": "Navigation",
      "path": "",
      "visibility": [
        "public"
      ],
      "tags": [
        "portal"
      ],
      "features": [
        "feature-a"
      ],
      "permissions": [
      ],
      "component": "PublicNavigationFeature",
      "sourceFile": "apps/shell/src/PublicNavigation.tsx",
      "children": []
    },
    ...
  ],
  ...
}

which can either be used locally or registered via a server component.

Let's see how to boot an application. First thing we need to do is to setup the di container and add a couple of instances inside of the main "module"


import manifest from './manifest.json';

@Module({
  id: 'shell',
  label: 'Shell',
  version: '1.0.0',
  description: 'Shell',
  name: '',
})
export class ApplicationModule extends AbstractModule {
  @create()
  createSessionManager() : SessionManager<any,any> {
    return new SessionManager(new DummyAuthenticationService()); // for now, would be OIDC in reality
  }

  @create()
  createDeploymentManager(featureRegistry: FeatureRegistry) : DeploymentManager {
      return new DeploymentManager(
        featureRegistry: featureRegistry,
        loader: new EmptyDeploymentLoader(), // only local, so far 
        localManifest: manifest as Manifest // that's the local generated manifest.json
      );
  }

  // lifecycle

  @onRunning()
  async onRunning(featureRegistry: FeatureRegistry, deploymentManager: DeploymentManager, sessionManager: SessionManager<any,any>, routerManager: RouterManager) {
      // load deployment

      await deploymentManager.loadDeployment({
          application: "portal",
          client: deploymentManager.clientInfo(),
      });

      // the root if the router will be a feature with tag "portal" and the correct visibility

      routerManager.setRoot(featureRegistry.finder()
        .withTag('portal')
        .matchingSession(sessionManager.hasSession()) // we typically define two separate root pages, a public and a private!
        .findOne());

      // restore existing session
        
      await sessionManager.init();
    }
}

// create environment

export const createEnvironment = async () : Promise<Environment> => {
    const environment = new Environment({module: ApplicationModule})

    await environment.start()

    return environment
}

The crucial parts are

  • the DeploymentManager which is responsible to compute a merged manifest.json. Since we are still completely local, it will only return the local manifest.json
  • A FeatureRegistry collects all features and will be filled with the gathered information of the deployment manager. Since it knows about all registered components - local or remote - a <FeatureOutlet> component is now available that renders any registered feature by name, which is the basis for a number of mechanisms.
  • The RoutingManager will compute dynamic routes based on the provided features and a handpicked "root" feature The routing logic will simply pick all features that have a defined "path" and add them as children - inserting a feature outlet - to the desired root feature, which in this case has a defined tag "portal" and has a visibility property that matches the current session state.

Launching the application is now just a couple lines of code.

const environment = await createEnvironment();

const root = createRoot(document.getElementById('root')!);
root.render(
    <EnvironmentContext.Provider value={environment}>
      <App />
    </EnvironmentContext.Provider>
    );

while the application

export class App extends React.Component {
    routerManager!: RouterManager;

    static contextType = EnvironmentContext

    declare context: Environment

    async componentDidMount() { 
        this.routerManager = this.context.get(RouterManager);
    }

    override render() {
        return (
            this.routerManager.renderRouter()
        );
    }
}

simply delegates to rendering to the defined routes. So what is the root? Well, a specific feature which acts as the main page typically offering navigation possibilities ( as a side bar ).

The interesting part, is that since we already have the complete meta-data available, we dont need to hardcode the navigation entries anymore, but can rely on a couple of conventions to list the available routes.

Example:

const features = featureRegistry
    .finder()
    .withPath()
    .withoutParent()
    .matchingSession(sessionManager.hasSession())
    .withTag('menu')
    .find();

In this case all features, that have the corresponding visibility status matching the session state and have a tag "menu", will be listed as corresponding <Link>s.

Isn't that awsome?

Ok, but we promised microfrontends as well, were are they?

Federated modules will take a similar approach, by defining a root module, exposing a manifest.json, etc. The main application will only need to change the corresponding deployment loader to integrate it.

  return new RemoteDeploymentLoader([
        { name: 'microfrontend', url: 'http://localhost:3001' },
      ]);

In this case, the manifest is fetched dynamically from the known url and merged with the local manifest.

This is good enough for a local environment used for development purposes, in production the logic would be handed over to a server component that is aware of different microfrontends and configurations also including more sophisticated logic to filter features according to feature flags, etc.

Showcase

A showcase app shows a shell and a microfreontend.

API Docs

The complete APIs can be found here