Skip to content

Commit

Permalink
Merge pull request #119 from sima-land/38-examples-bun
Browse files Browse the repository at this point in the history
 Шаг 72 #38
  • Loading branch information
krutoo committed Mar 12, 2024
2 parents 74a8b3b + 43cb581 commit 7c6dc42
Show file tree
Hide file tree
Showing 8 changed files with 258 additions and 245 deletions.
19 changes: 12 additions & 7 deletions src/preset/bun-handler/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ import { provideAbortController } from '../isomorphic/providers/abort-controller
import { provideFetch } from '../isomorphic/providers/fetch';
import { provideReduxMiddlewareSaga } from '../isomorphic/providers/redux-middleware-saga';
import { providePageRender } from '../node-handler/providers/page-render';
import { provideFetchLogHandler } from './providers/fetch-log-handler';
import { provideFetchMiddleware } from './providers/fetch-middleware';
import { provideHandlerMain } from './providers/handler-main';
import { providePageHelmet } from './providers/page-helmet';
import { provideSpecificParams } from './providers/specific-params';
import { provideCookieStore } from './providers/cookie-store';
import { SpecificExtras } from '../server/utils/specific-extras';
import { HandlerProviders } from './providers';

/**
* Возвращает preset с зависимостями для формирования обработчика входящего http-запроса.
Expand All @@ -20,16 +25,16 @@ export function PresetBunHandler(customize?: PresetTuner) {
// http fetch
preset.set(KnownToken.Http.fetch, provideFetch);
preset.set(KnownToken.Http.Fetch.abortController, provideAbortController);
preset.set(KnownToken.Http.Fetch.cookieStore, HandlerProviders.cookieStore);
preset.set(KnownToken.Http.Fetch.middleware, HandlerProviders.fetchMiddleware);
preset.set(KnownToken.Http.Fetch.Middleware.Log.handler, HandlerProviders.fetchLogHandler);
preset.set(KnownToken.Http.Fetch.cookieStore, provideCookieStore);
preset.set(KnownToken.Http.Fetch.middleware, provideFetchMiddleware);
preset.set(KnownToken.Http.Fetch.Middleware.Log.handler, provideFetchLogHandler);

// handler
preset.set(KnownToken.Http.Handler.main, HandlerProviders.handlerMain);
preset.set(KnownToken.Http.Handler.Request.specificParams, HandlerProviders.specificParams);
preset.set(KnownToken.Http.Handler.main, provideHandlerMain);
preset.set(KnownToken.Http.Handler.Request.specificParams, provideSpecificParams);
preset.set(KnownToken.Http.Handler.Response.specificExtras, () => new SpecificExtras());
preset.set(KnownToken.Http.Handler.Page.assets, () => ({ js: '', css: '' }));
preset.set(KnownToken.Http.Handler.Page.helmet, HandlerProviders.pageHelmet);
preset.set(KnownToken.Http.Handler.Page.helmet, providePageHelmet);
preset.set(KnownToken.Http.Handler.Page.render, providePageRender);

// redux saga
Expand Down
10 changes: 10 additions & 0 deletions src/preset/bun-handler/providers/cookie-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* eslint-disable require-jsdoc, jsdoc/require-jsdoc */
import { Resolve } from '../../../di';
import { KnownToken } from '../../../tokens';
import { CookieStore, createCookieStore } from '../../../http';

export function provideCookieStore(resolve: Resolve): CookieStore {
const context = resolve(KnownToken.Http.Handler.context);

return createCookieStore(context.request.headers.get('cookie') ?? undefined);
}
22 changes: 22 additions & 0 deletions src/preset/bun-handler/providers/fetch-log-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/* eslint-disable require-jsdoc, jsdoc/require-jsdoc */
import { Resolve } from '../../../di';
import { KnownToken } from '../../../tokens';
import { LogHandler, LogHandlerFactory } from '../../../http';
import { FetchLogging } from '../../isomorphic/utils/fetch-logging';

/**
* Провайдер обработчика логирования axios.
* @param resolve Функция для получения зависимости по токену.
* @return Обработчик логирования.
*/
export function provideFetchLogHandler(resolve: Resolve): LogHandler | LogHandlerFactory {
const logger = resolve(KnownToken.logger);
const abortController = resolve(KnownToken.Http.Fetch.abortController);

const logHandler = new FetchLogging(logger);

// ВАЖНО: отключаем логирование если запрос прерван
logHandler.disabled = () => abortController.signal.aborted;

return logHandler;
}
33 changes: 33 additions & 0 deletions src/preset/bun-handler/providers/fetch-middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/* eslint-disable require-jsdoc, jsdoc/require-jsdoc */
import { Resolve } from '../../../di';
import { KnownToken } from '../../../tokens';
import { Middleware, cookie, defaultHeaders } from '../../../http';
import { getFetchErrorLogging } from '../../isomorphic/utils/get-fetch-error-logging';
import { getFetchExtraAborting } from '../../isomorphic/utils/get-fetch-extra-aborting';
import { getFetchLogging } from '../../isomorphic/utils/get-fetch-logging';
import { getForwardedHeaders } from '../../server/utils/get-forwarded-headers';

