@equinor/fusion-framework-plugin-context-navigation

中文文档
1.1.1发布于 2026/11/12TypeScriptES Module零依赖近 30 天下载 6725

Adapter-based context-to-URL reconciler for Fusion Framework portals

原始使用文档
文档目录21 节
  1. When to Use
  2. For App Developers
  3. Declare a routing strategy
  4. Custom URL shapes
  5. For Portal Developers
  6. Key Concepts
  7. How It Works
  8. Installation
  9. Quick Start
  10. App-portal (app switches lead)
  11. Context-portal (context changes lead)
  12. Built-in Adapters
  13. Registering a Custom Adapter
  14. Configuration
  15. Events
  16. Skip Reasons
  17. Intercepting Navigation
  18. Adapter Interface
  19. Exports
  20. Peer Dependencies
  21. See Also

Context Navigation Plugin (@equinor/fusion-framework-plugin-context-navigation)

Event-driven context-to-URL reconciler plugin for Fusion Framework portals. It keeps the active context synchronized with the browser URL by reacting to context changes and app switches, encoding context into the URL, and optionally guarding against manual URL edits that drop context.

When to Use

Use this plugin when your portal needs to:

  • Reflect the active context (project, facility, etc.) in the browser URL automatically
  • Support multiple URL encoding strategies (path segment, query parameter, or app-defined custom encoding)
  • Preserve context across app switches and page reloads
  • Prevent users from accidentally losing context by editing the URL manually

[!NOTE] This plugin is intended for portal hosts, not individual applications. Applications declare their preferred routing strategy via the app manifest's build.options.contextRouting field — the portal's context-navigation plugin picks up that declaration and applies the correct URL encoding automatically.

For App Developers

You don't need to install or configure this plugin. The portal handles it. Your only responsibility is to declare how context should appear in your URL.

Declare a routing strategy

Set contextRouting in your app manifest's build.options:

// app.manifest.config.ts
export default defineAppManifest((env) => ({
  // ...
  build: {
    options: {
      contextRouting: 'query',  // or 'path', or omit for default path behavior
    },
  },
}));
ValueURL ShapeWhen to Use
'path' (or omitted)/apps/{appKey}/{contextId}/sub-routeDefault — simple apps without complex routing
'query'/apps/{appKey}/route?$contextId={id}Apps with path-based sub-routes that conflict with a context segment

Apps with custom URL shapes should omit contextRouting and register custom hooks instead (see below).

[!NOTE] When contextRouting is not set (or set to null), the portal defaults to the path adapter which encodes context as a path segment after the app key. If the app registers custom hooks (setContextPathExtractor / setContextPathGenerator), the custom adapter takes priority over the path adapter for path/default routing. An explicit contextRouting: 'query' selects the query adapter even when custom path hooks are registered.

Custom URL shapes

If your app encodes context in a non-standard position (e.g. /route-a/{contextId} instead of /{contextId}/route-a), register custom hooks in your app's context configuration:

// app config
builder.setContextPathExtractor((pathname) => {
  // Extract context id from your custom URL position
  const segments = pathname.split('/').filter(Boolean);
  return segments[1]; // e.g. /route-a/{contextId} → contextId
});

builder.setContextPathGenerator((context, pathname) => {
  // Generate a URL with context in your custom position
  const segments = pathname.split('/').filter(Boolean);
  const route = segments[0] ?? '';
  return `/${route}/${context.id}`;
});

When these hooks are registered, the plugin's custom adapter picks them up automatically — no manifest contextRouting declaration needed.

The app owns its URL shape, so the URL is the source of truth for the URL guard:

  • A route where the extractor returns no context (e.g. a list page such as /categories) is a route without context. The URL guard does not put the active context back into it.
  • A route where the extractor returns a context that is not active (e.g. a link from a list page to /app/{key}) applies that context, in both push and replace mode and also when no context is active.
  • Clearing context while on a route without context keeps the user there. Clearing context on a route that has context navigates to the app root.
  • Selecting a context while on a route without context still navigates to the path from generatePathFromContext.

The extracted value does not have to be the context id. If it is a key or slug, the URL guard treats the URL as in sync when generatePathFromContext writes that same value for the active context. So navigating within the same context does not resolve it again. A generator that returns the path unchanged doesn't prove anything, so the guard resolves the value from the URL instead. Repeated keys in unrelated route segments do not trigger another lookup.

When the URL guard resolves a context from the URL, the lookup belongs to that URL. If the user navigates elsewhere before the lookup finishes, the guard cancels it: a late result neither changes the context nor redirects the user. App dismissal, app switches, and module loading also cancel pending lookups, even before the next navigation event or when the destination has no context module.

For Portal Developers

The rest of this README covers portal-level setup and configuration.

Key Concepts

ConceptDescription
AdapterA self-selecting URL encoder/decoder. Each adapter declares canHandle() and provides encode() / decode() methods.
ReconcilerThe reactive loop that watches context + app changes and triggers navigation when the URL is out of sync.
URL GuardAn optional secondary subscription that re-applies context encoding if an external navigation drops the context from the URL.
SourceThe observable factory that drives the reconciler — determines whether app switches or context changes take priority.

