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

ng-weld

v0.2.1

Published

Weld Angular Module Federation remotes to a host shell: one typed contract for declaring embeddable components, emitted as a descriptor JSON the host consumes. Angular micro-frontend embed manifest.

Downloads

77

Readme

ng-weld

Weld Angular Module Federation remotes to a host shell. One typed contract for declaring embeddable components, emitted as a descriptor JSON the host consumes — so no team has to read another team's source to wire it up.

When several teams each ship an Angular Module Federation remote, every team invents its own way to expose a component or page for embedding: different window config keys, different custom-element tags, different bootstrap boilerplate. A host shell then has to learn each remote's quirks by hand.

ng-weld gives every remote one typed way to declare what it exposes, and produces one predictable embed-descriptor.json that any host can read — no more reading each remote's source to wire it up.

remote's embed.config.mjs ──defineEmbed()──▶  validated contract
                                                     │
                                            serializeDescriptor()
                                                     │
                                                     ▼
                                          embed-descriptor.json  ──▶  host shell

Install

npm install ng-weld

Quickstart for a remote (producer)

A remote ends up with two files — one config, one element file — plus a one-line change to an existing file and a one-line change to package.json. The steps below are in the order you'll actually do them: write the element before you declare it, so the config never describes something that doesn't exist yet.

Prerequisite: a native-federation Angular remote with a web-component expose key (e.g. './WebComponent') in federation.config.js. Note the file that key points at — call it your entry file; you'll re-export through it in step 4.

1. Install

pnpm add -D ng-weld

2. Write the element

For a partial element — one feature component exposed on its own, with no routing — definePartialElement() (from ng-weld/runtime) removes the boilerplate:

// partial-elements.ts
import { provideHttpClient } from '@angular/common/http';
import { definePartialElement } from 'ng-weld/runtime';
import { ReportsTableComponent } from './features/reports-table.component';

export const defineRemoteReportsTableElement = definePartialElement({
  elementTag: 'remote-reports-table-element',
  component: ReportsTableComponent,
  providers: [provideHttpClient()],
});

That's the whole element. The helper handles the idempotency guard, createApplication(), createCustomElement(), and customElements.define() — and returns the idempotent async function the host calls as defineExport.

Why partial elements matter: each one boots its own Angular application, so it shares no Router and no global state with any other element. That's what lets a host place several widgets from the same remote on one page. A whole-page element that owns routing can only be mounted once per page — two of them fight over the browser URL. For that reason definePartialElement() rejects routing providers with a clear error rather than letting the collision reach runtime. If an element genuinely needs its own router, it's a whole-page element: write it by hand (see createApplication + a silent LocationStrategy — no ng-weld helper for this yet).

ensureRemoteStyles() is also available for injecting the remote's stylesheet when it's embedded.

Import path matters. ng-weld (main) has zero dependencies and is safe to import from a Node build script — that's why embed.config.mjs (step 4) uses it. ng-weld/runtime needs Angular as a peer dependency and is browser-only. Never import ng-weld/runtime from your config.

3. Re-export it through your entry file

A host Connection stores a single exposedModule, so every element you offer must be reachable through that one key — otherwise the host would need a separate connection per file. Add this to the entry file from the Prerequisite:

// remote-entry.element.ts  ← the file './WebComponent' points at
export * from './partial-elements';

4. Declare the contract — embed.config.mjs (repo root)

Author it as pure data (no Angular imports) so the emit runs in plain node with no risk of a browser-only import crashing the build:

// embed.config.mjs
import { defineEmbed } from 'ng-weld';

export default defineEmbed({
  provider: 'ANGULAR_COMPONENT',
  exposedModule: './WebComponent',
  windowConfigKey: '__remoteReportsConfig',
  navigateEventName: 'remoteReports:navigate',
  name: 'Reports',
  defaultWindowConfig: { basePath: '/reports-widget', mfeName: 'remoteReports' },
  elements: [
    {
      elementTag: 'remote-reports-table-element',   // must match step 2, exactly
      description: 'Reports table, embedded standalone.',
    },
    // A remote exposing more elements just adds more entries here.
  ],
});

defineEmbed() validates as you write — hyphen in elementTag, __ prefix on windowConfigKey, known provider, unique tags — throwing EmbedConfigError on any violation. It does not check that elementTag matches something you actually exported in step 2 — see the drift warning below.

⚠️ Step 2 and step 4 must agree, and nothing checks that for you. ng-weld emit (step 6) reads only this config — it never inspects your TypeScript. defineExport is derived from elementTag (remote-reports-table-elementdefineRemoteReportsTableElement); if your step-2 export doesn't use that exact name, or you delete/rename it later without updating this array, the descriptor will keep advertising an element that doesn't exist. The host only discovers this at runtime, with "Remote did not export function …". Re-read steps 2 and 4 side by side before you ship.

5. Wire the emit into your build

ng-weld ships the emitter as a CLI, so there's no script to copy into your repo. Run it before ng build:

{
  "scripts": {
    "start":      "ng-weld emit && ng serve",
    "build":      "ng-weld emit && ng build",
    "build:prod": "ng-weld emit && ng build --configuration production",
    "emit:descriptor": "ng-weld emit"
  }
}

