UNPKG

mustard-app

Version:

个人前端微应用建设中。。。

160 lines (136 loc) 5.73 kB
/* eslint-disable no-use-before-define */ import { getAllApp, getAppFromInstance } from '../global'; import { MainMustardApp, MustardName, MustardURL } from '../typings'; import { getLocationNameByAppName } from '../utils'; import { isFunction, isMustardState, isURL } from '../utils/tools'; type ChangeState = (_state:unknown, _unused:string, _url:string | URL, _isMustard?:boolean) => void; export interface MustardStateOptions { origin?:MustardURL, // document 来源 flushed?:boolean // 是否更新文档 } export interface MustardState extends MustardStateOptions{ data?: unknown, // pushState 和 replaceState 第一个参数 index: number, // 用于计算当前state 的顺序 } export type State = { [key: string]: MustardState; } & { isMustard: 'MustardApp'; [MainMustardApp]: undefined } export function encodeState (data:unknown, appName:MustardName, options?:MustardStateOptions) { const app = getAppFromInstance(appName); const index = getStateIndex(appName) + 1; // todo origin const { flushed = false, origin = app?.state?.origin } = options ?? {}; const allAppState = getAllAppState(); return { ...allAppState, [appName]: { data, index, origin, flushed } }; } export function decodeState (appName:MustardName):MustardState|undefined { const state = history.state; if(state?.[appName]) { return state[appName]; } } export function getAllAppState () { const state = { isMustard: 'MustardApp', [MainMustardApp]: undefined } as State; getAllApp().forEach(name => { if(decodeState(name)) { state[name] = decodeState(name); } }); return state; } export function getStateIndex (appName:MustardName) { return decodeState(appName)?.index ?? 0; } export function initState (appName: MustardName, state:unknown, unused:string, url:string) { let preState = history.state; if(!isMustardState(preState)) { preState = { isMustard: 'MustardApp', [MainMustardApp]: preState }; } // eslint-disable-next-line @typescript-eslint/no-explicit-any (history.replaceState as any as ChangeState)({ ...preState, [appName]: { index: 0, origin: url } }, unused, undefined, true); } export function navigateTo (appName:MustardName, type: 'pushState'|'replaceState', flushed?:boolean) { const navigateToMehtods = function (_state:unknown, _unused:string, _url?:string | URL) { return history[type].call(history, _state, _unused, _url, true); }; const pathKey = getLocationNameByAppName(appName); return function (_state:unknown, _unused:string, _url?:string | URL) { const app = getAppFromInstance(appName); const preState = decodeState(appName); if(!_url) { // 不刷新 return navigateToMehtods(encodeState(_state, appName, { flushed: type === 'replaceState' ? preState?.flushed : flushed }), _unused); } // 处理_url:相对地址->绝对地址 const url = new URL(isURL(_url) ? _url.href : _url, app.url); const state = encodeState(_state, appName, { flushed: type === 'replaceState' ? preState?.flushed : flushed, origin: !flushed ? app.state.origin : url.href // 不刷新页面 使用当前 app.state.origin,否则使用跳转的地址做文档来源 }); const { pathname: pathnameFromLocation, search: searchFromLocation = '', hash: hashFromLocation } = location; const searchFromLocationParams = new URLSearchParams(searchFromLocation); searchFromLocationParams.set(pathKey, encodeURIComponent(url.href)); // 设置app对应的地址 const searchParams = searchFromLocationParams.toString(); app.state = state[appName]; return navigateToMehtods(state, _unused, `${pathnameFromLocation}${searchParams ? '?' + searchParams : ''}${hashFromLocation}`); }; } export function proxyHistory (appName:MustardName) { return new Proxy(history, { get (target, key) { if(key === 'pushState') { return navigateTo(appName, 'pushState'); }else if(key === 'replaceState') { return navigateTo(appName, 'replaceState'); }else if(key === 'state') { return decodeState(appName)?.data; }else if(isFunction(target[key])) { return target[key].bind(history); } return target[key]; } }); } // 修改全局history方法 export function changeHistoryPropety () { const pushState = History.prototype.pushState; const replaceState = History.prototype.replaceState; function changeState (type:'pushState'|'replaceState') { const methodState = type === 'pushState' ? pushState : replaceState; return function (_state:unknown, _unused:string, _url:string | URL, _isMustard?:boolean) { if(_isMustard) { return methodState.call(this, _state, _unused, _url); }else{ const allAppState = getAllAppState(); return methodState.call(this, { ...allAppState, [MainMustardApp]: _state }, _unused, _url); } }; } History.prototype.pushState = changeState('pushState'); History.prototype.replaceState = changeState('replaceState'); }