How It Works

  1. Observe — The reconciler watches the current app and current context via a configurable source factory.
  2. Resolve adapter — For each app, the first adapter whose canHandle() returns true is selected.
  3. Encode — The selected adapter encodes the context into a target URL.
  4. Compare — If the target URL differs from the current URL, navigation proceeds.
  5. Dispatch — A cancelable onContextNavigationNavigate event fires. Listeners can call preventDefault() to abort.
  6. Navigate — The framework navigation module performs the URL update.
  7. Confirm — An onContextNavigationNavigated event fires after navigation completes.

Installation

pnpm add @equinor/fusion-framework-plugin-context-navigation

Quick Start

App-portal (app switches lead)

An app-portal shows one app at a time. When the user switches apps, the reconciler picks up the new app's context and encodes it into the URL. This is the default behavior.

import { enableContextNavigation } from '@equinor/fusion-framework-plugin-context-navigation';

export const configure = (configurator) => {
  enableContextNavigation(configurator, (builder) => {
    builder.setPortalName('app-portal');
    builder.setDebug(true);
  });
};

The default source factory (createAppFirstSource) watches app.current$ and resolves the app's context modules before emitting to the reconciler. When an app switch happens, the new app's active context drives the URL update.

Context-portal (context changes lead)

A context-portal is organized around a shared context (e.g. a project). When the user selects a context, navigation updates the URL immediately — regardless of which app is active. Clearing context navigates back to the portal root.

import { enableContextNavigation } from '@equinor/fusion-framework-plugin-context-navigation';
import { createContextFirstSource } from '@equinor/fusion-framework-plugin-context-navigation/sources';

export const configure = (configurator) => {
  enableContextNavigation(configurator, (builder) => {
    builder.setPortalName('context-portal');
    builder.setSourceFactory(createContextFirstSource());
    builder.setNullContextUrl('/');
    builder.setDebug(true);
  });
};

Key differences from app-portal:

  • createContextFirstSource() — context changes are the primary trigger; app modules are resolved as a dependency.
  • setNullContextUrl('/') — when context is cleared, navigate to the portal landing page instead of delegating to the adapter.

Built-in Adapters

The plugin ships with three adapters evaluated in priority order:

AdapterURL ShapeWhen Selected
customApp-defined (via generatePathFromContext / extractContextIdFromPath hooks)App provides custom hooks and does not declare contextRouting: 'query'
query/apps/{appKey}/route?$contextId={id}App manifest declares build.options.contextRouting: 'query'
path/apps/{appKey}/{contextId}/sub-routeDefault fallback — matches when contextRouting is 'path', null, or omitted

When no custom adapters are registered, all three built-in adapters are available. The first whose canHandle() returns true for the current app wins.

Registering a Custom Adapter

You can register your own adapters for URL shapes not covered by the built-in set. The canHandle predicate controls when your adapter is selected — use any signal available in AdapterResolutionContext (app key, URL, context provider, routing strategy).

import type { ContextNavigationAdapter } from '@equinor/fusion-framework-plugin-context-navigation/adapters';

