From 38e51ae5f7b64694c3cf3c7827f1bb432d1bc4f3 Mon Sep 17 00:00:00 2001 From: Florian Dieminger Date: Fri, 5 Apr 2024 13:13:27 +0200 Subject: [PATCH 01/19] feat(ssr): remove external files dependencies Before the ssr dist had dependencies on external files (e.g. index.html). By in-lining those dependencies we can generate a portable standalone main.js. --- package.json | 3 +- ssr/.gitignore | 1 + ssr/index.ts | 10 ------ ssr/prepare.ts | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++ ssr/render.ts | 83 +++++++++----------------------------------------- yarn.lock | 8 +++++ 6 files changed, 108 insertions(+), 80 deletions(-) create mode 100644 ssr/.gitignore create mode 100644 ssr/prepare.ts diff --git a/package.json b/package.json index e016a979a9ac..1ae668893ab3 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "build:dist": "tsc -p tsconfig.dist.json", "build:glean": "cd client && cross-env VIRTUAL_ENV=venv glean translate src/telemetry/metrics.yaml src/telemetry/pings.yaml -f typescript -o src/telemetry/generated", "build:prepare": "yarn build:client && yarn build:ssr && yarn tool optimize-client-build && yarn tool google-analytics-code && yarn tool popularities && yarn tool spas && yarn tool gather-git-history && yarn tool build-robots-txt", - "build:ssr": "cd ssr && webpack --mode=production", + "build:ssr": "cd ssr && cross-env NODE_ENV=production NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node prepare.ts && webpack --mode=production", "build:sw": "cd client/pwa && yarn && yarn build:prod", "build:sw-dev": "cd client/pwa && yarn && yarn build", "check:tsc": "find . -name 'tsconfig.json' ! -wholename '**/node_modules/**' -print0 | xargs -n1 -P 2 -0 sh -c 'cd `dirname $0` && echo \"🔄 $(pwd)\" && npx tsc --noEmit && echo \"☑️ $(pwd)\" || exit 255'", @@ -223,6 +223,7 @@ "prettier": "^3.2.5", "prettier-plugin-packagejson": "^2.4.14", "prompts": "^2.4.2", + "raw-loader": "^4.0.2", "react": "^18.2.0", "react-app-polyfill": "^3.0.0", "react-dev-utils": "^12.0.1", diff --git a/ssr/.gitignore b/ssr/.gitignore new file mode 100644 index 000000000000..5a14d43f75dc --- /dev/null +++ b/ssr/.gitignore @@ -0,0 +1 @@ +include.ts diff --git a/ssr/index.ts b/ssr/index.ts index 23d97df35368..0322a0228441 100644 --- a/ssr/index.ts +++ b/ssr/index.ts @@ -1,19 +1,9 @@ -import path from "node:path"; -import { fileURLToPath } from "node:url"; - -import * as dotenv from "dotenv"; import React from "react"; import { StaticRouter } from "react-router-dom/server"; import { App } from "../client/src/app"; import render from "./render"; -const dirname = fileURLToPath(new URL(".", import.meta.url)); - -dotenv.config({ - path: path.join(dirname, "..", process.env.ENV_FILE || ".env"), -}); - export function renderHTML(url, context) { return render( React.createElement( diff --git a/ssr/prepare.ts b/ssr/prepare.ts new file mode 100644 index 000000000000..24c7139f8d4b --- /dev/null +++ b/ssr/prepare.ts @@ -0,0 +1,83 @@ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { + ALWAYS_ALLOW_ROBOTS, + BUILD_OUT_ROOT, + BASE_URL, +} from "../libs/env/index.js"; + +const dirname = path.dirname(fileURLToPath(new URL(".", import.meta.url))); +const clientBuildRoot = path.resolve(dirname, "client/build"); + +function extractWebFontURLs() { + const urls: string[] = []; + const manifest = JSON.parse( + fs.readFileSync(path.join(clientBuildRoot, "asset-manifest.json"), "utf-8") + ); + for (const entrypoint of manifest.entrypoints) { + if (!entrypoint.endsWith(".css")) continue; + const css = fs.readFileSync( + path.join(clientBuildRoot, entrypoint), + "utf-8" + ); + const generator = extractCSSURLs(css, (url) => url.endsWith(".woff2")); + urls.push(...generator); + } + return [...new Set(urls)]; +} + +function* extractCSSURLs(css, filterFunction) { + for (const match of css.matchAll(/url\((.*?)\)/g)) { + const url = match[1]; + if (filterFunction(url)) { + yield url; + } + } +} + +function webfontTags(webfontURLs): string { + return webfontURLs + .map( + (url) => `` + ) + .join(""); +} + +function gtagScriptPath(relPath = "/static/js/gtag.js") { + // Return the relative path if there exists a `BUILD_ROOT/static/js/gtag.js`. + // If the file doesn't exist, return falsy. + // Remember, unless explicitly set, the BUILD_OUT_ROOT defaults to a path + // based on `dirname` but that's wrong when compared as a source and as + // a webpack built asset. So we need to remove the `/ssr/` portion of the path. + let root = BUILD_OUT_ROOT; + if (!fs.existsSync(root)) { + root = root + .split(path.sep) + .filter((x) => x !== "ssr") + .join(path.sep); + } + const filePath = relPath.split("/").slice(1).join(path.sep); + if (fs.existsSync(path.join(root, filePath))) { + return relPath; + } + return null; +} + +function prepare() { + const webfontURLs = extractWebFontURLs(); + const tags = webfontTags(webfontURLs); + const gtagPath = gtagScriptPath(); + + fs.writeFileSync( + "include.ts", + ` +export const WEBFONT_TAGS = ${JSON.stringify(tags)}; +export const GTAG_PATH = ${JSON.stringify(gtagPath)}; +export const BASE_URL = ${JSON.stringify(BASE_URL)}; +export const ALWAYS_ALLOW_ROBOTS = ${JSON.stringify(ALWAYS_ALLOW_ROBOTS)}; +` + ); +} + +prepare(); diff --git a/ssr/render.ts b/ssr/render.ts index e4b24eaf8c8d..a07022467e8b 100644 --- a/ssr/render.ts +++ b/ssr/render.ts @@ -1,14 +1,15 @@ -import fs from "node:fs"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; - import { renderToString } from "react-dom/server"; import { HydrationData } from "../libs/types/hydration"; -import { DEFAULT_LOCALE } from "../libs/constants"; -import { ALWAYS_ALLOW_ROBOTS, BUILD_OUT_ROOT, BASE_URL } from "../libs/env"; +import { DEFAULT_LOCALE } from "../libs/constants/index"; +import { + ALWAYS_ALLOW_ROBOTS, + BASE_URL, + WEBFONT_TAGS, + GTAG_PATH, +} from "./include"; -const dirname = path.dirname(fileURLToPath(new URL(".", import.meta.url))); +import HTML from "!!raw-loader!../client/build/index.html"; // When there are multiple options for a given language, this gives the // preferred locale for that language (language => preferred locale). @@ -75,74 +76,25 @@ const lazy = (creator) => { }; // Path strings are preferred over URLs here to mitigate Webpack resolution -const clientBuildRoot = path.resolve(dirname, "../../client/build"); const readBuildHTML = lazy(() => { - let html = fs.readFileSync(path.join(clientBuildRoot, "index.html"), "utf-8"); - if (!html.includes('
')) { + if (!HTML.includes('
')) { throw new Error( 'The render depends on being able to inject into
' ); } const scripts: string[] = []; - const gaScriptPathName = getGAScriptPathName(); + const gaScriptPathName = GTAG_PATH; if (gaScriptPathName) { scripts.push(``); } - html = html.replace('', () => scripts.join("")); - return html; -}); - -const getGAScriptPathName = lazy((relPath = "/static/js/gtag.js") => { - // Return the relative path if there exists a `BUILD_ROOT/static/js/gtag.js`. - // If the file doesn't exist, return falsy. - // Remember, unless explicitly set, the BUILD_OUT_ROOT defaults to a path - // based on `dirname` but that's wrong when compared as a source and as - // a webpack built asset. So we need to remove the `/ssr/` portion of the path. - let root = BUILD_OUT_ROOT; - if (!fs.existsSync(root)) { - root = root - .split(path.sep) - .filter((x) => x !== "ssr") - .join(path.sep); - } - const filePath = relPath.split("/").slice(1).join(path.sep); - if (fs.existsSync(path.join(root, filePath))) { - return relPath; - } - return null; -}); - -const extractWebFontURLs = lazy(() => { - const urls: string[] = []; - const manifest = JSON.parse( - fs.readFileSync(path.join(clientBuildRoot, "asset-manifest.json"), "utf-8") + const html = HTML.replace('', () => + scripts.join("") ); - for (const entrypoint of manifest.entrypoints) { - if (!entrypoint.endsWith(".css")) continue; - const css = fs.readFileSync( - path.join(clientBuildRoot, entrypoint), - "utf-8" - ); - const generator = extractCSSURLs( - css, - (url) => url.endsWith(".woff2") && /Bold/i.test(url) - ); - urls.push(...generator); - } - return urls; + return html; }); -function* extractCSSURLs(css, filterFunction) { - for (const match of css.matchAll(/url\((.*?)\)/g)) { - const url = match[1]; - if (filterFunction(url)) { - yield url; - } - } -} - export default function render( renderApp, { @@ -158,7 +110,6 @@ export default function render( }: HydrationData = {} ) { const buildHtml = readBuildHTML(); - const webfontURLs = extractWebFontURLs(); const rendered = renderToString(renderApp); let canonicalURL = BASE_URL; @@ -226,12 +177,6 @@ export default function render( } const titleTag = `${escapedPageTitle || "MDN Web Docs"}`; - const webfontTags = webfontURLs - .map( - (url) => - `` - ) - .join(""); // Open Graph protocol expects `language_TERRITORY` format. const ogLocale = (locale || (doc && doc.locale) || DEFAULT_LOCALE).replace( @@ -270,7 +215,7 @@ export default function render( : "index, follow"; const robotsMeta = ``; const rssLink = ``; - const ssr_data = [...translations, ...webfontTags, rssLink, robotsMeta]; + const ssr_data = [...translations, ...WEBFONT_TAGS, rssLink, robotsMeta]; let html = buildHtml; html = html.replace( ' Date: Tue, 9 Apr 2024 23:29:50 +0200 Subject: [PATCH 02/19] build gtag before ssr --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1ae668893ab3..07339bbfe445 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "build:curriculum": "cross-env NODE_ENV=production NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node build/build-curriculum.ts", "build:dist": "tsc -p tsconfig.dist.json", "build:glean": "cd client && cross-env VIRTUAL_ENV=venv glean translate src/telemetry/metrics.yaml src/telemetry/pings.yaml -f typescript -o src/telemetry/generated", - "build:prepare": "yarn build:client && yarn build:ssr && yarn tool optimize-client-build && yarn tool google-analytics-code && yarn tool popularities && yarn tool spas && yarn tool gather-git-history && yarn tool build-robots-txt", + "build:prepare": "yarn build:client && yarn tool google-analytics-code && yarn build:ssr && yarn tool optimize-client-build && yarn tool popularities && yarn tool spas && yarn tool gather-git-history && yarn tool build-robots-txt", "build:ssr": "cd ssr && cross-env NODE_ENV=production NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node prepare.ts && webpack --mode=production", "build:sw": "cd client/pwa && yarn && yarn build:prod", "build:sw-dev": "cd client/pwa && yarn && yarn build", From 4722a8f0be5e0d0249bd429469425c040eff9519 Mon Sep 17 00:00:00 2001 From: Florian Dieminger Date: Wed, 10 Apr 2024 00:30:50 +0200 Subject: [PATCH 03/19] extract ga generation --- package.json | 3 ++- tool/cli.ts | 69 ---------------------------------------------------- tool/ga.ts | 48 ++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 70 deletions(-) create mode 100644 tool/ga.ts diff --git a/package.json b/package.json index 07339bbfe445..ef98dff57986 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "build:curriculum": "cross-env NODE_ENV=production NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node build/build-curriculum.ts", "build:dist": "tsc -p tsconfig.dist.json", "build:glean": "cd client && cross-env VIRTUAL_ENV=venv glean translate src/telemetry/metrics.yaml src/telemetry/pings.yaml -f typescript -o src/telemetry/generated", - "build:prepare": "yarn build:client && yarn tool google-analytics-code && yarn build:ssr && yarn tool optimize-client-build && yarn tool popularities && yarn tool spas && yarn tool gather-git-history && yarn tool build-robots-txt", + "build:prepare": "yarn build:client && yarn ga && yarn build:ssr && yarn tool optimize-client-build && yarn tool popularities && yarn tool spas && yarn tool gather-git-history && yarn tool build-robots-txt", "build:ssr": "cd ssr && cross-env NODE_ENV=production NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node prepare.ts && webpack --mode=production", "build:sw": "cd client/pwa && yarn && yarn build:prod", "build:sw-dev": "cd client/pwa && yarn && yarn build", @@ -30,6 +30,7 @@ "dev": "yarn build:prepare && nf -j Procfile.dev start", "eslint": "eslint .", "filecheck": "cross-env NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node filecheck/cli.ts", + "ga": "cross-env NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node ./tool/ga.ts", "install:all": "find . -mindepth 2 -name 'yarn.lock' ! -wholename '**/node_modules/**' -print0 | xargs -n1 -0 sh -cx 'yarn --cwd $(dirname $0) install'", "install:all:npm": "find . -mindepth 2 -name 'package-lock.json' ! -wholename '**/node_modules/**' -print0 | xargs -n1 -0 sh -cx 'npm --prefix $(dirname $0) install'", "jest": "node --experimental-vm-modules --expose-gc ./node_modules/.bin/jest --logHeapUsage", diff --git a/tool/cli.ts b/tool/cli.ts index 4f70d2a3a9a6..9a52d2405675 100644 --- a/tool/cli.ts +++ b/tool/cli.ts @@ -29,7 +29,6 @@ import { BUILD_OUT_ROOT, CONTENT_ROOT, CONTENT_TRANSLATED_ROOT, - GOOGLE_ANALYTICS_MEASUREMENT_ID, } from "../libs/env/index.js"; import { runMakePopularitiesFile } from "./popularities.js"; import { runOptimizeClientBuild } from "./optimize-client-build.js"; @@ -179,14 +178,6 @@ interface PopularitiesActionParameters extends ActionParameters { logger: Logger; } -interface GoogleAnalyticsCodeActionParameters extends ActionParameters { - options: { - measurementId: string; - debug: boolean; - outfile: string; - }; -} - interface BuildRobotsTxtActionParameters extends ActionParameters { options: { outfile: string; @@ -873,66 +864,6 @@ program }) ) - .command( - "google-analytics-code", - "Generate a .js file that can be used in SSR rendering" - ) - .option("--outfile ", "name of the generated script file", { - default: path.join(BUILD_OUT_ROOT, "static", "js", "gtag.js"), - }) - .option( - "--measurement-id [,]", - "Google Analytics measurement IDs (defaults to value of $GOOGLE_ANALYTICS_MEASUREMENT_ID)", - { - default: GOOGLE_ANALYTICS_MEASUREMENT_ID, - } - ) - .action( - tryOrExit( - async ({ options, logger }: GoogleAnalyticsCodeActionParameters) => { - const { outfile, measurementId } = options; - const measurementIds = measurementId.split(",").filter(Boolean); - if (measurementIds.length) { - const dntHelperCode = fs - .readFileSync( - new URL("mozilla.dnthelper.min.js", import.meta.url), - "utf-8" - ) - .trim(); - - const firstMeasurementId = measurementIds.at(0); - const gaScriptURL = `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(firstMeasurementId)}`; - - const code = ` -// Mozilla DNT Helper -${dntHelperCode} -// Load GA unless DNT is enabled. -if (Mozilla && !Mozilla.dntEnabled()) { - window.dataLayer = window.dataLayer || []; - function gtag(){dataLayer.push(arguments);} - gtag('js', new Date()); - ${measurementIds - .map((id) => `gtag('config', '${id}', { 'anonymize_ip': true });`) - .join("\n ")} - - var gaScript = document.createElement('script'); - gaScript.async = true; - gaScript.src = '${gaScriptURL}'; - document.head.appendChild(gaScript); -}`.trim(); - fs.writeFileSync(outfile, `${code}\n`, "utf-8"); - logger.info( - chalk.green( - `Generated ${outfile} for SSR rendering using ${measurementId}.` - ) - ); - } else { - logger.info(chalk.yellow("No Google Analytics code file generated")); - } - } - ) - ) - .command( "build-robots-txt", "Generate a robots.txt in the build root depending ALWAYS_ALLOW_ROBOTS" diff --git a/tool/ga.ts b/tool/ga.ts new file mode 100644 index 000000000000..fa80a0698b9c --- /dev/null +++ b/tool/ga.ts @@ -0,0 +1,48 @@ +import fs from "node:fs"; +import { + BUILD_OUT_ROOT, + GOOGLE_ANALYTICS_MEASUREMENT_ID, +} from "../libs/env/index.js"; +import path from "node:path"; + +async function main() { + const measurementId = GOOGLE_ANALYTICS_MEASUREMENT_ID; + const outFile = path.join(BUILD_OUT_ROOT, "static", "js", "gtag.js"); + const measurementIds = measurementId.split(",").filter(Boolean); + if (measurementIds.length) { + const dntHelperCode = fs + .readFileSync( + new URL("mozilla.dnthelper.min.js", import.meta.url), + "utf-8" + ) + .trim(); + + const firstMeasurementId = measurementIds.at(0); + const gaScriptURL = `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(firstMeasurementId)}`; + + const code = ` +// Mozilla DNT Helper +${dntHelperCode} +// Load GA unless DNT is enabled. +if (Mozilla && !Mozilla.dntEnabled()) { + window.dataLayer = window.dataLayer || []; + function gtag(){dataLayer.push(arguments);} + gtag('js', new Date()); + ${measurementIds + .map((id) => `gtag('config', '${id}', { 'anonymize_ip': true });`) + .join("\n ")} + + var gaScript = document.createElement('script'); + gaScript.async = true; + gaScript.src = '${gaScriptURL}'; + document.head.appendChild(gaScript); +}`.trim(); + fs.writeFileSync(outFile, `${code}\n`, "utf-8"); + console.log( + `Generated ${outFile} for SSR rendering using ${measurementId}.` + ); + } else { + console.log("No Google Analytics code file generated"); + } +} +main(); From 3f53f7217e69604e3f265f4065a1011b71a5238b Mon Sep 17 00:00:00 2001 From: Florian Dieminger Date: Wed, 10 Apr 2024 10:14:57 +0200 Subject: [PATCH 04/19] move ga generation to ssr --- .gitignore | 1 + package.json | 2 +- {tool => ssr}/ga.ts | 11 +++++------ {tool => ssr}/mozilla.dnthelper.min.js | 0 ssr/prepare.ts | 6 ++++-- 5 files changed, 11 insertions(+), 9 deletions(-) rename {tool => ssr}/ga.ts (80%) rename {tool => ssr}/mozilla.dnthelper.min.js (100%) diff --git a/.gitignore b/.gitignore index 29b2e3c9078f..2426cd7bcd72 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,7 @@ yarn-error.log* /server/*.js.map /ssr/dist/ /ssr/*.js +!/ssr/mozilla.dnthelper.min.js !/ssr/webpack.config.js /ssr/*.js.map /tool/*.js diff --git a/package.json b/package.json index ef98dff57986..60a17d2bb5db 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "build:curriculum": "cross-env NODE_ENV=production NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node build/build-curriculum.ts", "build:dist": "tsc -p tsconfig.dist.json", "build:glean": "cd client && cross-env VIRTUAL_ENV=venv glean translate src/telemetry/metrics.yaml src/telemetry/pings.yaml -f typescript -o src/telemetry/generated", - "build:prepare": "yarn build:client && yarn ga && yarn build:ssr && yarn tool optimize-client-build && yarn tool popularities && yarn tool spas && yarn tool gather-git-history && yarn tool build-robots-txt", + "build:prepare": "yarn build:client && yarn build:ssr && yarn tool optimize-client-build && yarn tool popularities && yarn tool spas && yarn tool gather-git-history && yarn tool build-robots-txt", "build:ssr": "cd ssr && cross-env NODE_ENV=production NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node prepare.ts && webpack --mode=production", "build:sw": "cd client/pwa && yarn && yarn build:prod", "build:sw-dev": "cd client/pwa && yarn && yarn build", diff --git a/tool/ga.ts b/ssr/ga.ts similarity index 80% rename from tool/ga.ts rename to ssr/ga.ts index fa80a0698b9c..ad21f7c3a9a7 100644 --- a/tool/ga.ts +++ b/ssr/ga.ts @@ -5,10 +5,10 @@ import { } from "../libs/env/index.js"; import path from "node:path"; -async function main() { - const measurementId = GOOGLE_ANALYTICS_MEASUREMENT_ID; +export async function generateGA() { const outFile = path.join(BUILD_OUT_ROOT, "static", "js", "gtag.js"); - const measurementIds = measurementId.split(",").filter(Boolean); + const measurementIds = + GOOGLE_ANALYTICS_MEASUREMENT_ID.split(",").filter(Boolean); if (measurementIds.length) { const dntHelperCode = fs .readFileSync( @@ -17,7 +17,7 @@ async function main() { ) .trim(); - const firstMeasurementId = measurementIds.at(0); + const firstMeasurementId = measurementIds[0]; const gaScriptURL = `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(firstMeasurementId)}`; const code = ` @@ -39,10 +39,9 @@ if (Mozilla && !Mozilla.dntEnabled()) { }`.trim(); fs.writeFileSync(outFile, `${code}\n`, "utf-8"); console.log( - `Generated ${outFile} for SSR rendering using ${measurementId}.` + `Generated ${outFile} for SSR rendering using ${GOOGLE_ANALYTICS_MEASUREMENT_ID}.` ); } else { console.log("No Google Analytics code file generated"); } } -main(); diff --git a/tool/mozilla.dnthelper.min.js b/ssr/mozilla.dnthelper.min.js similarity index 100% rename from tool/mozilla.dnthelper.min.js rename to ssr/mozilla.dnthelper.min.js diff --git a/ssr/prepare.ts b/ssr/prepare.ts index 24c7139f8d4b..e9f4d02baf5e 100644 --- a/ssr/prepare.ts +++ b/ssr/prepare.ts @@ -6,6 +6,7 @@ import { BUILD_OUT_ROOT, BASE_URL, } from "../libs/env/index.js"; +import { generateGA } from "./ga.js"; const dirname = path.dirname(fileURLToPath(new URL(".", import.meta.url))); const clientBuildRoot = path.resolve(dirname, "client/build"); @@ -39,7 +40,8 @@ function* extractCSSURLs(css, filterFunction) { function webfontTags(webfontURLs): string { return webfontURLs .map( - (url) => `` + (url) => + `` ) .join(""); } @@ -80,4 +82,4 @@ export const ALWAYS_ALLOW_ROBOTS = ${JSON.stringify(ALWAYS_ALLOW_ROBOTS)}; ); } -prepare(); +generateGA().then(() => prepare()); From 8568ff768133d1081d9c03f7646c73a3a9275ac7 Mon Sep 17 00:00:00 2001 From: Florian Dieminger Date: Wed, 10 Apr 2024 12:52:06 +0200 Subject: [PATCH 05/19] use asset-loader and ignore ts and esling error --- package.json | 1 - ssr/render.ts | 4 +++- ssr/webpack.config.js | 4 ++++ yarn.lock | 8 -------- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 60a17d2bb5db..ccf900c77af3 100644 --- a/package.json +++ b/package.json @@ -224,7 +224,6 @@ "prettier": "^3.2.5", "prettier-plugin-packagejson": "^2.4.14", "prompts": "^2.4.2", - "raw-loader": "^4.0.2", "react": "^18.2.0", "react-app-polyfill": "^3.0.0", "react-dev-utils": "^12.0.1", diff --git a/ssr/render.ts b/ssr/render.ts index a07022467e8b..12f9d27c8624 100644 --- a/ssr/render.ts +++ b/ssr/render.ts @@ -9,7 +9,9 @@ import { GTAG_PATH, } from "./include"; -import HTML from "!!raw-loader!../client/build/index.html"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import HTML from "../client/build/index.html?raw"; // When there are multiple options for a given language, this gives the // preferred locale for that language (language => preferred locale). diff --git a/ssr/webpack.config.js b/ssr/webpack.config.js index e66ff19f784b..57ebbdc5d566 100644 --- a/ssr/webpack.config.js +++ b/ssr/webpack.config.js @@ -23,6 +23,10 @@ const config = { }, module: { rules: [ + { + resourceQuery: /raw/, + type: "asset/source", + }, { test: /\.tsx?$/, exclude: /node_modules/, diff --git a/yarn.lock b/yarn.lock index bc2f9c736bde..d58106d633e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12700,14 +12700,6 @@ raw-body@2.5.2: iconv-lite "0.4.24" unpipe "1.0.0" -raw-loader@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-4.0.2.tgz#1aac6b7d1ad1501e66efdac1522c73e59a584eb6" - integrity sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA== - dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - react-app-polyfill@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-3.0.0.tgz#95221e0a9bd259e5ca6b177c7bb1cb6768f68fd7" From da96be85a83e680ba2d31b91120e750cf4e9dac9 Mon Sep 17 00:00:00 2001 From: Florian Dieminger Date: Wed, 10 Apr 2024 12:55:57 +0200 Subject: [PATCH 06/19] add include.d.ts --- ssr/include.d.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 ssr/include.d.ts diff --git a/ssr/include.d.ts b/ssr/include.d.ts new file mode 100644 index 000000000000..6cd056c3dc5d --- /dev/null +++ b/ssr/include.d.ts @@ -0,0 +1,4 @@ +export const WEBFONT_TAGS: string; +export const GTAG_PATH: null | string; +export const BASE_URL: sting; +export const ALWAYS_ALLOW_ROBOTS: boolean; From e4ab74998691194264cb164ac2ed8aaac4f17d8b Mon Sep 17 00:00:00 2001 From: Florian Dieminger Date: Wed, 10 Apr 2024 13:46:36 +0200 Subject: [PATCH 07/19] fixes --- libs/env/index.js | 1 + package.json | 2 +- ssr/include.d.ts | 2 +- ssr/prepare.ts | 16 ++-------------- 4 files changed, 5 insertions(+), 16 deletions(-) diff --git a/libs/env/index.js b/libs/env/index.js index f6a71a7ad79e..7759aaba2156 100644 --- a/libs/env/index.js +++ b/libs/env/index.js @@ -113,6 +113,7 @@ function correctPathFromEnv(envVarName) { return; } pathName = fs.realpathSync(pathName); + console.log("realPathName", pathName); return pathName; } diff --git a/package.json b/package.json index ccf900c77af3..50e2d23d352e 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "build:dist": "tsc -p tsconfig.dist.json", "build:glean": "cd client && cross-env VIRTUAL_ENV=venv glean translate src/telemetry/metrics.yaml src/telemetry/pings.yaml -f typescript -o src/telemetry/generated", "build:prepare": "yarn build:client && yarn build:ssr && yarn tool optimize-client-build && yarn tool popularities && yarn tool spas && yarn tool gather-git-history && yarn tool build-robots-txt", - "build:ssr": "cd ssr && cross-env NODE_ENV=production NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node prepare.ts && webpack --mode=production", + "build:ssr": "cross-env NODE_ENV=production NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node ssr/prepare.ts && cd ssr && webpack --mode=production", "build:sw": "cd client/pwa && yarn && yarn build:prod", "build:sw-dev": "cd client/pwa && yarn && yarn build", "check:tsc": "find . -name 'tsconfig.json' ! -wholename '**/node_modules/**' -print0 | xargs -n1 -P 2 -0 sh -c 'cd `dirname $0` && echo \"🔄 $(pwd)\" && npx tsc --noEmit && echo \"☑️ $(pwd)\" || exit 255'", diff --git a/ssr/include.d.ts b/ssr/include.d.ts index 6cd056c3dc5d..b7b76f87e0c2 100644 --- a/ssr/include.d.ts +++ b/ssr/include.d.ts @@ -1,4 +1,4 @@ export const WEBFONT_TAGS: string; export const GTAG_PATH: null | string; -export const BASE_URL: sting; +export const BASE_URL: string; export const ALWAYS_ALLOW_ROBOTS: boolean; diff --git a/ssr/prepare.ts b/ssr/prepare.ts index e9f4d02baf5e..fce23157a5fa 100644 --- a/ssr/prepare.ts +++ b/ssr/prepare.ts @@ -47,20 +47,8 @@ function webfontTags(webfontURLs): string { } function gtagScriptPath(relPath = "/static/js/gtag.js") { - // Return the relative path if there exists a `BUILD_ROOT/static/js/gtag.js`. - // If the file doesn't exist, return falsy. - // Remember, unless explicitly set, the BUILD_OUT_ROOT defaults to a path - // based on `dirname` but that's wrong when compared as a source and as - // a webpack built asset. So we need to remove the `/ssr/` portion of the path. - let root = BUILD_OUT_ROOT; - if (!fs.existsSync(root)) { - root = root - .split(path.sep) - .filter((x) => x !== "ssr") - .join(path.sep); - } const filePath = relPath.split("/").slice(1).join(path.sep); - if (fs.existsSync(path.join(root, filePath))) { + if (fs.existsSync(path.join(BUILD_OUT_ROOT, filePath))) { return relPath; } return null; @@ -72,7 +60,7 @@ function prepare() { const gtagPath = gtagScriptPath(); fs.writeFileSync( - "include.ts", + path.join(dirname, "ssr", "include.ts"), ` export const WEBFONT_TAGS = ${JSON.stringify(tags)}; export const GTAG_PATH = ${JSON.stringify(gtagPath)}; From 3a7feb4fc0e2d38467ad03839ec6dd1949f92329 Mon Sep 17 00:00:00 2001 From: Florian Dieminger Date: Wed, 10 Apr 2024 14:32:33 +0200 Subject: [PATCH 08/19] remove console.log --- libs/env/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/env/index.js b/libs/env/index.js index 7759aaba2156..f6a71a7ad79e 100644 --- a/libs/env/index.js +++ b/libs/env/index.js @@ -113,7 +113,6 @@ function correctPathFromEnv(envVarName) { return; } pathName = fs.realpathSync(pathName); - console.log("realPathName", pathName); return pathName; } From 74a488b14a7fde66bea3701cd48947d285b39cea Mon Sep 17 00:00:00 2001 From: Florian Dieminger Date: Thu, 11 Apr 2024 12:30:41 +0200 Subject: [PATCH 09/19] optimize client --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 50e2d23d352e..4c29cab0005b 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "build:curriculum": "cross-env NODE_ENV=production NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node build/build-curriculum.ts", "build:dist": "tsc -p tsconfig.dist.json", "build:glean": "cd client && cross-env VIRTUAL_ENV=venv glean translate src/telemetry/metrics.yaml src/telemetry/pings.yaml -f typescript -o src/telemetry/generated", - "build:prepare": "yarn build:client && yarn build:ssr && yarn tool optimize-client-build && yarn tool popularities && yarn tool spas && yarn tool gather-git-history && yarn tool build-robots-txt", + "build:prepare": "yarn build:client && yarn tool optimize-client-build && yarn build:ssr && yarn tool popularities && yarn tool spas && yarn tool gather-git-history && yarn tool build-robots-txt", "build:ssr": "cross-env NODE_ENV=production NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node ssr/prepare.ts && cd ssr && webpack --mode=production", "build:sw": "cd client/pwa && yarn && yarn build:prod", "build:sw-dev": "cd client/pwa && yarn && yarn build", From 9fb44aa0c404d5a135e328691cdd9e61f2a492b1 Mon Sep 17 00:00:00 2001 From: Florian Dieminger Date: Thu, 11 Apr 2024 14:49:41 +0200 Subject: [PATCH 10/19] move optimize client to build step Also fix PUBLIC_URL support --- client/config/paths.js | 2 +- client/public/index.html | 6 ++-- client/scripts/build.js | 12 +++++++ .../scripts/postprocess-client-build.js | 27 +++++++------- .../homepage/contributor-spotlight/index.tsx | 4 +-- client/src/ui/atoms/avatar/index.tsx | 2 +- package.json | 2 +- tool/cli.ts | 35 ------------------- 8 files changed, 33 insertions(+), 57 deletions(-) rename tool/optimize-client-build.ts => client/scripts/postprocess-client-build.js (79%) diff --git a/client/config/paths.js b/client/config/paths.js index e240d3803ba2..1f51b90a41f7 100644 --- a/client/config/paths.js +++ b/client/config/paths.js @@ -19,7 +19,7 @@ const appPackage = JSON.parse(fs.readFileSync(resolveApp("package.json"))); const publicUrlOrPath = getPublicUrlOrPath( process.env.NODE_ENV === "development", appPackage.homepage, - process.env.PUBLIC_URL + process.env.PUBLIC_URL || process.env.BASE_URL ); const buildPath = process.env.BUILD_PATH || "build"; diff --git a/client/public/index.html b/client/public/index.html index 82f3d5e47791..14dcb98b73c9 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -9,13 +9,13 @@ of the file that has a hash in it. --> - + - + - + { + const { results } = await hashSomeStaticFilesForClientBuild(paths.appBuild); + console.log( + chalk.green( + `Hashed ${results.length} files in ${path.join( + paths.appBuild, + "index.html" + )}` + ) + ); + }) .catch((err) => { if (err && err.message) { console.log(err.message); diff --git a/tool/optimize-client-build.ts b/client/scripts/postprocess-client-build.js similarity index 79% rename from tool/optimize-client-build.ts rename to client/scripts/postprocess-client-build.js index 8e093008d117..558506729d5b 100644 --- a/tool/optimize-client-build.ts +++ b/client/scripts/postprocess-client-build.js @@ -9,7 +9,7 @@ import path from "node:path"; import cheerio from "cheerio"; import md5File from "md5-file"; -export async function runOptimizeClientBuild(buildRoot) { +export async function hashSomeStaticFilesForClientBuild(buildRoot) { const indexHtmlFilePath = path.join(buildRoot, "index.html"); const indexHtml = fs.readFileSync(indexHtmlFilePath, "utf-8"); @@ -28,15 +28,6 @@ export async function runOptimizeClientBuild(buildRoot) { } href = element.attribs.content; attributeKey = "content"; - // This is an unfortunate hack. The value for the - // needs to be an absolute URL. - // We tested with a relative URL and it seems it doesn't work in Twitter. - // So we hardcode the URL to be our production domain so the URL is - // always absolute. - // Yes, this makes it a bit weird to use a build of this on a dev, - // stage, preview, or a local build. Especially if the hashed URL doesn't - // always work. But it's a fair price to pay. - hrefPrefix = "https://developer.mozilla.org"; } else { href = element.attribs.href; if (!href) { @@ -75,7 +66,7 @@ export async function runOptimizeClientBuild(buildRoot) { const splitName = filePath.split(extName); const hashedFilePath = `${splitName[0]}.${hash}${extName}`; fs.copyFileSync(filePath, hashedFilePath); - const hashedHref = filePathToHref(buildRoot, hashedFilePath); + const hashedHref = filePathToHref(buildRoot, hashedFilePath, href); results.push({ filePath, href, @@ -101,8 +92,18 @@ export async function runOptimizeClientBuild(buildRoot) { } // Turn 'C:\Path\to\client\build\favicon.ico' to '/favicon.ico' -function filePathToHref(root, filePath) { - return `${filePath.replace(root, "").replace(path.sep, "/")}`; +function filePathToHref(root, filePath, href) { + let dummyOrExistingUrl = new URL(href, "http://localhost.example"); + dummyOrExistingUrl.pathname = ""; + let url = new URL( + `${filePath.replace(root, "").replace(path.sep, "/")}`, + dummyOrExistingUrl + ); + if (url.hostname === "localhost.example") { + return url.pathname; + } else { + return url.href; + } } // Turn '/favicon.ico' to 'C:\Path\to\client\build\favicon.ico' diff --git a/client/src/homepage/contributor-spotlight/index.tsx b/client/src/homepage/contributor-spotlight/index.tsx index 827f1d5ea594..e0ce29da6673 100644 --- a/client/src/homepage/contributor-spotlight/index.tsx +++ b/client/src/homepage/contributor-spotlight/index.tsx @@ -5,9 +5,7 @@ import { Icon } from "../../ui/atoms/icon"; import Mandala from "../../ui/molecules/mandala"; import "./index.scss"; -const contributorGraphic = `${ - process.env.PUBLIC_URL || "" -}/assets/mdn_contributor.png`; +const contributorGraphic = "/assets/mdn_contributor.png"; export function ContributorSpotlight(props: HydrationData) { const fallbackData = props.hyData ? props : undefined; diff --git a/client/src/ui/atoms/avatar/index.tsx b/client/src/ui/atoms/avatar/index.tsx index 6c254fe636e1..cacc938102c8 100644 --- a/client/src/ui/atoms/avatar/index.tsx +++ b/client/src/ui/atoms/avatar/index.tsx @@ -6,7 +6,7 @@ export const Avatar = ({ userData }: { userData: UserData }) => { // If we have user data and the user is logged in, show their // profile pic, defaulting to the dino head if the avatar // URL doesn't work. - const avatarImage = `${process.env.PUBLIC_URL || ""}/assets/avatar.png`; + const avatarImage = "/assets/avatar.png"; return (
", "directory where react-scripts built", { - default: path.join("client", "build"), - }) - .action( - tryOrExit( - async ({ - args, - options, - logger, - }: OptimizeClientBuildActionParameters) => { - const { buildroot } = args; - const { results } = await runOptimizeClientBuild(buildroot); - if (options.verbose) { - for (const result of results) { - logger.info(`${result.filePath} -> ${result.hashedHref}`); - } - } else { - logger.info( - chalk.green( - `Hashed ${results.length} files in ${path.join( - buildroot, - "index.html" - )}` - ) - ); - } - } - ) - ) - .command( "macro-usage-report", "Counts occurrences of each macro and prints it as a table." From 00346b775c579807886330b096766859299e9919 Mon Sep 17 00:00:00 2001 From: Florian Dieminger Date: Thu, 11 Apr 2024 16:48:44 +0200 Subject: [PATCH 11/19] fix test by adding default --- client/config/paths.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/config/paths.js b/client/config/paths.js index 1f51b90a41f7..ff08946009d1 100644 --- a/client/config/paths.js +++ b/client/config/paths.js @@ -19,7 +19,9 @@ const appPackage = JSON.parse(fs.readFileSync(resolveApp("package.json"))); const publicUrlOrPath = getPublicUrlOrPath( process.env.NODE_ENV === "development", appPackage.homepage, - process.env.PUBLIC_URL || process.env.BASE_URL + process.env.PUBLIC_URL || + process.env.BASE_URL || + "https://developer.mozilla.org/" ); const buildPath = process.env.BUILD_PATH || "build"; From 7c801567834593172360345dd8d802578c7a0d38 Mon Sep 17 00:00:00 2001 From: Florian Dieminger Date: Thu, 11 Apr 2024 17:03:09 +0200 Subject: [PATCH 12/19] comment --- client/scripts/postprocess-client-build.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/scripts/postprocess-client-build.js b/client/scripts/postprocess-client-build.js index 558506729d5b..1afebf032f44 100644 --- a/client/scripts/postprocess-client-build.js +++ b/client/scripts/postprocess-client-build.js @@ -92,6 +92,7 @@ export async function hashSomeStaticFilesForClientBuild(buildRoot) { } // Turn 'C:\Path\to\client\build\favicon.ico' to '/favicon.ico' +// or 'https://foo.bar/favicon.ico' if href is an absolute URL. function filePathToHref(root, filePath, href) { let dummyOrExistingUrl = new URL(href, "http://localhost.example"); dummyOrExistingUrl.pathname = ""; From 8d3f145c1e9d0eb4f2f1e8478fdde400e15eebb7 Mon Sep 17 00:00:00 2001 From: Florian Dieminger Date: Fri, 12 Apr 2024 10:18:34 +0200 Subject: [PATCH 13/19] lint --- tool/cli.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tool/cli.ts b/tool/cli.ts index 0fa2da595535..348ca7cb6c94 100644 --- a/tool/cli.ts +++ b/tool/cli.ts @@ -194,12 +194,6 @@ interface MacrosActionParameters extends ActionParameters { }; } -interface OptimizeClientBuildActionParameters extends ActionParameters { - args: { - buildroot: string; - }; -} - interface MacroUsageReportActionParameters extends ActionParameters { options: { deprecatedOnly: boolean; From 7ffbf3bab501901a5278fcbd2b11e9a8fa1c60b5 Mon Sep 17 00:00:00 2001 From: Florian Dieminger Date: Fri, 12 Apr 2024 13:51:59 +0200 Subject: [PATCH 14/19] don't touch PUBLIC_URL --- client/config/env.js | 3 ++- client/config/paths.js | 11 ++++++++--- client/config/webpack.config.js | 5 ++++- client/public/index.html | 2 +- client/scripts/start.js | 5 ++++- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/client/config/env.js b/client/config/env.js index 3ca362ebadab..0ef2fd27b74d 100644 --- a/client/config/env.js +++ b/client/config/env.js @@ -40,7 +40,7 @@ process.env.NODE_PATH = (process.env.NODE_PATH || "") // injected into the application via DefinePlugin in webpack configuration. const REACT_APP = /^REACT_APP_/i; -function getClientEnvironment(publicUrl) { +function getClientEnvironment(publicUrl, baseUrl) { const raw = Object.keys(process.env) .filter((key) => REACT_APP.test(key)) .reduce( @@ -57,6 +57,7 @@ function getClientEnvironment(publicUrl) { // This should only be used as an escape hatch. Normally you would put // images into the `src` and `import` them in code to get their paths. PUBLIC_URL: publicUrl, + BASE_URL: baseUrl, // We support configuring the sockjs pathname during development. // These settings let a developer run multiple simultaneous projects. // They are used as the connection `hostname`, `pathname` and `port` diff --git a/client/config/paths.js b/client/config/paths.js index ff08946009d1..a7c2e9d53042 100644 --- a/client/config/paths.js +++ b/client/config/paths.js @@ -19,9 +19,13 @@ const appPackage = JSON.parse(fs.readFileSync(resolveApp("package.json"))); const publicUrlOrPath = getPublicUrlOrPath( process.env.NODE_ENV === "development", appPackage.homepage, - process.env.PUBLIC_URL || - process.env.BASE_URL || - "https://developer.mozilla.org/" + process.env.PUBLIC_URL +); + +const baseUrl = getPublicUrlOrPath( + process.env.NODE_ENV === "development", + appPackage.homepage, + process.env.BASE_URL || "/" ); const buildPath = process.env.BUILD_PATH || "build"; @@ -73,6 +77,7 @@ const config = { appTsBuildInfoFile: resolveApp("../node_modules/.cache/tsconfig.tsbuildinfo"), swSrc: resolveModule(resolveApp, "src/service-worker"), publicUrlOrPath, + baseUrl, libsPath: resolveApp("../libs"), moduleFileExtensions, }; diff --git a/client/config/webpack.config.js b/client/config/webpack.config.js index 80a0686f0a35..079630ba8a97 100644 --- a/client/config/webpack.config.js +++ b/client/config/webpack.config.js @@ -95,7 +95,10 @@ function config(webpackEnv) { // as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. // Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz. // Get environment variables to inject into our app. - const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1)); + const env = getClientEnvironment( + paths.publicUrlOrPath.replace(/\/$/, ""), + paths.baseUrl.replace(/\/$/, "") + ); const shouldUseReactRefresh = env.raw.FAST_REFRESH; diff --git a/client/public/index.html b/client/public/index.html index 14dcb98b73c9..71a2cf938b17 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -58,7 +58,7 @@ property="og:description" content="The MDN Web Docs site provides information about Open Web technologies including HTML, CSS, and APIs for both Web sites and progressive web apps." /> - + diff --git a/client/scripts/start.js b/client/scripts/start.js index c1cce67b3bac..afd00a32c649 100644 --- a/client/scripts/start.js +++ b/client/scripts/start.js @@ -32,7 +32,10 @@ process.on("unhandledRejection", (err) => { const appPackageJson = JSON.parse(fs.readFileSync(paths.appPackageJson)); -const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1)); +const env = getClientEnvironment( + paths.publicUrlOrPath.replace(/\/$/, ""), + paths.baseUrl.replace(/\/$/, "") +); const useYarn = fs.existsSync(paths.yarnLockFile); const isInteractive = process.stdout.isTTY; From 593d82e46437e0a710b46109b81a883b47c8bf02 Mon Sep 17 00:00:00 2001 From: Florian Dieminger Date: Mon, 22 Apr 2024 14:37:21 +0200 Subject: [PATCH 15/19] remove ga step --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 72c7745fcd8a..7360027a68f7 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "dev": "yarn build:prepare && nf -j Procfile.dev start", "eslint": "eslint .", "filecheck": "cross-env NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node filecheck/cli.ts", - "ga": "cross-env NODE_OPTIONS='--no-warnings=ExperimentalWarning --loader ts-node/esm' node ./tool/ga.ts", "install:all": "find . -mindepth 2 -name 'yarn.lock' ! -wholename '**/node_modules/**' -print0 | xargs -n1 -0 sh -cx 'yarn --cwd $(dirname $0) install'", "install:all:npm": "find . -mindepth 2 -name 'package-lock.json' ! -wholename '**/node_modules/**' -print0 | xargs -n1 -0 sh -cx 'npm --prefix $(dirname $0) install'", "jest": "node --experimental-vm-modules --expose-gc ./node_modules/.bin/jest --logHeapUsage", From cdee3406f63f02e335a3082e2da82cc1b7984a01 Mon Sep 17 00:00:00 2001 From: Florian Dieminger Date: Mon, 22 Apr 2024 15:18:54 +0200 Subject: [PATCH 16/19] hack base url --- .env.testing | 2 ++ client/config/env.js | 3 +-- client/config/paths.js | 7 ------- client/config/webpack.config.js | 5 +---- client/scripts/postprocess-client-build.js | 3 +++ testing/tests/index.test.ts | 1 + 6 files changed, 8 insertions(+), 13 deletions(-) diff --git a/.env.testing b/.env.testing index 4265dba530e8..d9097039efb7 100644 --- a/.env.testing +++ b/.env.testing @@ -34,3 +34,5 @@ BUILD_ALWAYS_ALLOW_ROBOTS=true REACT_APP_ENABLE_PLUS=true REACT_APP_FXA_SIGNIN_URL=/users/fxa/login/authenticate/ + +BASE_URL="https://developer.mozilla.org" diff --git a/client/config/env.js b/client/config/env.js index 0ef2fd27b74d..3ca362ebadab 100644 --- a/client/config/env.js +++ b/client/config/env.js @@ -40,7 +40,7 @@ process.env.NODE_PATH = (process.env.NODE_PATH || "") // injected into the application via DefinePlugin in webpack configuration. const REACT_APP = /^REACT_APP_/i; -function getClientEnvironment(publicUrl, baseUrl) { +function getClientEnvironment(publicUrl) { const raw = Object.keys(process.env) .filter((key) => REACT_APP.test(key)) .reduce( @@ -57,7 +57,6 @@ function getClientEnvironment(publicUrl, baseUrl) { // This should only be used as an escape hatch. Normally you would put // images into the `src` and `import` them in code to get their paths. PUBLIC_URL: publicUrl, - BASE_URL: baseUrl, // We support configuring the sockjs pathname during development. // These settings let a developer run multiple simultaneous projects. // They are used as the connection `hostname`, `pathname` and `port` diff --git a/client/config/paths.js b/client/config/paths.js index a7c2e9d53042..e240d3803ba2 100644 --- a/client/config/paths.js +++ b/client/config/paths.js @@ -22,12 +22,6 @@ const publicUrlOrPath = getPublicUrlOrPath( process.env.PUBLIC_URL ); -const baseUrl = getPublicUrlOrPath( - process.env.NODE_ENV === "development", - appPackage.homepage, - process.env.BASE_URL || "/" -); - const buildPath = process.env.BUILD_PATH || "build"; const moduleFileExtensions = [ @@ -77,7 +71,6 @@ const config = { appTsBuildInfoFile: resolveApp("../node_modules/.cache/tsconfig.tsbuildinfo"), swSrc: resolveModule(resolveApp, "src/service-worker"), publicUrlOrPath, - baseUrl, libsPath: resolveApp("../libs"), moduleFileExtensions, }; diff --git a/client/config/webpack.config.js b/client/config/webpack.config.js index 079630ba8a97..f65872efcad8 100644 --- a/client/config/webpack.config.js +++ b/client/config/webpack.config.js @@ -95,10 +95,7 @@ function config(webpackEnv) { // as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. // Omit trailing slash as %PUBLIC_URL%/xyz looks better than %PUBLIC_URL%xyz. // Get environment variables to inject into our app. - const env = getClientEnvironment( - paths.publicUrlOrPath.replace(/\/$/, ""), - paths.baseUrl.replace(/\/$/, "") - ); + const env = getClientEnvironment(paths.publicUrlOrPath.replace(/\/$/, "")); const shouldUseReactRefresh = env.raw.FAST_REFRESH; diff --git a/client/scripts/postprocess-client-build.js b/client/scripts/postprocess-client-build.js index 1afebf032f44..446f27d6420e 100644 --- a/client/scripts/postprocess-client-build.js +++ b/client/scripts/postprocess-client-build.js @@ -26,6 +26,9 @@ export async function hashSomeStaticFilesForClientBuild(buildRoot) { if (element.attribs.property !== "og:image") { return; } + // This is a can of worms. Using from environment for now. + // We need to use an absolute URL for "og:image". + hrefPrefix = process.env.BASE_URL || ""; href = element.attribs.content; attributeKey = "content"; } else { diff --git a/testing/tests/index.test.ts b/testing/tests/index.test.ts index 7499bb80727c..ab6abb7341f8 100644 --- a/testing/tests/index.test.ts +++ b/testing/tests/index.test.ts @@ -49,6 +49,7 @@ test("all favicons on the home page", () => { // Check that the og:image resolves $('meta[property="og:image"]').each((i, element) => { const url = $(element).attr("content"); + console.log("og:image", url); const href = new URL(url).pathname; // There should always be a 8 character hash in the href expect(/\.[a-f0-9]{8}\./.test(href)).toBeTruthy(); From 4e5e120139dce5054f3f3b956f743e40d72f351c Mon Sep 17 00:00:00 2001 From: Florian Dieminger Date: Mon, 22 Apr 2024 15:23:52 +0200 Subject: [PATCH 17/19] fix index.html --- client/public/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/public/index.html b/client/public/index.html index 71a2cf938b17..3c5f36af573c 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -58,7 +58,7 @@ property="og:description" content="The MDN Web Docs site provides information about Open Web technologies including HTML, CSS, and APIs for both Web sites and progressive web apps." /> - + From 3e77fc272ec823a3779ef6e29504ea7cfe4a6307 Mon Sep 17 00:00:00 2001 From: Florian Dieminger Date: Mon, 22 Apr 2024 16:36:11 +0200 Subject: [PATCH 18/19] fix server --- client/scripts/start.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client/scripts/start.js b/client/scripts/start.js index afd00a32c649..5d1875f853d1 100644 --- a/client/scripts/start.js +++ b/client/scripts/start.js @@ -32,10 +32,7 @@ process.on("unhandledRejection", (err) => { const appPackageJson = JSON.parse(fs.readFileSync(paths.appPackageJson)); -const env = getClientEnvironment( - paths.publicUrlOrPath.replace(/\/$/, ""), - paths.baseUrl.replace(/\/$/, "") -); +const env = getClientEnvironment(paths.publicUrlOrPath.replace(/\/$/, "")); const useYarn = fs.existsSync(paths.yarnLockFile); const isInteractive = process.stdout.isTTY; From 0af8d629dc3bf09f10de221327f6d8882eb83a77 Mon Sep 17 00:00:00 2001 From: Florian Dieminger Date: Thu, 25 Apr 2024 11:34:32 +0200 Subject: [PATCH 19/19] remove log --- testing/tests/index.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/testing/tests/index.test.ts b/testing/tests/index.test.ts index ab6abb7341f8..7499bb80727c 100644 --- a/testing/tests/index.test.ts +++ b/testing/tests/index.test.ts @@ -49,7 +49,6 @@ test("all favicons on the home page", () => { // Check that the og:image resolves $('meta[property="og:image"]').each((i, element) => { const url = $(element).attr("content"); - console.log("og:image", url); const href = new URL(url).pathname; // There should always be a 8 character hash in the href expect(/\.[a-f0-9]{8}\./.test(href)).toBeTruthy();