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

@edynamix/mf-isolation

v0.0.28

Published

CSS and overlay isolation helpers for eDynamix microfrontends.

Readme

@edynamix/mf-isolation

CSS isolation and Angular overlay helpers for microfrontends.

Install

bun add @edynamix/mf-isolation@latest

Runtime boundary

import { prepareMfBoundary } from '@edynamix/mf-isolation';

const disposeBoundary = prepareMfBoundary(element, {
  scopeId: 'portal',
});

// When the microfrontend unmounts:
disposeBoundary();

Build-time CSS isolation

For global and vendor CSS:

import { scopeRemoteCss } from '@edynamix/mf-isolation/postcss';

export default {
  css: {
    postcss: {
      plugins: [scopeRemoteCss({ scopeId: 'portal' })],
    },
  },
};

The host and remote must use the same scopeId. Both PostCSS plugins keep ordinary global and vendor selectors under the zero-specificity :where([data-edyn-mf="<scope-id>"]) boundary. Document-root descendants such as html body#app button instead use [data-edyn-mf="<scope-id>"] so module overrides retain sufficient cascade strength.

Angular with Vite

Angular applications must use the Angular-aware plugin so component CSS remains compatible with Emulated view encapsulation:

import { scopeAngularRemoteCss } from '@edynamix/mf-isolation/postcss';
import { defineConfig } from 'vite';

export default defineConfig({
  css: {
    postcss: {
      plugins: [scopeAngularRemoteCss({ scopeId: 'portal' })],
    },
  },
});

The Angular-aware plugin uses :host-context([data-edyn-mf="<scope-id>"]) for Emulated .component.css, .component.scss, .component.sass, and .component.less files. Components declaring ViewEncapsulation.None use the global selector behavior described above.

Run this plugin through Vite/PostCSS before Angular AOT compilation. Do not combine it with scopeRemoteCss in the same Vite configuration.

Host contract

The host passes an IMfShellContext to a module's mount function and expects an IMfModuleLifecycle export. Check the host version with isSupportedMfHostContract: it accepts MF_MINIMUM_HOST_CONTRACT_VERSION and every later version, so additive contract changes do not need a lockstep release of the host and all modules.

import { type IMfShellContext, isSupportedMfHostContract, MF_MINIMUM_HOST_CONTRACT_VERSION } from '@edynamix/mf-isolation/host-contract';

export const mount = (element: HTMLElement, shellContext: IMfShellContext): void => {
  if (!isSupportedMfHostContract(shellContext)) {
    throw new Error(`This module requires host contract version ${MF_MINIMUM_HOST_CONTRACT_VERSION} or later.`);
  }
  // ...
};

Navigation manifest

Declare module-owned navigation as validated data and emit navigation.json from the same Vite build:

import {
  defineMfNavigation,
  MF_NAVIGATION_CONTRACT_VERSION,
} from '@edynamix/mf-isolation/navigation';
import { mfNavigationPlugin } from '@edynamix/mf-isolation/vite';
import { defineConfig } from 'vite';

const navigation = defineMfNavigation({
  applicationId: 22,
  contractVersion: MF_NAVIGATION_CONTRACT_VERSION,
  items: [
    {
      id: 'stockmaster.for-sale',
      label: 'For Sale',
      order: 1,
      path: 'usedstock/for-sale',
    },
  ],
  label: 'Stock Master',
  landingPath: 'usedstock',
  moduleId: 'stockmaster',
});

export default defineConfig({
  plugins: [mfNavigationPlugin({ navigation })],
});

Routes are module-relative and contain no leading slash. The plugin validates the complete tree, emits deterministic JSON with a SHA-256 revision during builds, and serves the same manifest with Cache-Control: no-cache in development.

For standalone development, combine the manifest with the host-owned navigation browser entry and development BFF:

import {
  MF_DEVELOPMENT_HOST_NAVIGATION_PATH,
  mfDevelopmentBffPlugin,
} from '@edynamix/mf-isolation/vite-development';

