pict-microapp
v1.0.0
Published
Present a large pict application as a small, focused one: a declarative manifest gates the host's routes, swaps its navigation graph and re-points its landing page, so a micro app reuses every view, provider and style the host already has.
Downloads
241
Readme
pict-microapp
Present a large pict application as a small, focused one.
A micro app is not a fork and not a rebuild: it is the same application, booted the same way, with a smaller surface. You keep every view, provider, template, style and bug fix the host app already has — and an enhancement to the host lands in every micro app on its next build — but the routes, the navigation and the landing page are the micro app's own.
const libHostApplication = require('../../some-big-app/source/Big-Application-Web.js');
const libMicroApp = require('pict-microapp');
class WidgetInspection extends libMicroApp.composeMicroApp(libHostApplication, require('./Widget-MicroApp-Manifest.js'),
{
HostRouterProviderHash: 'BigApplicationRouter',
NavigationGateMethod: '_applyCustomerNavGate',
HostPermittedMethod: '_navItemPermitted',
IsPrivilegedMethod: '_isSuperUser'
}) {}
module.exports = WidgetInspection;
module.exports.default_configuration = libHostApplication.default_configuration;The seam
A pict application registers every route it owns through one funnel: PictRouter.addRoute. That is
true of the application's own routes, of routes contributed by modules like pict-section-workspace,
and of the wildcard record-set routes from pict-section-recordset.
pict-microapp wraps that funnel with an allow list. The host's registration code runs completely
unmodified — it just finds that some of what it registered did not stick. Nothing else about the
boot sequence changes, which is what keeps a micro app cheap to build and cheap to keep working as
the host evolves.
Three seams, in order of how much they matter:
| Seam | What it does |
| --- | --- |
| gateRouter() | Wraps addRoute with the manifest's allow / deny / guard decision |
| applyNavigationGraph() / applyNavigationGate() | Swaps the navigation graph and its visibility filter |
| applyDefaultRoute() | Re-points the landing route |
What this does not do
It does not make the JavaScript bundle smaller. The host's module graph is still bundled whole, because the micro app extends the host's application class and that class requires everything. A micro app slims the app the user sees, not the bytes the browser downloads.
That is usually the right trade. The alternative — composing an app out of deep subpath requires into the host — genuinely shrinks the bundle, but it means reimplementing the host's boot sequence and re-deciding it every time the host changes, which is exactly the coupling a micro app is trying to avoid. Reach for it when download size becomes a real constraint, not before.
The manifest
Everything a micro app declares lives in one plain object.
module.exports =
{
Name: 'Widget Inspection',
Hash: 'WidgetInspection',
// Where the app lands, and where a blocked route bounces to.
DefaultRoute: '/WidgetDashboard',
Routes:
{
// Route patterns exactly as the host registers them. A trailing '*' is a prefix glob.
// An EMPTY list exposes everything — useful when you only want different navigation.
Allow:
[
'/WidgetDashboard',
'/WidgetDashboard/:View',
'/Widget/Workspace/:ID',
'/Widget/Workspace/:ID/:Tab',
'/Gadget/*'
],
// Removed even when Allow would admit them. Deny wins.
Deny: [],
// Wildcard record-set routes ('/PSRS/:RecordSet/List') are registered ONCE by the host and
// fan out over every entity it knows, so they cannot be refused per-entity at registration
// time. They are GUARDED instead: the route registers, but its handler bounces to Fallback
// when the resolved entity is not listed here. An empty list exposes every entity.
EntityParameter: 'RecordSet',
Entities: [ 'Widget', 'Gadget' ],
// A hash matching no surviving route lands on Fallback rather than on a blank page.
CatchUnmatched: true,
Fallback: '',
// Always admitted, whatever Allow says — the boot and logout plumbing.
AlwaysAllow: [ '/', '/Logout' ]
},
Navigation:
{
// The graph this micro app renders, in the host navigation module's own shape.
Sections: [ /* … */ ],
// Keep the host's capability / module / session gates …
HonorHostGates: true,
// … but drop its audience short-circuit. See below.
SuperUserSeesEverything: true
},
Branding: { Title: 'Widget Inspection' }
};Why SuperUserSeesEverything defaults to true
A host that serves many audiences usually trims its own large graph hard. The platform application this module was extracted from, for example, shows platform super-users only the handful of cross-customer destinations, because the full menu is meaningless without a customer context.
Applied to a micro app graph — which is already the slimmed surface — that trim empties the menu completely, for exactly the staff building and demoing the app. So the default keeps the host's real gates (session, module, capability) and drops only the audience short-circuit. A privileged session holds no entitlements in a customer's own tenancy, so it skips the capability test too; otherwise the menu blanks a second way the moment the capability map finishes loading.
Compose options
composeMicroApp(pHostApplicationClass, pManifest, pComposeOptions) binds the manifest to one host's
naming. Every option is a service hash or a method name on the host.
| Option | Default | Meaning |
| --- | --- | --- |
| ProviderHash | 'MicroApp' | Where the provider registers on the host's pict instance |
| RouterProviderHash | 'PictRouter' | The pict-router provider — the route funnel |
| NavigationProviderHash | 'Pict-Navigation' | The pict-section-navigation provider |
| HostRouterProviderHash | RouterProviderHash | The provider that owns defaultRoute (often an application router wrapping pict-router) |
| AttachOn | 'onAfterInitializeAsync' | The lifecycle hook the gate installs from |
| NavigationGateMethod | '' | Host method that installs the nav filter; overridden when named |
| HostPermittedMethod | '' | Host method answering "may this session see this item?" |
| IsPrivilegedMethod | '' | Host method answering "is this a privileged platform session?" |
Getting AttachOn right
The gate must be installed after the router provider exists and before the host registers its
routes. In a typical pict application the router provider is created during onInitializeAsync
and routes are registered during the login / data-load cycle, which makes onAfterInitializeAsync
the correct hook — the composed override runs the gate, then calls the host's implementation.
If routes come up ungated, the hook fired too late. MicroApp.routeTally tells you immediately:
_Pict.providers.MicroApp.routeTally;
// { Name: 'Widget Inspection', Allowed: 24, Guarded: 7, Blocked: 96 }
_Pict.providers.MicroApp.blockedRoutes; // every pattern that did not surviveblockedRoutes is the first place to look when a screen is missing: a route you expected to keep
but spelled differently than the host registers it shows up there.
Sharing the host's DOM shell
Reused views render into the host's container addresses, which are baked into each view's
configuration. A micro app's index.html therefore has to reproduce the host's DOM contract —
the application container id, the loading-splash element, the dynamic-CSS <style> tag, and any
wrapper class the host's layout modes toggle. Only the chrome around that contract (the top bar,
the branding, the panels) is the micro app's to redesign.
This is the one place a micro app is genuinely coupled to its host. Keep the host's ids; change the chrome.
Installation
npm install pict-microappLicense
MIT
