@real-router/browser-plugin 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -30
- package/dist/cjs/index.d.ts +7 -153
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/metafile-cjs.json +1 -1
- package/dist/esm/index.d.mts +7 -153
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/metafile-esm.json +1 -1
- package/package.json +5 -4
- package/src/constants.ts +2 -36
- package/src/factory.ts +27 -58
- package/src/index.ts +3 -1
- package/src/plugin.ts +62 -206
- package/src/types.ts +2 -179
- package/src/url-utils.ts +12 -80
- package/src/validation.ts +7 -63
- package/src/browser.ts +0 -128
- package/src/popstate-utils.ts +0 -60
package/dist/esm/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getPluginApi as e,RouterError as t}from"@real-router/core";
|
|
1
|
+
import{getPluginApi as e,RouterError as t}from"@real-router/core";var r=/^[A-Z_a-z][\w-]*(?:\.[A-Z_a-z][\w-]*)*$/;function n(e,t=new WeakSet){if(null==e)return!0;const r=typeof e;if("string"===r||"boolean"===r)return!0;if("number"===r)return Number.isFinite(e);if("function"===r||"symbol"===r)return!1;if(Array.isArray(e))return!t.has(e)&&(t.add(e),e.every(e=>n(e,t)));if("object"===r){if(t.has(e))return!1;t.add(e);const r=Object.getPrototypeOf(e);return(null===r||r===Object.prototype)&&Object.values(e).every(e=>n(e,t))}return!1}function o(e){if(null==e)return!0;const t=typeof e;return"string"===t||"boolean"===t||"number"===t&&Number.isFinite(e)}function a(e){if("object"!=typeof e||null===e||Array.isArray(e))return!1;const t=Object.getPrototypeOf(e);if(null!==t&&t!==Object.prototype)return!1;let r=!1;for(const t in e){if(!Object.hasOwn(e,t))continue;const n=e[t];if(!o(n)){const e=typeof n;if("function"===e||"symbol"===e)return!1;r=!0;break}}return!r||n(e)}function s(e){if(null==e)return!0;const t=typeof e;return"string"===t||"boolean"===t||("number"===t?Number.isFinite(e):!!Array.isArray(e)&&e.every(e=>{const t=typeof e;return"string"===t||"boolean"===t||"number"===t&&Number.isFinite(e)}))}function i(e){if("object"!=typeof e||null===e)return!1;const t=e;return!!function(e){return function(e){return"string"==typeof e&&(""===e||!(e.length>1e4)&&(!!e.startsWith("@@")||r.test(e)))}(e.name)&&"string"==typeof e.path&&a(e.params)}(t)&&(void 0===t.meta||function(e){if("object"!=typeof e||null===e)return!1;const t=e;return!("params"in t&&!function(e){if("object"!=typeof e||null===e||Array.isArray(e))return!1;for(const t in e)if(Object.hasOwn(e,t)&&!s(e[t]))return!1;return!0}(t.params)||"id"in t&&"number"!=typeof t.id)}(t.meta))}var c=(e,t)=>{globalThis.history.pushState(e,"",t)},u=(e,t)=>{globalThis.history.replaceState(e,"",t)},l=e=>(globalThis.addEventListener("popstate",e),()=>{globalThis.removeEventListener("popstate",e)}),p=()=>globalThis.location.hash,f=()=>{},h=e=>{let t=!1;return r=>{t||(console.warn(`[browser-env] Browser API is running in a non-browser environment (context: "${e}"). Method "${r}" is a no-op. This is expected for SSR, but may indicate misconfiguration if you expected browser behavior.`),t=!0)}},d=e=>{const t=h(e);return{pushState:()=>{t("pushState")},replaceState:()=>{t("replaceState")},addPopstateListener:()=>(t("addPopstateListener"),f),getHash:()=>(t("getHash"),"")}};function m(e,t,r,n){const o={meta:e.meta,name:e.name,params:e.params,path:e.path};r?n.replaceState(o,t):n.pushState(o,t)}function b(e){let r=!1,n=null;async function o(a){if(r)return console.warn(`[${e.loggerContext}] Transition in progress, deferring popstate event`),void(n=a);r=!0;try{const t=function(e,t,r){if(i(e.state))return{name:e.state.name,params:e.state.params};const n=t.matchPath(r.getLocation());return n?{name:n.name,params:n.params}:void 0}(a,e.api,e.browser);t?await e.router.navigate(t.name,t.params,e.transitionOptions):await e.router.navigateToDefault({...e.transitionOptions,reload:!0,replace:!0})}catch(r){r instanceof t||function(t){console.error(`[${e.loggerContext}] Critical error in onPopState`,t);try{const t=e.router.getState();if(t){const r=e.buildUrl(t.name,t.params);e.browser.replaceState(t,r)}}catch(t){console.error(`[${e.loggerContext}] Failed to recover from critical error`,t)}}(r)}finally{r=!1,function(){if(n){const t=n;n=null,console.warn(`[${e.loggerContext}] Processing deferred popstate event`),o(t)}}()}}return e=>{o(e)}}function g(e,t,r,n){return(o,a={})=>{const s=e.buildState(o,a);if(!s)throw new Error(`[real-router] Cannot replace state: route "${o}" is not found`);m(e.makeState(s.name,s.params,t.buildPath(s.name,s.params),{params:s.meta},1),n(o,a),!0,r)}}var v={forceDeactivate:!0,base:""},y="browser-plugin";function w(e,t){if(t&&e.startsWith(t)){const r=e.slice(t.length);return r.startsWith("/")?r:`/${r}`}return e}var S,P,L=class{#e;#t;#r;#n;#o;constructor(e,t,r,n,o,a){var s;this.#e=e,this.#t=n,this.#r=(s=n,t.addInterceptor("start",(e,t)=>e(t??s.getLocation())));const i=(t,n)=>{return o=e.buildPath(t,n),r.base+o;var o};this.#n=t.extendRouter({buildUrl:i,matchUrl:e=>{const n=function(e,t){const r=function(e,t){try{const r=new URL(e,globalThis.location.origin);return["http:","https:"].includes(r.protocol)?r:(console.warn(`[${t}] Invalid URL protocol in ${e}`),null)}catch(r){return console.warn(`[${t}] Could not parse url ${e}`,r),null}}(e,y);return r?w(r.pathname,t)+r.search:null}(e,r.base);return n?t.matchPath(n):void 0},replaceHistoryState:g(t,e,n,i)});const c=b({router:e,api:t,browser:n,transitionOptions:o,loggerContext:"browser-plugin",buildUrl:(t,r)=>e.buildUrl(t,r)});this.#o=function(e){return{onStart:()=>{e.shared.removePopStateListener&&e.shared.removePopStateListener(),e.shared.removePopStateListener=e.browser.addPopstateListener(e.handler)},onStop:()=>{e.shared.removePopStateListener&&(e.shared.removePopStateListener(),e.shared.removePopStateListener=void 0)},teardown:()=>{e.shared.removePopStateListener&&(e.shared.removePopStateListener(),e.shared.removePopStateListener=void 0),e.cleanup()}}}({browser:n,shared:a,handler:c,cleanup:()=>{this.#r(),this.#n()}})}getPlugin(){return{...this.#o,onTransitionSuccess:(e,t,r)=>{const n=(a=t,s=this.#e,((o=r).replace??!a)||!!o.reload&&s.areStatesEqual(e,a,!1));var o,a,s;const i=this.#e.buildUrl(e.name,e.params);m(e,t&&t.path!==e.path?i:i+this.#t.getHash(),n,this.#t)}}}},$=(S=v,P=y,e=>{if(e)for(const t of Object.keys(e))if(t in S){const r=e[t],n=typeof S[t],o=typeof r;if(void 0!==r&&o!==n)throw new Error(`[${P}] Invalid type for '${t}': expected ${n}, got ${o}`)}});function O(t,r){$(t);const n={...v,...t};n.base=function(e){if(!e)return e;let t=e;return t.startsWith("/")||(t=`/${t}`),t.endsWith("/")&&(t=t.slice(0,-1)),t}(n.base);const o=r??function(e,t){if(void 0!==globalThis.window&&globalThis.history)return{pushState:c,replaceState:u,addPopstateListener:l,getLocation:e,getHash:p};const r=h(t);return{...d(t),getLocation:()=>(r("getLocation"),"")}}(()=>(e=>{try{return encodeURI(decodeURI(e))}catch(t){return console.warn(`[browser-env] Could not encode path "${e}"`,t),e}})(w(globalThis.location.pathname,n.base))+globalThis.location.search,"browser-plugin"),a={forceDeactivate:n.forceDeactivate,source:"popstate",replace:!0},s={removePopStateListener:void 0};return function(t){return new L(t,e(t),n,o,a,s).getPlugin()}}export{O as browserPluginFactory,i as isState};//# sourceMappingURL=index.mjs.map
|
package/dist/esm/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/constants.ts","../../src/url-utils.ts","../../src/browser.ts","../../src/popstate-utils.ts","../../src/plugin.ts","../../src/validation.ts","../../src/factory.ts"],"names":["regExpCache","defaultOptions"],"mappings":"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","file":"index.mjs","sourcesContent":["// packages/browser-plugin/modules/constants.ts\n\n/**\n * Internal type for default options.\n *\n * Why separate type instead of BrowserPluginOptions?\n *\n * BrowserPluginOptions is a discriminated union:\n * - HashModeOptions: allows hashPrefix, forbids preserveHash (never)\n * - HistoryModeOptions: allows preserveHash, forbids hashPrefix (never)\n *\n * We cannot create a single object of type BrowserPluginOptions that contains\n * BOTH hashPrefix and preserveHash - one will always be 'never' depending on useHash.\n *\n * Example - this would fail TypeScript:\n * const defaults: BrowserPluginOptions = {\n * useHash: false, // → HistoryModeOptions branch\n * preserveHash: true, // ✅ OK\n * hashPrefix: \"\" // ❌ Error: Type 'string' is not assignable to type 'never'\n * };\n *\n * DefaultBrowserPluginOptions solves this by containing ALL options,\n * enabling:\n * - Default values for every option\n * - Type validation via typeof defaultOptions\n * - Runtime validation of user-provided option types\n */\nexport interface DefaultBrowserPluginOptions {\n forceDeactivate: boolean;\n useHash: boolean;\n base: string;\n preserveHash: boolean;\n hashPrefix: string;\n}\n\nexport const defaultOptions: DefaultBrowserPluginOptions = {\n forceDeactivate: true,\n useHash: false,\n hashPrefix: \"\",\n base: \"\",\n preserveHash: true,\n};\n\n/**\n * Source identifier for transitions triggered by browser events.\n * Used to distinguish browser-initiated navigation (back/forward buttons)\n * from programmatic navigation (router.navigate()).\n */\nexport const source = \"popstate\";\n\nexport const LOGGER_CONTEXT = \"browser-plugin\";\n","// packages/browser-plugin/src/url-utils.ts\n\nimport { LOGGER_CONTEXT } from \"./constants\";\n\nimport type { URLParseOptions, RegExpCache } from \"./types\";\n\nconst escapeRegExpCache = new Map<string, string>();\n\nexport const escapeRegExp = (str: string): string => {\n const cached = escapeRegExpCache.get(str);\n\n if (cached !== undefined) {\n return cached;\n }\n\n const escaped = str.replaceAll(/[$()*+.?[\\\\\\]^{|}-]/g, String.raw`\\$&`);\n\n escapeRegExpCache.set(str, escaped);\n\n return escaped;\n};\n\nexport function extractPath(\n pathname: string,\n hash: string,\n options: URLParseOptions,\n regExpCache: RegExpCache,\n): string {\n if (options.useHash) {\n const escapedHashPrefix = escapeRegExp(options.hashPrefix);\n const path = escapedHashPrefix\n ? hash.replace(regExpCache.get(`^#${escapedHashPrefix}`), \"\")\n : hash.slice(1);\n\n return path || \"/\";\n }\n\n if (options.base) {\n const escapedBase = escapeRegExp(options.base);\n const stripped = pathname.replace(regExpCache.get(`^${escapedBase}`), \"\");\n\n return stripped.startsWith(\"/\") ? stripped : `/${stripped}`;\n }\n\n return pathname;\n}\n\nexport function urlToPath(\n url: string,\n options: URLParseOptions,\n regExpCache: RegExpCache,\n): string | null {\n try {\n const parsedUrl = new URL(url, globalThis.location.origin);\n\n if (![\"http:\", \"https:\"].includes(parsedUrl.protocol)) {\n console.warn(`[${LOGGER_CONTEXT}] Invalid URL protocol in ${url}`);\n\n return null;\n }\n\n return (\n extractPath(parsedUrl.pathname, parsedUrl.hash, options, regExpCache) +\n parsedUrl.search\n );\n } catch (error) {\n console.warn(`[${LOGGER_CONTEXT}] Could not parse url ${url}`, error);\n\n return null;\n }\n}\n\nexport function buildUrl(path: string, base: string, prefix: string): string {\n return base + prefix + path;\n}\n\nexport function createRegExpCache(): RegExpCache {\n const cache = new Map<string, RegExp>();\n\n return {\n get(pattern: string): RegExp {\n const cached = cache.get(pattern);\n\n if (cached !== undefined) {\n return cached;\n }\n\n const newRegExp = new RegExp(pattern);\n\n cache.set(pattern, newRegExp);\n\n return newRegExp;\n },\n };\n}\n","// packages/browser-plugin/modules/browser.ts\n\nimport { logger } from \"@real-router/logger\";\n\nimport { LOGGER_CONTEXT } from \"./constants\";\nimport { createRegExpCache, extractPath } from \"./url-utils\";\n\nimport type { Browser, BrowserPluginOptions, URLParseOptions } from \"./types\";\nimport type { State } from \"@real-router/core\";\n\n/** No-operation cleanup function for fallback browser */\nconst NOOP = (): void => {};\n\nconst pushState = (state: State, path: string) => {\n globalThis.history.pushState(state, \"\", path);\n};\n\nconst replaceState = (state: State, path: string) => {\n globalThis.history.replaceState(state, \"\", path);\n};\n\nconst addPopstateListener: Browser[\"addPopstateListener\"] = (fn) => {\n globalThis.addEventListener(\"popstate\", fn);\n\n return () => {\n globalThis.removeEventListener(\"popstate\", fn);\n };\n};\n\nconst regExpCache = createRegExpCache();\n\n/**\n * Safely encodes/decodes path to normalize URL encoding\n *\n * @param path - Path to normalize\n * @returns Normalized path or original on error\n */\nconst safelyEncodePath = (path: string): string => {\n try {\n return encodeURI(decodeURI(path));\n } catch (error) {\n logger.warn(LOGGER_CONTEXT, `Could not encode path \"${path}\"`, error);\n\n return path;\n }\n};\n\nconst getLocation = (opts: BrowserPluginOptions) => {\n const rawPath = extractPath(\n globalThis.location.pathname,\n globalThis.location.hash,\n opts as URLParseOptions,\n regExpCache,\n );\n\n return safelyEncodePath(rawPath) + globalThis.location.search;\n};\n\n/**\n * Gets current URL hash\n */\nconst getHash = () => globalThis.location.hash;\n\n/**\n * Creates a fallback browser for non-browser environments (SSR).\n * Logs warning on first method call to help diagnose misconfiguration.\n *\n * @returns Browser API with no-op implementations\n */\nfunction createFallbackBrowser(): Browser {\n let hasWarned = false;\n\n const warnOnce = (method: string) => {\n if (!hasWarned) {\n logger.warn(\n LOGGER_CONTEXT,\n `Browser plugin is running in a non-browser environment. ` +\n `Method \"${method}\" is a no-op. ` +\n `This is expected for SSR, but may indicate misconfiguration if you expected browser behavior.`,\n );\n hasWarned = true;\n }\n };\n\n return {\n pushState: () => {\n warnOnce(\"pushState\");\n },\n replaceState: () => {\n warnOnce(\"replaceState\");\n },\n addPopstateListener: () => {\n warnOnce(\"addPopstateListener\");\n\n return NOOP;\n },\n getLocation: () => {\n warnOnce(\"getLocation\");\n\n return \"\";\n },\n getHash: () => {\n warnOnce(\"getHash\");\n\n return \"\";\n },\n };\n}\n\n/**\n * Creates browser API abstraction that works in both browser and SSR environments\n *\n * @returns Browser API object\n */\nexport function createSafeBrowser(): Browser {\n const isBrowser =\n typeof globalThis.window !== \"undefined\" && !!globalThis.history;\n\n return isBrowser\n ? {\n pushState,\n replaceState,\n addPopstateListener,\n getLocation,\n getHash,\n }\n : createFallbackBrowser();\n}\n","import { isStateStrict as isState } from \"type-guards\";\n\nimport type { BrowserPluginOptions, Browser } from \"./types\";\nimport type { PluginApi, State, Params } from \"@real-router/core\";\n\n/**\n * Extracts route name and params from a popstate event.\n *\n * - If history.state is a valid router state → returns name/params from it\n * - If not (e.g. manually entered URL) → matches current URL against route tree\n * - Returns undefined if no route matches\n *\n * @param evt - PopStateEvent from browser\n * @param api - PluginApi instance\n * @param browser - Browser API instance\n * @param options - Browser plugin options\n * @returns Route identifier or undefined\n */\nexport function getRouteFromEvent(\n evt: PopStateEvent,\n api: PluginApi,\n browser: Browser,\n options: BrowserPluginOptions,\n): { name: string; params: Params } | undefined {\n if (isState(evt.state)) {\n return { name: evt.state.name, params: evt.state.params };\n }\n\n const state = api.matchPath(browser.getLocation(options));\n\n return state ? { name: state.name, params: state.params } : undefined;\n}\n\n/**\n * Updates browser state (pushState or replaceState)\n *\n * @param state - Router state\n * @param url - URL to set\n * @param replace - Whether to replace instead of push\n * @param browser - Browser API instance\n */\nexport function updateBrowserState(\n state: State,\n url: string,\n replace: boolean,\n browser: Browser,\n): void {\n const historyState = {\n meta: state.meta,\n name: state.name,\n params: state.params,\n path: state.path,\n };\n\n if (replace) {\n browser.replaceState(historyState, url);\n } else {\n browser.pushState(historyState, url);\n }\n}\n","import { RouterError } from \"@real-router/core\";\n\nimport { LOGGER_CONTEXT } from \"./constants\";\nimport { getRouteFromEvent, updateBrowserState } from \"./popstate-utils\";\nimport { buildUrl, urlToPath } from \"./url-utils\";\n\nimport type {\n Browser,\n BrowserPluginOptions,\n RegExpCache,\n SharedFactoryState,\n URLParseOptions,\n} from \"./types\";\nimport type {\n NavigationOptions,\n PluginApi,\n Router,\n State,\n Plugin,\n} from \"@real-router/core\";\n\nexport class BrowserPlugin {\n readonly #router: Router;\n readonly #api: PluginApi;\n readonly #options: BrowserPluginOptions;\n readonly #browser: Browser;\n readonly #regExpCache: RegExpCache;\n readonly #prefix: string;\n readonly #transitionOptions: {\n source: string;\n replace: true;\n forceDeactivate?: boolean;\n };\n readonly #shared: SharedFactoryState;\n\n #isTransitioning = false;\n #deferredPopstateEvent: PopStateEvent | null = null;\n readonly #removeStartInterceptor: () => void;\n\n constructor(\n router: Router,\n api: PluginApi,\n options: BrowserPluginOptions,\n browser: Browser,\n regExpCache: RegExpCache,\n transitionOptions: {\n source: string;\n replace: true;\n forceDeactivate?: boolean;\n },\n shared: SharedFactoryState,\n ) {\n this.#router = router;\n this.#api = api;\n this.#options = options;\n this.#browser = browser;\n this.#regExpCache = regExpCache;\n this.#transitionOptions = transitionOptions;\n this.#shared = shared;\n\n const normalizedOptions = options as URLParseOptions;\n\n this.#prefix = options.useHash ? `#${normalizedOptions.hashPrefix}` : \"\";\n\n this.#removeStartInterceptor = this.#api.addInterceptor(\n \"start\",\n (next, path) => next(path ?? this.#browser.getLocation(this.#options)),\n );\n\n this.#augmentRouter();\n }\n\n getPlugin(): Plugin {\n return {\n onStart: () => {\n if (this.#shared.removePopStateListener) {\n this.#shared.removePopStateListener();\n }\n\n this.#shared.removePopStateListener = this.#browser.addPopstateListener(\n (evt: PopStateEvent) => void this.#onPopState(evt),\n );\n },\n\n onStop: () => {\n if (this.#shared.removePopStateListener) {\n this.#shared.removePopStateListener();\n this.#shared.removePopStateListener = undefined;\n }\n },\n\n onTransitionSuccess: (\n toState: State,\n fromState: State | undefined,\n navOptions: NavigationOptions,\n ) => {\n const shouldReplaceHistory =\n (navOptions.replace ?? !fromState) ||\n (!!navOptions.reload &&\n this.#router.areStatesEqual(toState, fromState, false));\n\n const url = this.#router.buildUrl(toState.name, toState.params);\n\n const shouldPreserveHash =\n !!this.#options.preserveHash &&\n (!fromState || fromState.path === toState.path);\n\n const finalUrl = shouldPreserveHash\n ? url + this.#browser.getHash()\n : url;\n\n updateBrowserState(\n toState,\n finalUrl,\n shouldReplaceHistory,\n this.#browser,\n );\n },\n\n teardown: () => {\n this.#cleanupAugmentation();\n },\n };\n }\n\n #augmentRouter(): void {\n const router = this.#router;\n\n router.buildUrl = (route, params) => {\n const path = router.buildPath(route, params);\n\n return buildUrl(\n path,\n (this.#options as URLParseOptions).base,\n this.#prefix,\n );\n };\n\n router.matchUrl = (url) => {\n const path = urlToPath(\n url,\n this.#options as URLParseOptions,\n this.#regExpCache,\n );\n\n return path ? this.#api.matchPath(path) : undefined;\n };\n\n router.replaceHistoryState = (name, params = {}) => {\n const state = this.#api.buildState(name, params);\n\n if (!state) {\n throw new Error(\n `[real-router] Cannot replace state: route \"${name}\" is not found`,\n );\n }\n\n const builtState = this.#api.makeState(\n state.name,\n state.params,\n router.buildPath(state.name, state.params),\n {\n params: state.meta,\n },\n 1, // forceId\n );\n const url = router.buildUrl(name, params);\n\n updateBrowserState(builtState, url, true, this.#browser);\n };\n }\n\n #cleanupAugmentation(): void {\n if (this.#shared.removePopStateListener) {\n this.#shared.removePopStateListener();\n this.#shared.removePopStateListener = undefined;\n }\n\n this.#removeStartInterceptor();\n\n delete (this.#router as Partial<Router>).buildUrl;\n delete (this.#router as Partial<Router>).matchUrl;\n delete (this.#router as Partial<Router>).replaceHistoryState;\n }\n\n #processDeferredEvent(): void {\n if (this.#deferredPopstateEvent) {\n const event = this.#deferredPopstateEvent;\n\n this.#deferredPopstateEvent = null;\n console.warn(`[${LOGGER_CONTEXT}] Processing deferred popstate event`);\n void this.#onPopState(event);\n }\n }\n\n async #onPopState(evt: PopStateEvent): Promise<void> {\n if (this.#isTransitioning) {\n console.warn(\n `[${LOGGER_CONTEXT}] Transition in progress, deferring popstate event`,\n );\n this.#deferredPopstateEvent = evt;\n\n return;\n }\n\n this.#isTransitioning = true;\n\n try {\n const route = getRouteFromEvent(\n evt,\n this.#api,\n this.#browser,\n this.#options,\n );\n\n // eslint-disable-next-line unicorn/prefer-ternary\n if (route) {\n await this.#router.navigate(\n route.name,\n route.params,\n this.#transitionOptions,\n );\n } else {\n await this.#router.navigateToDefault({\n ...this.#transitionOptions,\n reload: true,\n replace: true,\n });\n }\n } catch (error) {\n if (!(error instanceof RouterError)) {\n this.#recoverFromCriticalError(error);\n }\n } finally {\n this.#isTransitioning = false;\n this.#processDeferredEvent();\n }\n }\n\n #recoverFromCriticalError(error: unknown): void {\n console.error(`[${LOGGER_CONTEXT}] Critical error in onPopState`, error);\n\n try {\n const currentState = this.#router.getState();\n\n /* v8 ignore next -- @preserve: router always has state after start(); defensive guard for edge cases */\n if (currentState) {\n const url = this.#router.buildUrl(\n currentState.name,\n currentState.params,\n );\n\n this.#browser.replaceState(currentState, url);\n }\n } catch (recoveryError) {\n console.error(\n `[${LOGGER_CONTEXT}] Failed to recover from critical error`,\n recoveryError,\n );\n }\n }\n}\n","import { type DefaultBrowserPluginOptions, LOGGER_CONTEXT } from \"./constants\";\n\nimport type { BrowserPluginOptions } from \"./types\";\n\nfunction isDefaultOptionKey(\n key: string,\n defaults: DefaultBrowserPluginOptions,\n): key is keyof DefaultBrowserPluginOptions {\n return key in defaults;\n}\n\nfunction validateOptionType(\n key: keyof DefaultBrowserPluginOptions,\n value: unknown,\n expectedType: string,\n): boolean {\n const actualType = typeof value;\n\n if (actualType !== expectedType && value !== undefined) {\n console.warn(\n `[${LOGGER_CONTEXT}] Invalid type for '${key}': expected ${expectedType}, got ${actualType}`,\n );\n\n return false;\n }\n\n return true;\n}\n\nexport function validateOptions(\n opts: Partial<BrowserPluginOptions> | undefined,\n defaultOptions: DefaultBrowserPluginOptions,\n): boolean {\n if (!opts) {\n return false;\n }\n\n let hasInvalidTypes = false;\n\n for (const key of Object.keys(opts)) {\n if (isDefaultOptionKey(key, defaultOptions)) {\n const expectedType = typeof defaultOptions[key];\n const value = opts[key];\n const isValid = validateOptionType(key, value, expectedType);\n\n if (!isValid) {\n hasInvalidTypes = true;\n }\n }\n }\n\n if (opts.useHash === true && \"preserveHash\" in opts) {\n console.warn(`[${LOGGER_CONTEXT}] preserveHash ignored in hash mode`);\n }\n\n if (opts.useHash === false && \"hashPrefix\" in opts) {\n const optsRecord = opts as unknown as Record<string, unknown>;\n const hashPrefix = optsRecord.hashPrefix;\n\n if (hashPrefix !== undefined && hashPrefix !== \"\") {\n console.warn(`[${LOGGER_CONTEXT}] hashPrefix ignored in history mode`);\n }\n }\n\n return hasInvalidTypes;\n}\n","import { getPluginApi } from \"@real-router/core\";\n\nimport { createSafeBrowser } from \"./browser\";\nimport { defaultOptions, LOGGER_CONTEXT, source } from \"./constants\";\nimport { BrowserPlugin } from \"./plugin\";\nimport { createRegExpCache } from \"./url-utils\";\nimport { validateOptions } from \"./validation\";\n\nimport type {\n BrowserPluginOptions,\n Browser,\n SharedFactoryState,\n} from \"./types\";\nimport type { PluginFactory, Router } from \"@real-router/core\";\n\n/**\n * Browser plugin factory for real-router.\n * Integrates router with browser history API.\n *\n * @param opts - Plugin configuration options\n * @param browser - Browser API abstraction (for testing/SSR)\n * @returns Plugin factory function\n *\n * @example\n * ```ts\n * // Hash routing\n * router.usePlugin(browserPluginFactory({ useHash: true, hashPrefix: \"!\" }));\n *\n * // History routing with hash preservation\n * router.usePlugin(browserPluginFactory({ useHash: false, preserveHash: true }));\n * ```\n */\nexport function browserPluginFactory(\n opts?: Partial<BrowserPluginOptions>,\n browser: Browser = createSafeBrowser(),\n): PluginFactory {\n const hasInvalidTypes = validateOptions(opts, defaultOptions);\n\n let options = { ...defaultOptions, ...opts } as BrowserPluginOptions;\n\n if (hasInvalidTypes) {\n console.warn(\n `[${LOGGER_CONTEXT}] Using default options due to invalid types`,\n );\n options = { ...defaultOptions } as BrowserPluginOptions;\n }\n\n if (options.useHash === true) {\n delete (options as unknown as Record<string, unknown>).preserveHash;\n } else {\n delete (options as unknown as Record<string, unknown>).hashPrefix;\n }\n\n if (options.base) {\n if (!options.base.startsWith(\"/\")) {\n options.base = `/${options.base}`;\n }\n\n if (options.base.endsWith(\"/\")) {\n options.base = options.base.slice(0, -1);\n }\n }\n\n const regExpCache = createRegExpCache();\n\n const forceDeactivate = options.forceDeactivate;\n /* v8 ignore next 4 -- @preserve both branches tested, coverage tool limitation */\n const transitionOptions =\n forceDeactivate === undefined\n ? { source, replace: true as const }\n : { forceDeactivate, source, replace: true as const };\n\n const shared: SharedFactoryState = { removePopStateListener: undefined };\n\n return function browserPlugin(routerBase) {\n const plugin = new BrowserPlugin(\n routerBase as Router,\n getPluginApi(routerBase),\n options,\n browser,\n regExpCache,\n transitionOptions,\n shared,\n );\n\n return plugin.getPlugin();\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/constants.ts","../../src/url-utils.ts","../../src/plugin.ts","../../src/validation.ts","../../src/factory.ts"],"names":["i","f","c"],"mappings":"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","file":"index.mjs","sourcesContent":["// packages/browser-plugin/modules/constants.ts\n\nimport type { BrowserPluginOptions } from \"./types\";\n\nexport const defaultOptions: Required<BrowserPluginOptions> = {\n forceDeactivate: true,\n base: \"\",\n};\n\n/**\n * Source identifier for transitions triggered by browser events.\n * Used to distinguish browser-initiated navigation (back/forward buttons)\n * from programmatic navigation (router.navigate()).\n */\nexport const source = \"popstate\";\n\nexport const LOGGER_CONTEXT = \"browser-plugin\";\n","// packages/browser-plugin/src/url-utils.ts\n\nimport { safeParseUrl } from \"browser-env\";\n\nimport { LOGGER_CONTEXT } from \"./constants\";\n\nexport function extractPath(pathname: string, base: string): string {\n if (base && pathname.startsWith(base)) {\n const stripped = pathname.slice(base.length);\n\n return stripped.startsWith(\"/\") ? stripped : `/${stripped}`;\n }\n\n return pathname;\n}\n\nexport function buildUrl(path: string, base: string): string {\n return base + path;\n}\n\nexport function urlToPath(url: string, base: string): string | null {\n const parsedUrl = safeParseUrl(url, LOGGER_CONTEXT);\n\n return parsedUrl\n ? extractPath(parsedUrl.pathname, base) + parsedUrl.search\n : null;\n}\n","import {\n createPopstateHandler,\n createPopstateLifecycle,\n createStartInterceptor,\n createReplaceHistoryState,\n shouldReplaceHistory,\n updateBrowserState,\n} from \"browser-env\";\n\nimport { buildUrl, urlToPath } from \"./url-utils\";\n\nimport type { BrowserPluginOptions } from \"./types\";\nimport type {\n NavigationOptions,\n Params,\n PluginApi,\n Router,\n State,\n Plugin,\n} from \"@real-router/core\";\nimport type { Browser, SharedFactoryState } from \"browser-env\";\n\nexport class BrowserPlugin {\n readonly #router: Router;\n readonly #browser: Browser;\n readonly #removeStartInterceptor: () => void;\n readonly #removeExtensions: () => void;\n readonly #lifecycle: Pick<Plugin, \"onStart\" | \"onStop\" | \"teardown\">;\n\n constructor(\n router: Router,\n api: PluginApi,\n options: Required<BrowserPluginOptions>,\n browser: Browser,\n transitionOptions: {\n source: string;\n replace: true;\n forceDeactivate?: boolean;\n },\n shared: SharedFactoryState,\n ) {\n this.#router = router;\n this.#browser = browser;\n\n this.#removeStartInterceptor = createStartInterceptor(api, browser);\n\n const pluginBuildUrl = (route: string, params?: Params) => {\n const path = router.buildPath(route, params);\n\n return buildUrl(path, options.base);\n };\n\n this.#removeExtensions = api.extendRouter({\n buildUrl: pluginBuildUrl,\n matchUrl: (url: string) => {\n const path = urlToPath(url, options.base);\n\n return path ? api.matchPath(path) : undefined;\n },\n replaceHistoryState: createReplaceHistoryState(\n api,\n router,\n browser,\n pluginBuildUrl,\n ),\n });\n\n const handler = createPopstateHandler({\n router,\n api,\n browser,\n transitionOptions,\n loggerContext: \"browser-plugin\",\n buildUrl: (name: string, params?: Params) =>\n router.buildUrl(name, params),\n });\n\n this.#lifecycle = createPopstateLifecycle({\n browser,\n shared,\n handler,\n cleanup: () => {\n this.#removeStartInterceptor();\n this.#removeExtensions();\n },\n });\n }\n\n getPlugin(): Plugin {\n return {\n ...this.#lifecycle,\n\n onTransitionSuccess: (\n toState: State,\n fromState: State | undefined,\n navOptions: NavigationOptions,\n ) => {\n const replaceHistory = shouldReplaceHistory(\n navOptions,\n toState,\n fromState,\n this.#router,\n );\n\n const url = this.#router.buildUrl(toState.name, toState.params);\n\n const shouldPreserveHash =\n !fromState || fromState.path === toState.path;\n\n const finalUrl = shouldPreserveHash\n ? url + this.#browser.getHash()\n : url;\n\n updateBrowserState(toState, finalUrl, replaceHistory, this.#browser);\n },\n };\n }\n}\n","import { createOptionsValidator } from \"browser-env\";\n\nimport { LOGGER_CONTEXT, defaultOptions } from \"./constants\";\n\nimport type { BrowserPluginOptions } from \"./types\";\n\nexport const validateOptions = createOptionsValidator<BrowserPluginOptions>(\n defaultOptions,\n LOGGER_CONTEXT,\n);\n","import { getPluginApi } from \"@real-router/core\";\nimport {\n createSafeBrowser,\n normalizeBase,\n safelyEncodePath,\n} from \"browser-env\";\n\nimport { defaultOptions, source } from \"./constants\";\nimport { BrowserPlugin } from \"./plugin\";\nimport { extractPath } from \"./url-utils\";\nimport { validateOptions } from \"./validation\";\n\nimport type { BrowserPluginOptions } from \"./types\";\nimport type { PluginFactory, Router } from \"@real-router/core\";\nimport type { Browser, SharedFactoryState } from \"browser-env\";\n\nexport function browserPluginFactory(\n opts?: Partial<BrowserPluginOptions>,\n browser?: Browser,\n): PluginFactory {\n validateOptions(opts);\n\n const options: Required<BrowserPluginOptions> = {\n ...defaultOptions,\n ...opts,\n };\n\n options.base = normalizeBase(options.base);\n\n const resolvedBrowser =\n browser ??\n createSafeBrowser(\n () =>\n safelyEncodePath(\n extractPath(globalThis.location.pathname, options.base),\n ) + globalThis.location.search,\n \"browser-plugin\",\n );\n\n const forceDeactivate = options.forceDeactivate;\n const transitionOptions = { forceDeactivate, source, replace: true as const };\n\n const shared: SharedFactoryState = { removePopStateListener: undefined };\n\n return function browserPlugin(routerBase) {\n const plugin = new BrowserPlugin(\n routerBase as Router,\n getPluginApi(routerBase),\n options,\n resolvedBrowser,\n transitionOptions,\n shared,\n );\n\n return plugin.getPlugin();\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"inputs":{"
|
|
1
|
+
{"inputs":{"../type-guards/dist/esm/index.mjs":{"bytes":3451,"imports":[],"format":"esm"},"../browser-env/dist/esm/index.mjs":{"bytes":4084,"imports":[{"path":"../type-guards/dist/esm/index.mjs","kind":"import-statement","original":"type-guards"},{"path":"@real-router/core","kind":"import-statement","external":true}],"format":"esm"},"src/constants.ts":{"bytes":493,"imports":[],"format":"esm"},"src/url-utils.ts":{"bytes":703,"imports":[{"path":"../browser-env/dist/esm/index.mjs","kind":"import-statement","original":"browser-env"},{"path":"src/constants.ts","kind":"import-statement","original":"./constants"}],"format":"esm"},"src/plugin.ts":{"bytes":2848,"imports":[{"path":"../browser-env/dist/esm/index.mjs","kind":"import-statement","original":"browser-env"},{"path":"src/url-utils.ts","kind":"import-statement","original":"./url-utils"}],"format":"esm"},"src/validation.ts":{"bytes":288,"imports":[{"path":"../browser-env/dist/esm/index.mjs","kind":"import-statement","original":"browser-env"},{"path":"src/constants.ts","kind":"import-statement","original":"./constants"}],"format":"esm"},"src/factory.ts":{"bytes":1513,"imports":[{"path":"@real-router/core","kind":"import-statement","external":true},{"path":"../browser-env/dist/esm/index.mjs","kind":"import-statement","original":"browser-env"},{"path":"src/constants.ts","kind":"import-statement","original":"./constants"},{"path":"src/plugin.ts","kind":"import-statement","original":"./plugin"},{"path":"src/url-utils.ts","kind":"import-statement","original":"./url-utils"},{"path":"src/validation.ts","kind":"import-statement","original":"./validation"}],"format":"esm"},"src/index.ts":{"bytes":1311,"imports":[{"path":"src/factory.ts","kind":"import-statement","original":"./factory"},{"path":"../type-guards/dist/esm/index.mjs","kind":"import-statement","original":"type-guards"}],"format":"esm"}},"outputs":{"dist/esm/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":8787},"dist/esm/index.mjs":{"imports":[{"path":"@real-router/core","kind":"import-statement","external":true},{"path":"@real-router/core","kind":"import-statement","external":true}],"exports":["browserPluginFactory","isState"],"entryPoint":"src/index.ts","inputs":{"src/factory.ts":{"bytesInOutput":827},"../type-guards/dist/esm/index.mjs":{"bytesInOutput":2337},"../browser-env/dist/esm/index.mjs":{"bytesInOutput":4688},"src/constants.ts":{"bytesInOutput":126},"src/url-utils.ts":{"bytesInOutput":442},"src/plugin.ts":{"bytesInOutput":1737},"src/validation.ts":{"bytesInOutput":63},"src/index.ts":{"bytesInOutput":0}},"bytes":10466}}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@real-router/browser-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"type": "commonjs",
|
|
5
5
|
"description": "Browser integration plugin with History API, hash routing, and popstate support",
|
|
6
6
|
"main": "./dist/cjs/index.js",
|
|
@@ -45,13 +45,14 @@
|
|
|
45
45
|
},
|
|
46
46
|
"sideEffects": false,
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@real-router/
|
|
49
|
-
"@real-router/
|
|
48
|
+
"@real-router/logger": "^0.2.0",
|
|
49
|
+
"@real-router/core": "^0.34.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@testing-library/jest-dom": "6.9.1",
|
|
53
53
|
"jsdom": "27.4.0",
|
|
54
|
-
"
|
|
54
|
+
"browser-env": "^0.1.0",
|
|
55
|
+
"type-guards": "^0.3.4"
|
|
55
56
|
},
|
|
56
57
|
"scripts": {
|
|
57
58
|
"test": "vitest",
|
package/src/constants.ts
CHANGED
|
@@ -1,44 +1,10 @@
|
|
|
1
1
|
// packages/browser-plugin/modules/constants.ts
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
* Internal type for default options.
|
|
5
|
-
*
|
|
6
|
-
* Why separate type instead of BrowserPluginOptions?
|
|
7
|
-
*
|
|
8
|
-
* BrowserPluginOptions is a discriminated union:
|
|
9
|
-
* - HashModeOptions: allows hashPrefix, forbids preserveHash (never)
|
|
10
|
-
* - HistoryModeOptions: allows preserveHash, forbids hashPrefix (never)
|
|
11
|
-
*
|
|
12
|
-
* We cannot create a single object of type BrowserPluginOptions that contains
|
|
13
|
-
* BOTH hashPrefix and preserveHash - one will always be 'never' depending on useHash.
|
|
14
|
-
*
|
|
15
|
-
* Example - this would fail TypeScript:
|
|
16
|
-
* const defaults: BrowserPluginOptions = {
|
|
17
|
-
* useHash: false, // → HistoryModeOptions branch
|
|
18
|
-
* preserveHash: true, // ✅ OK
|
|
19
|
-
* hashPrefix: "" // ❌ Error: Type 'string' is not assignable to type 'never'
|
|
20
|
-
* };
|
|
21
|
-
*
|
|
22
|
-
* DefaultBrowserPluginOptions solves this by containing ALL options,
|
|
23
|
-
* enabling:
|
|
24
|
-
* - Default values for every option
|
|
25
|
-
* - Type validation via typeof defaultOptions
|
|
26
|
-
* - Runtime validation of user-provided option types
|
|
27
|
-
*/
|
|
28
|
-
export interface DefaultBrowserPluginOptions {
|
|
29
|
-
forceDeactivate: boolean;
|
|
30
|
-
useHash: boolean;
|
|
31
|
-
base: string;
|
|
32
|
-
preserveHash: boolean;
|
|
33
|
-
hashPrefix: string;
|
|
34
|
-
}
|
|
3
|
+
import type { BrowserPluginOptions } from "./types";
|
|
35
4
|
|
|
36
|
-
export const defaultOptions:
|
|
5
|
+
export const defaultOptions: Required<BrowserPluginOptions> = {
|
|
37
6
|
forceDeactivate: true,
|
|
38
|
-
useHash: false,
|
|
39
|
-
hashPrefix: "",
|
|
40
7
|
base: "",
|
|
41
|
-
preserveHash: true,
|
|
42
8
|
};
|
|
43
9
|
|
|
44
10
|
/**
|
package/src/factory.ts
CHANGED
|
@@ -1,74 +1,44 @@
|
|
|
1
1
|
import { getPluginApi } from "@real-router/core";
|
|
2
|
+
import {
|
|
3
|
+
createSafeBrowser,
|
|
4
|
+
normalizeBase,
|
|
5
|
+
safelyEncodePath,
|
|
6
|
+
} from "browser-env";
|
|
2
7
|
|
|
3
|
-
import {
|
|
4
|
-
import { defaultOptions, LOGGER_CONTEXT, source } from "./constants";
|
|
8
|
+
import { defaultOptions, source } from "./constants";
|
|
5
9
|
import { BrowserPlugin } from "./plugin";
|
|
6
|
-
import {
|
|
10
|
+
import { extractPath } from "./url-utils";
|
|
7
11
|
import { validateOptions } from "./validation";
|
|
8
12
|
|
|
9
|
-
import type {
|
|
10
|
-
BrowserPluginOptions,
|
|
11
|
-
Browser,
|
|
12
|
-
SharedFactoryState,
|
|
13
|
-
} from "./types";
|
|
13
|
+
import type { BrowserPluginOptions } from "./types";
|
|
14
14
|
import type { PluginFactory, Router } from "@real-router/core";
|
|
15
|
+
import type { Browser, SharedFactoryState } from "browser-env";
|
|
15
16
|
|
|
16
|
-
/**
|
|
17
|
-
* Browser plugin factory for real-router.
|
|
18
|
-
* Integrates router with browser history API.
|
|
19
|
-
*
|
|
20
|
-
* @param opts - Plugin configuration options
|
|
21
|
-
* @param browser - Browser API abstraction (for testing/SSR)
|
|
22
|
-
* @returns Plugin factory function
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* ```ts
|
|
26
|
-
* // Hash routing
|
|
27
|
-
* router.usePlugin(browserPluginFactory({ useHash: true, hashPrefix: "!" }));
|
|
28
|
-
*
|
|
29
|
-
* // History routing with hash preservation
|
|
30
|
-
* router.usePlugin(browserPluginFactory({ useHash: false, preserveHash: true }));
|
|
31
|
-
* ```
|
|
32
|
-
*/
|
|
33
17
|
export function browserPluginFactory(
|
|
34
18
|
opts?: Partial<BrowserPluginOptions>,
|
|
35
|
-
browser
|
|
19
|
+
browser?: Browser,
|
|
36
20
|
): PluginFactory {
|
|
37
|
-
|
|
21
|
+
validateOptions(opts);
|
|
38
22
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
`[${LOGGER_CONTEXT}] Using default options due to invalid types`,
|
|
44
|
-
);
|
|
45
|
-
options = { ...defaultOptions } as BrowserPluginOptions;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (options.useHash === true) {
|
|
49
|
-
delete (options as unknown as Record<string, unknown>).preserveHash;
|
|
50
|
-
} else {
|
|
51
|
-
delete (options as unknown as Record<string, unknown>).hashPrefix;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (options.base) {
|
|
55
|
-
if (!options.base.startsWith("/")) {
|
|
56
|
-
options.base = `/${options.base}`;
|
|
57
|
-
}
|
|
23
|
+
const options: Required<BrowserPluginOptions> = {
|
|
24
|
+
...defaultOptions,
|
|
25
|
+
...opts,
|
|
26
|
+
};
|
|
58
27
|
|
|
59
|
-
|
|
60
|
-
options.base = options.base.slice(0, -1);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
28
|
+
options.base = normalizeBase(options.base);
|
|
63
29
|
|
|
64
|
-
const
|
|
30
|
+
const resolvedBrowser =
|
|
31
|
+
browser ??
|
|
32
|
+
createSafeBrowser(
|
|
33
|
+
() =>
|
|
34
|
+
safelyEncodePath(
|
|
35
|
+
extractPath(globalThis.location.pathname, options.base),
|
|
36
|
+
) + globalThis.location.search,
|
|
37
|
+
"browser-plugin",
|
|
38
|
+
);
|
|
65
39
|
|
|
66
40
|
const forceDeactivate = options.forceDeactivate;
|
|
67
|
-
|
|
68
|
-
const transitionOptions =
|
|
69
|
-
forceDeactivate === undefined
|
|
70
|
-
? { source, replace: true as const }
|
|
71
|
-
: { forceDeactivate, source, replace: true as const };
|
|
41
|
+
const transitionOptions = { forceDeactivate, source, replace: true as const };
|
|
72
42
|
|
|
73
43
|
const shared: SharedFactoryState = { removePopStateListener: undefined };
|
|
74
44
|
|
|
@@ -77,8 +47,7 @@ export function browserPluginFactory(
|
|
|
77
47
|
routerBase as Router,
|
|
78
48
|
getPluginApi(routerBase),
|
|
79
49
|
options,
|
|
80
|
-
|
|
81
|
-
regExpCache,
|
|
50
|
+
resolvedBrowser,
|
|
82
51
|
transitionOptions,
|
|
83
52
|
shared,
|
|
84
53
|
);
|
package/src/index.ts
CHANGED
|
@@ -8,7 +8,9 @@ import type { Params, State } from "@real-router/core";
|
|
|
8
8
|
export { browserPluginFactory } from "./factory";
|
|
9
9
|
|
|
10
10
|
// Types
|
|
11
|
-
export type { BrowserPluginOptions
|
|
11
|
+
export type { BrowserPluginOptions } from "./types";
|
|
12
|
+
|
|
13
|
+
export type { Browser } from "browser-env";
|
|
12
14
|
|
|
13
15
|
// Type guards
|
|
14
16
|
export { isStateStrict as isState } from "type-guards";
|