# @real-router/hash-plugin

[![npm](https://img.shields.io/npm/v/@real-router/hash-plugin.svg?style=flat-square)](https://www.npmjs.com/package/@real-router/hash-plugin)
[![npm downloads](https://img.shields.io/npm/dm/@real-router/hash-plugin.svg?style=flat-square)](https://www.npmjs.com/package/@real-router/hash-plugin)
[![bundle size](https://deno.bundlejs.com/?q=@real-router/hash-plugin&treeshake=[*]&badge=detailed)](https://bundlejs.com/?q=@real-router/hash-plugin&treeshake=[*])
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](../../LICENSE)

> Hash-based routing plugin for [Real-Router](https://github.com/greydragon888/real-router). Uses URL hash fragment (`#/path`) for navigation — no server configuration needed.

Works on static hosting (GitHub Pages, S3, Netlify) without redirect rules. Tradeoff: URLs include `#` (`example.com/#!/users` vs `example.com/users`).

> **Looking for clean URLs?** Use [`@real-router/browser-plugin`](https://www.npmjs.com/package/@real-router/browser-plugin) (History API).

## Installation

```bash
npm install @real-router/hash-plugin
```

**Peer dependency:** `@real-router/core`

## Quick Start

```typescript
import { createRouter } from "@real-router/core";
import { hashPluginFactory } from "@real-router/hash-plugin";

const router = createRouter([
  { name: "home", path: "/" },
  { name: "users", path: "/users/:id" },
]);

router.usePlugin(hashPluginFactory());
await router.start(); // reads hash from browser location
```

## Options

| Option            | Type      | Default | Description                                           |
| ----------------- | --------- | ------- | ----------------------------------------------------- |
| `hashPrefix`      | `string`  | `""`    | Prefix after `#` (e.g., `"!"` → `#!/path`)            |
| `base`            | `string`  | `""`    | Base path before hash (e.g., `"/app"` → `/app#/path`) |
| `forceDeactivate` | `boolean` | `true`  | Bypass `canDeactivate` guards on back/forward         |

```typescript
router.usePlugin(hashPluginFactory({ hashPrefix: "!", base: "/app" }));

router.navigate("users", { id: "123" });
// URL: /app#!/users/123
```

## Router Extensions

The plugin extends the router instance with three methods via [`extendRouter()`](https://github.com/greydragon888/real-router/wiki/plugin-architecture):

| Method                                       | Returns              | Description                           |
| -------------------------------------------- | -------------------- | ------------------------------------- |
| `buildUrl(name, params?)`                    | `string`             | Build full URL with hash and prefix   |
| `matchUrl(url)`                              | `State \| undefined` | Parse hash URL to router state        |
| `replaceHistoryState(name, params?)`         | `void`               | Update browser URL without navigation |

```typescript
router.buildUrl("users", { id: "123" });
// => "#!/users/123" (with hashPrefix "!")

router.matchUrl("https://example.com/#!/users/123");
// => { name: "users", params: { id: "123" }, path: "/users/123" }

// Update URL silently (no transition, no guards)
router.replaceHistoryState("users", { id: "456" });
```

### `buildUrl` vs `buildPath`

```typescript
router.buildPath("users", { id: 1 }); // "/users/1"       — core, no hash
router.buildUrl("users", { id: 1 }); // "#!/users/1"     — plugin, with hash prefix
```

## Form Protection

Set `forceDeactivate: false` to respect `canDeactivate` guards on back/forward:

```typescript
router.usePlugin(hashPluginFactory({ forceDeactivate: false }));

import { getLifecycleApi } from "@real-router/core/api";

const lifecycle = getLifecycleApi(router);
lifecycle.addDeactivateGuard(
  "checkout",
  (router, getDep) => (toState, fromState) => {
    return !hasUnsavedChanges(); // false blocks back/forward
  },
);
```

## Limitations

### URL Fragments via `<Link hash>` / `opts.hash`

Hash-plugin uses `#` as the route delimiter — URL fragments are structurally incompatible. The signatures of `router.buildUrl(name, params, options?)` and `router.replaceHistoryState(name, params, options?)` accept `{ hash }` for typing parity with the other URL plugins, but at runtime the option is **silently ignored** and a one-time `console.warn` is emitted on the first invocation:

```text
[@real-router/hash-plugin] `hash` option is ignored — `#` is reserved for the route delimiter.
Use browser-plugin or navigation-plugin for URL fragments.
```

`state.context.url` is **not** populated under hash-plugin (`undefined` at runtime). Hash-aware sources (`useIsActiveRoute`, `<Link hash>` active state) consequently return `false` for any non-undefined `hash`. Only one URL plugin (`browser-plugin`, `navigation-plugin`, or `hash-plugin`) may be installed per router instance.

If you need URL fragments for tab-style UIs or anchor scrolling, use [`@real-router/browser-plugin`](../browser-plugin/) or [`@real-router/navigation-plugin`](../navigation-plugin/) instead — see the [Hash Fragment Support](https://github.com/greydragon888/real-router/wiki/Hash) wiki page.

## SSR Support

SSR-safe — automatically detects the environment and falls back to no-ops:

```typescript
router.usePlugin(hashPluginFactory());
router.buildUrl("home"); // returns hash path
router.matchUrl("/path"); // returns undefined
```

## Advanced Exports

| Export | Description |
|--------|-------------|
| `Browser` | Browser interface type (from `browser-env`) — for custom browser implementations |
| `isState` | Type guard to validate history state structure — `(value: unknown) => value is State` |

## Documentation

Full documentation: [Wiki — hash-plugin](https://github.com/greydragon888/real-router/wiki/hash-plugin)

- [Configuration Options](https://github.com/greydragon888/real-router/wiki/hash-plugin#3-configuration-options)
- [Behavior & Edge Cases](https://github.com/greydragon888/real-router/wiki/hash-plugin#8-behavior)

## Related Packages

| Package                                                                                  | Description                            |
| ---------------------------------------------------------------------------------------- | -------------------------------------- |
| [@real-router/core](https://www.npmjs.com/package/@real-router/core)                     | Core router (required peer dependency) |
| [@real-router/browser-plugin](https://www.npmjs.com/package/@real-router/browser-plugin) | History API routing (clean URLs)       |
| [@real-router/react](https://www.npmjs.com/package/@real-router/react)                   | React integration                      |

## Contributing

See [contributing guidelines](../../CONTRIBUTING.md) for development setup and PR process.

## License

[MIT](../../LICENSE) © [Oleg Ivanov](https://github.com/greydragon888)
