UNPKG

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
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; // }; // } };