export function provideFetchMiddleware(resolve: Resolve): Middleware[] {
const config = resolve(KnownToken.Config.base);
const context = resolve(KnownToken.Http.Handler.context);
const logHandler = resolve(KnownToken.Http.Fetch.Middleware.Log.handler);
const cookieStore = resolve(KnownToken.Http.Fetch.cookieStore);
const abortController = resolve(KnownToken.Http.Fetch.abortController);

return [
// ВАЖНО: слой логирования ошибки ПЕРЕД остальными слоями чтобы не упустить ошибки выше
getFetchErrorLogging(logHandler),

// обрывание по сигналу из обработчика входящего запроса и по сигналу из конфига исходящего запроса
getFetchExtraAborting(abortController),

cookie(cookieStore),

defaultHeaders(getForwardedHeaders(config, context.request)),

// @todo tracing

// ВАЖНО: слой логирования запроса и ответа ПОСЛЕ остальных слоев чтобы использовать актуальные данные
getFetchLogging(logHandler),
];
}
151 changes: 151 additions & 0 deletions src/preset/bun-handler/providers/handler-main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/* eslint-disable require-jsdoc, jsdoc/require-jsdoc */
import { renderToString } from 'react-dom/server';
import { Resolve } from '../../../di';
import { KnownToken } from '../../../tokens';
import { ResponseError, applyMiddleware } from '../../../http';
import { PageAssets } from '../../isomorphic/types';
import { PAGE_HANDLER_EVENT_TYPE } from '../../server/constants';
import { getPageResponseFormat } from '../../server/utils/get-page-response-format';
import { HelmetContext } from '../../server/utils/regular-helmet';

export function provideHandlerMain(resolve: Resolve) {
const config = resolve(KnownToken.Config.base);
const logger = resolve(KnownToken.logger);
const assetsInit = resolve(KnownToken.Http.Handler.Page.assets);
const render = resolve(KnownToken.Http.Handler.Page.render);
const extras = resolve(KnownToken.Http.Handler.Response.specificExtras);
const Helmet = resolve(KnownToken.Http.Handler.Page.helmet);
const abortController = resolve(KnownToken.Http.Fetch.abortController);
const context = resolve(KnownToken.Http.Handler.context);

// @todo https://github.com/sima-land/isomorph/issues/69
// const cookieStore = resolve(KnownToken.Http.Fetch.cookieStore);
// const forwardedSetCookie: string[] = [];
// const unsubscribeCookieStore = cookieStore.subscribe(setCookieList => {
// forwardedSetCookie.push(...setCookieList);
// });

const getAssets = typeof assetsInit === 'function' ? assetsInit : () => assetsInit;

const elementToString = (element: JSX.Element) => {
context.events.dispatchEvent(new Event(PAGE_HANDLER_EVENT_TYPE.renderStart));
const result = renderToString(element);
context.events.dispatchEvent(new Event(PAGE_HANDLER_EVENT_TYPE.renderFinish));

return result;
};

const getResponseHTML = (jsx: React.JSX.Element, assets: PageAssets, meta: unknown) => {
const headers = new Headers();

headers.set('content-type', 'text/html');
headers.set('simaland-bundle-js', assets.js);
headers.set('simaland-bundle-css', assets.css);

if (assets.criticalJs) {
headers.set('simaland-critical-js', assets.criticalJs);
}

if (assets.criticalCss) {
headers.set('simaland-critical-css', assets.criticalCss);
}

if (meta) {
headers.set('simaland-meta', JSON.stringify(meta));
}

// ВАЖНО: DOCTYPE обязательно нужен так как влияет на то как браузер будет парсить html/css
// ВАЖНО: DOCTYPE нужен только когда отдаем полноценную страницу
if (config.env === 'development') {
return new Response(`<!DOCTYPE html>${elementToString(jsx)}`, {
headers,
});
} else {
return new Response(elementToString(jsx), {
headers,
});
}
};

const getResponseJSON = (jsx: React.JSX.Element, assets: PageAssets, meta: unknown) => {
const headers = new Headers();

headers.set('content-type', 'application/json');

return new Response(
JSON.stringify({
markup: elementToString(jsx),
bundle_js: assets.js,
bundle_css: assets.css,
critical_js: assets.criticalJs,
critical_css: assets.criticalCss,
meta,
}),
{ headers },
);
};

const handler = async (request: Request): Promise<Response> => {
try {
const assets = await getAssets();
const meta = extras.getMeta();

const jsx = (
<HelmetContext.Provider value={{ title: config.appName, assets }}>
<Helmet>{await render()}</Helmet>
</HelmetContext.Provider>
);

switch (getPageResponseFormat(request)) {
case 'html': {
return getResponseHTML(jsx, assets, meta);
}
case 'json': {
return getResponseJSON(jsx, assets, meta);
}
}
} catch (error) {
let message: string;
let statusCode = 500; // по умолчанию, если на этапе подготовки страницы что-то не так, отдаем 500

if (error instanceof Error) {
message = error.message;

if (error instanceof ResponseError) {
statusCode = error.statusCode;
}
} else {
message = String(error);
}

logger.error(error);

return new Response(message, {
status: statusCode,
});
}
};

const enhancer = applyMiddleware(
// @todo https://github.com/sima-land/isomorph/issues/69
// async (request, next) => {
// const response = await next(request);
// for (const item of forwardedSetCookie) {
// response.headers.append('set-cookie', item);
// }
// unsubscribeCookieStore();
// return response;
// },

// ВАЖНО: прерываем исходящие в рамках обработчика http-запросы
async (request, next) => {
const response = await next(request);

abortController.abort();

return response;
},
);

return enhancer(handler);
}
Loading

0 comments on commit 7c6dc42

Please sign in to comment.