ng-weld emit reads embed.config.mjs and writes public/embed-descriptor.json. Emitting into public/ is what makes the descriptor available in both ng serve (dev) and ng build (prod — Angular copies public/ into the output, next to remoteEntry.json).

ng-weld emit                          # embed.config.mjs → public/embed-descriptor.json
ng-weld emit --config src/embed.mjs   # custom config path
ng-weld emit --out dist/app/browser   # custom output directory
ng-weld emit --silent                 # no success logging

The config is re-validated on emit, so a malformed contract fails your build instead of shipping a broken descriptor. (This re-validates the shape — it still can't catch the step 2/4 drift above, since it has no way to inspect your compiled code.)

6. Emit & verify

pnpm emit:descriptor        # or: pnpm build

Open it in a browser — it should sit right next to remoteEntry.json:

http://localhost:4204/embed-descriptor.json

The emitted JSON:

{
  "schemaVersion": 1,
  "provider": "ANGULAR_COMPONENT",
  "exposedModule": "./WebComponent",
  "windowConfigKey": "__remoteReportsConfig",
  "navigateEventName": "remoteReports:navigate",
  "defaultWindowConfig": { "basePath": "/reports-widget", "mfeName": "remoteReports" },
  "name": "Reports",
  "elements": [
    {
      "elementTag": "remote-reports-table-element",
      "defineExport": "defineRemoteReportsTableElement",
      "description": "Reports table, embedded standalone."
    }
  ]
}

Check that elementTag and defineExport here match your step-2 code exactly — this is the fastest place to catch the drift from step 4 before it reaches a host.

7. Build

pnpm build

8. Done

The host shell now fetches your descriptor from the Remote Entry URL and auto-fills its Connection

  • DataSource forms. The remote team does nothing else.

CORS: the descriptor is fetched cross-origin, so your remote must send CORS headers — the same requirement remoteEntry.json already meets.

Consuming the descriptor (host side)

The same package validates the descriptor on the host. Instead of hand-parsing the fetched JSON, call parseDescriptor() — it checks schemaVersion, validates the shape, and hands back a typed EmbedDescriptor, so a malformed or newer-than-supported descriptor fails loudly instead of rendering a broken widget:

import { parseDescriptor } from 'ng-weld';

const res = await fetch(`${remoteEntryUrl.replace('remoteEntry.json', 'embed-descriptor.json')}`);
const descriptor = parseDescriptor(await res.text());

// descriptor.exposedModule, .windowConfigKey, .navigateEventName, .defaultWindowConfig
//   → fill the host Connection form
// descriptor.elements[] (each .elementTag + .defineExport + .acceptsRoutes)
//   → one candidate DataSource per element

API

ng-weld — no dependencies, safe in Node

| Export | Side | Description | | --- | --- | --- | | defineEmbed(config) | producer | Validate and return a remote's embed contract. Throws EmbedConfigError. | | toDescriptor(config) | producer | Project a config into the plain-object descriptor. | | serializeDescriptor(config) | producer | Pretty-printed JSON string, ready to write to disk. | | parseDescriptor(json) | consumer | Validate + type a fetched descriptor. Throws DescriptorParseError. | | deriveDefineExport(tag) | — | The deterministic define<Pascal> name for an element tag. | | SCHEMA_VERSION | — | Current descriptor schema version. |

ng-weld/runtime — browser only, Angular peer dependency

| Export | Description | | --- | --- | | definePartialElement(opts) | Define a routing-free element with its own isolated application. Returns the idempotent defineExport function. | | ensureRemoteStyles(opts?) | Inject the remote's stylesheet once per document (handles hashed filenames and @layer stripping). |

CLI

| Command | Description | | --- | --- | | ng-weld emit | Read embed.config.mjs, write public/embed-descriptor.json. Supports --config, --out, --silent. |

Full TypeScript types (EmbedConfig, EmbedDescriptor, …) are shipped.

What ng-weld does and does not guarantee

  • The descriptor shape — every remote emits the same envelope, and parseDescriptor enforces it on the host.
  • The defineExport name — derived deterministically from the tag.
  • Correct partial-element behavior — when you build one with definePartialElement(), the idempotency guard and application isolation are handled for you, and routing providers are rejected outright.
  • ⚠️ Not that the declared elements actually exist. ng-weld emit reads only embed.config.mjs — it never inspects your TypeScript. If your config declares an element you never exported, the descriptor advertises it anyway and the host fails at runtime with "Remote did not export function …". Keep the config and your element file in sync. Closing this gap — generating the element code from the same config — is the remaining roadmap item.

Security note

The defaultWindowConfig and any per-connection config end up on window[windowConfigKey], readable by any co-tenant of the host shell. Put only configuration there — never API keys, tokens, or user identifiers.

Roadmap

  • v0.1 — typed defineEmbed() contract + descriptor emit + host-side parseDescriptor().
  • v0.2 (current)ng-weld emit CLI (no more copied emit script) and ng-weld/runtime's definePartialElement() for multi-element remotes.
  • next — generate the element code from embed.config.mjs so the config and the implementation cannot drift, and extend the same treatment to whole-page (routed) elements.

License

MIT © irsyadali1