From d9c4de4a277d79bb8fa74dd9e203543e47b08425 Mon Sep 17 00:00:00 2001
From: Paula Stachova
Date: Fri, 23 Feb 2024 11:10:23 +0100
Subject: [PATCH 01/15] feat(oidc-http-server-pages): generate JSON templates
COMPASS-7646
---
packages/oidc-http-server-pages/package.json | 2 +-
.../src/create-static-pages.ts | 272 ++++++++++++------
2 files changed, 178 insertions(+), 96 deletions(-)
diff --git a/packages/oidc-http-server-pages/package.json b/packages/oidc-http-server-pages/package.json
index 64a581ca..3e504c68 100644
--- a/packages/oidc-http-server-pages/package.json
+++ b/packages/oidc-http-server-pages/package.json
@@ -31,7 +31,7 @@
"scripts": {
"bootstrap": "npm run compile",
"prepublishOnly": "npm run compile",
- "compile": "tsc -p tsconfig.json && node dist/create-static-pages.js > dist/static-pages.js && gen-esm-wrapper . ./dist/.esm-wrapper.mjs",
+ "compile": "tsc -p tsconfig.json && node dist/create-static-pages.js > dist/templates.json && gen-esm-wrapper . ./dist/.esm-wrapper.mjs",
"typecheck": "tsc --noEmit",
"eslint": "eslint",
"prettier": "prettier",
diff --git a/packages/oidc-http-server-pages/src/create-static-pages.ts b/packages/oidc-http-server-pages/src/create-static-pages.ts
index 9492528c..7278a6aa 100644
--- a/packages/oidc-http-server-pages/src/create-static-pages.ts
+++ b/packages/oidc-http-server-pages/src/create-static-pages.ts
@@ -34,113 +34,195 @@ function placeholder(prop: string): string {
return `{{prop:${prop}}}`;
}
-const S = JSON.stringify; // Useful for embedding strings into real JS code.
-
-// Generate static pages based on a component.
-// Props need to be optional string props; for every subset of props,
-// a static page will be rendered that takes these props, and a function
-// to take that static page and replace placeholder with the actual prop values
-// will be emitted.
-// Rather than including the generated markup directly, it is saved to a global
-// table and emitted later, so that that global table can be compressed.
-function generateStaticPage(
- Component: React.FunctionComponent>> & {
- name: string;
- },
- props: string[],
- markupTable: Record
-): string {
- let result = `module.exports[${S(Component.name)}] = function(props) {
- const actualProps = JSON.stringify(
- Object.keys(props)
- .filter(prop => ${S(
- props
- )}.includes(prop) && props[prop] != null).sort());`;
- for (const availableProps of allSubsets(props)) {
- const propsObject: Partial> = {};
- const replacers: string[] = [];
- for (const prop of availableProps) {
- propsObject[prop] = placeholder(prop);
- replacers.push(
- `.replaceAll(${S(placeholder(prop))}, escapeHTML(props[${S(prop)}]))`
- );
- }
+// const S = JSON.stringify; // Useful for embedding strings into real JS code.
- const markup = renderStylesToString(
- renderToStaticMarkup(React.createElement(Component, propsObject))
- );
- const markupHash = createHash('sha256').update(markup).digest('hex');
- markupTable[markupHash] = markup;
+// // Generate static pages based on a component.
+// // Props need to be optional string props; for every subset of props,
+// // a static page will be rendered that takes these props, and a function
+// // to take that static page and replace placeholder with the actual prop values
+// // will be emitted.
+// // Rather than including the generated markup directly, it is saved to a global
+// // table and emitted later, so that that global table can be compressed.
+// function generateStaticPage(
+// Component: React.FunctionComponent>> & {
+// name: string;
+// },
+// props: string[],
+// markupTable: Record
+// ): string {
+// let result = `module.exports[${S(Component.name)}] = function(props) {
+// const actualProps = JSON.stringify(
+// Object.keys(props)
+// .filter(prop => ${S(
+// props
+// )}.includes(prop) && props[prop] != null).sort());`;
+// for (const availableProps of allSubsets(props)) {
+// const propsObject: Partial> = {};
+// const replacers: string[] = [];
+// for (const prop of availableProps) {
+// propsObject[prop] = placeholder(prop);
+// replacers.push(
+// `.replaceAll(${S(placeholder(prop))}, escapeHTML(props[${S(prop)}]))`
+// );
+// }
- result += `if (actualProps === ${S(S(availableProps.sort()))}) {
- return (getMarkup(${S(markupHash)})${replacers.join('')});
- }`;
- }
+// const markup = renderStylesToString(
+// renderToStaticMarkup(React.createElement(Component, propsObject))
+// );
+// const markupHash = createHash('sha256').update(markup).digest('hex');
+// markupTable[markupHash] = markup;
+
+// result += `if (actualProps === ${S(S(availableProps.sort()))}) {
+// return (getMarkup(${S(markupHash)})${replacers.join('')});
+// }`;
+// }
+
+// result += '}\n';
+// return result;
+// }
+
+// // Create CJS module with pages as exports.
+// export function generateStaticPagesModule(
+// components: [
+// React.FunctionComponent>> & {
+// name: string;
+// },
+// string[]
+// ][]
+// ): string {
+// let result = `
+// 'use strict';
+// function escapeHTML(str) {
+// return str.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'");
+// }
+// `;
+// const markupTable: Record = {};
+// for (const component of components) {
+// result += generateStaticPage(...component, markupTable);
+// }
+
+// // At the time of writing, brotli compression results in a 96% size decrease
+// // in the resulting file. We decompress the markup table lazily, so that it
+// // is not loaded into memory when OIDC is not in use.
+// const compressedMarkupTable = brotliCompressSync(S(markupTable), {
+// params: {
+// [zlibConstants.BROTLI_PARAM_MODE]: zlibConstants.BROTLI_MODE_TEXT,
+// [zlibConstants.BROTLI_PARAM_QUALITY]: zlibConstants.BROTLI_MAX_QUALITY,
+// },
+// });
+// result += `
+// const markupTableSrc = ${S(compressedMarkupTable.toString('base64'))};
+// let markupTable;
+// function getMarkup(hash) {
+// if (markupTable === undefined) {
+// markupTable = JSON.parse(
+// require('zlib').brotliDecompressSync(
+// Buffer.from(markupTableSrc, 'base64')));
+// }
+// return markupTable[hash];
+// }
+// `;
+// return result;
+// }
- result += '}\n';
- return result;
+// if (require.main === module) {
+// // eslint-disable-next-line no-console
+// console.log(
+// generateStaticPagesModule([
+// [
+// OIDCErrorPage,
+// [
+// 'error',
+// 'errorDescription',
+// 'errorURI',
+// 'productDocsLink',
+// 'productName',
+// ],
+// ],
+// [OIDCAcceptedPage, ['productDocsLink', 'productName']],
+// [OIDCNotFoundPage, ['productDocsLink', 'productName']],
+// ])
+// );
+// }
+
+interface ITemplate {
+ parameters: Record;
+ html: string;
}
+type ComponentTemplates = Record;
-// Create CJS module with pages as exports.
-export function generateStaticPagesModule(
- components: [
- React.FunctionComponent>> & {
- name: string;
- },
- string[]
- ][]
-): string {
- let result = `
- 'use strict';
- function escapeHTML(str) {
- return str.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'");
- }
- `;
- const markupTable: Record = {};
- for (const component of components) {
- result += generateStaticPage(...component, markupTable);
+type Component = React.FunctionComponent<
+ Partial>
+> & {
+ name: string;
+};
+
+function getComponentTemplates({
+ Component,
+ parameters,
+}: {
+ Component: Component;
+ parameters: string[];
+}): ITemplate[] {
+ const templates: ITemplate[] = [];
+ for (const paramsSubset of allSubsets(parameters)) {
+ const propsObject = paramsSubset.reduce((obj, prop) => {
+ obj[prop] = placeholder(prop);
+ return obj;
+ }, {} as Record);
+ const markup = renderStylesToString(
+ renderToStaticMarkup(React.createElement(Component, propsObject))
+ );
+ templates.push({
+ parameters: propsObject,
+ html: markup,
+ });
}
+ return templates;
+}
- // At the time of writing, brotli compression results in a 96% size decrease
- // in the resulting file. We decompress the markup table lazily, so that it
- // is not loaded into memory when OIDC is not in use.
- const compressedMarkupTable = brotliCompressSync(S(markupTable), {
- params: {
- [zlibConstants.BROTLI_PARAM_MODE]: zlibConstants.BROTLI_MODE_TEXT,
- [zlibConstants.BROTLI_PARAM_QUALITY]: zlibConstants.BROTLI_MAX_QUALITY,
- },
- });
- result += `
- const markupTableSrc = ${S(compressedMarkupTable.toString('base64'))};
- let markupTable;
- function getMarkup(hash) {
- if (markupTable === undefined) {
- markupTable = JSON.parse(
- require('zlib').brotliDecompressSync(
- Buffer.from(markupTableSrc, 'base64')));
- }
- return markupTable[hash];
+function generateTemplates(
+ components: {
+ name: string;
+ Component: Component;
+ parameters: string[];
+ }[]
+): ComponentTemplates {
+ const templates: ComponentTemplates = {};
+ for (const { name, Component, parameters } of components) {
+ const componentTemplates = getComponentTemplates({ Component, parameters });
+ templates[name] = componentTemplates;
}
- `;
- return result;
+ return templates;
}
if (require.main === module) {
// eslint-disable-next-line no-console
console.log(
- generateStaticPagesModule([
- [
- OIDCErrorPage,
- [
- 'error',
- 'errorDescription',
- 'errorURI',
- 'productDocsLink',
- 'productName',
- ],
- ],
- [OIDCAcceptedPage, ['productDocsLink', 'productName']],
- [OIDCNotFoundPage, ['productDocsLink', 'productName']],
- ])
+ JSON.stringify(
+ generateTemplates([
+ {
+ name: 'OIDCErrorPage',
+ Component: OIDCErrorPage,
+ parameters: [
+ 'error',
+ 'errorDescription',
+ 'errorURI',
+ 'productDocsLink',
+ 'productName',
+ ],
+ },
+ {
+ name: 'OIDCAcceptedPage',
+ Component: OIDCAcceptedPage,
+ parameters: ['productDocsLink', 'productName'],
+ },
+ {
+ name: 'OIDCNotFoundPage',
+ Component: OIDCNotFoundPage,
+ parameters: ['productDocsLink', 'productName'],
+ },
+ ])
+ )
);
}
From c9881c3a988800e7168967cc2016360feb61598c Mon Sep 17 00:00:00 2001
From: Paula Stachova
Date: Fri, 23 Feb 2024 14:18:53 +0100
Subject: [PATCH 02/15] add template reading util
---
.../src/create-static-pages.spec.tsx | 130 +++++++++---------
...te-static-pages.ts => create-templates.ts} | 47 +++----
.../src/get-static-page.ts | 34 +++++
.../src/templates.d.json.ts | 5 +
packages/oidc-http-server-pages/src/types.ts | 11 ++
packages/oidc-http-server-pages/tsconfig.json | 3 +-
6 files changed, 138 insertions(+), 92 deletions(-)
rename packages/oidc-http-server-pages/src/{create-static-pages.ts => create-templates.ts} (89%)
create mode 100644 packages/oidc-http-server-pages/src/get-static-page.ts
create mode 100644 packages/oidc-http-server-pages/src/templates.d.json.ts
create mode 100644 packages/oidc-http-server-pages/src/types.ts
diff --git a/packages/oidc-http-server-pages/src/create-static-pages.spec.tsx b/packages/oidc-http-server-pages/src/create-static-pages.spec.tsx
index fad6d245..1befc2ad 100644
--- a/packages/oidc-http-server-pages/src/create-static-pages.spec.tsx
+++ b/packages/oidc-http-server-pages/src/create-static-pages.spec.tsx
@@ -1,71 +1,71 @@
-import React from 'react';
-import type { Component } from 'react';
-import { generateStaticPagesModule } from './create-static-pages';
-import { H1 } from '@mongodb-js/compass-components';
-import { runInNewContext } from 'vm';
-import zlib from 'zlib';
-import sinon from 'ts-sinon';
-import chai, { expect } from 'chai';
-import sinonChai from 'sinon-chai';
-chai.use(sinonChai);
+// import React from 'react';
+// import type { Component } from 'react';
+// import { generateStaticPagesModule } from './create-static-pages';
+// import { H1 } from '@mongodb-js/compass-components';
+// import { runInNewContext } from 'vm';
+// import zlib from 'zlib';
+// import sinon from 'ts-sinon';
+// import chai, { expect } from 'chai';
+// import sinonChai from 'sinon-chai';
+// chai.use(sinonChai);
-describe('generateStaticPagesModule', function () {
- let exports: Record<
- string,
- (props: React.ComponentProps) => string
- >;
+// describe('generateStaticPagesModule', function () {
+// let exports: Record<
+// string,
+// (props: React.ComponentProps) => string
+// >;
- beforeEach(function () {
- function Component({ prop1, prop2 }: { prop1?: string; prop2?: string }) {
- return (
-
-
{prop1 || 'default'}
- {prop2 &&
{prop2}
}
-
- );
- }
+// beforeEach(function () {
+// function Component({ prop1, prop2 }: { prop1?: string; prop2?: string }) {
+// return (
+//
+//
{prop1 || 'default'}
+// {prop2 &&
{prop2}
}
+//
+// );
+// }
- const result = generateStaticPagesModule([[Component, ['prop1', 'prop2']]]);
- exports = {};
- const require = sinon.stub();
- require.throws();
- require.withArgs('zlib').returns(zlib);
- runInNewContext(result, {
- module: { exports },
- require,
- Buffer,
- });
- });
+// const result = generateStaticPagesModule([[Component, ['prop1', 'prop2']]]);
+// exports = {};
+// const require = sinon.stub();
+// require.throws();
+// require.withArgs('zlib').returns(zlib);
+// runInNewContext(result, {
+// module: { exports },
+// require,
+// Buffer,
+// });
+// });
- it('includes selected props', function () {
- expect(exports.Component({ prop1: 'prop1value' })).to.match(
- /]+>prop1value<\/h1>/
- );
- expect(exports.Component({ prop1: 'prop1value' })).not.to.include('prop2');
- expect(exports.Component({ prop1: 'prop1value' })).not.to.include(
- 'data-attr'
- );
- expect(exports.Component({ prop2: 'prop2value' })).to.match(
- /]+>default<\/h1>/
- );
- expect(exports.Component({ prop2: 'prop2value' })).to.match(
- /
]+>prop2value<\/p>/
- );
- expect(exports.Component({ prop2: 'prop2value' })).to.include(
- 'data-attr="prop2value"'
- );
- });
+// it('includes selected props', function () {
+// expect(exports.Component({ prop1: 'prop1value' })).to.match(
+// /
]+>prop1value<\/h1>/
+// );
+// expect(exports.Component({ prop1: 'prop1value' })).not.to.include('prop2');
+// expect(exports.Component({ prop1: 'prop1value' })).not.to.include(
+// 'data-attr'
+// );
+// expect(exports.Component({ prop2: 'prop2value' })).to.match(
+// /]+>default<\/h1>/
+// );
+// expect(exports.Component({ prop2: 'prop2value' })).to.match(
+// /
]+>prop2value<\/p>/
+// );
+// expect(exports.Component({ prop2: 'prop2value' })).to.include(
+// 'data-attr="prop2value"'
+// );
+// });
- it('properly escapes props', function () {
- expect(exports.Component({ prop2: 'prop2<&"\'>value' })).to.include(
- 'data-attr="prop2<&"'>value"'
- );
- expect(exports.Component({ prop2: 'prop2<&"\'>value' })).to.include(
- '>prop2<&"'>value
'
- );
- });
+// it('properly escapes props', function () {
+// expect(exports.Component({ prop2: 'prop2<&"\'>value' })).to.include(
+// 'data-attr="prop2<&"'>value"'
+// );
+// expect(exports.Component({ prop2: 'prop2<&"\'>value' })).to.include(
+// '>prop2<&"'>value
'
+// );
+// });
- it('includes relevant style information', function () {
- expect(exports.Component({ prop1: 'prop1value' })).to.include('