@dr.pogodin/react-utils 1.43.2 → 1.43.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/setup.js +3 -3
- package/build/development/server/renderer.js +3 -3
- package/build/development/server/renderer.js.map +1 -1
- package/build/development/shared/utils/jest/index.js +15 -2
- package/build/development/shared/utils/jest/index.js.map +1 -1
- package/build/production/server/renderer.js +3 -3
- package/build/production/server/renderer.js.map +1 -1
- package/build/production/shared/utils/jest/index.js +7 -2
- package/build/production/shared/utils/jest/index.js.map +1 -1
- package/build/types-code/shared/utils/jest/index.d.ts +4 -3
- package/config/eslint/default.mjs +5 -1
- package/package.json +7 -7
- package/src/server/renderer.tsx +3 -3
- package/src/shared/utils/jest/index.ts +23 -5
- package/types.d.ts +8 -2
package/bin/setup.js
CHANGED
|
@@ -105,7 +105,7 @@ function adoptDevDependencies(donorData, hostData) {
|
|
|
105
105
|
|
|
106
106
|
deps = Object.entries(deps).map(generateTargetPackage);
|
|
107
107
|
if (deps.length) {
|
|
108
|
-
const args = ['install', '--save-dev'].concat(deps);
|
|
108
|
+
const args = ['install', '--save-dev', '--save-exact'].concat(deps);
|
|
109
109
|
if (force) args.push('--force');
|
|
110
110
|
if (verbose) args.push('--verbose');
|
|
111
111
|
spawnSync(NPM_COMMAND, args, {
|
|
@@ -155,7 +155,7 @@ function getPackageJson(packageName = '@dr.pogodin/react-utils') {
|
|
|
155
155
|
function install(library) {
|
|
156
156
|
let name = library;
|
|
157
157
|
if (!name.includes('@', 1)) name += '@latest';
|
|
158
|
-
const args = ['install', '--save', name];
|
|
158
|
+
const args = ['install', '--save', '--save-exact', name];
|
|
159
159
|
|
|
160
160
|
if (force) args.push('--force');
|
|
161
161
|
if (verbose) {
|
|
@@ -186,7 +186,7 @@ function updateProdDependencies(donorData, hostData) {
|
|
|
186
186
|
});
|
|
187
187
|
if (deps.length) {
|
|
188
188
|
deps = deps.map(generateTargetPackage);
|
|
189
|
-
const args = ['install', '--save'].concat(deps);
|
|
189
|
+
const args = ['install', '--save', '--save-exact'].concat(deps);
|
|
190
190
|
if (force) args.push('--force');
|
|
191
191
|
if (verbose) args.push('--verbose');
|
|
192
192
|
spawnSync(NPM_COMMAND, args, { stdio: 'inherit' });
|
|
@@ -443,11 +443,11 @@ function factory(webpackConfig, options) {
|
|
|
443
443
|
<html lang="en">
|
|
444
444
|
<head>
|
|
445
445
|
${grouppedExtraScripts[SCRIPT_LOCATIONS.HEAD_OPEN]}
|
|
446
|
-
${helmet
|
|
447
|
-
${helmet
|
|
446
|
+
${helmet?.title.toString() ?? ''}
|
|
447
|
+
${helmet?.meta.toString() ?? ''}
|
|
448
448
|
<meta name="theme-color" content="#FFFFFF">
|
|
449
449
|
${manifestLink}
|
|
450
|
-
${styleChunkString}
|
|
450
|
+
${helmet?.link.toString() ?? ''}${styleChunkString}
|
|
451
451
|
${faviconLink}
|
|
452
452
|
<meta charset="utf-8">
|
|
453
453
|
<meta
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.js","names":["_fs","_interopRequireDefault","require","_path","_stream","_zlib","_winston","_reactGlobalState","_jsUtils","_lodash","_config","_nodeForge","_static","_reactHelmet","_reactRouter","_serializeJavascript","_buildInfo","_Cache","_jsxRuntime","sanitizedConfig","omit","config","SCRIPT_LOCATIONS","exports","ServerSsrContext","SsrContext","chunks","status","constructor","req","chunkGroups","initialState","cloneDeep","getBuildInfo","context","url","path","resolve","JSON","parse","fs","readFileSync","readChunkGroupsJson","buildDir","res","prepareCipher","key","Promise","reject","forge","random","getBytes","err","iv","cipher","createCipher","start","isBrotliAcceptable","acceptable","get","ops","split","op","type","priority","trim","parseFloat","groupExtraScripts","scripts","BODY_OPEN","DEFAULT","HEAD_OPEN","script","isString","code","location","Error","newDefaultLogger","defaultLogLevel","format","transports","winston","createLogger","combine","splat","timestamp","colorize","printf","level","message","stack","rest","Object","keys","length","stringify","Console","factory","webpackConfig","options","defaults","clone","beforeRender","maxSsrRounds","ssrTimeout","staticCacheSize","logger","defaultLoggerLogLevel","buildInfo","setBuildInfo","publicPath","outputPath","output","manifestLink","existsSync","cache","staticCacheController","Cache","CHUNK_GROUPS","next","set","cookie","csrfToken","cacheRef","data","buffer","noCsp","send","done","failed","brotliDecompress","error","html","h","toString","regex","RegExp","nonce","replace","configToInject","extraScripts","all","helmet","webpackStats","locals","mapValues","toJson","namedChunkGroups","item","assets","map","name","App","Application","appHtmlMarkup","ssrContext","stream","ssrStart","Date","now","App2","renderPass","helmetContext","prelude","prerenderToNodeStream","jsx","GlobalStateProvider","state","children","StaticRouter","HelmetProvider","onError","ssrRound","bailed","dirty","timeout","race","allSettled","pending","timer","then","logMsg","log","ready","pipe","Writable","destroy","write","chunk","_","payload","serializeJs","CONFIG","ISTATE","ignoreFunction","unsafe","update","util","createBuffer","finish","INJ","encode64","chunkSet","Set","forEach","asset","add","styleChunkString","scriptChunkString","endsWith","grouppedExtraScripts","faviconLink","favicon","title","meta","brotliCompress","b"],"sources":["../../../src/server/renderer.tsx"],"sourcesContent":["/**\n * ExpressJS middleware for server-side rendering of a ReactJS app.\n */\n\nimport fs from 'fs';\nimport path from 'path';\n\nimport type { Request, RequestHandler } from 'express';\nimport type { ComponentType } from 'react';\nimport { Writable } from 'stream';\nimport { brotliCompress, brotliDecompress } from 'zlib';\nimport winston from 'winston';\n\nimport { GlobalStateProvider, SsrContext } from '@dr.pogodin/react-global-state';\nimport { timer } from '@dr.pogodin/js-utils';\n\nimport {\n clone,\n cloneDeep,\n defaults,\n isString,\n get,\n mapValues,\n omit,\n} from 'lodash';\n\nimport config from 'config';\nimport forge from 'node-forge';\n\nimport { prerenderToNodeStream } from 'react-dom/static';\nimport { HelmetProvider, type HelmetDataContext } from '@dr.pogodin/react-helmet';\nimport { StaticRouter } from 'react-router';\nimport serializeJs from 'serialize-javascript';\nimport { type BuildInfoT, setBuildInfo } from 'utils/isomorphy/buildInfo';\n\nimport type { ChunkGroupsT, SsrContextT } from 'utils/globalState';\n\nimport type { Configuration, Stats } from 'webpack';\n\nimport Cache from './Cache';\n\nconst sanitizedConfig = omit(config, 'SECRET');\n\n// Note: These type definitions for logger are copied from Winston logger,\n// then simplified to make it easier to fit an alternative logger into this\n// interface.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\ninterface LogMethodI {\n // eslint-disable-next-line @typescript-eslint/prefer-function-type\n (level: string, message: string, ...meta: unknown[]): void;\n}\n\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\ninterface LeveledLogMethodI {\n // eslint-disable-next-line @typescript-eslint/prefer-function-type\n (message: string, ...meta: unknown[]): void;\n}\n\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface LoggerI {\n debug: LeveledLogMethodI;\n error: LeveledLogMethodI;\n info: LeveledLogMethodI;\n log: LogMethodI;\n warn: LeveledLogMethodI;\n}\n\nexport enum SCRIPT_LOCATIONS {\n BODY_OPEN = 'BODY_OPEN',\n DEFAULT = 'DEFAULT',\n HEAD_OPEN = 'HEAD_OPEN',\n}\n\nexport class ServerSsrContext<StateT>\n extends SsrContext<StateT>\n implements SsrContextT<StateT> {\n chunkGroups: ChunkGroupsT;\n\n chunks: string[] = [];\n\n req: Request;\n\n status: number = 200;\n\n constructor(\n req: Request,\n chunkGroups: ChunkGroupsT,\n initialState?: StateT,\n ) {\n super(cloneDeep(initialState) ?? ({} as StateT));\n this.chunkGroups = chunkGroups;\n this.req = req;\n }\n}\n\ntype ScriptT = {\n code: string;\n location: SCRIPT_LOCATIONS;\n};\n\n/**\n * Reads build-time information about the app. This information is generated\n * by our standard Webpack config for apps, and it is written into\n * \".build-info\" file in the context folder specified in Webpack config.\n * At the moment, that file contains build timestamp and a random 32-bit key,\n * suitable for cryptographical use.\n * @param context Webpack context path used during the build.\n * @return Resolves to the build-time information.\n */\nfunction getBuildInfo(context: string) {\n const url = path.resolve(context, '.build-info');\n return JSON.parse(fs.readFileSync(url, 'utf8')) as BuildInfoT;\n}\n\n/**\n * Attempts to read from disk the named chunk groups mapping generated\n * by Webpack during the compilation.\n * It will not work for development builds, where these stats should be captured\n * via compilator callback.\n * @param buildDir\n * @return\n */\nfunction readChunkGroupsJson(buildDir: string) {\n const url = path.resolve(buildDir, '__chunk_groups__.json');\n let res;\n try {\n res = JSON.parse(fs.readFileSync(url, 'utf8')) as Record<string, string[]>;\n } catch {\n // TODO: Should we message the error here somehow?\n res = null;\n }\n return res;\n}\n\n/**\n * Prepares a new Cipher for data encryption.\n * @param key Encryption key (32-bit random key is expected, see\n * node-forge documentation, in case of doubts).\n * @return Resolves to the object with two fields:\n * 1. cipher - a new Cipher, ready for encryption;\n * 2. iv - initial vector used by the cipher.\n */\nasync function prepareCipher(key: string): Promise<{\n cipher: forge.cipher.BlockCipher;\n iv: string;\n}> {\n return new Promise((resolve, reject) => {\n forge.random.getBytes(32, (err, iv) => {\n if (err) reject(err);\n else {\n const cipher = forge.cipher.createCipher('AES-CBC', key);\n cipher.start({ iv });\n resolve({ cipher, iv });\n }\n });\n });\n}\n\n/**\n * Given an incoming HTTP requests, it deduces whether Brotli-encoded responses\n * are acceptable to the caller.\n * @param req\n */\nexport function isBrotliAcceptable(req: Request): boolean {\n const acceptable = req.get('accept-encoding');\n if (acceptable) {\n const ops = acceptable.split(',');\n for (const op of ops) {\n const [type, priority] = op.trim().split(';q=');\n if ((type === '*' || type === 'br')\n && (!priority || parseFloat(priority) > 0)) {\n return true;\n }\n }\n }\n return false;\n}\n\n/**\n * Given an array of extra script strings / objects, it returns an object with\n * arrays of scripts to inject in different HTML template locations. During\n * the script groupping it also filters out any empty scripts.\n * @param {({\n * code: string;\n * location: string;\n * }|string)[]} [scripts=[]]\n * @return {{\n * BODY_OPEN: string[];\n * DEFAULT: string[];\n * HEAD_OPEN: string[];\n * }}\n */\nfunction groupExtraScripts(scripts: Array<string | ScriptT> = []) {\n const res = {\n [SCRIPT_LOCATIONS.BODY_OPEN]: '',\n [SCRIPT_LOCATIONS.DEFAULT]: '',\n [SCRIPT_LOCATIONS.HEAD_OPEN]: '',\n };\n for (const script of scripts) {\n if (isString(script)) {\n if (script) res[SCRIPT_LOCATIONS.DEFAULT] += script;\n } else if (script.code) {\n if (script.location in res) res[script.location] += script.code;\n else throw Error(`Invalid location \"${script.location}\"`);\n }\n }\n return res;\n}\n\n/**\n * Creates a new default (Winston) logger.\n * @param {object} [options={}]\n * @param {string} [options.defaultLogLevel='info']\n * @return {object}\n */\nexport function newDefaultLogger({\n defaultLogLevel = 'info',\n} = {}): winston.Logger {\n const { format, transports } = winston;\n return winston.createLogger({\n format: format.combine(\n format.splat(),\n format.timestamp(),\n format.colorize(),\n format.printf(\n ({\n level,\n message,\n timestamp,\n stack,\n ...rest\n }) => {\n let res = `${level}\\t(at ${timestamp as string}):\\t${message as string}`;\n if (Object.keys(rest).length) {\n res += `\\n${JSON.stringify(rest, null, 2)}`;\n }\n if (stack) res += `\\n${stack as string}`;\n return res;\n },\n ),\n ),\n level: defaultLogLevel,\n transports: [new transports.Console()],\n });\n}\n\nexport type ConfigT = {\n [key: string]: ConfigT | string;\n};\n\nexport type BeforeRenderResT = {\n configToInject?: ConfigT;\n extraScripts?: Array<ScriptT | string>;\n initialState?: unknown;\n};\n\nexport type BeforeRenderT =\n(req: Request, config: ConfigT) => BeforeRenderResT | Promise<BeforeRenderResT>;\n\ntype CacheRefT = {\n key: string;\n maxage?: number;\n};\n\nexport type OptionsT = {\n Application?: ComponentType;\n beforeRender?: BeforeRenderT;\n buildInfo?: BuildInfoT;\n defaultLoggerLogLevel?: string;\n favicon?: string;\n logger?: LoggerI;\n maxSsrRounds?: number;\n noCsp?: boolean;\n staticCacheSize?: number;\n ssrTimeout?: number;\n staticCacheController?: (req: Request) => CacheRefT | null | undefined;\n};\n\n/**\n * Creates the middleware.\n * @param webpackConfig\n * @param options Additional options:\n * @param [options.Application] The root ReactJS component of\n * the app to use for the server-side rendering. When not provided\n * the server-side rendering is disabled.\n * @param [options.buildInfo] \"Build info\" object to use. If provided,\n * it will be used, instead of trying to load from the filesystem the one\n * generated by the Webpack build. It is intended for test environments,\n * where passing this stuff via file system is no bueno.\n * @param [options.favicon] `true` will include favicon\n * link into the rendered HTML templates.\n * @param [options.noCsp] `true` means that no\n * Content-Security-Policy (CSP) is used by server, thus the renderer\n * may cut a few corners.\n * @param [options.maxSsrRounds=10] Maximum number of SSR rounds.\n * @param [options.ssrTimeout=1000] SSR timeout in milliseconds,\n * defaults to 1 second.\n * @param [options.staticCacheSize=1.e7] The maximum\n * static cache size in bytes. Defaults to ~10 MB.\n * @param [options.staticCacheController] When given, it activates,\n * and controls the static caching of generated HTML markup. When this function\n * is provided, on each incoming request it is triggered with the request\n * passed in as the argument. To attempt to serve the response from the cache\n * it should return the object with the following fields:\n * - `key: string` – the cache key for the response;\n * - `maxage?: number` – the maximum age of cached result in ms.\n * If undefined - infinite age is assumed.\n * @return Created middleware.\n */\nexport default function factory(\n webpackConfig: Configuration,\n options: OptionsT,\n): RequestHandler {\n const ops: OptionsT = defaults(clone(options), {\n beforeRender: async () => Promise.resolve({}),\n maxSsrRounds: 10,\n ssrTimeout: 1000,\n staticCacheSize: 1.e7,\n });\n\n // Note: in normal use the default logger is created and set in the root\n // server function, and this initialization is for testing uses, where\n // renderer is imported directly.\n ops.logger ??= newDefaultLogger({\n defaultLogLevel: ops.defaultLoggerLogLevel,\n });\n\n const buildInfo = ops.buildInfo ?? getBuildInfo(webpackConfig.context!);\n setBuildInfo(buildInfo);\n\n // publicPath from webpack.output has a trailing slash at the end.\n const { publicPath, path: outputPath } = webpackConfig.output!;\n\n const manifestLink = fs.existsSync(`${outputPath}/manifest.json`)\n ? `<link rel=\"manifest\" href=\"${publicPath as string}manifest.json\">` : '';\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-definitions\n interface BufferWithNonce extends Buffer {\n nonce: string;\n }\n\n const cache = ops.staticCacheController\n ? new Cache<{\n buffer: BufferWithNonce;\n status: number;\n }>(ops.staticCacheSize!)\n : null;\n\n const CHUNK_GROUPS = readChunkGroupsJson(outputPath!);\n\n // TODO: Look at it later.\n // eslint-disable-next-line complexity\n return async (req, res, next) => {\n try {\n // Ensures any caches always revalidate HTML markup before reuse.\n res.set('Cache-Control', 'no-cache');\n\n res.cookie('csrfToken', req.csrfToken());\n\n let cacheRef: CacheRefT | null | undefined;\n if (cache) {\n cacheRef = ops.staticCacheController!(req);\n if (cacheRef) {\n const data = cache.get(cacheRef);\n if (data !== null) {\n const { buffer, status } = data;\n if (ops.noCsp && isBrotliAcceptable(req)) {\n res.set('Content-Type', 'text/html');\n res.set('Content-Encoding', 'br');\n if (status !== 200) res.status(status);\n res.send(buffer);\n } else {\n await new Promise<void>((done, failed) => {\n brotliDecompress(buffer, (error, html) => {\n if (error) failed(error);\n else {\n let h = html.toString();\n if (!ops.noCsp) {\n // TODO: Starting from Node v15 we'll be able to use string's\n // .replaceAll() method instead relying on reg. expression for\n // global matching.\n const regex = new RegExp(buffer.nonce, 'g');\n\n // TODO: It should be implemented more careful.\n h = h.replace(regex, (req as unknown as {\n nonce: string;\n }).nonce);\n }\n if (status !== 200) res.status(status);\n res.send(h);\n done();\n }\n });\n });\n }\n return;\n }\n }\n }\n\n const [{\n configToInject,\n extraScripts,\n initialState,\n }, {\n cipher,\n iv,\n }] = await Promise.all([\n ops.beforeRender!(req, sanitizedConfig as unknown as ConfigT),\n prepareCipher(buildInfo.key),\n ]);\n\n let helmet: HelmetDataContext['helmet'];\n\n // Gets the mapping between code chunk names and their asset files.\n // These data come from the Webpack compilation, either from the stats\n // attached to the request (in dev mode), or from a file output during\n // the build (in prod mode).\n let chunkGroups: ChunkGroupsT;\n const webpackStats = get(res.locals, 'webpack.devMiddleware.stats') as Stats | undefined;\n if (webpackStats) {\n chunkGroups = mapValues(\n webpackStats.toJson({\n all: false,\n chunkGroups: true,\n }).namedChunkGroups,\n (item) => item.assets?.map(({ name }: { name: string }) => name)\n ?? [],\n );\n } else if (CHUNK_GROUPS) chunkGroups = CHUNK_GROUPS;\n else chunkGroups = {};\n\n /* Optional server-side rendering. */\n const App = ops.Application;\n let appHtmlMarkup: string = '';\n const ssrContext = new ServerSsrContext(req, chunkGroups, initialState);\n let stream: NodeJS.ReadableStream;\n if (App) {\n const ssrStart = Date.now();\n\n // TODO: Somehow, without it TS does not realise that\n // App has been checked to exist.\n const App2 = App;\n\n const renderPass = async () => {\n ssrContext.chunks = [];\n\n // TODO: prerenderToNodeStream has (abort) \"signal\" option,\n // and we should wire it up to the SSR timeout below.\n const helmetContext = {} as HelmetDataContext;\n const { prelude } = await prerenderToNodeStream(\n <GlobalStateProvider\n initialState={ssrContext.state}\n ssrContext={ssrContext}\n >\n <StaticRouter location={req.url}>\n <HelmetProvider context={helmetContext}>\n <App2 />\n </HelmetProvider>\n </StaticRouter>\n </GlobalStateProvider>,\n { onError: (error) => {\n throw error;\n } },\n );\n ({ helmet } = helmetContext);\n\n return prelude;\n };\n\n let ssrRound = 0;\n let bailed = false;\n for (; ssrRound < ops.maxSsrRounds!; ++ssrRound) {\n stream = await renderPass();\n\n if (!ssrContext.dirty) break;\n\n const timeout = ops.ssrTimeout! + ssrStart - Date.now();\n bailed = timeout <= 0 || !await Promise.race([\n Promise.allSettled(ssrContext.pending),\n timer(timeout).then(() => false),\n ]);\n if (bailed) break;\n }\n\n let logMsg;\n if (ssrContext.dirty) {\n // NOTE: In the case of incomplete SSR one more round is necessary\n // to ensure the correct hydration when some pending promises have\n // resolved and placed their data into the initial global state.\n stream = await renderPass();\n\n logMsg = bailed ? `SSR timed out after ${ops.ssrTimeout} second(s)`\n : `SSR bailed out after ${ops.maxSsrRounds} round(s)`;\n } else logMsg = `SSR completed in ${ssrRound + 1} round(s)`;\n\n ops.logger!.log(ssrContext.dirty ? 'warn' : 'info', logMsg);\n\n await new Promise((ready) => {\n stream!.pipe(new Writable({\n destroy: ready,\n write: (chunk: { toString: () => string }, _, done) => {\n appHtmlMarkup += chunk.toString();\n done();\n },\n }));\n });\n }\n\n /* Encrypts data to be injected into HTML.\n * Keep in mind, that this encryption is no way secure: as the JS bundle\n * contains decryption key and is able to decode it at the client side.\n * Hovewer, for a number of reasons, encryption of injected data is still\n * better than injection of a plain text. */\n const payload = serializeJs({\n CHUNK_GROUPS: chunkGroups,\n CONFIG: configToInject ?? sanitizedConfig,\n ISTATE: ssrContext.state,\n }, {\n ignoreFunction: true,\n unsafe: true,\n });\n cipher.update(forge.util.createBuffer(payload, 'utf8'));\n cipher.finish();\n const INJ = forge.util.encode64(`${iv}${cipher.output.data}`);\n\n const chunkSet = new Set<string>();\n\n // TODO: \"main\" chunk has to be added explicitly,\n // because unlike all other chunks they are not managed by <CodeSplit>\n // component, thus they are not added to the ssrContext.chunks\n // automatically. Actually, names of these entry chunks should be\n // read from Wepback config, as the end user may customize them,\n // remove or add other entry points, but it requires additional\n // efforts to figure out how to automatically order them right,\n // thus for now this handles the default config.\n [\n 'main',\n ...ssrContext.chunks,\n ].forEach((chunk) => {\n const assets = chunkGroups[chunk];\n if (assets) assets.forEach((asset) => chunkSet.add(asset));\n });\n\n let styleChunkString = '';\n let scriptChunkString = '';\n chunkSet.forEach((chunk) => {\n if (chunk.endsWith('.css')) {\n styleChunkString += `<link href=\"${publicPath as string}${chunk}\" rel=\"stylesheet\">`;\n } else if (\n chunk.endsWith('.js')\n // In dev mode HMR adds JS updates into asset arrays,\n // and they (updates) should be ignored.\n && !chunk.endsWith('.hot-update.js')\n ) {\n scriptChunkString += `<script src=\"${publicPath as string}${chunk}\" type=\"application/javascript\"></script>`;\n }\n });\n\n const grouppedExtraScripts = groupExtraScripts(extraScripts);\n\n const faviconLink = ops.favicon\n ? '<link rel=\"shortcut icon\" href=\"/favicon.ico\">'\n : '';\n\n const html = `<!DOCTYPE html>\n <html lang=\"en\">\n <head>\n ${grouppedExtraScripts[SCRIPT_LOCATIONS.HEAD_OPEN]}\n ${helmet ? helmet.title.toString() : ''}\n ${helmet ? helmet.meta.toString() : ''}\n <meta name=\"theme-color\" content=\"#FFFFFF\">\n ${manifestLink}\n ${styleChunkString}\n ${faviconLink}\n <meta charset=\"utf-8\">\n <meta\n content=\"width=device-width,initial-scale=1.0\"\n name=\"viewport\"\n >\n <meta itemprop=\"drpruinj\" content=\"${INJ}\">\n </head>\n <body>\n ${grouppedExtraScripts[SCRIPT_LOCATIONS.BODY_OPEN]}\n <div id=\"react-view\">${appHtmlMarkup}</div>\n ${scriptChunkString}\n ${grouppedExtraScripts[SCRIPT_LOCATIONS.DEFAULT]}\n </body>\n </html>`;\n\n const status = ssrContext.status || 200;\n if (status !== 200) res.status(status);\n\n if (cacheRef && status < 500) {\n // Note: waiting for the caching to complete is not strictly necessary,\n // but it greately simplifies testing, and error reporting.\n await new Promise<void>((done, failed) => {\n brotliCompress(html, (error, buffer) => {\n const b = buffer as BufferWithNonce;\n if (error) failed(error);\n else {\n b.nonce = (req as unknown as {\n nonce: string;\n }).nonce;\n cache!.add({ buffer: b, status }, cacheRef.key, buffer.length);\n done();\n }\n });\n });\n }\n\n // Note: as caching code above may throw in some cases, sending response\n // before it completes will likely hide the error, making it difficult\n // to debug. Thus, at least for now, lets send response after it.\n res.send(html);\n } catch (error) {\n next(error);\n }\n };\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"renderer.js","names":["_fs","_interopRequireDefault","require","_path","_stream","_zlib","_winston","_reactGlobalState","_jsUtils","_lodash","_config","_nodeForge","_static","_reactHelmet","_reactRouter","_serializeJavascript","_buildInfo","_Cache","_jsxRuntime","sanitizedConfig","omit","config","SCRIPT_LOCATIONS","exports","ServerSsrContext","SsrContext","chunks","status","constructor","req","chunkGroups","initialState","cloneDeep","getBuildInfo","context","url","path","resolve","JSON","parse","fs","readFileSync","readChunkGroupsJson","buildDir","res","prepareCipher","key","Promise","reject","forge","random","getBytes","err","iv","cipher","createCipher","start","isBrotliAcceptable","acceptable","get","ops","split","op","type","priority","trim","parseFloat","groupExtraScripts","scripts","BODY_OPEN","DEFAULT","HEAD_OPEN","script","isString","code","location","Error","newDefaultLogger","defaultLogLevel","format","transports","winston","createLogger","combine","splat","timestamp","colorize","printf","level","message","stack","rest","Object","keys","length","stringify","Console","factory","webpackConfig","options","defaults","clone","beforeRender","maxSsrRounds","ssrTimeout","staticCacheSize","logger","defaultLoggerLogLevel","buildInfo","setBuildInfo","publicPath","outputPath","output","manifestLink","existsSync","cache","staticCacheController","Cache","CHUNK_GROUPS","next","set","cookie","csrfToken","cacheRef","data","buffer","noCsp","send","done","failed","brotliDecompress","error","html","h","toString","regex","RegExp","nonce","replace","configToInject","extraScripts","all","helmet","webpackStats","locals","mapValues","toJson","namedChunkGroups","item","assets","map","name","App","Application","appHtmlMarkup","ssrContext","stream","ssrStart","Date","now","App2","renderPass","helmetContext","prelude","prerenderToNodeStream","jsx","GlobalStateProvider","state","children","StaticRouter","HelmetProvider","onError","ssrRound","bailed","dirty","timeout","race","allSettled","pending","timer","then","logMsg","log","ready","pipe","Writable","destroy","write","chunk","_","payload","serializeJs","CONFIG","ISTATE","ignoreFunction","unsafe","update","util","createBuffer","finish","INJ","encode64","chunkSet","Set","forEach","asset","add","styleChunkString","scriptChunkString","endsWith","grouppedExtraScripts","faviconLink","favicon","title","meta","link","brotliCompress","b"],"sources":["../../../src/server/renderer.tsx"],"sourcesContent":["/**\n * ExpressJS middleware for server-side rendering of a ReactJS app.\n */\n\nimport fs from 'fs';\nimport path from 'path';\n\nimport type { Request, RequestHandler } from 'express';\nimport type { ComponentType } from 'react';\nimport { Writable } from 'stream';\nimport { brotliCompress, brotliDecompress } from 'zlib';\nimport winston from 'winston';\n\nimport { GlobalStateProvider, SsrContext } from '@dr.pogodin/react-global-state';\nimport { timer } from '@dr.pogodin/js-utils';\n\nimport {\n clone,\n cloneDeep,\n defaults,\n isString,\n get,\n mapValues,\n omit,\n} from 'lodash';\n\nimport config from 'config';\nimport forge from 'node-forge';\n\nimport { prerenderToNodeStream } from 'react-dom/static';\nimport { HelmetProvider, type HelmetDataContext } from '@dr.pogodin/react-helmet';\nimport { StaticRouter } from 'react-router';\nimport serializeJs from 'serialize-javascript';\nimport { type BuildInfoT, setBuildInfo } from 'utils/isomorphy/buildInfo';\n\nimport type { ChunkGroupsT, SsrContextT } from 'utils/globalState';\n\nimport type { Configuration, Stats } from 'webpack';\n\nimport Cache from './Cache';\n\nconst sanitizedConfig = omit(config, 'SECRET');\n\n// Note: These type definitions for logger are copied from Winston logger,\n// then simplified to make it easier to fit an alternative logger into this\n// interface.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\ninterface LogMethodI {\n // eslint-disable-next-line @typescript-eslint/prefer-function-type\n (level: string, message: string, ...meta: unknown[]): void;\n}\n\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\ninterface LeveledLogMethodI {\n // eslint-disable-next-line @typescript-eslint/prefer-function-type\n (message: string, ...meta: unknown[]): void;\n}\n\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface LoggerI {\n debug: LeveledLogMethodI;\n error: LeveledLogMethodI;\n info: LeveledLogMethodI;\n log: LogMethodI;\n warn: LeveledLogMethodI;\n}\n\nexport enum SCRIPT_LOCATIONS {\n BODY_OPEN = 'BODY_OPEN',\n DEFAULT = 'DEFAULT',\n HEAD_OPEN = 'HEAD_OPEN',\n}\n\nexport class ServerSsrContext<StateT>\n extends SsrContext<StateT>\n implements SsrContextT<StateT> {\n chunkGroups: ChunkGroupsT;\n\n chunks: string[] = [];\n\n req: Request;\n\n status: number = 200;\n\n constructor(\n req: Request,\n chunkGroups: ChunkGroupsT,\n initialState?: StateT,\n ) {\n super(cloneDeep(initialState) ?? ({} as StateT));\n this.chunkGroups = chunkGroups;\n this.req = req;\n }\n}\n\ntype ScriptT = {\n code: string;\n location: SCRIPT_LOCATIONS;\n};\n\n/**\n * Reads build-time information about the app. This information is generated\n * by our standard Webpack config for apps, and it is written into\n * \".build-info\" file in the context folder specified in Webpack config.\n * At the moment, that file contains build timestamp and a random 32-bit key,\n * suitable for cryptographical use.\n * @param context Webpack context path used during the build.\n * @return Resolves to the build-time information.\n */\nfunction getBuildInfo(context: string) {\n const url = path.resolve(context, '.build-info');\n return JSON.parse(fs.readFileSync(url, 'utf8')) as BuildInfoT;\n}\n\n/**\n * Attempts to read from disk the named chunk groups mapping generated\n * by Webpack during the compilation.\n * It will not work for development builds, where these stats should be captured\n * via compilator callback.\n * @param buildDir\n * @return\n */\nfunction readChunkGroupsJson(buildDir: string) {\n const url = path.resolve(buildDir, '__chunk_groups__.json');\n let res;\n try {\n res = JSON.parse(fs.readFileSync(url, 'utf8')) as Record<string, string[]>;\n } catch {\n // TODO: Should we message the error here somehow?\n res = null;\n }\n return res;\n}\n\n/**\n * Prepares a new Cipher for data encryption.\n * @param key Encryption key (32-bit random key is expected, see\n * node-forge documentation, in case of doubts).\n * @return Resolves to the object with two fields:\n * 1. cipher - a new Cipher, ready for encryption;\n * 2. iv - initial vector used by the cipher.\n */\nasync function prepareCipher(key: string): Promise<{\n cipher: forge.cipher.BlockCipher;\n iv: string;\n}> {\n return new Promise((resolve, reject) => {\n forge.random.getBytes(32, (err, iv) => {\n if (err) reject(err);\n else {\n const cipher = forge.cipher.createCipher('AES-CBC', key);\n cipher.start({ iv });\n resolve({ cipher, iv });\n }\n });\n });\n}\n\n/**\n * Given an incoming HTTP requests, it deduces whether Brotli-encoded responses\n * are acceptable to the caller.\n * @param req\n */\nexport function isBrotliAcceptable(req: Request): boolean {\n const acceptable = req.get('accept-encoding');\n if (acceptable) {\n const ops = acceptable.split(',');\n for (const op of ops) {\n const [type, priority] = op.trim().split(';q=');\n if ((type === '*' || type === 'br')\n && (!priority || parseFloat(priority) > 0)) {\n return true;\n }\n }\n }\n return false;\n}\n\n/**\n * Given an array of extra script strings / objects, it returns an object with\n * arrays of scripts to inject in different HTML template locations. During\n * the script groupping it also filters out any empty scripts.\n * @param {({\n * code: string;\n * location: string;\n * }|string)[]} [scripts=[]]\n * @return {{\n * BODY_OPEN: string[];\n * DEFAULT: string[];\n * HEAD_OPEN: string[];\n * }}\n */\nfunction groupExtraScripts(scripts: Array<string | ScriptT> = []) {\n const res = {\n [SCRIPT_LOCATIONS.BODY_OPEN]: '',\n [SCRIPT_LOCATIONS.DEFAULT]: '',\n [SCRIPT_LOCATIONS.HEAD_OPEN]: '',\n };\n for (const script of scripts) {\n if (isString(script)) {\n if (script) res[SCRIPT_LOCATIONS.DEFAULT] += script;\n } else if (script.code) {\n if (script.location in res) res[script.location] += script.code;\n else throw Error(`Invalid location \"${script.location}\"`);\n }\n }\n return res;\n}\n\n/**\n * Creates a new default (Winston) logger.\n * @param {object} [options={}]\n * @param {string} [options.defaultLogLevel='info']\n * @return {object}\n */\nexport function newDefaultLogger({\n defaultLogLevel = 'info',\n} = {}): winston.Logger {\n const { format, transports } = winston;\n return winston.createLogger({\n format: format.combine(\n format.splat(),\n format.timestamp(),\n format.colorize(),\n format.printf(\n ({\n level,\n message,\n timestamp,\n stack,\n ...rest\n }) => {\n let res = `${level}\\t(at ${timestamp as string}):\\t${message as string}`;\n if (Object.keys(rest).length) {\n res += `\\n${JSON.stringify(rest, null, 2)}`;\n }\n if (stack) res += `\\n${stack as string}`;\n return res;\n },\n ),\n ),\n level: defaultLogLevel,\n transports: [new transports.Console()],\n });\n}\n\nexport type ConfigT = {\n [key: string]: ConfigT | string;\n};\n\nexport type BeforeRenderResT = {\n configToInject?: ConfigT;\n extraScripts?: Array<ScriptT | string>;\n initialState?: unknown;\n};\n\nexport type BeforeRenderT =\n(req: Request, config: ConfigT) => BeforeRenderResT | Promise<BeforeRenderResT>;\n\ntype CacheRefT = {\n key: string;\n maxage?: number;\n};\n\nexport type OptionsT = {\n Application?: ComponentType;\n beforeRender?: BeforeRenderT;\n buildInfo?: BuildInfoT;\n defaultLoggerLogLevel?: string;\n favicon?: string;\n logger?: LoggerI;\n maxSsrRounds?: number;\n noCsp?: boolean;\n staticCacheSize?: number;\n ssrTimeout?: number;\n staticCacheController?: (req: Request) => CacheRefT | null | undefined;\n};\n\n/**\n * Creates the middleware.\n * @param webpackConfig\n * @param options Additional options:\n * @param [options.Application] The root ReactJS component of\n * the app to use for the server-side rendering. When not provided\n * the server-side rendering is disabled.\n * @param [options.buildInfo] \"Build info\" object to use. If provided,\n * it will be used, instead of trying to load from the filesystem the one\n * generated by the Webpack build. It is intended for test environments,\n * where passing this stuff via file system is no bueno.\n * @param [options.favicon] `true` will include favicon\n * link into the rendered HTML templates.\n * @param [options.noCsp] `true` means that no\n * Content-Security-Policy (CSP) is used by server, thus the renderer\n * may cut a few corners.\n * @param [options.maxSsrRounds=10] Maximum number of SSR rounds.\n * @param [options.ssrTimeout=1000] SSR timeout in milliseconds,\n * defaults to 1 second.\n * @param [options.staticCacheSize=1.e7] The maximum\n * static cache size in bytes. Defaults to ~10 MB.\n * @param [options.staticCacheController] When given, it activates,\n * and controls the static caching of generated HTML markup. When this function\n * is provided, on each incoming request it is triggered with the request\n * passed in as the argument. To attempt to serve the response from the cache\n * it should return the object with the following fields:\n * - `key: string` – the cache key for the response;\n * - `maxage?: number` – the maximum age of cached result in ms.\n * If undefined - infinite age is assumed.\n * @return Created middleware.\n */\nexport default function factory(\n webpackConfig: Configuration,\n options: OptionsT,\n): RequestHandler {\n const ops: OptionsT = defaults(clone(options), {\n beforeRender: async () => Promise.resolve({}),\n maxSsrRounds: 10,\n ssrTimeout: 1000,\n staticCacheSize: 1.e7,\n });\n\n // Note: in normal use the default logger is created and set in the root\n // server function, and this initialization is for testing uses, where\n // renderer is imported directly.\n ops.logger ??= newDefaultLogger({\n defaultLogLevel: ops.defaultLoggerLogLevel,\n });\n\n const buildInfo = ops.buildInfo ?? getBuildInfo(webpackConfig.context!);\n setBuildInfo(buildInfo);\n\n // publicPath from webpack.output has a trailing slash at the end.\n const { publicPath, path: outputPath } = webpackConfig.output!;\n\n const manifestLink = fs.existsSync(`${outputPath}/manifest.json`)\n ? `<link rel=\"manifest\" href=\"${publicPath as string}manifest.json\">` : '';\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-definitions\n interface BufferWithNonce extends Buffer {\n nonce: string;\n }\n\n const cache = ops.staticCacheController\n ? new Cache<{\n buffer: BufferWithNonce;\n status: number;\n }>(ops.staticCacheSize!)\n : null;\n\n const CHUNK_GROUPS = readChunkGroupsJson(outputPath!);\n\n // TODO: Look at it later.\n // eslint-disable-next-line complexity\n return async (req, res, next) => {\n try {\n // Ensures any caches always revalidate HTML markup before reuse.\n res.set('Cache-Control', 'no-cache');\n\n res.cookie('csrfToken', req.csrfToken());\n\n let cacheRef: CacheRefT | null | undefined;\n if (cache) {\n cacheRef = ops.staticCacheController!(req);\n if (cacheRef) {\n const data = cache.get(cacheRef);\n if (data !== null) {\n const { buffer, status } = data;\n if (ops.noCsp && isBrotliAcceptable(req)) {\n res.set('Content-Type', 'text/html');\n res.set('Content-Encoding', 'br');\n if (status !== 200) res.status(status);\n res.send(buffer);\n } else {\n await new Promise<void>((done, failed) => {\n brotliDecompress(buffer, (error, html) => {\n if (error) failed(error);\n else {\n let h = html.toString();\n if (!ops.noCsp) {\n // TODO: Starting from Node v15 we'll be able to use string's\n // .replaceAll() method instead relying on reg. expression for\n // global matching.\n const regex = new RegExp(buffer.nonce, 'g');\n\n // TODO: It should be implemented more careful.\n h = h.replace(regex, (req as unknown as {\n nonce: string;\n }).nonce);\n }\n if (status !== 200) res.status(status);\n res.send(h);\n done();\n }\n });\n });\n }\n return;\n }\n }\n }\n\n const [{\n configToInject,\n extraScripts,\n initialState,\n }, {\n cipher,\n iv,\n }] = await Promise.all([\n ops.beforeRender!(req, sanitizedConfig as unknown as ConfigT),\n prepareCipher(buildInfo.key),\n ]);\n\n let helmet: HelmetDataContext['helmet'];\n\n // Gets the mapping between code chunk names and their asset files.\n // These data come from the Webpack compilation, either from the stats\n // attached to the request (in dev mode), or from a file output during\n // the build (in prod mode).\n let chunkGroups: ChunkGroupsT;\n const webpackStats = get(res.locals, 'webpack.devMiddleware.stats') as Stats | undefined;\n if (webpackStats) {\n chunkGroups = mapValues(\n webpackStats.toJson({\n all: false,\n chunkGroups: true,\n }).namedChunkGroups,\n (item) => item.assets?.map(({ name }: { name: string }) => name)\n ?? [],\n );\n } else if (CHUNK_GROUPS) chunkGroups = CHUNK_GROUPS;\n else chunkGroups = {};\n\n /* Optional server-side rendering. */\n const App = ops.Application;\n let appHtmlMarkup: string = '';\n const ssrContext = new ServerSsrContext(req, chunkGroups, initialState);\n let stream: NodeJS.ReadableStream;\n if (App) {\n const ssrStart = Date.now();\n\n // TODO: Somehow, without it TS does not realise that\n // App has been checked to exist.\n const App2 = App;\n\n const renderPass = async () => {\n ssrContext.chunks = [];\n\n // TODO: prerenderToNodeStream has (abort) \"signal\" option,\n // and we should wire it up to the SSR timeout below.\n const helmetContext = {} as HelmetDataContext;\n const { prelude } = await prerenderToNodeStream(\n <GlobalStateProvider\n initialState={ssrContext.state}\n ssrContext={ssrContext}\n >\n <StaticRouter location={req.url}>\n <HelmetProvider context={helmetContext}>\n <App2 />\n </HelmetProvider>\n </StaticRouter>\n </GlobalStateProvider>,\n { onError: (error) => {\n throw error;\n } },\n );\n ({ helmet } = helmetContext);\n\n return prelude;\n };\n\n let ssrRound = 0;\n let bailed = false;\n for (; ssrRound < ops.maxSsrRounds!; ++ssrRound) {\n stream = await renderPass();\n\n if (!ssrContext.dirty) break;\n\n const timeout = ops.ssrTimeout! + ssrStart - Date.now();\n bailed = timeout <= 0 || !await Promise.race([\n Promise.allSettled(ssrContext.pending),\n timer(timeout).then(() => false),\n ]);\n if (bailed) break;\n }\n\n let logMsg;\n if (ssrContext.dirty) {\n // NOTE: In the case of incomplete SSR one more round is necessary\n // to ensure the correct hydration when some pending promises have\n // resolved and placed their data into the initial global state.\n stream = await renderPass();\n\n logMsg = bailed ? `SSR timed out after ${ops.ssrTimeout} second(s)`\n : `SSR bailed out after ${ops.maxSsrRounds} round(s)`;\n } else logMsg = `SSR completed in ${ssrRound + 1} round(s)`;\n\n ops.logger!.log(ssrContext.dirty ? 'warn' : 'info', logMsg);\n\n await new Promise((ready) => {\n stream!.pipe(new Writable({\n destroy: ready,\n write: (chunk: { toString: () => string }, _, done) => {\n appHtmlMarkup += chunk.toString();\n done();\n },\n }));\n });\n }\n\n /* Encrypts data to be injected into HTML.\n * Keep in mind, that this encryption is no way secure: as the JS bundle\n * contains decryption key and is able to decode it at the client side.\n * Hovewer, for a number of reasons, encryption of injected data is still\n * better than injection of a plain text. */\n const payload = serializeJs({\n CHUNK_GROUPS: chunkGroups,\n CONFIG: configToInject ?? sanitizedConfig,\n ISTATE: ssrContext.state,\n }, {\n ignoreFunction: true,\n unsafe: true,\n });\n cipher.update(forge.util.createBuffer(payload, 'utf8'));\n cipher.finish();\n const INJ = forge.util.encode64(`${iv}${cipher.output.data}`);\n\n const chunkSet = new Set<string>();\n\n // TODO: \"main\" chunk has to be added explicitly,\n // because unlike all other chunks they are not managed by <CodeSplit>\n // component, thus they are not added to the ssrContext.chunks\n // automatically. Actually, names of these entry chunks should be\n // read from Wepback config, as the end user may customize them,\n // remove or add other entry points, but it requires additional\n // efforts to figure out how to automatically order them right,\n // thus for now this handles the default config.\n [\n 'main',\n ...ssrContext.chunks,\n ].forEach((chunk) => {\n const assets = chunkGroups[chunk];\n if (assets) assets.forEach((asset) => chunkSet.add(asset));\n });\n\n let styleChunkString = '';\n let scriptChunkString = '';\n chunkSet.forEach((chunk) => {\n if (chunk.endsWith('.css')) {\n styleChunkString += `<link href=\"${publicPath as string}${chunk}\" rel=\"stylesheet\">`;\n } else if (\n chunk.endsWith('.js')\n // In dev mode HMR adds JS updates into asset arrays,\n // and they (updates) should be ignored.\n && !chunk.endsWith('.hot-update.js')\n ) {\n scriptChunkString += `<script src=\"${publicPath as string}${chunk}\" type=\"application/javascript\"></script>`;\n }\n });\n\n const grouppedExtraScripts = groupExtraScripts(extraScripts);\n\n const faviconLink = ops.favicon\n ? '<link rel=\"shortcut icon\" href=\"/favicon.ico\">'\n : '';\n\n const html = `<!DOCTYPE html>\n <html lang=\"en\">\n <head>\n ${grouppedExtraScripts[SCRIPT_LOCATIONS.HEAD_OPEN]}\n ${helmet?.title.toString() ?? ''}\n ${helmet?.meta.toString() ?? ''}\n <meta name=\"theme-color\" content=\"#FFFFFF\">\n ${manifestLink}\n ${helmet?.link.toString() ?? ''}${styleChunkString}\n ${faviconLink}\n <meta charset=\"utf-8\">\n <meta\n content=\"width=device-width,initial-scale=1.0\"\n name=\"viewport\"\n >\n <meta itemprop=\"drpruinj\" content=\"${INJ}\">\n </head>\n <body>\n ${grouppedExtraScripts[SCRIPT_LOCATIONS.BODY_OPEN]}\n <div id=\"react-view\">${appHtmlMarkup}</div>\n ${scriptChunkString}\n ${grouppedExtraScripts[SCRIPT_LOCATIONS.DEFAULT]}\n </body>\n </html>`;\n\n const status = ssrContext.status || 200;\n if (status !== 200) res.status(status);\n\n if (cacheRef && status < 500) {\n // Note: waiting for the caching to complete is not strictly necessary,\n // but it greately simplifies testing, and error reporting.\n await new Promise<void>((done, failed) => {\n brotliCompress(html, (error, buffer) => {\n const b = buffer as BufferWithNonce;\n if (error) failed(error);\n else {\n b.nonce = (req as unknown as {\n nonce: string;\n }).nonce;\n cache!.add({ buffer: b, status }, cacheRef.key, buffer.length);\n done();\n }\n });\n });\n }\n\n // Note: as caching code above may throw in some cases, sending response\n // before it completes will likely hide the error, making it difficult\n // to debug. Thus, at least for now, lets send response after it.\n res.send(html);\n } catch (error) {\n next(error);\n }\n };\n}\n"],"mappings":";;;;;;;;;;AAIA,IAAAA,GAAA,GAAAC,sBAAA,CAAAC,OAAA;AACA,IAAAC,KAAA,GAAAF,sBAAA,CAAAC,OAAA;AAIA,IAAAE,OAAA,GAAAF,OAAA;AACA,IAAAG,KAAA,GAAAH,OAAA;AACA,IAAAI,QAAA,GAAAL,sBAAA,CAAAC,OAAA;AAEA,IAAAK,iBAAA,GAAAL,OAAA;AACA,IAAAM,QAAA,GAAAN,OAAA;AAEA,IAAAO,OAAA,GAAAP,OAAA;AAUA,IAAAQ,OAAA,GAAAT,sBAAA,CAAAC,OAAA;AACA,IAAAS,UAAA,GAAAV,sBAAA,CAAAC,OAAA;AAEA,IAAAU,OAAA,GAAAV,OAAA;AACA,IAAAW,YAAA,GAAAX,OAAA;AACA,IAAAY,YAAA,GAAAZ,OAAA;AACA,IAAAa,oBAAA,GAAAd,sBAAA,CAAAC,OAAA;AACA,IAAAc,UAAA,GAAAd,OAAA;AAMA,IAAAe,MAAA,GAAAhB,sBAAA,CAAAC,OAAA;AAA4B,IAAAgB,WAAA,GAAAhB,OAAA;AAvC5B;AACA;AACA;;AAuCA,MAAMiB,eAAe,GAAG,IAAAC,YAAI,EAACC,eAAM,EAAE,QAAQ,CAAC;;AAE9C;AACA;AACA;AACA;;AAMA;;AAMA;AAAA,IASYC,gBAAgB,GAAAC,OAAA,CAAAD,gBAAA,0BAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAhBA,gBAAgB;EAAA,OAAhBA,gBAAgB;AAAA;AAMrB,MAAME,gBAAgB,SACnBC,4BAAU,CACa;EAG/BC,MAAM,GAAa,EAAE;EAIrBC,MAAM,GAAW,GAAG;EAEpBC,WAAWA,CACTC,GAAY,EACZC,WAAyB,EACzBC,YAAqB,EACrB;IACA,KAAK,CAAC,IAAAC,iBAAS,EAACD,YAAY,CAAC,IAAK,CAAC,CAAY,CAAC;IAChD,IAAI,CAACD,WAAW,GAAGA,WAAW;IAC9B,IAAI,CAACD,GAAG,GAAGA,GAAG;EAChB;AACF;AAACN,OAAA,CAAAC,gBAAA,GAAAA,gBAAA;AAOD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASS,YAAYA,CAACC,OAAe,EAAE;EACrC,MAAMC,GAAG,GAAGC,aAAI,CAACC,OAAO,CAACH,OAAO,EAAE,aAAa,CAAC;EAChD,OAAOI,IAAI,CAACC,KAAK,CAACC,WAAE,CAACC,YAAY,CAACN,GAAG,EAAE,MAAM,CAAC,CAAC;AACjD;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASO,mBAAmBA,CAACC,QAAgB,EAAE;EAC7C,MAAMR,GAAG,GAAGC,aAAI,CAACC,OAAO,CAACM,QAAQ,EAAE,uBAAuB,CAAC;EAC3D,IAAIC,GAAG;EACP,IAAI;IACFA,GAAG,GAAGN,IAAI,CAACC,KAAK,CAACC,WAAE,CAACC,YAAY,CAACN,GAAG,EAAE,MAAM,CAAC,CAA6B;EAC5E,CAAC,CAAC,MAAM;IACN;IACAS,GAAG,GAAG,IAAI;EACZ;EACA,OAAOA,GAAG;AACZ;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,eAAeC,aAAaA,CAACC,GAAW,EAGrC;EACD,OAAO,IAAIC,OAAO,CAAC,CAACV,OAAO,EAAEW,MAAM,KAAK;IACtCC,kBAAK,CAACC,MAAM,CAACC,QAAQ,CAAC,EAAE,EAAE,CAACC,GAAG,EAAEC,EAAE,KAAK;MACrC,IAAID,GAAG,EAAEJ,MAAM,CAACI,GAAG,CAAC,CAAC,KAChB;QACH,MAAME,MAAM,GAAGL,kBAAK,CAACK,MAAM,CAACC,YAAY,CAAC,SAAS,EAAET,GAAG,CAAC;QACxDQ,MAAM,CAACE,KAAK,CAAC;UAAEH;QAAG,CAAC,CAAC;QACpBhB,OAAO,CAAC;UAAEiB,MAAM;UAAED;QAAG,CAAC,CAAC;MACzB;IACF,CAAC,CAAC;EACJ,CAAC,CAAC;AACJ;;AAEA;AACA;AACA;AACA;AACA;AACO,SAASI,kBAAkBA,CAAC5B,GAAY,EAAW;EACxD,MAAM6B,UAAU,GAAG7B,GAAG,CAAC8B,GAAG,CAAC,iBAAiB,CAAC;EAC7C,IAAID,UAAU,EAAE;IACd,MAAME,GAAG,GAAGF,UAAU,CAACG,KAAK,CAAC,GAAG,CAAC;IACjC,KAAK,MAAMC,EAAE,IAAIF,GAAG,EAAE;MACpB,MAAM,CAACG,IAAI,EAAEC,QAAQ,CAAC,GAAGF,EAAE,CAACG,IAAI,CAAC,CAAC,CAACJ,KAAK,CAAC,KAAK,CAAC;MAC/C,IAAI,CAACE,IAAI,KAAK,GAAG,IAAIA,IAAI,KAAK,IAAI,MAC5B,CAACC,QAAQ,IAAIE,UAAU,CAACF,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE;QAC5C,OAAO,IAAI;MACb;IACF;EACF;EACA,OAAO,KAAK;AACd;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASG,iBAAiBA,CAACC,OAAgC,GAAG,EAAE,EAAE;EAChE,MAAMxB,GAAG,GAAG;IACV,CAACtB,gBAAgB,CAAC+C,SAAS,GAAG,EAAE;IAChC,CAAC/C,gBAAgB,CAACgD,OAAO,GAAG,EAAE;IAC9B,CAAChD,gBAAgB,CAACiD,SAAS,GAAG;EAChC,CAAC;EACD,KAAK,MAAMC,MAAM,IAAIJ,OAAO,EAAE;IAC5B,IAAI,IAAAK,gBAAQ,EAACD,MAAM,CAAC,EAAE;MACpB,IAAIA,MAAM,EAAE5B,GAAG,CAACtB,gBAAgB,CAACgD,OAAO,CAAC,IAAIE,MAAM;IACrD,CAAC,MAAM,IAAIA,MAAM,CAACE,IAAI,EAAE;MACtB,IAAIF,MAAM,CAACG,QAAQ,IAAI/B,GAAG,EAAEA,GAAG,CAAC4B,MAAM,CAACG,QAAQ,CAAC,IAAIH,MAAM,CAACE,IAAI,CAAC,KAC3D,MAAME,KAAK,CAAC,qBAAqBJ,MAAM,CAACG,QAAQ,GAAG,CAAC;IAC3D;EACF;EACA,OAAO/B,GAAG;AACZ;;AAEA;AACA;AACA;AACA;AACA;AACA;AACO,SAASiC,gBAAgBA,CAAC;EAC/BC,eAAe,GAAG;AACpB,CAAC,GAAG,CAAC,CAAC,EAAkB;EACtB,MAAM;IAAEC,MAAM;IAAEC;EAAW,CAAC,GAAGC,gBAAO;EACtC,OAAOA,gBAAO,CAACC,YAAY,CAAC;IAC1BH,MAAM,EAAEA,MAAM,CAACI,OAAO,CACpBJ,MAAM,CAACK,KAAK,CAAC,CAAC,EACdL,MAAM,CAACM,SAAS,CAAC,CAAC,EAClBN,MAAM,CAACO,QAAQ,CAAC,CAAC,EACjBP,MAAM,CAACQ,MAAM,CACX,CAAC;MACCC,KAAK;MACLC,OAAO;MACPJ,SAAS;MACTK,KAAK;MACL,GAAGC;IACL,CAAC,KAAK;MACJ,IAAI/C,GAAG,GAAG,GAAG4C,KAAK,SAASH,SAAS,OAAiBI,OAAO,EAAY;MACxE,IAAIG,MAAM,CAACC,IAAI,CAACF,IAAI,CAAC,CAACG,MAAM,EAAE;QAC5BlD,GAAG,IAAI,KAAKN,IAAI,CAACyD,SAAS,CAACJ,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;MAC7C;MACA,IAAID,KAAK,EAAE9C,GAAG,IAAI,KAAK8C,KAAK,EAAY;MACxC,OAAO9C,GAAG;IACZ,CACF,CACF,CAAC;IACD4C,KAAK,EAAEV,eAAe;IACtBE,UAAU,EAAE,CAAC,IAAIA,UAAU,CAACgB,OAAO,CAAC,CAAC;EACvC,CAAC,CAAC;AACJ;AAkCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe,SAASC,OAAOA,CAC7BC,aAA4B,EAC5BC,OAAiB,EACD;EAChB,MAAMvC,GAAa,GAAG,IAAAwC,gBAAQ,EAAC,IAAAC,aAAK,EAACF,OAAO,CAAC,EAAE;IAC7CG,YAAY,EAAE,MAAAA,CAAA,KAAYvD,OAAO,CAACV,OAAO,CAAC,CAAC,CAAC,CAAC;IAC7CkE,YAAY,EAAE,EAAE;IAChBC,UAAU,EAAE,IAAI;IAChBC,eAAe,EAAE;EACnB,CAAC,CAAC;;EAEF;EACA;EACA;EACA7C,GAAG,CAAC8C,MAAM,KAAK7B,gBAAgB,CAAC;IAC9BC,eAAe,EAAElB,GAAG,CAAC+C;EACvB,CAAC,CAAC;EAEF,MAAMC,SAAS,GAAGhD,GAAG,CAACgD,SAAS,IAAI3E,YAAY,CAACiE,aAAa,CAAChE,OAAQ,CAAC;EACvE,IAAA2E,uBAAY,EAACD,SAAS,CAAC;;EAEvB;EACA,MAAM;IAAEE,UAAU;IAAE1E,IAAI,EAAE2E;EAAW,CAAC,GAAGb,aAAa,CAACc,MAAO;EAE9D,MAAMC,YAAY,GAAGzE,WAAE,CAAC0E,UAAU,CAAC,GAAGH,UAAU,gBAAgB,CAAC,GAC7D,8BAA8BD,UAAU,iBAA2B,GAAG,EAAE;;EAE5E;;EAKA,MAAMK,KAAK,GAAGvD,GAAG,CAACwD,qBAAqB,GACnC,IAAIC,cAAK,CAGRzD,GAAG,CAAC6C,eAAgB,CAAC,GACtB,IAAI;EAER,MAAMa,YAAY,GAAG5E,mBAAmB,CAACqE,UAAW,CAAC;;EAErD;EACA;EACA,OAAO,OAAOlF,GAAG,EAAEe,GAAG,EAAE2E,IAAI,KAAK;IAC/B,IAAI;MACF;MACA3E,GAAG,CAAC4E,GAAG,CAAC,eAAe,EAAE,UAAU,CAAC;MAEpC5E,GAAG,CAAC6E,MAAM,CAAC,WAAW,EAAE5F,GAAG,CAAC6F,SAAS,CAAC,CAAC,CAAC;MAExC,IAAIC,QAAsC;MAC1C,IAAIR,KAAK,EAAE;QACTQ,QAAQ,GAAG/D,GAAG,CAACwD,qBAAqB,CAAEvF,GAAG,CAAC;QAC1C,IAAI8F,QAAQ,EAAE;UACZ,MAAMC,IAAI,GAAGT,KAAK,CAACxD,GAAG,CAACgE,QAAQ,CAAC;UAChC,IAAIC,IAAI,KAAK,IAAI,EAAE;YACjB,MAAM;cAAEC,MAAM;cAAElG;YAAO,CAAC,GAAGiG,IAAI;YAC/B,IAAIhE,GAAG,CAACkE,KAAK,IAAIrE,kBAAkB,CAAC5B,GAAG,CAAC,EAAE;cACxCe,GAAG,CAAC4E,GAAG,CAAC,cAAc,EAAE,WAAW,CAAC;cACpC5E,GAAG,CAAC4E,GAAG,CAAC,kBAAkB,EAAE,IAAI,CAAC;cACjC,IAAI7F,MAAM,KAAK,GAAG,EAAEiB,GAAG,CAACjB,MAAM,CAACA,MAAM,CAAC;cACtCiB,GAAG,CAACmF,IAAI,CAACF,MAAM,CAAC;YAClB,CAAC,MAAM;cACL,MAAM,IAAI9E,OAAO,CAAO,CAACiF,IAAI,EAAEC,MAAM,KAAK;gBACxC,IAAAC,sBAAgB,EAACL,MAAM,EAAE,CAACM,KAAK,EAAEC,IAAI,KAAK;kBACxC,IAAID,KAAK,EAAEF,MAAM,CAACE,KAAK,CAAC,CAAC,KACpB;oBACH,IAAIE,CAAC,GAAGD,IAAI,CAACE,QAAQ,CAAC,CAAC;oBACvB,IAAI,CAAC1E,GAAG,CAACkE,KAAK,EAAE;sBACd;sBACA;sBACA;sBACA,MAAMS,KAAK,GAAG,IAAIC,MAAM,CAACX,MAAM,CAACY,KAAK,EAAE,GAAG,CAAC;;sBAE3C;sBACAJ,CAAC,GAAGA,CAAC,CAACK,OAAO,CAACH,KAAK,EAAG1G,GAAG,CAEtB4G,KAAK,CAAC;oBACX;oBACA,IAAI9G,MAAM,KAAK,GAAG,EAAEiB,GAAG,CAACjB,MAAM,CAACA,MAAM,CAAC;oBACtCiB,GAAG,CAACmF,IAAI,CAACM,CAAC,CAAC;oBACXL,IAAI,CAAC,CAAC;kBACR;gBACF,CAAC,CAAC;cACJ,CAAC,CAAC;YACJ;YACA;UACF;QACF;MACF;MAEA,MAAM,CAAC;QACLW,cAAc;QACdC,YAAY;QACZ7G;MACF,CAAC,EAAE;QACDuB,MAAM;QACND;MACF,CAAC,CAAC,GAAG,MAAMN,OAAO,CAAC8F,GAAG,CAAC,CACrBjF,GAAG,CAAC0C,YAAY,CAAEzE,GAAG,EAAEV,eAAqC,CAAC,EAC7D0B,aAAa,CAAC+D,SAAS,CAAC9D,GAAG,CAAC,CAC7B,CAAC;MAEF,IAAIgG,MAAmC;;MAEvC;MACA;MACA;MACA;MACA,IAAIhH,WAAyB;MAC7B,MAAMiH,YAAY,GAAG,IAAApF,WAAG,EAACf,GAAG,CAACoG,MAAM,EAAE,6BAA6B,CAAsB;MACxF,IAAID,YAAY,EAAE;QAChBjH,WAAW,GAAG,IAAAmH,iBAAS,EACrBF,YAAY,CAACG,MAAM,CAAC;UAClBL,GAAG,EAAE,KAAK;UACV/G,WAAW,EAAE;QACf,CAAC,CAAC,CAACqH,gBAAgB,EAClBC,IAAI,IAAKA,IAAI,CAACC,MAAM,EAAEC,GAAG,CAAC,CAAC;UAAEC;QAAuB,CAAC,KAAKA,IAAI,CAAC,IAC3D,EACP,CAAC;MACH,CAAC,MAAM,IAAIjC,YAAY,EAAExF,WAAW,GAAGwF,YAAY,CAAC,KAC/CxF,WAAW,GAAG,CAAC,CAAC;;MAErB;MACA,MAAM0H,GAAG,GAAG5F,GAAG,CAAC6F,WAAW;MAC3B,IAAIC,aAAqB,GAAG,EAAE;MAC9B,MAAMC,UAAU,GAAG,IAAInI,gBAAgB,CAACK,GAAG,EAAEC,WAAW,EAAEC,YAAY,CAAC;MACvE,IAAI6H,MAA6B;MACjC,IAAIJ,GAAG,EAAE;QACP,MAAMK,QAAQ,GAAGC,IAAI,CAACC,GAAG,CAAC,CAAC;;QAE3B;QACA;QACA,MAAMC,IAAI,GAAGR,GAAG;QAEhB,MAAMS,UAAU,GAAG,MAAAA,CAAA,KAAY;UAC7BN,UAAU,CAACjI,MAAM,GAAG,EAAE;;UAEtB;UACA;UACA,MAAMwI,aAAa,GAAG,CAAC,CAAsB;UAC7C,MAAM;YAAEC;UAAQ,CAAC,GAAG,MAAM,IAAAC,6BAAqB,eAC7C,IAAAlJ,WAAA,CAAAmJ,GAAA,EAAC9J,iBAAA,CAAA+J,mBAAmB;YAClBvI,YAAY,EAAE4H,UAAU,CAACY,KAAM;YAC/BZ,UAAU,EAAEA,UAAW;YAAAa,QAAA,eAEvB,IAAAtJ,WAAA,CAAAmJ,GAAA,EAACvJ,YAAA,CAAA2J,YAAY;cAAC9F,QAAQ,EAAE9C,GAAG,CAACM,GAAI;cAAAqI,QAAA,eAC9B,IAAAtJ,WAAA,CAAAmJ,GAAA,EAACxJ,YAAA,CAAA6J,cAAc;gBAACxI,OAAO,EAAEgI,aAAc;gBAAAM,QAAA,eACrC,IAAAtJ,WAAA,CAAAmJ,GAAA,EAACL,IAAI,IAAE;cAAC,CACM;YAAC,CACL;UAAC,CACI,CAAC,EACtB;YAAEW,OAAO,EAAGxC,KAAK,IAAK;cACpB,MAAMA,KAAK;YACb;UAAE,CACJ,CAAC;UACD,CAAC;YAAEW;UAAO,CAAC,GAAGoB,aAAa;UAE3B,OAAOC,OAAO;QAChB,CAAC;QAED,IAAIS,QAAQ,GAAG,CAAC;QAChB,IAAIC,MAAM,GAAG,KAAK;QAClB,OAAOD,QAAQ,GAAGhH,GAAG,CAAC2C,YAAa,EAAE,EAAEqE,QAAQ,EAAE;UAC/ChB,MAAM,GAAG,MAAMK,UAAU,CAAC,CAAC;UAE3B,IAAI,CAACN,UAAU,CAACmB,KAAK,EAAE;UAEvB,MAAMC,OAAO,GAAGnH,GAAG,CAAC4C,UAAU,GAAIqD,QAAQ,GAAGC,IAAI,CAACC,GAAG,CAAC,CAAC;UACvDc,MAAM,GAAGE,OAAO,IAAI,CAAC,IAAI,EAAC,MAAMhI,OAAO,CAACiI,IAAI,CAAC,CAC3CjI,OAAO,CAACkI,UAAU,CAACtB,UAAU,CAACuB,OAAO,CAAC,EACtC,IAAAC,cAAK,EAACJ,OAAO,CAAC,CAACK,IAAI,CAAC,MAAM,KAAK,CAAC,CACjC,CAAC;UACF,IAAIP,MAAM,EAAE;QACd;QAEA,IAAIQ,MAAM;QACV,IAAI1B,UAAU,CAACmB,KAAK,EAAE;UACpB;UACA;UACA;UACAlB,MAAM,GAAG,MAAMK,UAAU,CAAC,CAAC;UAE3BoB,MAAM,GAAGR,MAAM,GAAG,uBAAuBjH,GAAG,CAAC4C,UAAU,YAAY,GAC/D,wBAAwB5C,GAAG,CAAC2C,YAAY,WAAW;QACzD,CAAC,MAAM8E,MAAM,GAAG,oBAAoBT,QAAQ,GAAG,CAAC,WAAW;QAE3DhH,GAAG,CAAC8C,MAAM,CAAE4E,GAAG,CAAC3B,UAAU,CAACmB,KAAK,GAAG,MAAM,GAAG,MAAM,EAAEO,MAAM,CAAC;QAE3D,MAAM,IAAItI,OAAO,CAAEwI,KAAK,IAAK;UAC3B3B,MAAM,CAAE4B,IAAI,CAAC,IAAIC,gBAAQ,CAAC;YACxBC,OAAO,EAAEH,KAAK;YACdI,KAAK,EAAEA,CAACC,KAAiC,EAAEC,CAAC,EAAE7D,IAAI,KAAK;cACrD0B,aAAa,IAAIkC,KAAK,CAACtD,QAAQ,CAAC,CAAC;cACjCN,IAAI,CAAC,CAAC;YACR;UACF,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;MACJ;;MAEA;AACN;AACA;AACA;AACA;MACM,MAAM8D,OAAO,GAAG,IAAAC,4BAAW,EAAC;QAC1BzE,YAAY,EAAExF,WAAW;QACzBkK,MAAM,EAAErD,cAAc,IAAIxH,eAAe;QACzC8K,MAAM,EAAEtC,UAAU,CAACY;MACrB,CAAC,EAAE;QACD2B,cAAc,EAAE,IAAI;QACpBC,MAAM,EAAE;MACV,CAAC,CAAC;MACF7I,MAAM,CAAC8I,MAAM,CAACnJ,kBAAK,CAACoJ,IAAI,CAACC,YAAY,CAACR,OAAO,EAAE,MAAM,CAAC,CAAC;MACvDxI,MAAM,CAACiJ,MAAM,CAAC,CAAC;MACf,MAAMC,GAAG,GAAGvJ,kBAAK,CAACoJ,IAAI,CAACI,QAAQ,CAAC,GAAGpJ,EAAE,GAAGC,MAAM,CAAC0D,MAAM,CAACY,IAAI,EAAE,CAAC;MAE7D,MAAM8E,QAAQ,GAAG,IAAIC,GAAG,CAAS,CAAC;;MAElC;MACA;MACA;MACA;MACA;MACA;MACA;MACA;MACA,CACE,MAAM,EACN,GAAGhD,UAAU,CAACjI,MAAM,CACrB,CAACkL,OAAO,CAAEhB,KAAK,IAAK;QACnB,MAAMvC,MAAM,GAAGvH,WAAW,CAAC8J,KAAK,CAAC;QACjC,IAAIvC,MAAM,EAAEA,MAAM,CAACuD,OAAO,CAAEC,KAAK,IAAKH,QAAQ,CAACI,GAAG,CAACD,KAAK,CAAC,CAAC;MAC5D,CAAC,CAAC;MAEF,IAAIE,gBAAgB,GAAG,EAAE;MACzB,IAAIC,iBAAiB,GAAG,EAAE;MAC1BN,QAAQ,CAACE,OAAO,CAAEhB,KAAK,IAAK;QAC1B,IAAIA,KAAK,CAACqB,QAAQ,CAAC,MAAM,CAAC,EAAE;UAC1BF,gBAAgB,IAAI,eAAejG,UAAU,GAAa8E,KAAK,qBAAqB;QACtF,CAAC,MAAM,IACLA,KAAK,CAACqB,QAAQ,CAAC,KAAK;QACpB;QACA;QAAA,GACG,CAACrB,KAAK,CAACqB,QAAQ,CAAC,gBAAgB,CAAC,EACpC;UACAD,iBAAiB,IAAI,gBAAgBlG,UAAU,GAAa8E,KAAK,2CAA2C;QAC9G;MACF,CAAC,CAAC;MAEF,MAAMsB,oBAAoB,GAAG/I,iBAAiB,CAACyE,YAAY,CAAC;MAE5D,MAAMuE,WAAW,GAAGvJ,GAAG,CAACwJ,OAAO,GAC3B,gDAAgD,GAChD,EAAE;MAEN,MAAMhF,IAAI,GAAG;AACnB;AACA;AACA,cAAc8E,oBAAoB,CAAC5L,gBAAgB,CAACiD,SAAS,CAAC;AAC9D,cAAcuE,MAAM,EAAEuE,KAAK,CAAC/E,QAAQ,CAAC,CAAC,IAAI,EAAE;AAC5C,cAAcQ,MAAM,EAAEwE,IAAI,CAAChF,QAAQ,CAAC,CAAC,IAAI,EAAE;AAC3C;AACA,cAAcrB,YAAY;AAC1B,cAAc6B,MAAM,EAAEyE,IAAI,CAACjF,QAAQ,CAAC,CAAC,IAAI,EAAE,GAAGyE,gBAAgB;AAC9D,cAAcI,WAAW;AACzB;AACA;AACA;AACA;AACA;AACA,iDAAiDX,GAAG;AACpD;AACA;AACA,cAAcU,oBAAoB,CAAC5L,gBAAgB,CAAC+C,SAAS,CAAC;AAC9D,mCAAmCqF,aAAa;AAChD,cAAcsD,iBAAiB;AAC/B,cAAcE,oBAAoB,CAAC5L,gBAAgB,CAACgD,OAAO,CAAC;AAC5D;AACA,gBAAgB;MAEV,MAAM3C,MAAM,GAAGgI,UAAU,CAAChI,MAAM,IAAI,GAAG;MACvC,IAAIA,MAAM,KAAK,GAAG,EAAEiB,GAAG,CAACjB,MAAM,CAACA,MAAM,CAAC;MAEtC,IAAIgG,QAAQ,IAAIhG,MAAM,GAAG,GAAG,EAAE;QAC5B;QACA;QACA,MAAM,IAAIoB,OAAO,CAAO,CAACiF,IAAI,EAAEC,MAAM,KAAK;UACxC,IAAAuF,oBAAc,EAACpF,IAAI,EAAE,CAACD,KAAK,EAAEN,MAAM,KAAK;YACtC,MAAM4F,CAAC,GAAG5F,MAAyB;YACnC,IAAIM,KAAK,EAAEF,MAAM,CAACE,KAAK,CAAC,CAAC,KACpB;cACHsF,CAAC,CAAChF,KAAK,GAAI5G,GAAG,CAEX4G,KAAK;cACRtB,KAAK,CAAE2F,GAAG,CAAC;gBAAEjF,MAAM,EAAE4F,CAAC;gBAAE9L;cAAO,CAAC,EAAEgG,QAAQ,CAAC7E,GAAG,EAAE+E,MAAM,CAAC/B,MAAM,CAAC;cAC9DkC,IAAI,CAAC,CAAC;YACR;UACF,CAAC,CAAC;QACJ,CAAC,CAAC;MACJ;;MAEA;MACA;MACA;MACApF,GAAG,CAACmF,IAAI,CAACK,IAAI,CAAC;IAChB,CAAC,CAAC,OAAOD,KAAK,EAAE;MACdZ,IAAI,CAACY,KAAK,CAAC;IACb;EACF,CAAC;AACH","ignoreList":[]}
|
|
@@ -148,7 +148,12 @@ function mount(scene) {
|
|
|
148
148
|
});
|
|
149
149
|
return res;
|
|
150
150
|
}
|
|
151
|
-
|
|
151
|
+
|
|
152
|
+
// NOTE: If in future we have additional options here, they should be distributed
|
|
153
|
+
// across two objects, depending whether they are applicable to the sync, or async
|
|
154
|
+
// versions of snapshot(), or both.
|
|
155
|
+
|
|
156
|
+
function snapshot(element, options) {
|
|
152
157
|
let res;
|
|
153
158
|
|
|
154
159
|
// TODO: Just adding async to the actor function breaks stuff, as it makes
|
|
@@ -162,7 +167,15 @@ async function snapshot(element, options) {
|
|
|
162
167
|
return options?.await;
|
|
163
168
|
});
|
|
164
169
|
if (res === undefined) throw Error('Render failed');
|
|
165
|
-
if (options?.await)
|
|
170
|
+
if (options?.await) {
|
|
171
|
+
// TODO: Then body of .then() is the same as the last three lines in this
|
|
172
|
+
// function, executed for the non-async variant. We should re-use that.
|
|
173
|
+
return promise.then(() => {
|
|
174
|
+
const nodes = res.asFragment().childNodes;
|
|
175
|
+
expect(nodes.length > 1 ? [...nodes] : nodes[0]).toMatchSnapshot();
|
|
176
|
+
return res;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
166
179
|
const nodes = res.asFragment().childNodes;
|
|
167
180
|
expect(nodes.length > 1 ? [...nodes] : nodes[0]).toMatchSnapshot();
|
|
168
181
|
return res;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["_mockdate","_interopRequireDefault","require","_react","_client","_react2","_global","global","IS_REACT_ACT_ENVIRONMENT","originalProcessVersions","process","versions","mockClientSide","Object","defineProperty","value","undefined","unmockClientSide","writable","getMockUuid","seed","x","toString","padStart","slice","mockAxios","handlers","axios","jest","requireActual","defaults","adapter","config","handler","res","data","headers","status","statusText","console","warn","JSON","stringify","e","mockTimer","time","mockdate","set","Date","now","advanceTimersByTimeAsync","mount","scene","root","element","document","createElement","body","appendChild","destroy","act","unmount","remove","snapshot","expect","toMatchSnapshot","createRoot","render","options","promise","await","Error","nodes","asFragment","childNodes","length"],"sources":["../../../../../src/shared/utils/jest/index.ts"],"sourcesContent":["/* global jest, document */\n/* eslint-disable import/no-extraneous-dependencies */\n\nimport type {\n AxiosRequestConfig,\n AxiosResponse,\n AxiosStatic,\n InternalAxiosRequestConfig,\n} from 'axios';\n\nimport mockdate from 'mockdate';\nimport { type ReactNode, act } from 'react';\nimport { type Root, createRoot } from 'react-dom/client';\n\nimport { type RenderResult, render } from '@testing-library/react';\n\n/**\n * An alias for [act(..)](https://reactjs.org/docs/test-utils.html#act)\n * from `react`.\n */\nexport { act };\n\nexport { default as getGlobal } from './global';\n\nglobal.IS_REACT_ACT_ENVIRONMENT = true;\n\nconst originalProcessVersions = process.versions;\n\n/**\n * Tricks **react-utils** into thinking the test is running within client-side\n * (browser) environment.\n */\nexport function mockClientSide(): void {\n Object.defineProperty(process, 'versions', { value: undefined });\n}\n\n/**\n * Reverts the effect of {@link module:JU.mockClientSide mockClientSide(..)}.\n */\nexport function unmockClientSide(): void {\n Object.defineProperty(process, 'versions', {\n value: originalProcessVersions,\n writable: false,\n });\n}\n\n/**\n * Generates a mock UUID, or better said it determenistically transforms given\n * `seed` number into a UUID-formatted string.\n * @param {number} seed\n * @return {string}\n */\nexport function getMockUuid(seed = 0): string {\n const x = seed.toString(16).padStart(32, '0');\n return `${x.slice(0, 8)}-${x.slice(8, 12)}-${x.slice(12, 16)}-${x.slice(16, 20)}-${x.slice(20)}`;\n}\n\nexport type AxiosRequestHandlerT =\n (config: AxiosRequestConfig) => Partial<AxiosResponse> | null | undefined;\n\nexport function mockAxios(handlers: AxiosRequestHandlerT[]): AxiosStatic {\n const axios: AxiosStatic = jest.requireActual('axios');\n\n axios.defaults.adapter = async (\n config: AxiosRequestConfig,\n ): Promise<AxiosResponse> => {\n for (const handler of handlers) {\n const res = handler(config);\n if (res) {\n return {\n config: config as InternalAxiosRequestConfig,\n data: null,\n headers: {},\n status: 200,\n statusText: 'OK',\n ...res,\n };\n }\n }\n\n // Fallback to the regular network request.\n let res: AxiosResponse;\n try {\n res = await axios({ ...config, adapter: ['xhr', 'http', 'fetch'] });\n // eslint-disable-next-line no-console\n console.warn(\n 'Network request has not been mocked for a test.\\n\\nConfig:\\n',\n config,\n '\\n\\nResult:\\n',\n JSON.stringify(res, null, 2),\n );\n } catch (e) {\n // eslint-disable-next-line no-console\n console.warn(\n 'Network request has not been mocked for a test, and failed.\\n\\nConfig:\\n',\n config,\n '\\n\\nError\\n',\n JSON.stringify(e, null, 2),\n );\n throw e;\n }\n\n return res;\n };\n\n return axios;\n}\n\n/**\n * Advances mock timers, and mock date by the specified time.\n * @param {number} time Time step [ms].\n * @returns {Promise} Wait for this to \"jump after\" any async code which should\n * be executed because of the mock time movement.\n */\nexport async function mockTimer(time: number): Promise<void> {\n mockdate.set(time + Date.now());\n await jest.advanceTimersByTimeAsync(time);\n}\n\nexport type MountedSceneT = HTMLElement & {\n destroy: () => void;\n snapshot: () => void;\n};\n\n/**\n * Mounts `scene` to the DOM, and returns the root scene element.\n * @param scene\n * @return Created container DOM element with destroy() function\n * attached.\n */\nexport function mount(scene: ReactNode): MountedSceneT {\n let root: Root;\n const element = document.createElement('div');\n document.body.appendChild(element);\n\n const res: MountedSceneT = (element as unknown) as MountedSceneT;\n res.destroy = () => {\n // NOTE: As it seems @testing-library may reset this flag to false\n // when it is simulating user events.\n global.IS_REACT_ACT_ENVIRONMENT = true;\n\n act(() => {\n root.unmount();\n });\n res.remove();\n };\n\n res.snapshot = () => {\n expect(res).toMatchSnapshot();\n };\n\n // NOTE: As it seems @testing-library may reset this flag to false\n // when it is simulating user events.\n global.IS_REACT_ACT_ENVIRONMENT = true;\n\n act(() => {\n root = createRoot(res);\n root.render(scene);\n });\n return res;\n}\n\ntype SnapshotOptionsT = {\n await?: Promise<void>;\n};\n\nexport async function snapshot(\n element: React.ReactElement,\n options?: SnapshotOptionsT,\n): Promise<RenderResult> {\n let res: RenderResult | undefined;\n\n // TODO: Just adding async to the actor function breaks stuff, as it makes\n // act() asynchronous no matter the `options.await` value, thus breaking all\n // calls that do not await for snapshot() result... thus... perhaps we need\n // to have a more complex typing to ensure it all works as intended in all\n // cases, and while being correctly enforced by TypeScript.\n // eslint-disable-next-line @typescript-eslint/promise-function-async\n const promise = act(() => {\n res = render(element);\n return options?.await;\n });\n\n if (res === undefined) throw Error('Render failed');\n if (options?.await) await promise;\n\n const nodes = res.asFragment().childNodes;\n expect(nodes.length > 1 ? [...nodes] : nodes[0]).toMatchSnapshot();\n return res;\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"index.js","names":["_mockdate","_interopRequireDefault","require","_react","_client","_react2","_global","global","IS_REACT_ACT_ENVIRONMENT","originalProcessVersions","process","versions","mockClientSide","Object","defineProperty","value","undefined","unmockClientSide","writable","getMockUuid","seed","x","toString","padStart","slice","mockAxios","handlers","axios","jest","requireActual","defaults","adapter","config","handler","res","data","headers","status","statusText","console","warn","JSON","stringify","e","mockTimer","time","mockdate","set","Date","now","advanceTimersByTimeAsync","mount","scene","root","element","document","createElement","body","appendChild","destroy","act","unmount","remove","snapshot","expect","toMatchSnapshot","createRoot","render","options","promise","await","Error","then","nodes","asFragment","childNodes","length"],"sources":["../../../../../src/shared/utils/jest/index.ts"],"sourcesContent":["/* global jest, document */\n/* eslint-disable import/no-extraneous-dependencies */\n\nimport type {\n AxiosRequestConfig,\n AxiosResponse,\n AxiosStatic,\n InternalAxiosRequestConfig,\n} from 'axios';\n\nimport mockdate from 'mockdate';\nimport { type ReactElement, type ReactNode, act } from 'react';\nimport { type Root, createRoot } from 'react-dom/client';\n\nimport { type RenderResult, render } from '@testing-library/react';\n\n/**\n * An alias for [act(..)](https://reactjs.org/docs/test-utils.html#act)\n * from `react`.\n */\nexport { act };\n\nexport { default as getGlobal } from './global';\n\nglobal.IS_REACT_ACT_ENVIRONMENT = true;\n\nconst originalProcessVersions = process.versions;\n\n/**\n * Tricks **react-utils** into thinking the test is running within client-side\n * (browser) environment.\n */\nexport function mockClientSide(): void {\n Object.defineProperty(process, 'versions', { value: undefined });\n}\n\n/**\n * Reverts the effect of {@link module:JU.mockClientSide mockClientSide(..)}.\n */\nexport function unmockClientSide(): void {\n Object.defineProperty(process, 'versions', {\n value: originalProcessVersions,\n writable: false,\n });\n}\n\n/**\n * Generates a mock UUID, or better said it determenistically transforms given\n * `seed` number into a UUID-formatted string.\n * @param {number} seed\n * @return {string}\n */\nexport function getMockUuid(seed = 0): string {\n const x = seed.toString(16).padStart(32, '0');\n return `${x.slice(0, 8)}-${x.slice(8, 12)}-${x.slice(12, 16)}-${x.slice(16, 20)}-${x.slice(20)}`;\n}\n\nexport type AxiosRequestHandlerT =\n (config: AxiosRequestConfig) => Partial<AxiosResponse> | null | undefined;\n\nexport function mockAxios(handlers: AxiosRequestHandlerT[]): AxiosStatic {\n const axios: AxiosStatic = jest.requireActual('axios');\n\n axios.defaults.adapter = async (\n config: AxiosRequestConfig,\n ): Promise<AxiosResponse> => {\n for (const handler of handlers) {\n const res = handler(config);\n if (res) {\n return {\n config: config as InternalAxiosRequestConfig,\n data: null,\n headers: {},\n status: 200,\n statusText: 'OK',\n ...res,\n };\n }\n }\n\n // Fallback to the regular network request.\n let res: AxiosResponse;\n try {\n res = await axios({ ...config, adapter: ['xhr', 'http', 'fetch'] });\n // eslint-disable-next-line no-console\n console.warn(\n 'Network request has not been mocked for a test.\\n\\nConfig:\\n',\n config,\n '\\n\\nResult:\\n',\n JSON.stringify(res, null, 2),\n );\n } catch (e) {\n // eslint-disable-next-line no-console\n console.warn(\n 'Network request has not been mocked for a test, and failed.\\n\\nConfig:\\n',\n config,\n '\\n\\nError\\n',\n JSON.stringify(e, null, 2),\n );\n throw e;\n }\n\n return res;\n };\n\n return axios;\n}\n\n/**\n * Advances mock timers, and mock date by the specified time.\n * @param {number} time Time step [ms].\n * @returns {Promise} Wait for this to \"jump after\" any async code which should\n * be executed because of the mock time movement.\n */\nexport async function mockTimer(time: number): Promise<void> {\n mockdate.set(time + Date.now());\n await jest.advanceTimersByTimeAsync(time);\n}\n\nexport type MountedSceneT = HTMLElement & {\n destroy: () => void;\n snapshot: () => void;\n};\n\n/**\n * Mounts `scene` to the DOM, and returns the root scene element.\n * @param scene\n * @return Created container DOM element with destroy() function\n * attached.\n */\nexport function mount(scene: ReactNode): MountedSceneT {\n let root: Root;\n const element = document.createElement('div');\n document.body.appendChild(element);\n\n const res: MountedSceneT = (element as unknown) as MountedSceneT;\n res.destroy = () => {\n // NOTE: As it seems @testing-library may reset this flag to false\n // when it is simulating user events.\n global.IS_REACT_ACT_ENVIRONMENT = true;\n\n act(() => {\n root.unmount();\n });\n res.remove();\n };\n\n res.snapshot = () => {\n expect(res).toMatchSnapshot();\n };\n\n // NOTE: As it seems @testing-library may reset this flag to false\n // when it is simulating user events.\n global.IS_REACT_ACT_ENVIRONMENT = true;\n\n act(() => {\n root = createRoot(res);\n root.render(scene);\n });\n return res;\n}\n\n// NOTE: If in future we have additional options here, they should be distributed\n// across two objects, depending whether they are applicable to the sync, or async\n// versions of snapshot(), or both.\ntype AsyncSnapshotOptionsT = {\n await?: Promise<void>;\n};\n\nexport function snapshot(element: ReactElement): RenderResult;\n\nexport async function snapshot(\n element: ReactElement,\n options: AsyncSnapshotOptionsT,\n): Promise<RenderResult>;\n\nexport function snapshot(\n element: React.ReactElement,\n options?: AsyncSnapshotOptionsT,\n): Promise<RenderResult> | RenderResult {\n let res: RenderResult | undefined;\n\n // TODO: Just adding async to the actor function breaks stuff, as it makes\n // act() asynchronous no matter the `options.await` value, thus breaking all\n // calls that do not await for snapshot() result... thus... perhaps we need\n // to have a more complex typing to ensure it all works as intended in all\n // cases, and while being correctly enforced by TypeScript.\n // eslint-disable-next-line @typescript-eslint/promise-function-async\n const promise = act(() => {\n res = render(element);\n return options?.await;\n });\n\n if (res === undefined) throw Error('Render failed');\n if (options?.await) {\n // TODO: Then body of .then() is the same as the last three lines in this\n // function, executed for the non-async variant. We should re-use that.\n return promise.then(() => {\n const nodes = res!.asFragment().childNodes;\n expect(nodes.length > 1 ? [...nodes] : nodes[0]).toMatchSnapshot();\n return res!;\n });\n }\n\n const nodes = res.asFragment().childNodes;\n expect(nodes.length > 1 ? [...nodes] : nodes[0]).toMatchSnapshot();\n return res;\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -122,11 +122,11 @@ stream=await renderPass();logMsg=bailed?`SSR timed out after ${ops.ssrTimeout} s
|
|
|
122
122
|
<html lang="en">
|
|
123
123
|
<head>
|
|
124
124
|
${grouppedExtraScripts[SCRIPT_LOCATIONS.HEAD_OPEN]}
|
|
125
|
-
${helmet
|
|
126
|
-
${helmet
|
|
125
|
+
${helmet?.title.toString()??""}
|
|
126
|
+
${helmet?.meta.toString()??""}
|
|
127
127
|
<meta name="theme-color" content="#FFFFFF">
|
|
128
128
|
${manifestLink}
|
|
129
|
-
${styleChunkString}
|
|
129
|
+
${helmet?.link.toString()??""}${styleChunkString}
|
|
130
130
|
${faviconLink}
|
|
131
131
|
<meta charset="utf-8">
|
|
132
132
|
<meta
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.js","names":["_fs","_interopRequireDefault","require","_path","_stream","_zlib","_winston","_reactGlobalState","_jsUtils","_lodash","_config","_nodeForge","_static","_reactHelmet","_reactRouter","_serializeJavascript","_buildInfo","_Cache","_jsxRuntime","sanitizedConfig","omit","config","SCRIPT_LOCATIONS","exports","ServerSsrContext","SsrContext","chunks","status","constructor","req","chunkGroups","initialState","cloneDeep","getBuildInfo","context","url","path","resolve","JSON","parse","fs","readFileSync","readChunkGroupsJson","buildDir","res","prepareCipher","key","Promise","reject","forge","random","getBytes","err","iv","cipher","createCipher","start","isBrotliAcceptable","acceptable","get","ops","split","op","type","priority","trim","parseFloat","groupExtraScripts","scripts","BODY_OPEN","DEFAULT","HEAD_OPEN","script","isString","code","location","Error","newDefaultLogger","defaultLogLevel","format","transports","winston","createLogger","combine","splat","timestamp","colorize","printf","level","message","stack","rest","Object","keys","length","stringify","Console","factory","webpackConfig","options","defaults","clone","beforeRender","maxSsrRounds","ssrTimeout","staticCacheSize","logger","defaultLoggerLogLevel","buildInfo","setBuildInfo","publicPath","outputPath","output","manifestLink","existsSync","cache","staticCacheController","Cache","CHUNK_GROUPS","next","set","cookie","csrfToken","cacheRef","data","buffer","noCsp","send","done","failed","brotliDecompress","error","html","h","toString","regex","RegExp","nonce","replace","configToInject","extraScripts","all","helmet","webpackStats","locals","mapValues","toJson","namedChunkGroups","item","assets","map","name","App","Application","appHtmlMarkup","ssrContext","stream","ssrStart","Date","now","App2","renderPass","helmetContext","prelude","prerenderToNodeStream","jsx","GlobalStateProvider","state","children","StaticRouter","HelmetProvider","onError","ssrRound","bailed","dirty","timeout","race","allSettled","pending","timer","then","logMsg","log","ready","pipe","Writable","destroy","write","chunk","_","payload","serializeJs","CONFIG","ISTATE","ignoreFunction","unsafe","update","util","createBuffer","finish","INJ","encode64","chunkSet","Set","forEach","asset","add","styleChunkString","scriptChunkString","endsWith","grouppedExtraScripts","faviconLink","favicon","title","meta","brotliCompress","b"],"sources":["../../../src/server/renderer.tsx"],"sourcesContent":["/**\n * ExpressJS middleware for server-side rendering of a ReactJS app.\n */\n\nimport fs from 'fs';\nimport path from 'path';\n\nimport type { Request, RequestHandler } from 'express';\nimport type { ComponentType } from 'react';\nimport { Writable } from 'stream';\nimport { brotliCompress, brotliDecompress } from 'zlib';\nimport winston from 'winston';\n\nimport { GlobalStateProvider, SsrContext } from '@dr.pogodin/react-global-state';\nimport { timer } from '@dr.pogodin/js-utils';\n\nimport {\n clone,\n cloneDeep,\n defaults,\n isString,\n get,\n mapValues,\n omit,\n} from 'lodash';\n\nimport config from 'config';\nimport forge from 'node-forge';\n\nimport { prerenderToNodeStream } from 'react-dom/static';\nimport { HelmetProvider, type HelmetDataContext } from '@dr.pogodin/react-helmet';\nimport { StaticRouter } from 'react-router';\nimport serializeJs from 'serialize-javascript';\nimport { type BuildInfoT, setBuildInfo } from 'utils/isomorphy/buildInfo';\n\nimport type { ChunkGroupsT, SsrContextT } from 'utils/globalState';\n\nimport type { Configuration, Stats } from 'webpack';\n\nimport Cache from './Cache';\n\nconst sanitizedConfig = omit(config, 'SECRET');\n\n// Note: These type definitions for logger are copied from Winston logger,\n// then simplified to make it easier to fit an alternative logger into this\n// interface.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\ninterface LogMethodI {\n // eslint-disable-next-line @typescript-eslint/prefer-function-type\n (level: string, message: string, ...meta: unknown[]): void;\n}\n\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\ninterface LeveledLogMethodI {\n // eslint-disable-next-line @typescript-eslint/prefer-function-type\n (message: string, ...meta: unknown[]): void;\n}\n\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface LoggerI {\n debug: LeveledLogMethodI;\n error: LeveledLogMethodI;\n info: LeveledLogMethodI;\n log: LogMethodI;\n warn: LeveledLogMethodI;\n}\n\nexport enum SCRIPT_LOCATIONS {\n BODY_OPEN = 'BODY_OPEN',\n DEFAULT = 'DEFAULT',\n HEAD_OPEN = 'HEAD_OPEN',\n}\n\nexport class ServerSsrContext<StateT>\n extends SsrContext<StateT>\n implements SsrContextT<StateT> {\n chunkGroups: ChunkGroupsT;\n\n chunks: string[] = [];\n\n req: Request;\n\n status: number = 200;\n\n constructor(\n req: Request,\n chunkGroups: ChunkGroupsT,\n initialState?: StateT,\n ) {\n super(cloneDeep(initialState) ?? ({} as StateT));\n this.chunkGroups = chunkGroups;\n this.req = req;\n }\n}\n\ntype ScriptT = {\n code: string;\n location: SCRIPT_LOCATIONS;\n};\n\n/**\n * Reads build-time information about the app. This information is generated\n * by our standard Webpack config for apps, and it is written into\n * \".build-info\" file in the context folder specified in Webpack config.\n * At the moment, that file contains build timestamp and a random 32-bit key,\n * suitable for cryptographical use.\n * @param context Webpack context path used during the build.\n * @return Resolves to the build-time information.\n */\nfunction getBuildInfo(context: string) {\n const url = path.resolve(context, '.build-info');\n return JSON.parse(fs.readFileSync(url, 'utf8')) as BuildInfoT;\n}\n\n/**\n * Attempts to read from disk the named chunk groups mapping generated\n * by Webpack during the compilation.\n * It will not work for development builds, where these stats should be captured\n * via compilator callback.\n * @param buildDir\n * @return\n */\nfunction readChunkGroupsJson(buildDir: string) {\n const url = path.resolve(buildDir, '__chunk_groups__.json');\n let res;\n try {\n res = JSON.parse(fs.readFileSync(url, 'utf8')) as Record<string, string[]>;\n } catch {\n // TODO: Should we message the error here somehow?\n res = null;\n }\n return res;\n}\n\n/**\n * Prepares a new Cipher for data encryption.\n * @param key Encryption key (32-bit random key is expected, see\n * node-forge documentation, in case of doubts).\n * @return Resolves to the object with two fields:\n * 1. cipher - a new Cipher, ready for encryption;\n * 2. iv - initial vector used by the cipher.\n */\nasync function prepareCipher(key: string): Promise<{\n cipher: forge.cipher.BlockCipher;\n iv: string;\n}> {\n return new Promise((resolve, reject) => {\n forge.random.getBytes(32, (err, iv) => {\n if (err) reject(err);\n else {\n const cipher = forge.cipher.createCipher('AES-CBC', key);\n cipher.start({ iv });\n resolve({ cipher, iv });\n }\n });\n });\n}\n\n/**\n * Given an incoming HTTP requests, it deduces whether Brotli-encoded responses\n * are acceptable to the caller.\n * @param req\n */\nexport function isBrotliAcceptable(req: Request): boolean {\n const acceptable = req.get('accept-encoding');\n if (acceptable) {\n const ops = acceptable.split(',');\n for (const op of ops) {\n const [type, priority] = op.trim().split(';q=');\n if ((type === '*' || type === 'br')\n && (!priority || parseFloat(priority) > 0)) {\n return true;\n }\n }\n }\n return false;\n}\n\n/**\n * Given an array of extra script strings / objects, it returns an object with\n * arrays of scripts to inject in different HTML template locations. During\n * the script groupping it also filters out any empty scripts.\n * @param {({\n * code: string;\n * location: string;\n * }|string)[]} [scripts=[]]\n * @return {{\n * BODY_OPEN: string[];\n * DEFAULT: string[];\n * HEAD_OPEN: string[];\n * }}\n */\nfunction groupExtraScripts(scripts: Array<string | ScriptT> = []) {\n const res = {\n [SCRIPT_LOCATIONS.BODY_OPEN]: '',\n [SCRIPT_LOCATIONS.DEFAULT]: '',\n [SCRIPT_LOCATIONS.HEAD_OPEN]: '',\n };\n for (const script of scripts) {\n if (isString(script)) {\n if (script) res[SCRIPT_LOCATIONS.DEFAULT] += script;\n } else if (script.code) {\n if (script.location in res) res[script.location] += script.code;\n else throw Error(`Invalid location \"${script.location}\"`);\n }\n }\n return res;\n}\n\n/**\n * Creates a new default (Winston) logger.\n * @param {object} [options={}]\n * @param {string} [options.defaultLogLevel='info']\n * @return {object}\n */\nexport function newDefaultLogger({\n defaultLogLevel = 'info',\n} = {}): winston.Logger {\n const { format, transports } = winston;\n return winston.createLogger({\n format: format.combine(\n format.splat(),\n format.timestamp(),\n format.colorize(),\n format.printf(\n ({\n level,\n message,\n timestamp,\n stack,\n ...rest\n }) => {\n let res = `${level}\\t(at ${timestamp as string}):\\t${message as string}`;\n if (Object.keys(rest).length) {\n res += `\\n${JSON.stringify(rest, null, 2)}`;\n }\n if (stack) res += `\\n${stack as string}`;\n return res;\n },\n ),\n ),\n level: defaultLogLevel,\n transports: [new transports.Console()],\n });\n}\n\nexport type ConfigT = {\n [key: string]: ConfigT | string;\n};\n\nexport type BeforeRenderResT = {\n configToInject?: ConfigT;\n extraScripts?: Array<ScriptT | string>;\n initialState?: unknown;\n};\n\nexport type BeforeRenderT =\n(req: Request, config: ConfigT) => BeforeRenderResT | Promise<BeforeRenderResT>;\n\ntype CacheRefT = {\n key: string;\n maxage?: number;\n};\n\nexport type OptionsT = {\n Application?: ComponentType;\n beforeRender?: BeforeRenderT;\n buildInfo?: BuildInfoT;\n defaultLoggerLogLevel?: string;\n favicon?: string;\n logger?: LoggerI;\n maxSsrRounds?: number;\n noCsp?: boolean;\n staticCacheSize?: number;\n ssrTimeout?: number;\n staticCacheController?: (req: Request) => CacheRefT | null | undefined;\n};\n\n/**\n * Creates the middleware.\n * @param webpackConfig\n * @param options Additional options:\n * @param [options.Application] The root ReactJS component of\n * the app to use for the server-side rendering. When not provided\n * the server-side rendering is disabled.\n * @param [options.buildInfo] \"Build info\" object to use. If provided,\n * it will be used, instead of trying to load from the filesystem the one\n * generated by the Webpack build. It is intended for test environments,\n * where passing this stuff via file system is no bueno.\n * @param [options.favicon] `true` will include favicon\n * link into the rendered HTML templates.\n * @param [options.noCsp] `true` means that no\n * Content-Security-Policy (CSP) is used by server, thus the renderer\n * may cut a few corners.\n * @param [options.maxSsrRounds=10] Maximum number of SSR rounds.\n * @param [options.ssrTimeout=1000] SSR timeout in milliseconds,\n * defaults to 1 second.\n * @param [options.staticCacheSize=1.e7] The maximum\n * static cache size in bytes. Defaults to ~10 MB.\n * @param [options.staticCacheController] When given, it activates,\n * and controls the static caching of generated HTML markup. When this function\n * is provided, on each incoming request it is triggered with the request\n * passed in as the argument. To attempt to serve the response from the cache\n * it should return the object with the following fields:\n * - `key: string` – the cache key for the response;\n * - `maxage?: number` – the maximum age of cached result in ms.\n * If undefined - infinite age is assumed.\n * @return Created middleware.\n */\nexport default function factory(\n webpackConfig: Configuration,\n options: OptionsT,\n): RequestHandler {\n const ops: OptionsT = defaults(clone(options), {\n beforeRender: async () => Promise.resolve({}),\n maxSsrRounds: 10,\n ssrTimeout: 1000,\n staticCacheSize: 1.e7,\n });\n\n // Note: in normal use the default logger is created and set in the root\n // server function, and this initialization is for testing uses, where\n // renderer is imported directly.\n ops.logger ??= newDefaultLogger({\n defaultLogLevel: ops.defaultLoggerLogLevel,\n });\n\n const buildInfo = ops.buildInfo ?? getBuildInfo(webpackConfig.context!);\n setBuildInfo(buildInfo);\n\n // publicPath from webpack.output has a trailing slash at the end.\n const { publicPath, path: outputPath } = webpackConfig.output!;\n\n const manifestLink = fs.existsSync(`${outputPath}/manifest.json`)\n ? `<link rel=\"manifest\" href=\"${publicPath as string}manifest.json\">` : '';\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-definitions\n interface BufferWithNonce extends Buffer {\n nonce: string;\n }\n\n const cache = ops.staticCacheController\n ? new Cache<{\n buffer: BufferWithNonce;\n status: number;\n }>(ops.staticCacheSize!)\n : null;\n\n const CHUNK_GROUPS = readChunkGroupsJson(outputPath!);\n\n // TODO: Look at it later.\n // eslint-disable-next-line complexity\n return async (req, res, next) => {\n try {\n // Ensures any caches always revalidate HTML markup before reuse.\n res.set('Cache-Control', 'no-cache');\n\n res.cookie('csrfToken', req.csrfToken());\n\n let cacheRef: CacheRefT | null | undefined;\n if (cache) {\n cacheRef = ops.staticCacheController!(req);\n if (cacheRef) {\n const data = cache.get(cacheRef);\n if (data !== null) {\n const { buffer, status } = data;\n if (ops.noCsp && isBrotliAcceptable(req)) {\n res.set('Content-Type', 'text/html');\n res.set('Content-Encoding', 'br');\n if (status !== 200) res.status(status);\n res.send(buffer);\n } else {\n await new Promise<void>((done, failed) => {\n brotliDecompress(buffer, (error, html) => {\n if (error) failed(error);\n else {\n let h = html.toString();\n if (!ops.noCsp) {\n // TODO: Starting from Node v15 we'll be able to use string's\n // .replaceAll() method instead relying on reg. expression for\n // global matching.\n const regex = new RegExp(buffer.nonce, 'g');\n\n // TODO: It should be implemented more careful.\n h = h.replace(regex, (req as unknown as {\n nonce: string;\n }).nonce);\n }\n if (status !== 200) res.status(status);\n res.send(h);\n done();\n }\n });\n });\n }\n return;\n }\n }\n }\n\n const [{\n configToInject,\n extraScripts,\n initialState,\n }, {\n cipher,\n iv,\n }] = await Promise.all([\n ops.beforeRender!(req, sanitizedConfig as unknown as ConfigT),\n prepareCipher(buildInfo.key),\n ]);\n\n let helmet: HelmetDataContext['helmet'];\n\n // Gets the mapping between code chunk names and their asset files.\n // These data come from the Webpack compilation, either from the stats\n // attached to the request (in dev mode), or from a file output during\n // the build (in prod mode).\n let chunkGroups: ChunkGroupsT;\n const webpackStats = get(res.locals, 'webpack.devMiddleware.stats') as Stats | undefined;\n if (webpackStats) {\n chunkGroups = mapValues(\n webpackStats.toJson({\n all: false,\n chunkGroups: true,\n }).namedChunkGroups,\n (item) => item.assets?.map(({ name }: { name: string }) => name)\n ?? [],\n );\n } else if (CHUNK_GROUPS) chunkGroups = CHUNK_GROUPS;\n else chunkGroups = {};\n\n /* Optional server-side rendering. */\n const App = ops.Application;\n let appHtmlMarkup: string = '';\n const ssrContext = new ServerSsrContext(req, chunkGroups, initialState);\n let stream: NodeJS.ReadableStream;\n if (App) {\n const ssrStart = Date.now();\n\n // TODO: Somehow, without it TS does not realise that\n // App has been checked to exist.\n const App2 = App;\n\n const renderPass = async () => {\n ssrContext.chunks = [];\n\n // TODO: prerenderToNodeStream has (abort) \"signal\" option,\n // and we should wire it up to the SSR timeout below.\n const helmetContext = {} as HelmetDataContext;\n const { prelude } = await prerenderToNodeStream(\n <GlobalStateProvider\n initialState={ssrContext.state}\n ssrContext={ssrContext}\n >\n <StaticRouter location={req.url}>\n <HelmetProvider context={helmetContext}>\n <App2 />\n </HelmetProvider>\n </StaticRouter>\n </GlobalStateProvider>,\n { onError: (error) => {\n throw error;\n } },\n );\n ({ helmet } = helmetContext);\n\n return prelude;\n };\n\n let ssrRound = 0;\n let bailed = false;\n for (; ssrRound < ops.maxSsrRounds!; ++ssrRound) {\n stream = await renderPass();\n\n if (!ssrContext.dirty) break;\n\n const timeout = ops.ssrTimeout! + ssrStart - Date.now();\n bailed = timeout <= 0 || !await Promise.race([\n Promise.allSettled(ssrContext.pending),\n timer(timeout).then(() => false),\n ]);\n if (bailed) break;\n }\n\n let logMsg;\n if (ssrContext.dirty) {\n // NOTE: In the case of incomplete SSR one more round is necessary\n // to ensure the correct hydration when some pending promises have\n // resolved and placed their data into the initial global state.\n stream = await renderPass();\n\n logMsg = bailed ? `SSR timed out after ${ops.ssrTimeout} second(s)`\n : `SSR bailed out after ${ops.maxSsrRounds} round(s)`;\n } else logMsg = `SSR completed in ${ssrRound + 1} round(s)`;\n\n ops.logger!.log(ssrContext.dirty ? 'warn' : 'info', logMsg);\n\n await new Promise((ready) => {\n stream!.pipe(new Writable({\n destroy: ready,\n write: (chunk: { toString: () => string }, _, done) => {\n appHtmlMarkup += chunk.toString();\n done();\n },\n }));\n });\n }\n\n /* Encrypts data to be injected into HTML.\n * Keep in mind, that this encryption is no way secure: as the JS bundle\n * contains decryption key and is able to decode it at the client side.\n * Hovewer, for a number of reasons, encryption of injected data is still\n * better than injection of a plain text. */\n const payload = serializeJs({\n CHUNK_GROUPS: chunkGroups,\n CONFIG: configToInject ?? sanitizedConfig,\n ISTATE: ssrContext.state,\n }, {\n ignoreFunction: true,\n unsafe: true,\n });\n cipher.update(forge.util.createBuffer(payload, 'utf8'));\n cipher.finish();\n const INJ = forge.util.encode64(`${iv}${cipher.output.data}`);\n\n const chunkSet = new Set<string>();\n\n // TODO: \"main\" chunk has to be added explicitly,\n // because unlike all other chunks they are not managed by <CodeSplit>\n // component, thus they are not added to the ssrContext.chunks\n // automatically. Actually, names of these entry chunks should be\n // read from Wepback config, as the end user may customize them,\n // remove or add other entry points, but it requires additional\n // efforts to figure out how to automatically order them right,\n // thus for now this handles the default config.\n [\n 'main',\n ...ssrContext.chunks,\n ].forEach((chunk) => {\n const assets = chunkGroups[chunk];\n if (assets) assets.forEach((asset) => chunkSet.add(asset));\n });\n\n let styleChunkString = '';\n let scriptChunkString = '';\n chunkSet.forEach((chunk) => {\n if (chunk.endsWith('.css')) {\n styleChunkString += `<link href=\"${publicPath as string}${chunk}\" rel=\"stylesheet\">`;\n } else if (\n chunk.endsWith('.js')\n // In dev mode HMR adds JS updates into asset arrays,\n // and they (updates) should be ignored.\n && !chunk.endsWith('.hot-update.js')\n ) {\n scriptChunkString += `<script src=\"${publicPath as string}${chunk}\" type=\"application/javascript\"></script>`;\n }\n });\n\n const grouppedExtraScripts = groupExtraScripts(extraScripts);\n\n const faviconLink = ops.favicon\n ? '<link rel=\"shortcut icon\" href=\"/favicon.ico\">'\n : '';\n\n const html = `<!DOCTYPE html>\n <html lang=\"en\">\n <head>\n ${grouppedExtraScripts[SCRIPT_LOCATIONS.HEAD_OPEN]}\n ${helmet ? helmet.title.toString() : ''}\n ${helmet ? helmet.meta.toString() : ''}\n <meta name=\"theme-color\" content=\"#FFFFFF\">\n ${manifestLink}\n ${styleChunkString}\n ${faviconLink}\n <meta charset=\"utf-8\">\n <meta\n content=\"width=device-width,initial-scale=1.0\"\n name=\"viewport\"\n >\n <meta itemprop=\"drpruinj\" content=\"${INJ}\">\n </head>\n <body>\n ${grouppedExtraScripts[SCRIPT_LOCATIONS.BODY_OPEN]}\n <div id=\"react-view\">${appHtmlMarkup}</div>\n ${scriptChunkString}\n ${grouppedExtraScripts[SCRIPT_LOCATIONS.DEFAULT]}\n </body>\n </html>`;\n\n const status = ssrContext.status || 200;\n if (status !== 200) res.status(status);\n\n if (cacheRef && status < 500) {\n // Note: waiting for the caching to complete is not strictly necessary,\n // but it greately simplifies testing, and error reporting.\n await new Promise<void>((done, failed) => {\n brotliCompress(html, (error, buffer) => {\n const b = buffer as BufferWithNonce;\n if (error) failed(error);\n else {\n b.nonce = (req as unknown as {\n nonce: string;\n }).nonce;\n cache!.add({ buffer: b, status }, cacheRef.key, buffer.length);\n done();\n }\n });\n });\n }\n\n // Note: as caching code above may throw in some cases, sending response\n // before it completes will likely hide the error, making it difficult\n // to debug. Thus, at least for now, lets send response after it.\n res.send(html);\n } catch (error) {\n next(error);\n }\n };\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"renderer.js","names":["_fs","_interopRequireDefault","require","_path","_stream","_zlib","_winston","_reactGlobalState","_jsUtils","_lodash","_config","_nodeForge","_static","_reactHelmet","_reactRouter","_serializeJavascript","_buildInfo","_Cache","_jsxRuntime","sanitizedConfig","omit","config","SCRIPT_LOCATIONS","exports","ServerSsrContext","SsrContext","chunks","status","constructor","req","chunkGroups","initialState","cloneDeep","getBuildInfo","context","url","path","resolve","JSON","parse","fs","readFileSync","readChunkGroupsJson","buildDir","res","prepareCipher","key","Promise","reject","forge","random","getBytes","err","iv","cipher","createCipher","start","isBrotliAcceptable","acceptable","get","ops","split","op","type","priority","trim","parseFloat","groupExtraScripts","scripts","BODY_OPEN","DEFAULT","HEAD_OPEN","script","isString","code","location","Error","newDefaultLogger","defaultLogLevel","format","transports","winston","createLogger","combine","splat","timestamp","colorize","printf","level","message","stack","rest","Object","keys","length","stringify","Console","factory","webpackConfig","options","defaults","clone","beforeRender","maxSsrRounds","ssrTimeout","staticCacheSize","logger","defaultLoggerLogLevel","buildInfo","setBuildInfo","publicPath","outputPath","output","manifestLink","existsSync","cache","staticCacheController","Cache","CHUNK_GROUPS","next","set","cookie","csrfToken","cacheRef","data","buffer","noCsp","send","done","failed","brotliDecompress","error","html","h","toString","regex","RegExp","nonce","replace","configToInject","extraScripts","all","helmet","webpackStats","locals","mapValues","toJson","namedChunkGroups","item","assets","map","name","App","Application","appHtmlMarkup","ssrContext","stream","ssrStart","Date","now","App2","renderPass","helmetContext","prelude","prerenderToNodeStream","jsx","GlobalStateProvider","state","children","StaticRouter","HelmetProvider","onError","ssrRound","bailed","dirty","timeout","race","allSettled","pending","timer","then","logMsg","log","ready","pipe","Writable","destroy","write","chunk","_","payload","serializeJs","CONFIG","ISTATE","ignoreFunction","unsafe","update","util","createBuffer","finish","INJ","encode64","chunkSet","Set","forEach","asset","add","styleChunkString","scriptChunkString","endsWith","grouppedExtraScripts","faviconLink","favicon","title","meta","link","brotliCompress","b"],"sources":["../../../src/server/renderer.tsx"],"sourcesContent":["/**\n * ExpressJS middleware for server-side rendering of a ReactJS app.\n */\n\nimport fs from 'fs';\nimport path from 'path';\n\nimport type { Request, RequestHandler } from 'express';\nimport type { ComponentType } from 'react';\nimport { Writable } from 'stream';\nimport { brotliCompress, brotliDecompress } from 'zlib';\nimport winston from 'winston';\n\nimport { GlobalStateProvider, SsrContext } from '@dr.pogodin/react-global-state';\nimport { timer } from '@dr.pogodin/js-utils';\n\nimport {\n clone,\n cloneDeep,\n defaults,\n isString,\n get,\n mapValues,\n omit,\n} from 'lodash';\n\nimport config from 'config';\nimport forge from 'node-forge';\n\nimport { prerenderToNodeStream } from 'react-dom/static';\nimport { HelmetProvider, type HelmetDataContext } from '@dr.pogodin/react-helmet';\nimport { StaticRouter } from 'react-router';\nimport serializeJs from 'serialize-javascript';\nimport { type BuildInfoT, setBuildInfo } from 'utils/isomorphy/buildInfo';\n\nimport type { ChunkGroupsT, SsrContextT } from 'utils/globalState';\n\nimport type { Configuration, Stats } from 'webpack';\n\nimport Cache from './Cache';\n\nconst sanitizedConfig = omit(config, 'SECRET');\n\n// Note: These type definitions for logger are copied from Winston logger,\n// then simplified to make it easier to fit an alternative logger into this\n// interface.\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\ninterface LogMethodI {\n // eslint-disable-next-line @typescript-eslint/prefer-function-type\n (level: string, message: string, ...meta: unknown[]): void;\n}\n\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\ninterface LeveledLogMethodI {\n // eslint-disable-next-line @typescript-eslint/prefer-function-type\n (message: string, ...meta: unknown[]): void;\n}\n\n// eslint-disable-next-line @typescript-eslint/consistent-type-definitions\nexport interface LoggerI {\n debug: LeveledLogMethodI;\n error: LeveledLogMethodI;\n info: LeveledLogMethodI;\n log: LogMethodI;\n warn: LeveledLogMethodI;\n}\n\nexport enum SCRIPT_LOCATIONS {\n BODY_OPEN = 'BODY_OPEN',\n DEFAULT = 'DEFAULT',\n HEAD_OPEN = 'HEAD_OPEN',\n}\n\nexport class ServerSsrContext<StateT>\n extends SsrContext<StateT>\n implements SsrContextT<StateT> {\n chunkGroups: ChunkGroupsT;\n\n chunks: string[] = [];\n\n req: Request;\n\n status: number = 200;\n\n constructor(\n req: Request,\n chunkGroups: ChunkGroupsT,\n initialState?: StateT,\n ) {\n super(cloneDeep(initialState) ?? ({} as StateT));\n this.chunkGroups = chunkGroups;\n this.req = req;\n }\n}\n\ntype ScriptT = {\n code: string;\n location: SCRIPT_LOCATIONS;\n};\n\n/**\n * Reads build-time information about the app. This information is generated\n * by our standard Webpack config for apps, and it is written into\n * \".build-info\" file in the context folder specified in Webpack config.\n * At the moment, that file contains build timestamp and a random 32-bit key,\n * suitable for cryptographical use.\n * @param context Webpack context path used during the build.\n * @return Resolves to the build-time information.\n */\nfunction getBuildInfo(context: string) {\n const url = path.resolve(context, '.build-info');\n return JSON.parse(fs.readFileSync(url, 'utf8')) as BuildInfoT;\n}\n\n/**\n * Attempts to read from disk the named chunk groups mapping generated\n * by Webpack during the compilation.\n * It will not work for development builds, where these stats should be captured\n * via compilator callback.\n * @param buildDir\n * @return\n */\nfunction readChunkGroupsJson(buildDir: string) {\n const url = path.resolve(buildDir, '__chunk_groups__.json');\n let res;\n try {\n res = JSON.parse(fs.readFileSync(url, 'utf8')) as Record<string, string[]>;\n } catch {\n // TODO: Should we message the error here somehow?\n res = null;\n }\n return res;\n}\n\n/**\n * Prepares a new Cipher for data encryption.\n * @param key Encryption key (32-bit random key is expected, see\n * node-forge documentation, in case of doubts).\n * @return Resolves to the object with two fields:\n * 1. cipher - a new Cipher, ready for encryption;\n * 2. iv - initial vector used by the cipher.\n */\nasync function prepareCipher(key: string): Promise<{\n cipher: forge.cipher.BlockCipher;\n iv: string;\n}> {\n return new Promise((resolve, reject) => {\n forge.random.getBytes(32, (err, iv) => {\n if (err) reject(err);\n else {\n const cipher = forge.cipher.createCipher('AES-CBC', key);\n cipher.start({ iv });\n resolve({ cipher, iv });\n }\n });\n });\n}\n\n/**\n * Given an incoming HTTP requests, it deduces whether Brotli-encoded responses\n * are acceptable to the caller.\n * @param req\n */\nexport function isBrotliAcceptable(req: Request): boolean {\n const acceptable = req.get('accept-encoding');\n if (acceptable) {\n const ops = acceptable.split(',');\n for (const op of ops) {\n const [type, priority] = op.trim().split(';q=');\n if ((type === '*' || type === 'br')\n && (!priority || parseFloat(priority) > 0)) {\n return true;\n }\n }\n }\n return false;\n}\n\n/**\n * Given an array of extra script strings / objects, it returns an object with\n * arrays of scripts to inject in different HTML template locations. During\n * the script groupping it also filters out any empty scripts.\n * @param {({\n * code: string;\n * location: string;\n * }|string)[]} [scripts=[]]\n * @return {{\n * BODY_OPEN: string[];\n * DEFAULT: string[];\n * HEAD_OPEN: string[];\n * }}\n */\nfunction groupExtraScripts(scripts: Array<string | ScriptT> = []) {\n const res = {\n [SCRIPT_LOCATIONS.BODY_OPEN]: '',\n [SCRIPT_LOCATIONS.DEFAULT]: '',\n [SCRIPT_LOCATIONS.HEAD_OPEN]: '',\n };\n for (const script of scripts) {\n if (isString(script)) {\n if (script) res[SCRIPT_LOCATIONS.DEFAULT] += script;\n } else if (script.code) {\n if (script.location in res) res[script.location] += script.code;\n else throw Error(`Invalid location \"${script.location}\"`);\n }\n }\n return res;\n}\n\n/**\n * Creates a new default (Winston) logger.\n * @param {object} [options={}]\n * @param {string} [options.defaultLogLevel='info']\n * @return {object}\n */\nexport function newDefaultLogger({\n defaultLogLevel = 'info',\n} = {}): winston.Logger {\n const { format, transports } = winston;\n return winston.createLogger({\n format: format.combine(\n format.splat(),\n format.timestamp(),\n format.colorize(),\n format.printf(\n ({\n level,\n message,\n timestamp,\n stack,\n ...rest\n }) => {\n let res = `${level}\\t(at ${timestamp as string}):\\t${message as string}`;\n if (Object.keys(rest).length) {\n res += `\\n${JSON.stringify(rest, null, 2)}`;\n }\n if (stack) res += `\\n${stack as string}`;\n return res;\n },\n ),\n ),\n level: defaultLogLevel,\n transports: [new transports.Console()],\n });\n}\n\nexport type ConfigT = {\n [key: string]: ConfigT | string;\n};\n\nexport type BeforeRenderResT = {\n configToInject?: ConfigT;\n extraScripts?: Array<ScriptT | string>;\n initialState?: unknown;\n};\n\nexport type BeforeRenderT =\n(req: Request, config: ConfigT) => BeforeRenderResT | Promise<BeforeRenderResT>;\n\ntype CacheRefT = {\n key: string;\n maxage?: number;\n};\n\nexport type OptionsT = {\n Application?: ComponentType;\n beforeRender?: BeforeRenderT;\n buildInfo?: BuildInfoT;\n defaultLoggerLogLevel?: string;\n favicon?: string;\n logger?: LoggerI;\n maxSsrRounds?: number;\n noCsp?: boolean;\n staticCacheSize?: number;\n ssrTimeout?: number;\n staticCacheController?: (req: Request) => CacheRefT | null | undefined;\n};\n\n/**\n * Creates the middleware.\n * @param webpackConfig\n * @param options Additional options:\n * @param [options.Application] The root ReactJS component of\n * the app to use for the server-side rendering. When not provided\n * the server-side rendering is disabled.\n * @param [options.buildInfo] \"Build info\" object to use. If provided,\n * it will be used, instead of trying to load from the filesystem the one\n * generated by the Webpack build. It is intended for test environments,\n * where passing this stuff via file system is no bueno.\n * @param [options.favicon] `true` will include favicon\n * link into the rendered HTML templates.\n * @param [options.noCsp] `true` means that no\n * Content-Security-Policy (CSP) is used by server, thus the renderer\n * may cut a few corners.\n * @param [options.maxSsrRounds=10] Maximum number of SSR rounds.\n * @param [options.ssrTimeout=1000] SSR timeout in milliseconds,\n * defaults to 1 second.\n * @param [options.staticCacheSize=1.e7] The maximum\n * static cache size in bytes. Defaults to ~10 MB.\n * @param [options.staticCacheController] When given, it activates,\n * and controls the static caching of generated HTML markup. When this function\n * is provided, on each incoming request it is triggered with the request\n * passed in as the argument. To attempt to serve the response from the cache\n * it should return the object with the following fields:\n * - `key: string` – the cache key for the response;\n * - `maxage?: number` – the maximum age of cached result in ms.\n * If undefined - infinite age is assumed.\n * @return Created middleware.\n */\nexport default function factory(\n webpackConfig: Configuration,\n options: OptionsT,\n): RequestHandler {\n const ops: OptionsT = defaults(clone(options), {\n beforeRender: async () => Promise.resolve({}),\n maxSsrRounds: 10,\n ssrTimeout: 1000,\n staticCacheSize: 1.e7,\n });\n\n // Note: in normal use the default logger is created and set in the root\n // server function, and this initialization is for testing uses, where\n // renderer is imported directly.\n ops.logger ??= newDefaultLogger({\n defaultLogLevel: ops.defaultLoggerLogLevel,\n });\n\n const buildInfo = ops.buildInfo ?? getBuildInfo(webpackConfig.context!);\n setBuildInfo(buildInfo);\n\n // publicPath from webpack.output has a trailing slash at the end.\n const { publicPath, path: outputPath } = webpackConfig.output!;\n\n const manifestLink = fs.existsSync(`${outputPath}/manifest.json`)\n ? `<link rel=\"manifest\" href=\"${publicPath as string}manifest.json\">` : '';\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-definitions\n interface BufferWithNonce extends Buffer {\n nonce: string;\n }\n\n const cache = ops.staticCacheController\n ? new Cache<{\n buffer: BufferWithNonce;\n status: number;\n }>(ops.staticCacheSize!)\n : null;\n\n const CHUNK_GROUPS = readChunkGroupsJson(outputPath!);\n\n // TODO: Look at it later.\n // eslint-disable-next-line complexity\n return async (req, res, next) => {\n try {\n // Ensures any caches always revalidate HTML markup before reuse.\n res.set('Cache-Control', 'no-cache');\n\n res.cookie('csrfToken', req.csrfToken());\n\n let cacheRef: CacheRefT | null | undefined;\n if (cache) {\n cacheRef = ops.staticCacheController!(req);\n if (cacheRef) {\n const data = cache.get(cacheRef);\n if (data !== null) {\n const { buffer, status } = data;\n if (ops.noCsp && isBrotliAcceptable(req)) {\n res.set('Content-Type', 'text/html');\n res.set('Content-Encoding', 'br');\n if (status !== 200) res.status(status);\n res.send(buffer);\n } else {\n await new Promise<void>((done, failed) => {\n brotliDecompress(buffer, (error, html) => {\n if (error) failed(error);\n else {\n let h = html.toString();\n if (!ops.noCsp) {\n // TODO: Starting from Node v15 we'll be able to use string's\n // .replaceAll() method instead relying on reg. expression for\n // global matching.\n const regex = new RegExp(buffer.nonce, 'g');\n\n // TODO: It should be implemented more careful.\n h = h.replace(regex, (req as unknown as {\n nonce: string;\n }).nonce);\n }\n if (status !== 200) res.status(status);\n res.send(h);\n done();\n }\n });\n });\n }\n return;\n }\n }\n }\n\n const [{\n configToInject,\n extraScripts,\n initialState,\n }, {\n cipher,\n iv,\n }] = await Promise.all([\n ops.beforeRender!(req, sanitizedConfig as unknown as ConfigT),\n prepareCipher(buildInfo.key),\n ]);\n\n let helmet: HelmetDataContext['helmet'];\n\n // Gets the mapping between code chunk names and their asset files.\n // These data come from the Webpack compilation, either from the stats\n // attached to the request (in dev mode), or from a file output during\n // the build (in prod mode).\n let chunkGroups: ChunkGroupsT;\n const webpackStats = get(res.locals, 'webpack.devMiddleware.stats') as Stats | undefined;\n if (webpackStats) {\n chunkGroups = mapValues(\n webpackStats.toJson({\n all: false,\n chunkGroups: true,\n }).namedChunkGroups,\n (item) => item.assets?.map(({ name }: { name: string }) => name)\n ?? [],\n );\n } else if (CHUNK_GROUPS) chunkGroups = CHUNK_GROUPS;\n else chunkGroups = {};\n\n /* Optional server-side rendering. */\n const App = ops.Application;\n let appHtmlMarkup: string = '';\n const ssrContext = new ServerSsrContext(req, chunkGroups, initialState);\n let stream: NodeJS.ReadableStream;\n if (App) {\n const ssrStart = Date.now();\n\n // TODO: Somehow, without it TS does not realise that\n // App has been checked to exist.\n const App2 = App;\n\n const renderPass = async () => {\n ssrContext.chunks = [];\n\n // TODO: prerenderToNodeStream has (abort) \"signal\" option,\n // and we should wire it up to the SSR timeout below.\n const helmetContext = {} as HelmetDataContext;\n const { prelude } = await prerenderToNodeStream(\n <GlobalStateProvider\n initialState={ssrContext.state}\n ssrContext={ssrContext}\n >\n <StaticRouter location={req.url}>\n <HelmetProvider context={helmetContext}>\n <App2 />\n </HelmetProvider>\n </StaticRouter>\n </GlobalStateProvider>,\n { onError: (error) => {\n throw error;\n } },\n );\n ({ helmet } = helmetContext);\n\n return prelude;\n };\n\n let ssrRound = 0;\n let bailed = false;\n for (; ssrRound < ops.maxSsrRounds!; ++ssrRound) {\n stream = await renderPass();\n\n if (!ssrContext.dirty) break;\n\n const timeout = ops.ssrTimeout! + ssrStart - Date.now();\n bailed = timeout <= 0 || !await Promise.race([\n Promise.allSettled(ssrContext.pending),\n timer(timeout).then(() => false),\n ]);\n if (bailed) break;\n }\n\n let logMsg;\n if (ssrContext.dirty) {\n // NOTE: In the case of incomplete SSR one more round is necessary\n // to ensure the correct hydration when some pending promises have\n // resolved and placed their data into the initial global state.\n stream = await renderPass();\n\n logMsg = bailed ? `SSR timed out after ${ops.ssrTimeout} second(s)`\n : `SSR bailed out after ${ops.maxSsrRounds} round(s)`;\n } else logMsg = `SSR completed in ${ssrRound + 1} round(s)`;\n\n ops.logger!.log(ssrContext.dirty ? 'warn' : 'info', logMsg);\n\n await new Promise((ready) => {\n stream!.pipe(new Writable({\n destroy: ready,\n write: (chunk: { toString: () => string }, _, done) => {\n appHtmlMarkup += chunk.toString();\n done();\n },\n }));\n });\n }\n\n /* Encrypts data to be injected into HTML.\n * Keep in mind, that this encryption is no way secure: as the JS bundle\n * contains decryption key and is able to decode it at the client side.\n * Hovewer, for a number of reasons, encryption of injected data is still\n * better than injection of a plain text. */\n const payload = serializeJs({\n CHUNK_GROUPS: chunkGroups,\n CONFIG: configToInject ?? sanitizedConfig,\n ISTATE: ssrContext.state,\n }, {\n ignoreFunction: true,\n unsafe: true,\n });\n cipher.update(forge.util.createBuffer(payload, 'utf8'));\n cipher.finish();\n const INJ = forge.util.encode64(`${iv}${cipher.output.data}`);\n\n const chunkSet = new Set<string>();\n\n // TODO: \"main\" chunk has to be added explicitly,\n // because unlike all other chunks they are not managed by <CodeSplit>\n // component, thus they are not added to the ssrContext.chunks\n // automatically. Actually, names of these entry chunks should be\n // read from Wepback config, as the end user may customize them,\n // remove or add other entry points, but it requires additional\n // efforts to figure out how to automatically order them right,\n // thus for now this handles the default config.\n [\n 'main',\n ...ssrContext.chunks,\n ].forEach((chunk) => {\n const assets = chunkGroups[chunk];\n if (assets) assets.forEach((asset) => chunkSet.add(asset));\n });\n\n let styleChunkString = '';\n let scriptChunkString = '';\n chunkSet.forEach((chunk) => {\n if (chunk.endsWith('.css')) {\n styleChunkString += `<link href=\"${publicPath as string}${chunk}\" rel=\"stylesheet\">`;\n } else if (\n chunk.endsWith('.js')\n // In dev mode HMR adds JS updates into asset arrays,\n // and they (updates) should be ignored.\n && !chunk.endsWith('.hot-update.js')\n ) {\n scriptChunkString += `<script src=\"${publicPath as string}${chunk}\" type=\"application/javascript\"></script>`;\n }\n });\n\n const grouppedExtraScripts = groupExtraScripts(extraScripts);\n\n const faviconLink = ops.favicon\n ? '<link rel=\"shortcut icon\" href=\"/favicon.ico\">'\n : '';\n\n const html = `<!DOCTYPE html>\n <html lang=\"en\">\n <head>\n ${grouppedExtraScripts[SCRIPT_LOCATIONS.HEAD_OPEN]}\n ${helmet?.title.toString() ?? ''}\n ${helmet?.meta.toString() ?? ''}\n <meta name=\"theme-color\" content=\"#FFFFFF\">\n ${manifestLink}\n ${helmet?.link.toString() ?? ''}${styleChunkString}\n ${faviconLink}\n <meta charset=\"utf-8\">\n <meta\n content=\"width=device-width,initial-scale=1.0\"\n name=\"viewport\"\n >\n <meta itemprop=\"drpruinj\" content=\"${INJ}\">\n </head>\n <body>\n ${grouppedExtraScripts[SCRIPT_LOCATIONS.BODY_OPEN]}\n <div id=\"react-view\">${appHtmlMarkup}</div>\n ${scriptChunkString}\n ${grouppedExtraScripts[SCRIPT_LOCATIONS.DEFAULT]}\n </body>\n </html>`;\n\n const status = ssrContext.status || 200;\n if (status !== 200) res.status(status);\n\n if (cacheRef && status < 500) {\n // Note: waiting for the caching to complete is not strictly necessary,\n // but it greately simplifies testing, and error reporting.\n await new Promise<void>((done, failed) => {\n brotliCompress(html, (error, buffer) => {\n const b = buffer as BufferWithNonce;\n if (error) failed(error);\n else {\n b.nonce = (req as unknown as {\n nonce: string;\n }).nonce;\n cache!.add({ buffer: b, status }, cacheRef.key, buffer.length);\n done();\n }\n });\n });\n }\n\n // Note: as caching code above may throw in some cases, sending response\n // before it completes will likely hide the error, making it difficult\n // to debug. Thus, at least for now, lets send response after it.\n res.send(html);\n } catch (error) {\n next(error);\n }\n };\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -28,11 +28,16 @@ console.warn("Network request has not been mocked for a test, and failed.\n\nCon
|
|
|
28
28
|
// when it is simulating user events.
|
|
29
29
|
global.IS_REACT_ACT_ENVIRONMENT=true;(0,_react.act)(()=>{root.unmount()});res.remove()};res.snapshot=()=>{expect(res).toMatchSnapshot()};// NOTE: As it seems @testing-library may reset this flag to false
|
|
30
30
|
// when it is simulating user events.
|
|
31
|
-
global.IS_REACT_ACT_ENVIRONMENT=true;(0,_react.act)(()=>{root=(0,_client.createRoot)(res);root.render(scene)});return res}
|
|
31
|
+
global.IS_REACT_ACT_ENVIRONMENT=true;(0,_react.act)(()=>{root=(0,_client.createRoot)(res);root.render(scene)});return res}// NOTE: If in future we have additional options here, they should be distributed
|
|
32
|
+
// across two objects, depending whether they are applicable to the sync, or async
|
|
33
|
+
// versions of snapshot(), or both.
|
|
34
|
+
function snapshot(element,options){let res;// TODO: Just adding async to the actor function breaks stuff, as it makes
|
|
32
35
|
// act() asynchronous no matter the `options.await` value, thus breaking all
|
|
33
36
|
// calls that do not await for snapshot() result... thus... perhaps we need
|
|
34
37
|
// to have a more complex typing to ensure it all works as intended in all
|
|
35
38
|
// cases, and while being correctly enforced by TypeScript.
|
|
36
39
|
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
37
|
-
const promise=(0,_react.act)(()=>{res=(0,_react2.render)(element);return options?.await});if(res===undefined)throw Error("Render failed");if(options?.await)
|
|
40
|
+
const promise=(0,_react.act)(()=>{res=(0,_react2.render)(element);return options?.await});if(res===undefined)throw Error("Render failed");if(options?.await){// TODO: Then body of .then() is the same as the last three lines in this
|
|
41
|
+
// function, executed for the non-async variant. We should re-use that.
|
|
42
|
+
return promise.then(()=>{const nodes=res.asFragment().childNodes;expect(nodes.length>1?[...nodes]:nodes[0]).toMatchSnapshot();return res})}const nodes=res.asFragment().childNodes;expect(nodes.length>1?[...nodes]:nodes[0]).toMatchSnapshot();return res}
|
|
38
43
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["_mockdate","_interopRequireDefault","require","_react","_client","_react2","_global","global","IS_REACT_ACT_ENVIRONMENT","originalProcessVersions","process","versions","mockClientSide","Object","defineProperty","value","undefined","unmockClientSide","writable","getMockUuid","seed","x","toString","padStart","slice","mockAxios","handlers","axios","jest","requireActual","defaults","adapter","config","handler","res","data","headers","status","statusText","console","warn","JSON","stringify","e","mockTimer","time","mockdate","set","Date","now","advanceTimersByTimeAsync","mount","scene","root","element","document","createElement","body","appendChild","destroy","act","unmount","remove","snapshot","expect","toMatchSnapshot","createRoot","render","options","promise","await","Error","nodes","asFragment","childNodes","length"],"sources":["../../../../../src/shared/utils/jest/index.ts"],"sourcesContent":["/* global jest, document */\n/* eslint-disable import/no-extraneous-dependencies */\n\nimport type {\n AxiosRequestConfig,\n AxiosResponse,\n AxiosStatic,\n InternalAxiosRequestConfig,\n} from 'axios';\n\nimport mockdate from 'mockdate';\nimport { type ReactNode, act } from 'react';\nimport { type Root, createRoot } from 'react-dom/client';\n\nimport { type RenderResult, render } from '@testing-library/react';\n\n/**\n * An alias for [act(..)](https://reactjs.org/docs/test-utils.html#act)\n * from `react`.\n */\nexport { act };\n\nexport { default as getGlobal } from './global';\n\nglobal.IS_REACT_ACT_ENVIRONMENT = true;\n\nconst originalProcessVersions = process.versions;\n\n/**\n * Tricks **react-utils** into thinking the test is running within client-side\n * (browser) environment.\n */\nexport function mockClientSide(): void {\n Object.defineProperty(process, 'versions', { value: undefined });\n}\n\n/**\n * Reverts the effect of {@link module:JU.mockClientSide mockClientSide(..)}.\n */\nexport function unmockClientSide(): void {\n Object.defineProperty(process, 'versions', {\n value: originalProcessVersions,\n writable: false,\n });\n}\n\n/**\n * Generates a mock UUID, or better said it determenistically transforms given\n * `seed` number into a UUID-formatted string.\n * @param {number} seed\n * @return {string}\n */\nexport function getMockUuid(seed = 0): string {\n const x = seed.toString(16).padStart(32, '0');\n return `${x.slice(0, 8)}-${x.slice(8, 12)}-${x.slice(12, 16)}-${x.slice(16, 20)}-${x.slice(20)}`;\n}\n\nexport type AxiosRequestHandlerT =\n (config: AxiosRequestConfig) => Partial<AxiosResponse> | null | undefined;\n\nexport function mockAxios(handlers: AxiosRequestHandlerT[]): AxiosStatic {\n const axios: AxiosStatic = jest.requireActual('axios');\n\n axios.defaults.adapter = async (\n config: AxiosRequestConfig,\n ): Promise<AxiosResponse> => {\n for (const handler of handlers) {\n const res = handler(config);\n if (res) {\n return {\n config: config as InternalAxiosRequestConfig,\n data: null,\n headers: {},\n status: 200,\n statusText: 'OK',\n ...res,\n };\n }\n }\n\n // Fallback to the regular network request.\n let res: AxiosResponse;\n try {\n res = await axios({ ...config, adapter: ['xhr', 'http', 'fetch'] });\n // eslint-disable-next-line no-console\n console.warn(\n 'Network request has not been mocked for a test.\\n\\nConfig:\\n',\n config,\n '\\n\\nResult:\\n',\n JSON.stringify(res, null, 2),\n );\n } catch (e) {\n // eslint-disable-next-line no-console\n console.warn(\n 'Network request has not been mocked for a test, and failed.\\n\\nConfig:\\n',\n config,\n '\\n\\nError\\n',\n JSON.stringify(e, null, 2),\n );\n throw e;\n }\n\n return res;\n };\n\n return axios;\n}\n\n/**\n * Advances mock timers, and mock date by the specified time.\n * @param {number} time Time step [ms].\n * @returns {Promise} Wait for this to \"jump after\" any async code which should\n * be executed because of the mock time movement.\n */\nexport async function mockTimer(time: number): Promise<void> {\n mockdate.set(time + Date.now());\n await jest.advanceTimersByTimeAsync(time);\n}\n\nexport type MountedSceneT = HTMLElement & {\n destroy: () => void;\n snapshot: () => void;\n};\n\n/**\n * Mounts `scene` to the DOM, and returns the root scene element.\n * @param scene\n * @return Created container DOM element with destroy() function\n * attached.\n */\nexport function mount(scene: ReactNode): MountedSceneT {\n let root: Root;\n const element = document.createElement('div');\n document.body.appendChild(element);\n\n const res: MountedSceneT = (element as unknown) as MountedSceneT;\n res.destroy = () => {\n // NOTE: As it seems @testing-library may reset this flag to false\n // when it is simulating user events.\n global.IS_REACT_ACT_ENVIRONMENT = true;\n\n act(() => {\n root.unmount();\n });\n res.remove();\n };\n\n res.snapshot = () => {\n expect(res).toMatchSnapshot();\n };\n\n // NOTE: As it seems @testing-library may reset this flag to false\n // when it is simulating user events.\n global.IS_REACT_ACT_ENVIRONMENT = true;\n\n act(() => {\n root = createRoot(res);\n root.render(scene);\n });\n return res;\n}\n\ntype SnapshotOptionsT = {\n await?: Promise<void>;\n};\n\nexport async function snapshot(\n element: React.ReactElement,\n options?: SnapshotOptionsT,\n): Promise<RenderResult> {\n let res: RenderResult | undefined;\n\n // TODO: Just adding async to the actor function breaks stuff, as it makes\n // act() asynchronous no matter the `options.await` value, thus breaking all\n // calls that do not await for snapshot() result... thus... perhaps we need\n // to have a more complex typing to ensure it all works as intended in all\n // cases, and while being correctly enforced by TypeScript.\n // eslint-disable-next-line @typescript-eslint/promise-function-async\n const promise = act(() => {\n res = render(element);\n return options?.await;\n });\n\n if (res === undefined) throw Error('Render failed');\n if (options?.await) await promise;\n\n const nodes = res.asFragment().childNodes;\n expect(nodes.length > 1 ? [...nodes] : nodes[0]).toMatchSnapshot();\n return res;\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"index.js","names":["_mockdate","_interopRequireDefault","require","_react","_client","_react2","_global","global","IS_REACT_ACT_ENVIRONMENT","originalProcessVersions","process","versions","mockClientSide","Object","defineProperty","value","undefined","unmockClientSide","writable","getMockUuid","seed","x","toString","padStart","slice","mockAxios","handlers","axios","jest","requireActual","defaults","adapter","config","handler","res","data","headers","status","statusText","console","warn","JSON","stringify","e","mockTimer","time","mockdate","set","Date","now","advanceTimersByTimeAsync","mount","scene","root","element","document","createElement","body","appendChild","destroy","act","unmount","remove","snapshot","expect","toMatchSnapshot","createRoot","render","options","promise","await","Error","then","nodes","asFragment","childNodes","length"],"sources":["../../../../../src/shared/utils/jest/index.ts"],"sourcesContent":["/* global jest, document */\n/* eslint-disable import/no-extraneous-dependencies */\n\nimport type {\n AxiosRequestConfig,\n AxiosResponse,\n AxiosStatic,\n InternalAxiosRequestConfig,\n} from 'axios';\n\nimport mockdate from 'mockdate';\nimport { type ReactElement, type ReactNode, act } from 'react';\nimport { type Root, createRoot } from 'react-dom/client';\n\nimport { type RenderResult, render } from '@testing-library/react';\n\n/**\n * An alias for [act(..)](https://reactjs.org/docs/test-utils.html#act)\n * from `react`.\n */\nexport { act };\n\nexport { default as getGlobal } from './global';\n\nglobal.IS_REACT_ACT_ENVIRONMENT = true;\n\nconst originalProcessVersions = process.versions;\n\n/**\n * Tricks **react-utils** into thinking the test is running within client-side\n * (browser) environment.\n */\nexport function mockClientSide(): void {\n Object.defineProperty(process, 'versions', { value: undefined });\n}\n\n/**\n * Reverts the effect of {@link module:JU.mockClientSide mockClientSide(..)}.\n */\nexport function unmockClientSide(): void {\n Object.defineProperty(process, 'versions', {\n value: originalProcessVersions,\n writable: false,\n });\n}\n\n/**\n * Generates a mock UUID, or better said it determenistically transforms given\n * `seed` number into a UUID-formatted string.\n * @param {number} seed\n * @return {string}\n */\nexport function getMockUuid(seed = 0): string {\n const x = seed.toString(16).padStart(32, '0');\n return `${x.slice(0, 8)}-${x.slice(8, 12)}-${x.slice(12, 16)}-${x.slice(16, 20)}-${x.slice(20)}`;\n}\n\nexport type AxiosRequestHandlerT =\n (config: AxiosRequestConfig) => Partial<AxiosResponse> | null | undefined;\n\nexport function mockAxios(handlers: AxiosRequestHandlerT[]): AxiosStatic {\n const axios: AxiosStatic = jest.requireActual('axios');\n\n axios.defaults.adapter = async (\n config: AxiosRequestConfig,\n ): Promise<AxiosResponse> => {\n for (const handler of handlers) {\n const res = handler(config);\n if (res) {\n return {\n config: config as InternalAxiosRequestConfig,\n data: null,\n headers: {},\n status: 200,\n statusText: 'OK',\n ...res,\n };\n }\n }\n\n // Fallback to the regular network request.\n let res: AxiosResponse;\n try {\n res = await axios({ ...config, adapter: ['xhr', 'http', 'fetch'] });\n // eslint-disable-next-line no-console\n console.warn(\n 'Network request has not been mocked for a test.\\n\\nConfig:\\n',\n config,\n '\\n\\nResult:\\n',\n JSON.stringify(res, null, 2),\n );\n } catch (e) {\n // eslint-disable-next-line no-console\n console.warn(\n 'Network request has not been mocked for a test, and failed.\\n\\nConfig:\\n',\n config,\n '\\n\\nError\\n',\n JSON.stringify(e, null, 2),\n );\n throw e;\n }\n\n return res;\n };\n\n return axios;\n}\n\n/**\n * Advances mock timers, and mock date by the specified time.\n * @param {number} time Time step [ms].\n * @returns {Promise} Wait for this to \"jump after\" any async code which should\n * be executed because of the mock time movement.\n */\nexport async function mockTimer(time: number): Promise<void> {\n mockdate.set(time + Date.now());\n await jest.advanceTimersByTimeAsync(time);\n}\n\nexport type MountedSceneT = HTMLElement & {\n destroy: () => void;\n snapshot: () => void;\n};\n\n/**\n * Mounts `scene` to the DOM, and returns the root scene element.\n * @param scene\n * @return Created container DOM element with destroy() function\n * attached.\n */\nexport function mount(scene: ReactNode): MountedSceneT {\n let root: Root;\n const element = document.createElement('div');\n document.body.appendChild(element);\n\n const res: MountedSceneT = (element as unknown) as MountedSceneT;\n res.destroy = () => {\n // NOTE: As it seems @testing-library may reset this flag to false\n // when it is simulating user events.\n global.IS_REACT_ACT_ENVIRONMENT = true;\n\n act(() => {\n root.unmount();\n });\n res.remove();\n };\n\n res.snapshot = () => {\n expect(res).toMatchSnapshot();\n };\n\n // NOTE: As it seems @testing-library may reset this flag to false\n // when it is simulating user events.\n global.IS_REACT_ACT_ENVIRONMENT = true;\n\n act(() => {\n root = createRoot(res);\n root.render(scene);\n });\n return res;\n}\n\n// NOTE: If in future we have additional options here, they should be distributed\n// across two objects, depending whether they are applicable to the sync, or async\n// versions of snapshot(), or both.\ntype AsyncSnapshotOptionsT = {\n await?: Promise<void>;\n};\n\nexport function snapshot(element: ReactElement): RenderResult;\n\nexport async function snapshot(\n element: ReactElement,\n options: AsyncSnapshotOptionsT,\n): Promise<RenderResult>;\n\nexport function snapshot(\n element: React.ReactElement,\n options?: AsyncSnapshotOptionsT,\n): Promise<RenderResult> | RenderResult {\n let res: RenderResult | undefined;\n\n // TODO: Just adding async to the actor function breaks stuff, as it makes\n // act() asynchronous no matter the `options.await` value, thus breaking all\n // calls that do not await for snapshot() result... thus... perhaps we need\n // to have a more complex typing to ensure it all works as intended in all\n // cases, and while being correctly enforced by TypeScript.\n // eslint-disable-next-line @typescript-eslint/promise-function-async\n const promise = act(() => {\n res = render(element);\n return options?.await;\n });\n\n if (res === undefined) throw Error('Render failed');\n if (options?.await) {\n // TODO: Then body of .then() is the same as the last three lines in this\n // function, executed for the non-async variant. We should re-use that.\n return promise.then(() => {\n const nodes = res!.asFragment().childNodes;\n expect(nodes.length > 1 ? [...nodes] : nodes[0]).toMatchSnapshot();\n return res!;\n });\n }\n\n const nodes = res.asFragment().childNodes;\n expect(nodes.length > 1 ? [...nodes] : nodes[0]).toMatchSnapshot();\n return res;\n}\n"],"mappings":"4iBAUA,IAAAA,SAAA,CAAAC,sBAAA,CAAAC,OAAA,cACA,IAAAC,MAAA,CAAAD,OAAA,UACA,IAAAE,OAAA,CAAAF,OAAA,qBAEA,IAAAG,OAAA,CAAAH,OAAA,2BAQA,IAAAI,OAAA,CAAAL,sBAAA,CAAAC,OAAA,cAtBA,2BACA,sDAeA;AACA;AACA;AACA,GAKAK,MAAM,CAACC,wBAAwB,CAAG,IAAI,CAEtC,KAAM,CAAAC,uBAAuB,CAAGC,OAAO,CAACC,QAAQ,CAEhD;AACA;AACA;AACA,GACO,QAAS,CAAAC,cAAcA,CAAA,CAAS,CACrCC,MAAM,CAACC,cAAc,CAACJ,OAAO,CAAE,UAAU,CAAE,CAAEK,KAAK,CAAEC,SAAU,CAAC,CACjE,CAEA;AACA;AACA,GACO,QAAS,CAAAC,gBAAgBA,CAAA,CAAS,CACvCJ,MAAM,CAACC,cAAc,CAACJ,OAAO,CAAE,UAAU,CAAE,CACzCK,KAAK,CAAEN,uBAAuB,CAC9BS,QAAQ,CAAE,KACZ,CAAC,CACH,CAEA;AACA;AACA;AACA;AACA;AACA,GACO,QAAS,CAAAC,WAAWA,CAACC,IAAI,CAAG,CAAC,CAAU,CAC5C,KAAM,CAAAC,CAAC,CAAGD,IAAI,CAACE,QAAQ,CAAC,EAAE,CAAC,CAACC,QAAQ,CAAC,EAAE,CAAE,GAAG,CAAC,CAC7C,MAAO,GAAGF,CAAC,CAACG,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC,IAAIH,CAAC,CAACG,KAAK,CAAC,CAAC,CAAE,EAAE,CAAC,IAAIH,CAAC,CAACG,KAAK,CAAC,EAAE,CAAE,EAAE,CAAC,IAAIH,CAAC,CAACG,KAAK,CAAC,EAAE,CAAE,EAAE,CAAC,IAAIH,CAAC,CAACG,KAAK,CAAC,EAAE,CAAC,EAChG,CAKO,QAAS,CAAAC,SAASA,CAACC,QAAgC,CAAe,CACvE,KAAM,CAAAC,KAAkB,CAAGC,IAAI,CAACC,aAAa,CAAC,OAAO,CAAC,CAEtDF,KAAK,CAACG,QAAQ,CAACC,OAAO,CAAG,KACvB,CAAAC,MAA0B,EACC,CAC3B,IAAK,KAAM,CAAAC,OAAO,GAAI,CAAAP,QAAQ,CAAE,CAC9B,KAAM,CAAAQ,GAAG,CAAGD,OAAO,CAACD,MAAM,CAAC,CAC3B,GAAIE,GAAG,CAAE,CACP,MAAO,CACLF,MAAM,CAAEA,MAAoC,CAC5CG,IAAI,CAAE,IAAI,CACVC,OAAO,CAAE,CAAC,CAAC,CACXC,MAAM,CAAE,GAAG,CACXC,UAAU,CAAE,IAAI,CAChB,GAAGJ,GACL,CACF,CACF,CAEA;AACA,GAAI,CAAAA,GAAkB,CACtB,GAAI,CACFA,GAAG,CAAG,KAAM,CAAAP,KAAK,CAAC,CAAE,GAAGK,MAAM,CAAED,OAAO,CAAE,CAAC,KAAK,CAAE,MAAM,CAAE,OAAO,CAAE,CAAC,CAAC,CACnE;AACAQ,OAAO,CAACC,IAAI,CACV,8DAA8D,CAC9DR,MAAM,CACN,eAAe,CACfS,IAAI,CAACC,SAAS,CAACR,GAAG,CAAE,IAAI,CAAE,CAAC,CAC7B,CACF,CAAE,MAAOS,CAAC,CAAE,CACV;AACAJ,OAAO,CAACC,IAAI,CACV,0EAA0E,CAC1ER,MAAM,CACN,aAAa,CACbS,IAAI,CAACC,SAAS,CAACC,CAAC,CAAE,IAAI,CAAE,CAAC,CAC3B,CAAC,CACD,KAAM,CAAAA,CACR,CAEA,MAAO,CAAAT,GACT,CAAC,CAED,MAAO,CAAAP,KACT,CAEA;AACA;AACA;AACA;AACA;AACA,GACO,cAAe,CAAAiB,SAASA,CAACC,IAAY,CAAiB,CAC3DC,iBAAQ,CAACC,GAAG,CAACF,IAAI,CAAGG,IAAI,CAACC,GAAG,CAAC,CAAC,CAAC,CAC/B,KAAM,CAAArB,IAAI,CAACsB,wBAAwB,CAACL,IAAI,CAC1C,CAOA;AACA;AACA;AACA;AACA;AACA,GACO,QAAS,CAAAM,KAAKA,CAACC,KAAgB,CAAiB,CACrD,GAAI,CAAAC,IAAU,CACd,KAAM,CAAAC,OAAO,CAAGC,QAAQ,CAACC,aAAa,CAAC,KAAK,CAAC,CAC7CD,QAAQ,CAACE,IAAI,CAACC,WAAW,CAACJ,OAAO,CAAC,CAElC,KAAM,CAAApB,GAAkB,CAAIoB,OAAoC,CAChEpB,GAAG,CAACyB,OAAO,CAAG,IAAM,CAClB;AACA;AACApD,MAAM,CAACC,wBAAwB,CAAG,IAAI,CAEtC,GAAAoD,UAAG,EAAC,IAAM,CACRP,IAAI,CAACQ,OAAO,CAAC,CACf,CAAC,CAAC,CACF3B,GAAG,CAAC4B,MAAM,CAAC,CACb,CAAC,CAED5B,GAAG,CAAC6B,QAAQ,CAAG,IAAM,CACnBC,MAAM,CAAC9B,GAAG,CAAC,CAAC+B,eAAe,CAAC,CAC9B,CAAC,CAED;AACA;AACA1D,MAAM,CAACC,wBAAwB,CAAG,IAAI,CAEtC,GAAAoD,UAAG,EAAC,IAAM,CACRP,IAAI,CAAG,GAAAa,kBAAU,EAAChC,GAAG,CAAC,CACtBmB,IAAI,CAACc,MAAM,CAACf,KAAK,CACnB,CAAC,CAAC,CACF,MAAO,CAAAlB,GACT,CAEA;AACA;AACA;AAYO,QAAS,CAAA6B,QAAQA,CACtBT,OAA2B,CAC3Bc,OAA+B,CACO,CACtC,GAAI,CAAAlC,GAA6B,CAEjC;AACA;AACA;AACA;AACA;AACA;AACA,KAAM,CAAAmC,OAAO,CAAG,GAAAT,UAAG,EAAC,IAAM,CACxB1B,GAAG,CAAG,GAAAiC,cAAM,EAACb,OAAO,CAAC,CACrB,MAAO,CAAAc,OAAO,EAAEE,KAClB,CAAC,CAAC,CAEF,GAAIpC,GAAG,GAAKlB,SAAS,CAAE,KAAM,CAAAuD,KAAK,CAAC,eAAe,CAAC,CACnD,GAAIH,OAAO,EAAEE,KAAK,CAAE,CAClB;AACA;AACA,MAAO,CAAAD,OAAO,CAACG,IAAI,CAAC,IAAM,CACxB,KAAM,CAAAC,KAAK,CAAGvC,GAAG,CAAEwC,UAAU,CAAC,CAAC,CAACC,UAAU,CAC1CX,MAAM,CAACS,KAAK,CAACG,MAAM,CAAG,CAAC,CAAG,CAAC,GAAGH,KAAK,CAAC,CAAGA,KAAK,CAAC,CAAC,CAAC,CAAC,CAACR,eAAe,CAAC,CAAC,CAClE,MAAO,CAAA/B,GACT,CAAC,CACH,CAEA,KAAM,CAAAuC,KAAK,CAAGvC,GAAG,CAACwC,UAAU,CAAC,CAAC,CAACC,UAAU,CACzCX,MAAM,CAACS,KAAK,CAACG,MAAM,CAAG,CAAC,CAAG,CAAC,GAAGH,KAAK,CAAC,CAAGA,KAAK,CAAC,CAAC,CAAC,CAAC,CAACR,eAAe,CAAC,CAAC,CAClE,MAAO,CAAA/B,GACT","ignoreList":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AxiosRequestConfig, AxiosResponse, AxiosStatic } from 'axios';
|
|
2
|
-
import { type ReactNode, act } from 'react';
|
|
2
|
+
import { type ReactElement, type ReactNode, act } from 'react';
|
|
3
3
|
import { type RenderResult } from '@testing-library/react';
|
|
4
4
|
/**
|
|
5
5
|
* An alias for [act(..)](https://reactjs.org/docs/test-utils.html#act)
|
|
@@ -43,7 +43,8 @@ export type MountedSceneT = HTMLElement & {
|
|
|
43
43
|
* attached.
|
|
44
44
|
*/
|
|
45
45
|
export declare function mount(scene: ReactNode): MountedSceneT;
|
|
46
|
-
type
|
|
46
|
+
type AsyncSnapshotOptionsT = {
|
|
47
47
|
await?: Promise<void>;
|
|
48
48
|
};
|
|
49
|
-
export declare function snapshot(element:
|
|
49
|
+
export declare function snapshot(element: ReactElement): RenderResult;
|
|
50
|
+
export declare function snapshot(element: ReactElement, options: AsyncSnapshotOptionsT): Promise<RenderResult>;
|
|
@@ -5,7 +5,6 @@ import eslintConfigs from '@dr.pogodin/eslint-configs';
|
|
|
5
5
|
|
|
6
6
|
export default defineConfig([{
|
|
7
7
|
ignores: [
|
|
8
|
-
'*.d.ts',
|
|
9
8
|
'__coverage__',
|
|
10
9
|
'build/',
|
|
11
10
|
'docs/build/',
|
|
@@ -29,4 +28,9 @@ export default defineConfig([{
|
|
|
29
28
|
eslintConfigs.configs.jest,
|
|
30
29
|
],
|
|
31
30
|
files: ['__tests__/**'],
|
|
31
|
+
rules: {
|
|
32
|
+
'jest/expect-expect': ['warn', {
|
|
33
|
+
assertFunctionNames: ['expect', 'snapshot'],
|
|
34
|
+
}],
|
|
35
|
+
},
|
|
32
36
|
}]);
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.43.
|
|
2
|
+
"version": "1.43.4",
|
|
3
3
|
"bin": {
|
|
4
4
|
"react-utils-build": "bin/build.js",
|
|
5
5
|
"react-utils-setup": "bin/setup.js"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"@dr.pogodin/react-helmet": "^3.0.1",
|
|
17
17
|
"@dr.pogodin/react-themes": "^1.8.0",
|
|
18
18
|
"@jest/environment": "^29.7.0",
|
|
19
|
-
"axios": "^1.
|
|
19
|
+
"axios": "^1.9.0",
|
|
20
20
|
"commander": "^13.1.0",
|
|
21
21
|
"compression": "^1.8.0",
|
|
22
22
|
"config": "^3.3.12",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"raf": "^3.4.1",
|
|
36
36
|
"react": "^19.1.0",
|
|
37
37
|
"react-dom": "^19.1.0",
|
|
38
|
-
"react-router": "^7.5.
|
|
38
|
+
"react-router": "^7.5.2",
|
|
39
39
|
"request-ip": "^3.3.0",
|
|
40
40
|
"rimraf": "^6.0.0",
|
|
41
41
|
"serialize-javascript": "^6.0.2",
|
|
@@ -103,18 +103,18 @@
|
|
|
103
103
|
"react-refresh": "^0.17.0",
|
|
104
104
|
"regenerator-runtime": "^0.14.1",
|
|
105
105
|
"resolve-url-loader": "^5.0.0",
|
|
106
|
-
"sass": "^1.
|
|
106
|
+
"sass": "^1.87.0",
|
|
107
107
|
"sass-loader": "^16.0.5",
|
|
108
108
|
"sitemap": "^8.0.0",
|
|
109
109
|
"source-map-loader": "^5.0.0",
|
|
110
|
-
"stylelint": "^16.
|
|
110
|
+
"stylelint": "^16.19.1",
|
|
111
111
|
"stylelint-config-standard-scss": "^14.0.0",
|
|
112
112
|
"supertest": "^7.1.0",
|
|
113
|
-
"tsc-alias": "
|
|
113
|
+
"tsc-alias": "1.8.13",
|
|
114
114
|
"tstyche": "^3.5.0",
|
|
115
115
|
"typed-scss-modules": "^8.1.1",
|
|
116
116
|
"typescript": "^5.8.3",
|
|
117
|
-
"webpack": "^5.99.
|
|
117
|
+
"webpack": "^5.99.7",
|
|
118
118
|
"webpack-dev-middleware": "^7.4.2",
|
|
119
119
|
"webpack-hot-middleware": "^2.26.1",
|
|
120
120
|
"webpack-merge": "^6.0.1",
|
package/src/server/renderer.tsx
CHANGED
|
@@ -567,11 +567,11 @@ export default function factory(
|
|
|
567
567
|
<html lang="en">
|
|
568
568
|
<head>
|
|
569
569
|
${grouppedExtraScripts[SCRIPT_LOCATIONS.HEAD_OPEN]}
|
|
570
|
-
${helmet
|
|
571
|
-
${helmet
|
|
570
|
+
${helmet?.title.toString() ?? ''}
|
|
571
|
+
${helmet?.meta.toString() ?? ''}
|
|
572
572
|
<meta name="theme-color" content="#FFFFFF">
|
|
573
573
|
${manifestLink}
|
|
574
|
-
${styleChunkString}
|
|
574
|
+
${helmet?.link.toString() ?? ''}${styleChunkString}
|
|
575
575
|
${faviconLink}
|
|
576
576
|
<meta charset="utf-8">
|
|
577
577
|
<meta
|
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
} from 'axios';
|
|
10
10
|
|
|
11
11
|
import mockdate from 'mockdate';
|
|
12
|
-
import { type ReactNode, act } from 'react';
|
|
12
|
+
import { type ReactElement, type ReactNode, act } from 'react';
|
|
13
13
|
import { type Root, createRoot } from 'react-dom/client';
|
|
14
14
|
|
|
15
15
|
import { type RenderResult, render } from '@testing-library/react';
|
|
@@ -160,14 +160,24 @@ export function mount(scene: ReactNode): MountedSceneT {
|
|
|
160
160
|
return res;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
|
|
163
|
+
// NOTE: If in future we have additional options here, they should be distributed
|
|
164
|
+
// across two objects, depending whether they are applicable to the sync, or async
|
|
165
|
+
// versions of snapshot(), or both.
|
|
166
|
+
type AsyncSnapshotOptionsT = {
|
|
164
167
|
await?: Promise<void>;
|
|
165
168
|
};
|
|
166
169
|
|
|
170
|
+
export function snapshot(element: ReactElement): RenderResult;
|
|
171
|
+
|
|
167
172
|
export async function snapshot(
|
|
173
|
+
element: ReactElement,
|
|
174
|
+
options: AsyncSnapshotOptionsT,
|
|
175
|
+
): Promise<RenderResult>;
|
|
176
|
+
|
|
177
|
+
export function snapshot(
|
|
168
178
|
element: React.ReactElement,
|
|
169
|
-
options?:
|
|
170
|
-
): Promise<RenderResult> {
|
|
179
|
+
options?: AsyncSnapshotOptionsT,
|
|
180
|
+
): Promise<RenderResult> | RenderResult {
|
|
171
181
|
let res: RenderResult | undefined;
|
|
172
182
|
|
|
173
183
|
// TODO: Just adding async to the actor function breaks stuff, as it makes
|
|
@@ -182,7 +192,15 @@ export async function snapshot(
|
|
|
182
192
|
});
|
|
183
193
|
|
|
184
194
|
if (res === undefined) throw Error('Render failed');
|
|
185
|
-
if (options?.await)
|
|
195
|
+
if (options?.await) {
|
|
196
|
+
// TODO: Then body of .then() is the same as the last three lines in this
|
|
197
|
+
// function, executed for the non-async variant. We should re-use that.
|
|
198
|
+
return promise.then(() => {
|
|
199
|
+
const nodes = res!.asFragment().childNodes;
|
|
200
|
+
expect(nodes.length > 1 ? [...nodes] : nodes[0]).toMatchSnapshot();
|
|
201
|
+
return res!;
|
|
202
|
+
});
|
|
203
|
+
}
|
|
186
204
|
|
|
187
205
|
const nodes = res.asFragment().childNodes;
|
|
188
206
|
expect(nodes.length > 1 ? [...nodes] : nodes[0]).toMatchSnapshot();
|
package/types.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
/* eslint-disable no-
|
|
1
|
+
/* eslint-disable import/no-extraneous-dependencies */
|
|
2
2
|
|
|
3
|
+
// TODO: Double-check that let does not work?
|
|
4
|
+
// eslint-disable-next-line no-var
|
|
3
5
|
declare var IS_REACT_ACT_ENVIRONMENT: boolean | undefined;
|
|
6
|
+
|
|
7
|
+
// eslint-disable-next-line no-var
|
|
4
8
|
declare var REACT_UTILS_FORCE_CLIENT_SIDE: boolean | undefined;
|
|
5
9
|
|
|
6
10
|
declare module '@babel/register/experimental-worker' {
|
|
@@ -30,12 +34,14 @@ declare module 'node-forge/lib/forge' {
|
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
declare module '*.png' {
|
|
33
|
-
|
|
37
|
+
const path: string;
|
|
38
|
+
export default path;
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
declare namespace React {
|
|
37
42
|
// This allows all JSX elements to have additional "styleName" attribute,
|
|
38
43
|
// handled by "@dr.pogodin/babel-plugin-react-css-modules".
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
39
45
|
interface Attributes {
|
|
40
46
|
styleName?: string;
|
|
41
47
|
}
|