From f5f75115bf14461076c70c291ad68e4c07304e6c Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 29 Jan 2024 13:34:05 -0500 Subject: [PATCH] Add a window var for CWV Tech Report to look for (#11222) --- .changeset/cwv-report-id.md | 6 ++ package.json | 4 +- .../rollup.config.js | 20 ++++- packages/react-router-dom/index.tsx | 17 ++++ packages/react-router-dom/rollup.config.js | 48 +++++++++--- rollup.utils.js | 77 +++++++++++++++++++ 6 files changed, 156 insertions(+), 16 deletions(-) create mode 100644 .changeset/cwv-report-id.md diff --git a/.changeset/cwv-report-id.md b/.changeset/cwv-report-id.md new file mode 100644 index 0000000000..99f011405e --- /dev/null +++ b/.changeset/cwv-report-id.md @@ -0,0 +1,6 @@ +--- +"react-router-dom-v5-compat": minor +"react-router-dom": minor +--- + +Include a window tag for CWV Report detection diff --git a/package.json b/package.json index 5b8d5f54c1..ec4a586323 100644 --- a/package.json +++ b/package.json @@ -125,10 +125,10 @@ "none": "17.1 kB" }, "packages/react-router-dom/dist/react-router-dom.production.min.js": { - "none": "16.9 kB" + "none": "17.0 kB" }, "packages/react-router-dom/dist/umd/react-router-dom.production.min.js": { - "none": "23.1 kB" + "none": "23.2 kB" } } } diff --git a/packages/react-router-dom-v5-compat/rollup.config.js b/packages/react-router-dom-v5-compat/rollup.config.js index 82fed85f33..37ef230b6a 100644 --- a/packages/react-router-dom-v5-compat/rollup.config.js +++ b/packages/react-router-dom-v5-compat/rollup.config.js @@ -7,8 +7,10 @@ const replace = require("@rollup/plugin-replace"); const { terser } = require("rollup-plugin-terser"); const typescript = require("@rollup/plugin-typescript"); const { + babelPluginReplaceVersionPlaceholder, createBanner, getBuildDirectories, + validateReplacedVersion, PRETTY, } = require("../../rollup.utils"); const { name, version } = require("./package.json"); @@ -57,7 +59,10 @@ module.exports = function rollup() { "@babel/preset-react", "@babel/preset-typescript", ], - plugins: ["babel-plugin-dev-expression"], + plugins: [ + "babel-plugin-dev-expression", + babelPluginReplaceVersionPlaceholder(), + ], extensions: [".ts", ".tsx"], }), typescript({ @@ -71,6 +76,7 @@ module.exports = function rollup() { ], verbose: true, }), + validateReplacedVersion(), ].concat(PRETTY ? prettier({ parser: "babel" }) : []), }, ]; @@ -110,13 +116,17 @@ module.exports = function rollup() { "@babel/preset-react", "@babel/preset-typescript", ], - plugins: ["babel-plugin-dev-expression"], + plugins: [ + "babel-plugin-dev-expression", + babelPluginReplaceVersionPlaceholder(), + ], extensions: [".ts", ".tsx"], }), replace({ preventAssignment: true, values: { "process.env.NODE_ENV": JSON.stringify("development") }, }), + validateReplacedVersion(), ].concat(PRETTY ? prettier({ parser: "babel" }) : []), }, { @@ -152,7 +162,10 @@ module.exports = function rollup() { "@babel/preset-react", "@babel/preset-typescript", ], - plugins: ["babel-plugin-dev-expression"], + plugins: [ + "babel-plugin-dev-expression", + babelPluginReplaceVersionPlaceholder(), + ], extensions: [".ts", ".tsx"], }), replace({ @@ -160,6 +173,7 @@ module.exports = function rollup() { values: { "process.env.NODE_ENV": JSON.stringify("production") }, }), terser(), + validateReplacedVersion(), ].concat(PRETTY ? prettier({ parser: "babel" }) : []), }, ]; diff --git a/packages/react-router-dom/index.tsx b/packages/react-router-dom/index.tsx index 532658706e..b30ed811ae 100644 --- a/packages/react-router-dom/index.tsx +++ b/packages/react-router-dom/index.tsx @@ -218,11 +218,28 @@ export { declare global { var __staticRouterHydrationData: HydrationState | undefined; + var __reactRouterVersion: string; interface Document { startViewTransition(cb: () => Promise | void): ViewTransition; } } +// HEY YOU! DON'T TOUCH THIS VARIABLE! +// +// It is replaced with the proper version at build time via a babel plugin in +// the rollup config. +// +// Export a global property onto the window for React Router detection by the +// Core Web Vitals Technology Report. This way they can configure the `wappalyzer` +// to detect and properly classify live websites as being built with React Router: +// https://github.com/HTTPArchive/wappalyzer/blob/main/src/technologies/r.json +const REACT_ROUTER_VERSION = "0"; +try { + window.__reactRouterVersion = REACT_ROUTER_VERSION; +} catch (e) { + // no-op +} + //////////////////////////////////////////////////////////////////////////////// //#region Routers //////////////////////////////////////////////////////////////////////////////// diff --git a/packages/react-router-dom/rollup.config.js b/packages/react-router-dom/rollup.config.js index 0a816d13a2..969291ed10 100644 --- a/packages/react-router-dom/rollup.config.js +++ b/packages/react-router-dom/rollup.config.js @@ -7,8 +7,10 @@ const replace = require("@rollup/plugin-replace"); const { terser } = require("rollup-plugin-terser"); const typescript = require("@rollup/plugin-typescript"); const { + babelPluginReplaceVersionPlaceholder, createBanner, getBuildDirectories, + validateReplacedVersion, PRETTY, } = require("../../rollup.utils"); const { name, version } = require("./package.json"); @@ -37,7 +39,10 @@ module.exports = function rollup() { "@babel/preset-react", "@babel/preset-typescript", ], - plugins: ["babel-plugin-dev-expression"], + plugins: [ + "babel-plugin-dev-expression", + babelPluginReplaceVersionPlaceholder(), + ], extensions: [".ts", ".tsx"], }), typescript({ @@ -51,6 +56,7 @@ module.exports = function rollup() { ], verbose: true, }), + validateReplacedVersion(), ].concat(PRETTY ? prettier({ parser: "babel" }) : []), }, ]; @@ -78,13 +84,17 @@ module.exports = function rollup() { "@babel/preset-react", "@babel/preset-typescript", ], - plugins: ["babel-plugin-dev-expression"], + plugins: [ + "babel-plugin-dev-expression", + babelPluginReplaceVersionPlaceholder(), + ], extensions: [".ts", ".tsx"], }), replace({ preventAssignment: true, values: { "process.env.NODE_ENV": JSON.stringify("development") }, }), + validateReplacedVersion(), ].concat(PRETTY ? prettier({ parser: "babel" }) : []), }, { @@ -118,14 +128,17 @@ module.exports = function rollup() { ], "@babel/preset-typescript", ], - plugins: ["babel-plugin-dev-expression"], + plugins: [ + "babel-plugin-dev-expression", + babelPluginReplaceVersionPlaceholder(), + ], extensions: [".ts", ".tsx"], }), replace({ preventAssignment: true, values: { "process.env.NODE_ENV": JSON.stringify("production") }, }), - // compiler(), + validateReplacedVersion(), terser({ ecma: 8, safari10: true }), ].concat(PRETTY ? prettier({ parser: "babel" }) : []), }, @@ -158,13 +171,17 @@ module.exports = function rollup() { "@babel/preset-react", "@babel/preset-typescript", ], - plugins: ["babel-plugin-dev-expression"], + plugins: [ + "babel-plugin-dev-expression", + babelPluginReplaceVersionPlaceholder(), + ], extensions: [".ts", ".tsx"], }), replace({ preventAssignment: true, values: { "process.env.NODE_ENV": JSON.stringify("development") }, }), + validateReplacedVersion(), ].concat(PRETTY ? prettier({ parser: "babel" }) : []), }, { @@ -192,15 +209,18 @@ module.exports = function rollup() { "@babel/preset-react", "@babel/preset-typescript", ], - plugins: ["babel-plugin-dev-expression"], + plugins: [ + "babel-plugin-dev-expression", + babelPluginReplaceVersionPlaceholder(), + ], extensions: [".ts", ".tsx"], }), replace({ preventAssignment: true, values: { "process.env.NODE_ENV": JSON.stringify("production") }, }), - // compiler(), terser(), + validateReplacedVersion(), ].concat(PRETTY ? prettier({ parser: "babel" }) : []), }, ]; @@ -247,7 +267,10 @@ module.exports = function rollup() { "@babel/preset-react", "@babel/preset-typescript", ], - plugins: ["babel-plugin-dev-expression"], + plugins: [ + "babel-plugin-dev-expression", + babelPluginReplaceVersionPlaceholder(), + ], extensions: [".ts", ".tsx"], }), typescript({ @@ -256,7 +279,7 @@ module.exports = function rollup() { exclude: ["__tests__"], noEmitOnError: true, }), - // compiler() + validateReplacedVersion(), ].concat(PRETTY ? prettier({ parser: "babel" }) : []), }, { @@ -296,10 +319,13 @@ module.exports = function rollup() { "@babel/preset-react", "@babel/preset-typescript", ], - plugins: ["babel-plugin-dev-expression"], + plugins: [ + "babel-plugin-dev-expression", + babelPluginReplaceVersionPlaceholder(), + ], extensions: [".ts", ".tsx"], }), - // compiler() + validateReplacedVersion(), ].concat(PRETTY ? prettier({ parser: "babel" }) : []), }, ]; diff --git a/rollup.utils.js b/rollup.utils.js index 021c6292c8..02f8892e2b 100644 --- a/rollup.utils.js +++ b/rollup.utils.js @@ -1,5 +1,7 @@ const path = require("path"); const fse = require("fs-extra"); +const { version } = require("./packages/react-router/package.json"); +const majorVersion = version.split(".").shift(); const PRETTY = !!process.env.PRETTY; @@ -62,8 +64,83 @@ function createBanner(packageName, version) { */`; } +// Babel plugin to replace `const REACT_ROUTER_VERSION = "0.0.0";` with the +// current version at build time, so we can set it on `window.__reactRouterVersion` +// for consumption by the Core Web Vitals Technology Report +function babelPluginReplaceVersionPlaceholder() { + return function (babel) { + var t = babel.types; + + const KIND = "const"; + const NAME = "REACT_ROUTER_VERSION"; + const PLACEHOLDER = "0"; + + return { + visitor: { + VariableDeclaration: { + enter: function (path) { + // Only operate on top-level variables + if (!path.parentPath.isProgram()) { + return; + } + + let { kind, declarations } = path.node; + if ( + kind === KIND && + declarations.length === 1 && + declarations[0].id.name === NAME && + declarations[0].init?.value === PLACEHOLDER + ) { + path.replaceWith( + t.variableDeclaration(KIND, [ + t.variableDeclarator( + t.identifier(NAME), + t.stringLiteral(majorVersion) + ), + ]) + ); + } + }, + }, + }, + }; + }; +} + +// Post-build plugin to validate that the version placeholder was replaced +function validateReplacedVersion() { + return { + name: "validate-replaced-version", + writeBundle(_, bundle) { + Object.entries(bundle).forEach(([filename, contents]) => { + if (!filename.endsWith(".js") || filename === "server.js") { + return; + } + + let requiredStrs = filename.endsWith(".min.js") + ? [`{window.__reactRouterVersion="${majorVersion}"}`] + : [ + `const REACT_ROUTER_VERSION = "${majorVersion}";`, + `window.__reactRouterVersion = REACT_ROUTER_VERSION;`, + ]; + + requiredStrs.forEach((str) => { + if (!contents.code.includes(str)) { + throw new Error( + `Expected ${filename} to include \`${str}\` but it did not` + ); + } + }); + }); + }, + }; +} + +// rollup.config.js module.exports = { getBuildDirectories, createBanner, + babelPluginReplaceVersionPlaceholder, + validateReplacedVersion, PRETTY, };