lupine.web
Version:
lupine.web is a extremely fast, small size and lightweight frontend framework, using React TSX syntax.
183 lines (163 loc) • 6.51 kB
text/typescript
import { generateAllGlobalStyles } from './bind-styles';
import { getCurrentTheme, updateTheme } from './bind-theme';
import { mountInnerComponent } from './mount-component';
import { renderComponentAsync } from './render-component';
// import { callPageResetEvent } from './page-reset-events';
import { callDomLoadedEvent } from './page-loaded-event';
import { IRequestContextProps, IToClientDelivery } from '../models';
import { getMetaDataObject, getMetaDataTags, getPageTitle } from './bind-meta';
import { initWebEnv } from '../lib/web-env';
import { PageProps, PageResultType } from '../models/page-props';
import { setRenderPageProps } from './export-lupine';
import { isFrontEnd } from '../lib/is-frontend';
import { WebConfig } from '../lib/web-config';
import { bindRequestContext } from './use-request-context';
import { CssProps } from '../jsx';
import { _lupineJs } from './lupine-instance';
const renderTargetPage = async (props: PageProps, renderPartPage: boolean) => {
if (typeof _lupineJs.router !== 'function') {
return _lupineJs.router.handleRoute(props.url, props, renderPartPage);
}
return await _lupineJs.router(props);
};
// this is called by server side for SSR (server-side-rendering)
const generatePage = async (props: PageProps, toClientDelivery: IToClientDelivery): Promise<PageResultType> => {
const gThis = globalThis as any;
if (!gThis.__SSR_ALS_PROPS__) {
throw new Error('__SSR_ALS_PROPS__ is not defined');
}
// Provide isolated tracking for this branch of execution against the shared V8 script Object memory
// not thread-safe
// setRenderPageProps(props);
// can't do bindRequestContext, because it's not thread-safe
// bindRequestContext(() => toClientDelivery.getRequestContext());
initWebEnv(toClientDelivery.getWebEnv());
WebConfig.initFromData(toClientDelivery.getWebSetting());
// initWebSetting(toClientDelivery.getWebSetting());
// callPageResetEvent();
callDomLoadedEvent();
const jsxNodes = await renderTargetPage(props, false);
if (!jsxNodes || !jsxNodes.props) {
return {
content: `Unexpected url: ${props.url}`,
title: '',
metaData: '',
globalCss: '',
themeName: getCurrentTheme().themeName,
};
}
await renderComponentAsync(jsxNodes.type, jsxNodes.props);
const currentTheme = getCurrentTheme();
const cssText = generateAllGlobalStyles();
const content = jsxNodes.props._html.join('');
return {
content,
title: getPageTitle(),
metaData: getMetaDataTags(),
globalCss: cssText,
themeName: currentTheme.themeName,
// metaObject: getMetaDataObject(),
};
};
_lupineJs.generatePage = generatePage;
const _initSaved = {
pageInitialized: false,
appInitialized: false,
};
// this is called in the FE when the document is loaded
// to avoid circular reference, bindLinks can't call initializePage directly
export const initializePage = async (newUrl?: string) => {
// Default context for Browser (singleton)
const defaultContext: IRequestContextProps = {
pageTitle: '',
metaData: {},
themeName: '',
langName: '',
globalStyles: new Map<string, { topUniqueClassName: string; noTopClassName: boolean; style: CssProps }>(),
globalStyleIds: new Map<CssProps, string>(),
coreData: {}, // for core development
devData: {}, // for secondary development
};
bindRequestContext(() => defaultContext);
const json = document.querySelector('#web-env')?.textContent;
if (json) {
initWebEnv(JSON.parse(json));
} else {
console.warn('web-env is not defined');
}
const json2 = document.querySelector('#web-setting')?.textContent;
if (json2) {
WebConfig.initFromData(JSON.parse(json2));
} else {
console.warn('web-setting is not defined');
}
const currentPageInitialized = _initSaved.pageInitialized;
_initSaved.pageInitialized = true;
console.log('initializePage: ', newUrl);
if (newUrl) {
window.history.pushState({ urlPath: newUrl }, '', newUrl);
// prevents browser from storing history with each change:
// window.history.replaceState({ html: '', pageTitle: newUrl }, '', newUrl);
}
const splitUrl = newUrl ? newUrl.split('?') : [];
const url = splitUrl[0] || document.location.pathname;
const queryString = splitUrl[1] || document.location.search;
const props: PageProps = {
url,
// urlSections: url.split('/').filter((i) => !!i),
query: Object.fromEntries(new URLSearchParams(queryString)), // new URLSearchParams(queryString),
urlParameters: {},
renderPageFunctions: _lupineJs.renderPageFunctions,
};
setRenderPageProps(props);
// can only call callPageLoadedEvent once after page refreshed
!currentPageInitialized && callDomLoadedEvent();
const jsxNodes = await renderTargetPage(props, currentPageInitialized);
if (jsxNodes === null) return;
if (!jsxNodes || !jsxNodes.props) {
document.querySelector('.lupine-root')!.innerHTML = `Error happened or unexpected url: ${url}`;
return;
}
// generateAllGlobalStyles will be updated directly in Browser
await mountInnerComponent('.lupine-root', jsxNodes);
updateTheme(getCurrentTheme().themeName);
// title
document.title = getPageTitle();
const metaData = getMetaDataObject();
// meta data?
};
_lupineJs.initializePage = initializePage;
// this is called in bindRouter to avoid tree shaking
export const initializeApp = () => {
if (isFrontEnd()) {
if (_initSaved.appInitialized) return;
_initSaved.appInitialized = true;
addEventListener('popstate', (event) => {
initializePage();
});
addEventListener('load', (event) => {
let redirect = new URLSearchParams(window.location.search).get('redirect');
if (!redirect) {
redirect = new URLSearchParams(window.location.hash.substring(1)).get('redirect');
}
initializePage(redirect || undefined);
});
}
// for SSR, it exports _lupineJs function for the server to call
// this should be loaded in a sandbox
if (typeof globalThis !== 'undefined') {
const gThis = globalThis as any;
if (gThis._lupineJs === null) {
gThis._lupineJs = () => {
return _lupineJs;
};
}
}
// if (typeof exports !== 'undefined') {
// // ignore esbuild's warnings:
// // The CommonJS "exports" variable is treated as a global variable in an ECMAScript module and may not work as expected [commonjs-variable-in-esm]
// exports._lupineJs = () => {
// return _lupineJs;
// };
// }
};