mfDevelopmentBffPlugin({
  hostNavigationUrl: 'https://host.example.test/host-navigation/index.js',
  moduleApiOrigin: 'https://module.example.test',
  moduleApiPath: '/stockmaster/api/',
});

const localHostNavigationUrl = new URL(
  MF_DEVELOPMENT_HOST_NAVIGATION_PATH,
  'https://localhost:4200',
).href;

hostNavigationUrl selects the host environment. Load the browser entry from localHostNavigationUrl; the BFF serves the selected bundle and its host-owned logo assets through the secure local origin, avoiding mixed-content failures when the selected host is an HTTP localhost instance. Host API calls go to the selected host origin, while module API calls go to the explicit moduleApiOrigin. The BFF performs OIDC authorization code with PKCE, retains tokens and module API cookies in the development process, and forwards authenticated API requests without exposing either to module JavaScript. The refresh token is also kept in an HttpOnly __Host-edyn_dev_refresh cookie for the local origin, so one sign-in survives dev server restarts and switching between modules until the token expires. The sign-in service accepts each refresh token once, so requests the browser sent before a restart's rotated cookie reached it share that restore instead of spending the token again and clearing the new cookie. Host API requests keep the browser's navigation selection headers, which the host validates. Module API requests instead carry the selection the host last confirmed, read from /api/navigation responses and navigation stream events, through X-Edyn-Navigation-Group-Id, X-Edyn-Navigation-Dealer-Ids and, for a single dealer, X-Edyn-Navigation-Dealer-Id; module backends must still authorize those values before constructing module-specific session state. Request bodies are forwarded with a fixed length (up to 100 MB), because deployed module front ends reject chunked bodies. Before a module's first API call the BFF asks the host for the current selection, because the local shell starts the module beside the navigation rather than after it. A module's redirect to its legacy /login page leads to the development sign-in while signed out; signed in, it shows that the module API refused the session instead of reloading the module in a loop.

Angular base path in any letter case

IIS serves a module at any letter case of its path, and old bookmarks keep the old case (/AutoPoint/...), but Angular's Location strips the base path only on an exact match. Install the module's LocationStrategy from this package so /AutoPoint/... and /autopoint/... both open the module, and the address keeps the case it was opened with (no redirect). Earlier names of the module's path can be passed as aliases:

import { NgModule } from '@angular/core';
import { provideCaseInsensitiveBasePath } from '@edynamix/mf-isolation/angular';

@NgModule({
  providers: [...provideCaseInsensitiveBasePath(['motcleanse'])],
})
export class AppModule {}

The base is the module's APP_BASE_HREF (or the document's <base>); a root base (/) is left untouched. A host that redirects case variants of a module's path to lowercase can stop doing so once the module installs this strategy.

The strategy also passes the router only the address changes within the module's base and aliases. In a host, Back or Forward to another module changes the address before the module is unmounted; the module's router would otherwise take that address for one of its own and either log NG04002 (no match) or write it back under the module's base (a catch-all or redirect), leaving the page in the wrong module.

Angular CDK overlays

The Angular integration targets Angular/CDK 19.x. In a hosted application, install the complete provider array so global and connected overlays share the module boundary's viewport and coordinate system:

import { provideMfOverlayRoot } from '@edynamix/mf-isolation/angular';

const providers = provideMfOverlayRoot(overlayRoot);

Pass all of providers through the application bootstrap. Calling provideMfOverlayRoot() without an element preserves CDK's normal document-viewport behavior.

Legacy NgModule applications that receive the root through an injection token must install both providers through the token-aware helper:

import { InjectionToken, NgModule } from '@angular/core';
import { provideMfOverlayRootFromToken } from '@edynamix/mf-isolation/angular';

export const MF_OVERLAY_ROOT = new InjectionToken<HTMLElement>('MF_OVERLAY_ROOT');

@NgModule({
  providers: [...provideMfOverlayRootFromToken(MF_OVERLAY_ROOT)],
})
export class AppModule {}

Provide the runtime MF_OVERLAY_ROOT value through the existing platform/bootstrap injector. Do not extract only the first provider from provideMfOverlayRoot(); that installs the container but omits boundary-aware connected positioning.

