mustard-app
Version:
个人前端微应用建设中。。。
160 lines (136 loc) • 5.73 kB
text/typescript
/* 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');
}