const hashAdapter: ContextNavigationAdapter = {
  id: 'hash',
  // Select this adapter for a specific app that uses hash-based context
  canHandle: ({ appKey }) => appKey === 'my-hash-app',
  encode: ({ context, currentURL }) => {
    const url = new URL(currentURL.href);
    url.hash = context ? `#ctx=${context.id}` : '';
    return url;
  },
  decode: (url) => {
    const match = url.hash.match(/^#ctx=(.+)$/);
    return match?.[1] ?? null;
  },
};

enableContextNavigation(configurator, (builder) => {
  builder.registerAdapter(hashAdapter);
  // NOTE: registering any adapter disables built-in defaults.
  // Register all adapters you need explicitly.
});

Configuration

Builder MethodDefaultDescription
registerAdapter(adapter)Built-in setRegister a navigation adapter (object or factory). First match wins.
setPortalName(name)'Portal'Name used in debug log output.
setOrigin(origin)window.location.originOrigin for constructing absolute URLs.
setUrlGuard(enabled)trueRe-sync context if an external navigation drops it from the URL. With replace: false, back/forward navigations update context from the URL instead of overwriting it. Apps with custom path hooks always have the URL treated as the source of truth (see Custom URL shapes).
setDebug(enabled)falseEnable verbose console.debug output.
setNullContextUrl(urlOrFn)—Function (or static string) that returns the URL to navigate to when context is cleared. Receives { appKey, currentURL }.
setRequireValidContext(enabled)falseValidate the context against the app's context module (validateContext) before navigating. If validation fails, navigation is skipped.
setNavigationOptions(options){ replace: true }Options passed to navigation.navigate() during URL updates. Set { replace: false } to push history entries — back/forward will then sync context from the URL rather than re-asserting the active context.
setOnTransition(fn)—Side-effect hook called after each successful navigation.
setSourceFactory(factory)createAppFirstSource()Observable source factory that drives the reconciler.

Events

The plugin dispatches events through the Fusion Framework event system. Subscribe via framework.event.addEventListener().

EventWhenCancelable
onContextNavigationNavigateBefore navigation — adapter resolved, target URL computedYes
onContextNavigationNavigatedAfter navigation completesNo
onContextNavigationAdapterResolvedWhen an adapter is selected for an appNo
onContextNavigationSkippedWhen reconciliation decides NOT to navigateNo

Skip Reasons

The onContextNavigationSkipped event includes a reason field:

ReasonMeaning
'url-matches'Target URL already matches the current URL
'no-context'Context is undefined (still initializing)
'no-adapter'No adapter could handle the current app
'invalid-app-context'setRequireValidContext(true) is set and the app's context module rejected the context
'encode-returned-null'Adapter's encode() returned null
'canceled'A listener called preventDefault() on the navigate event

Intercepting Navigation

framework.event.addEventListener('onContextNavigationNavigate', (event) => {
  console.log('About to navigate:', event.detail.targetURL.pathname);

  // Cancel navigation conditionally
  if (shouldBlock(event.detail)) {
    event.preventDefault();
  }
});

Adapter Interface

interface ContextNavigationAdapter {
  /** Unique identifier for logging and diagnostics. */
  id: string;
  /** Return `true` if this adapter handles the given app/URL combination. */
  canHandle(ctx: AdapterResolutionContext): boolean;
  /** Encode context into a URL. Return `null` to skip navigation. */
  encode(args: { context: ContextItem | null; currentURL: URL }): URL | null;
  /** Decode a context ID from a URL. Return `null` if not present. */
  decode(url: URL): string | null;
}

interface AdapterResolutionContext {
  appKey: string;
  appContext: IContextProvider;
  routingStrategy?: 'path' | 'query' | null;
  currentURL: URL;
}

Exports

SpecifierDescription
@equinor/fusion-framework-plugin-context-navigationPlugin runtime, configurator, enable helper, types, events
@equinor/fusion-framework-plugin-context-navigation/adaptersBuilt-in adapter factories (createPathAdapter, createQueryAdapter, createCustomAdapter)
@equinor/fusion-framework-plugin-context-navigation/sourcesSource factories (createAppFirstSource, createContextFirstSource) and reconciler types
@equinor/fusion-framework-plugin-context-navigation/utilsURL utility functions

Peer Dependencies

PackagePurpose
@equinor/fusion-framework-moduleBase module contract
@equinor/fusion-framework-module-appApp switching and instance loading
@equinor/fusion-framework-module-contextContext state management
@equinor/fusion-framework-module-navigationURL navigation
@equinor/fusion-framework-module-eventEvent dispatch
rxjsReactive streams

See Also

版本与发布记录

@equinor/fusion-framework-plugin-context-navigation 共发布 17 个版本,首次发布于 2026/08/06;过去 12 个月发布了 17 个版本。

版本发布日期距今
1.1.1latest2026/11/121 天前
1.1.1-next.1next2026/11/112 天前
1.1.1-next.02026/10/2816 天前
1.1.02026/10/2717 天前
1.0.62026/10/1727 天前
1.0.52026/10/1629 天前

其他发布通道:trusted-publishing-20261015 → 1.0.5-trusted-publishing-20261015-9dfe7602cd2a49a8a9df00d62c26e7304a82a85a,next → 1.1.1-next.1。

关于 @equinor/fusion-framework-plugin-context-navigation 的常见问题

@equinor/fusion-framework-plugin-context-navigation 是什么?

Adapter-based context-to-URL reconciler for Fusion Framework portals

如何安装 @equinor/fusion-framework-plugin-context-navigation?

在项目目录运行 npm install @equinor/fusion-framework-plugin-context-navigation,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:rxjs(^7.8.1)、@equinor/fusion-framework-module(^6.2.0)、@equinor/fusion-framework-module-app(^8.3.0)、@equinor/fusion-framework-module-event(^6.1.4)、@equinor/fusion-framework-module-context(^9.1.0)、@equinor/fusion-framework-module-navigation(^8.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@equinor/fusion-framework-plugin-context-navigation 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@equinor/fusion-framework-plugin-context-navigation 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@equinor/fusion-framework-plugin-context-navigation 可以商用吗?

@equinor/fusion-framework-plugin-context-navigation 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@equinor/fusion-framework-plugin-context-navigation 的下载量有多少?

根据 npm 官方下载统计,@equinor/fusion-framework-plugin-context-navigation 在 2026/10/14 – 2026/11/12 这 30 天内被下载 6,725 次,日均约 224 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@equinor/fusion-framework-plugin-context-navigation 还在维护吗?

最新版本 1.1.1 发布于 2026/11/12(1 天前);至今共发布 17 个版本,其中过去 12 个月发布了 17 个;首次发布于 2026/08/06。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。