Skip to content
Open
59 changes: 48 additions & 11 deletions dotcom-rendering/src/lib/fonts-css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,17 @@ type FontFamily =

type FontStyle = 'normal' | 'italic';

interface FontDisplay {
export interface FontDisplay {
family: FontFamily;
woff2: string;
woff: string;
ttf: string;
weight: number;
style: FontStyle;
uniqueName: string;
}

const fontList: FontDisplay[] = [
export const fontList: FontDisplay[] = [
// GH Guardian Headline, with legacy family name of Guardian Egyptian Web
{
family: 'GH Guardian Headline',
Expand All @@ -26,6 +27,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-headline/latin1-not-hinted/GHGuardianHeadline-Light.ttf',
weight: 300,
style: 'normal',
uniqueName: 'GHGuardianHeadline-Light',
},
{
family: 'Guardian Egyptian Web',
Expand All @@ -34,6 +36,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-headline/latin1-not-hinted/GHGuardianHeadline-Light.ttf',
weight: 300,
style: 'normal',
uniqueName: 'GuardianEgyptian-Light',
},
{
family: 'GH Guardian Headline',
Expand All @@ -42,6 +45,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-headline/latin1-not-hinted/GHGuardianHeadline-LightItalic.ttf',
weight: 300,
style: 'italic',
uniqueName: 'GHGuardianHeadline-LightItalic',
},
{
family: 'Guardian Egyptian Web',
Expand All @@ -50,6 +54,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-headline/latin1-not-hinted/GHGuardianHeadline-LightItalic.ttf',
weight: 300,
style: 'italic',
uniqueName: 'GuardianEgyptian-LightItalic',
},
{
family: 'GH Guardian Headline',
Expand All @@ -58,6 +63,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-headline/latin1-not-hinted/GHGuardianHeadline-Medium.ttf',
weight: 500,
style: 'normal',
uniqueName: 'GHGuardianHeadline-Medium',
},
{
family: 'Guardian Egyptian Web',
Expand All @@ -66,6 +72,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-headline/latin1-not-hinted/GHGuardianHeadline-Medium.ttf',
weight: 500,
style: 'normal',
uniqueName: 'GuardianEgyptian-Medium',
},
{
family: 'GH Guardian Headline',
Expand All @@ -74,6 +81,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-headline/latin1-not-hinted/GHGuardianHeadline-MediumItalic.ttf',
weight: 500,
style: 'italic',
uniqueName: 'GHGuardianHeadline-MediumItalic',
},
{
family: 'Guardian Egyptian Web',
Expand All @@ -82,6 +90,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-headline/latin1-not-hinted/GHGuardianHeadline-MediumItalic.ttf',
weight: 500,
style: 'italic',
uniqueName: 'GuardianEgyptian-MediumItalic',
},
{
family: 'GH Guardian Headline',
Expand All @@ -90,6 +99,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-headline/latin1-not-hinted/GHGuardianHeadline-Bold.ttf',
weight: 700,
style: 'normal',
uniqueName: 'GHGuardianHeadline-Bold',
},
{
family: 'Guardian Egyptian Web',
Expand All @@ -98,6 +108,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-headline/latin1-not-hinted/GHGuardianHeadline-Bold.ttf',
weight: 700,
style: 'normal',
uniqueName: 'GuardianEgyptian-Bold',
},
{
family: 'GH Guardian Headline',
Expand All @@ -106,6 +117,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-headline/latin1-not-hinted/GHGuardianHeadline-BoldItalic.ttf',
weight: 700,
style: 'italic',
uniqueName: 'GHGuardianHeadline-BoldItalic',
},
{
family: 'Guardian Egyptian Web',
Expand All @@ -114,6 +126,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-headline/latin1-not-hinted/GHGuardianHeadline-BoldItalic.ttf',
weight: 700,
style: 'italic',
uniqueName: 'GuardianEgyptian-BoldItalic',
},
// GuardianTextEgyptian, with legacy family name of Guardian Text Egyptian Web
{
Expand All @@ -123,6 +136,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-textegyptian/latin1-not-hinted/GuardianTextEgyptian-Regular.ttf',
weight: 400,
style: 'normal',
uniqueName: 'GuardianTextEgyptian-Regular',
},
{
family: 'Guardian Text Egyptian Web',
Expand All @@ -131,6 +145,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-textegyptian/latin1-not-hinted/GuardianTextEgyptian-Regular.ttf',
weight: 400,
style: 'normal',
uniqueName: 'GuardianTextEgyptianWeb-Regular',
},
{
family: 'GuardianTextEgyptian',
Expand All @@ -139,6 +154,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-textegyptian/latin1-not-hinted/GuardianTextEgyptian-RegularItalic.ttf',
weight: 400,
style: 'italic',
uniqueName: 'GuardianTextEgyptian-RegularItalic',
},
{
family: 'Guardian Text Egyptian Web',
Expand All @@ -147,6 +163,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-textegyptian/latin1-not-hinted/GuardianTextEgyptian-RegularItalic.ttf',
weight: 400,
style: 'italic',
uniqueName: 'GuardianTextEgyptianWeb-RegularItalic',
},
{
family: 'GuardianTextEgyptian',
Expand All @@ -155,6 +172,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-textegyptian/latin1-not-hinted/GuardianTextEgyptian-Bold.ttf',
weight: 700,
style: 'normal',
uniqueName: 'GuardianTextEgyptian-Bold',
},
{
family: 'Guardian Text Egyptian Web',
Expand All @@ -163,6 +181,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-textegyptian/latin1-not-hinted/GuardianTextEgyptian-Bold.ttf',
weight: 700,
style: 'normal',
uniqueName: 'GuardianTextEgyptianWeb-Bold',
},
{
family: 'GuardianTextEgyptian',
Expand All @@ -171,6 +190,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-textegyptian/latin1-not-hinted/GuardianTextEgyptian-BoldItalic.ttf',
weight: 700,
style: 'italic',
uniqueName: 'GuardianTextEgyptian-BoldItalic',
},
{
family: 'Guardian Text Egyptian Web',
Expand All @@ -179,6 +199,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-textegyptian/latin1-not-hinted/GuardianTextEgyptian-BoldItalic.ttf',
weight: 700,
style: 'italic',
uniqueName: 'GuardianTextEgyptianWeb-BoldItalic',
},
// GuardianTextSans, with legacy family name of Guardian Text Sans Web
{
Expand All @@ -188,6 +209,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-textsans/latin1-not-hinted/GuardianTextSans-Regular.ttf',
weight: 400,
style: 'normal',
uniqueName: 'GuardianTextSans-Regular',
},
{
family: 'Guardian Text Sans Web',
Expand All @@ -196,6 +218,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-textsans/latin1-not-hinted/GuardianTextSans-Regular.ttf',
weight: 400,
style: 'normal',
uniqueName: 'GuardianTextSansWeb-Regular',
},
{
family: 'GuardianTextSans',
Expand All @@ -204,6 +227,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-textsans/latin1-not-hinted/GuardianTextSans-RegularItalic.ttf',
weight: 400,
style: 'italic',
uniqueName: 'GuardianTextSans-RegularItalic',
},
{
family: 'Guardian Text Sans Web',
Expand All @@ -212,6 +236,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-textsans/latin1-not-hinted/GuardianTextSans-RegularItalic.ttf',
weight: 400,
style: 'italic',
uniqueName: 'GuardianTextSansWeb-RegularItalic',
},
{
family: 'GuardianTextSans',
Expand All @@ -220,6 +245,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-textsans/latin1-not-hinted/GuardianTextSans-Bold.ttf',
weight: 700,
style: 'normal',
uniqueName: 'GuardianTextSans-Bold',
},
{
family: 'Guardian Text Sans Web',
Expand All @@ -228,6 +254,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-textsans/latin1-not-hinted/GuardianTextSans-Bold.ttf',
weight: 700,
style: 'normal',
uniqueName: 'GuardianTextSansWeb-Bold',
},
{
family: 'GuardianTextSans',
Expand All @@ -236,6 +263,7 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-textsans/latin1-not-hinted/GuardianTextSans-BoldItalic.ttf',
weight: 700,
style: 'italic',
uniqueName: 'GuardianTextSans-BoldItalic',
},
{
family: 'Guardian Text Sans Web',
Expand All @@ -244,24 +272,33 @@ const fontList: FontDisplay[] = [
ttf: 'fonts/guardian-textsans/latin1-not-hinted/GuardianTextSans-BoldItalic.ttf',
weight: 700,
style: 'italic',
uniqueName: 'GuardianTextSansWeb-BoldItalic',
},
];

const getFontUrl = (path: string): string =>
`https://assets.guim.co.uk/static/frontend/${path}`;
const getFontUrl = (filePath: string): string =>
`https://assets.guim.co.uk/static/frontend/${filePath}`;

export const rawFontsCss = fontList
.map(
(font) => `
@font-face {
const getFontFaceCss = (font: FontDisplay): string => `@font-face {
font-family: "${font.family}";
src: url(${getFontUrl(font.woff2)}) format("woff2"),
url(${getFontUrl(font.woff)}) format("woff"),
url(${getFontUrl(font.ttf)}) format("truetype");
font-weight: ${font.weight};
font-style: ${font.style};
font-display: swap;
}
`,
)
}`;

export const rawFontsCss = fontList.map(getFontFaceCss).join('\n');

export const rawFontsCssWithClassNames = fontList
.map((font) => {
return `
${getFontFaceCss(font)}
.${font.uniqueName} {
font-family: "${font.family}";
font-weight: ${font.weight};
font-style: ${font.style};
}`;
})
.join('\n');
69 changes: 69 additions & 0 deletions dotcom-rendering/src/server/handler.assets.apps.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { createHash } from 'node:crypto';
import { isString } from '@guardian/libs';
import CleanCSS from 'clean-css';
import { type RequestHandler } from 'express';
import { renderToString } from 'react-dom/server';
import { generateScriptTags, getPathFromManifest } from '../lib/assets';
import {
type FontDisplay,
fontList,
rawFontsCssWithClassNames,
} from '../lib/fonts-css';

export const handleAppsAssets: RequestHandler = (req, res) => {
const clientScripts = [
getPathFromManifest('client.apps', 'index.js'),
].filter(isString);

const scriptTags = generateScriptTags([...clientScripts]);
const html = buildHtml(scriptTags);
res.status(200).send(html);
};

type Props = {
fontList: FontDisplay[];
};

export const buildHtml = (scriptTags: string[]) => {
const fontAssetsCss = new CleanCSS()
.minify(rawFontsCssWithClassNames)
.styles.trim();

const body = renderToString(<AssetsPage fontList={fontList} />);

return `<!doctype html>
<html lang='en'>
<head>
<title>The Guardian Rendered Items Assets Html</title>
<meta charset='utf-8'>

<style>
${fontAssetsCss}
</style>

<meta http-equiv='Content-Security-Policy' content="style-src 'sha256-${assetHash(
fontAssetsCss,
)}';">

${scriptTags.join('\n')}

<body>
${body}
</body>
</html>`;
};

const assetHash = (asset: string) =>
createHash('sha256').update(asset).digest('base64');

export const AssetsPage = ({ fontList: fonts }: Props) => {
return (
<>
{fonts.map((font) => (
<div key={font.uniqueName} className={font.uniqueName}>
.
</div>
))}
</>
);
};
3 changes: 3 additions & 0 deletions dotcom-rendering/src/server/server.dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
handleBlocks,
handleInteractive,
} from './handler.article.web';
import { handleAppsAssets } from './handler.assets.apps';
import { handleEditionsCrossword } from './handler.editionsCrossword';
import { handleFront, handleTagPage } from './handler.front.web';
import {
Expand Down Expand Up @@ -119,6 +120,8 @@ renderer.post('/FootballTablesPage', handleFootballTablesPage);
renderer.post('/CricketMatchPage', handleCricketMatchPage);
renderer.post('/FootballMatchSummaryPage', handleFootballMatchPage);

renderer.get('/assets/rendered-items-assets', handleAppsAssets);

const router = Router();
router.use('/pages', pages);
router.use('/targets', targets);
Expand Down
3 changes: 3 additions & 0 deletions dotcom-rendering/src/server/server.prod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
handleBlocks,
handleInteractive,
} from './handler.article.web';
import { handleAppsAssets } from './handler.assets.apps';
import { handleEditionsCrossword } from './handler.editionsCrossword';
import { handleFront, handleTagPage } from './handler.front.web';
import {
Expand Down Expand Up @@ -85,6 +86,8 @@ export const prodServer = (): void => {
app.post('/AppsBlocks', logRenderTime, handleAppsBlocks);
app.post('/EditionsCrossword', logRenderTime, handleEditionsCrossword);

app.get('/assets/rendered-items-assets', handleAppsAssets);

// All params to error handlers must be declared for express to identify them as error middleware
// https://expressjs.com/en/api.html#:~:text=Error%2Dhandling%20middleware%20always,see%3A%20Error%20handling
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- all params to error handlers must be declared
Expand Down
Loading