The Angular entry point requires @angular/core, @angular/common, and @angular/cdk 19.x.

Sharing Angular between modules

Without sharing, every hosted module ships and starts its own copy of Angular. With createMfAngularShared() from @edynamix/mf-isolation/vite, modules on the same versions load Angular, the CDK, Material and RxJS once per page:

import { federation } from '@module-federation/vite';
import { createMfAngularShared } from '@edynamix/mf-isolation/vite';
import packageJson from './package.json' with { type: 'json' };

federation({
  name: 'autopoint',
  filename: 'remoteEntry.js',
  exposes: { './lifecycle': './src/lifecycle.ts' },
  shared: createMfAngularShared(packageJson.dependencies),
});

It shares every script entry point of the module's @angular/* dependencies (read from each installed package's exports), plus rxjs and rxjs/operators, at the module's own version. Entry points that stylesheets also resolve are left out: the @angular/material and @angular/cdk roots answer @use '@angular/material', and Module Federation would answer the stylesheet with a script. @angular/compiler and @angular/platform-browser-dynamic are not shared: only just-in-time compilation needs them, and sharing them would put the compiler into every module's build. The versions must be exact (19.2.25, not ^19.2.25). A module on a different version keeps its own copy instead of mixing two Angular versions on one page. Pass { root } when the Vite config does not run from the module's package directory.

A module that starts first on a page loads its own copy of every shared entry point at start-up, in the batches the federation plugin orders them in, and an entry point reads another one through a stub that is filled only once that one has loaded. An entry point that reads another while it loads (a class extending it, a static field, a call at the top of the module) therefore needs that one to load in an earlier batch, and the plugin orders entry points only by the shared package roots their package depends on. createMfAngularShared() reads each entry point's source and leaves out those that would start with an undefined binding (@angular/material-moment-adapter extends @angular/material/core's DateAdapter, but depends on the unshared @angular/material root; @angular/cdk/listbox provides @angular/forms' NG_VALUE_ACCESSOR, which the CDK does not depend on), together with every shared entry point that imports them, so a module never holds two copies of one entry point. The module bundles its own copy of those, as before sharing. mfAssetManifestPlugin() checks the built output the same way and fails the build if a shared entry point still reads another one before it is sure to have loaded.

A shared Angular means one Angular platform per page. A module bootstrapped with createApplication() reuses that platform already. A module that bootstraps an NgModule replaces platformBrowserDynamic(providers) with createHostedAngularPlatform(providers):

import { createHostedAngularPlatform } from '@edynamix/mf-isolation/angular-platform';

const platform = createHostedAngularPlatform([{ provide: APP_BASE_HREF, useValue: baseHref }]);
const moduleRef = await platform.bootstrapModule(AppModule);
// on unmount
platform.destroy();

Its providers reach only the module's own NgModules, and destroy() destroys only them; the shared platform and the other modules keep running. Calling platformBrowserDynamic(providers) instead would hand the module another module's platform without its providers, and its destroy() would stop every module on the page. The entry point requires @angular/platform-browser-dynamic 19.x.

Host-owned Zone.js

Standalone Angular applications load their own Zone.js runtime as usual:

import 'zone.js';

When the application runs as a hosted lifecycle, the host owns the single Zone.js runtime. Check that runtime before dynamically importing any Angular code:

import { assertHostZoneRuntime } from '@edynamix/mf-isolation/angular-runtime';
import type { IShellContext } from './shell-context';

export const mount = async (host: HTMLElement, context: IShellContext): Promise<void> => {
  assertHostZoneRuntime();

  const lifecycle = await import('./lifecycle-impl');
  return lifecycle.mount(host, context);
};

angular-runtime is framework-free. It verifies that globalThis.Zone is a function; it does not load or modify Zone.js.

Exports

  • @edynamix/mf-isolation
  • @edynamix/mf-isolation/angular
  • @edynamix/mf-isolation/angular-runtime
  • @edynamix/mf-isolation/angular-platform
  • @edynamix/mf-isolation/host-contract
  • @edynamix/mf-isolation/postcss