From bdf857265f49a72ecf655d7608cba34a59b20e3c Mon Sep 17 00:00:00 2001 From: David Newell Date: Tue, 30 Apr 2024 14:03:35 +0100 Subject: [PATCH] feat: use postcss from node_modules and apply custom plugins --- .../session-recordings/player/rrweb/css.ts | 88 --------- patches/rrweb@2.0.0-alpha.13.patch | 172 +++++++++++++++++- pnpm-lock.yaml | 6 +- 3 files changed, 173 insertions(+), 93 deletions(-) delete mode 100644 frontend/src/scenes/session-recordings/player/rrweb/css.ts diff --git a/frontend/src/scenes/session-recordings/player/rrweb/css.ts b/frontend/src/scenes/session-recordings/player/rrweb/css.ts deleted file mode 100644 index 3e001a09c8780..0000000000000 --- a/frontend/src/scenes/session-recordings/player/rrweb/css.ts +++ /dev/null @@ -1,88 +0,0 @@ -import type { Plugin, Rule } from 'postcss' -import postcss from 'postcss' - -const MEDIA_SELECTOR = /(max|min)-device-(width|height)/ -const MEDIA_SELECTOR_GLOBAL = new RegExp(MEDIA_SELECTOR.source, 'g') - -export const parse = (cssText: string): string => { - const ast = postcss([mediaSelectorPlugin, pseudoClassPlugin]).process(cssText) - return ast.css -} - -const mediaSelectorPlugin: Plugin = { - postcssPlugin: 'postcss-custom-selectors', - prepare() { - return { - postcssPlugin: 'postcss-custom-selectors', - AtRule: function (atrule) { - if (atrule.params.match(MEDIA_SELECTOR_GLOBAL)) { - atrule.params = atrule.params.replace(MEDIA_SELECTOR_GLOBAL, '$1-$2') - } - }, - } - }, -} - -// Adapted from https://github.com/giuseppeg/postcss-pseudo-classes/blob/master/index.js -const pseudoClassPlugin: Plugin = { - postcssPlugin: 'postcss-hover-classes', - prepare: function () { - const fixed: Rule[] = [] - return { - Rule: function (rule) { - if (fixed.indexOf(rule) !== -1) { - return - } - fixed.push(rule) - - rule.selectors.forEach(function (selector) { - if (!selector.includes(':')) { - return - } - - const selectorParts = selector.replace(/\n/g, ' ').split(' ') - const pseudoedSelectorParts: string[] = [] - - selectorParts.forEach(function (selectorPart) { - const pseudos = selectorPart.match(/::?([^:]+)/g) - - if (!pseudos) { - pseudoedSelectorParts.push(selectorPart) - return - } - - const baseSelector = selectorPart.substr(0, selectorPart.length - pseudos.join('').length) - - const classPseudos = pseudos.map(function (pseudo) { - const pseudoToCheck = pseudo.replace(/\(.*/g, '') - if (pseudoToCheck !== ':hover') { - return pseudo - } - - // Ignore pseudo-elements! - if (pseudo.match(/^::/)) { - return pseudo - } - - // Kill the colon - pseudo = pseudo.substr(1) - - // Replace left and right parens - pseudo = pseudo.replace(/\(/g, '\\(') - pseudo = pseudo.replace(/\)/g, '\\)') - - return '.' + '\\:' + pseudo - }) - - pseudoedSelectorParts.push(baseSelector + classPseudos.join('')) - }) - - const newSelector = pseudoedSelectorParts.join(' ') - if (newSelector && newSelector !== selector) { - rule.selector += ',\n' + newSelector - } - }) - }, - } - }, -} diff --git a/patches/rrweb@2.0.0-alpha.13.patch b/patches/rrweb@2.0.0-alpha.13.patch index 7e21e3c9f16d0..493b783ab4f77 100644 --- a/patches/rrweb@2.0.0-alpha.13.patch +++ b/patches/rrweb@2.0.0-alpha.13.patch @@ -528,11 +528,110 @@ index 22fee601e786c1d8dfb5c01d2e359c8bcbac7c42..20c3e14adfde860563e8dd902041bd14 let playbackRate = 1; if (typeof mediaAttributes.rr_mediaPlaybackRate === 'number') { playbackRate = mediaAttributes.rr_mediaPlaybackRate; +diff --git a/es/rrweb/packages/rrweb-snapshot/es/css.js b/es/rrweb/packages/rrweb-snapshot/es/css.js +new file mode 100644 +index 0000000000000000000000000000000000000000..9e417600a9cb267ceb071ea9ffdd91cc9ddb779e +--- /dev/null ++++ b/es/rrweb/packages/rrweb-snapshot/es/css.js +@@ -0,0 +1,87 @@ ++import postcss from '../../../../../../postcss/lib/postcss.js' ++ ++const MEDIA_SELECTOR = /(max|min)-device-(width|height)/ ++const MEDIA_SELECTOR_GLOBAL = new RegExp(MEDIA_SELECTOR.source, 'g') ++ ++export const parse = (cssText) => { ++ const ast = postcss([mediaSelectorPlugin, pseudoClassPlugin]).process(cssText) ++ return ast.css ++} ++ ++const mediaSelectorPlugin = { ++ postcssPlugin: 'postcss-custom-selectors', ++ prepare() { ++ return { ++ postcssPlugin: 'postcss-custom-selectors', ++ AtRule: function (atrule) { ++ if (atrule.params.match(MEDIA_SELECTOR_GLOBAL)) { ++ atrule.params = atrule.params.replace(MEDIA_SELECTOR_GLOBAL, '$1-$2') ++ } ++ }, ++ } ++ }, ++} ++ ++// Adapted from https://github.com/giuseppeg/postcss-pseudo-classes/blob/master/index.js ++const pseudoClassPlugin = { ++ postcssPlugin: 'postcss-hover-classes', ++ prepare: function () { ++ const fixed = [] ++ return { ++ Rule: function (rule) { ++ if (fixed.indexOf(rule) !== -1) { ++ return ++ } ++ fixed.push(rule) ++ ++ rule.selectors.forEach(function (selector) { ++ if (!selector.includes(':')) { ++ return ++ } ++ ++ const selectorParts = selector.replace(/\n/g, ' ').split(' ') ++ const pseudoedSelectorParts = [] ++ ++ selectorParts.forEach(function (selectorPart) { ++ const pseudos = selectorPart.match(/::?([^:]+)/g) ++ ++ if (!pseudos) { ++ pseudoedSelectorParts.push(selectorPart) ++ return ++ } ++ ++ const baseSelector = selectorPart.substr(0, selectorPart.length - pseudos.join('').length) ++ ++ const classPseudos = pseudos.map(function (pseudo) { ++ const pseudoToCheck = pseudo.replace(/\(.*/g, '') ++ if (pseudoToCheck !== ':hover') { ++ return pseudo ++ } ++ ++ // Ignore pseudo-elements! ++ if (pseudo.match(/^::/)) { ++ return pseudo ++ } ++ ++ // Kill the colon ++ pseudo = pseudo.substr(1) ++ ++ // Replace left and right parens ++ pseudo = pseudo.replace(/\(/g, '\\(') ++ pseudo = pseudo.replace(/\)/g, '\\)') ++ ++ return '.' + '\\:' + pseudo ++ }) ++ ++ pseudoedSelectorParts.push(baseSelector + classPseudos.join('')) ++ }) ++ ++ const newSelector = pseudoedSelectorParts.join(' ') ++ if (newSelector && newSelector !== selector) { ++ rule.selector += ',\n' + newSelector ++ } ++ }) ++ }, ++ } ++ }, ++} diff --git a/es/rrweb/packages/rrweb-snapshot/es/rrweb-snapshot.js b/es/rrweb/packages/rrweb-snapshot/es/rrweb-snapshot.js -index 38a23aaae8d683fa584329eced277dd8de55d1ff..278e06bc6c8c964581d461405a0f0a4544344fa1 100644 +index 38a23aaae8d683fa584329eced277dd8de55d1ff..3bee75fa4a64e72b01e388afdf2433e00ae1156a 100644 --- a/es/rrweb/packages/rrweb-snapshot/es/rrweb-snapshot.js +++ b/es/rrweb/packages/rrweb-snapshot/es/rrweb-snapshot.js -@@ -1255,54 +1255,19 @@ function parse(css, options = {}) { +@@ -1,3 +1,5 @@ ++import {parse} from './css.js'; ++ + var NodeType; + (function (NodeType) { + NodeType[NodeType["Document"] = 0] = "Document"; +@@ -1255,54 +1257,19 @@ function parse(css, options = {}) { }); } function selector() { @@ -595,3 +694,72 @@ index 38a23aaae8d683fa584329eced277dd8de55d1ff..278e06bc6c8c964581d461405a0f0a45 } function declaration() { const pos = position(); +@@ -1651,67 +1618,11 @@ function getTagName(n) { + } + return tagName; + } +-function escapeRegExp(str) { +- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +-} +-const MEDIA_SELECTOR = /(max|min)-device-(width|height)/; +-const MEDIA_SELECTOR_GLOBAL = new RegExp(MEDIA_SELECTOR.source, 'g'); +-const HOVER_SELECTOR = /([^\\]):hover/; +-const HOVER_SELECTOR_GLOBAL = new RegExp(HOVER_SELECTOR.source, 'g'); + function adaptCssForReplay(cssText, cache) { + const cachedStyle = cache === null || cache === void 0 ? void 0 : cache.stylesWithHoverClass.get(cssText); + if (cachedStyle) + return cachedStyle; +- const ast = parse(cssText, { +- silent: true, +- }); +- if (!ast.stylesheet) { +- return cssText; +- } +- const selectors = []; +- const medias = []; +- function getSelectors(rule) { +- if ('selectors' in rule && rule.selectors) { +- rule.selectors.forEach((selector) => { +- if (HOVER_SELECTOR.test(selector)) { +- selectors.push(selector); +- } +- }); +- } +- if ('media' in rule && rule.media && MEDIA_SELECTOR.test(rule.media)) { +- medias.push(rule.media); +- } +- if ('rules' in rule && rule.rules) { +- rule.rules.forEach(getSelectors); +- } +- } +- getSelectors(ast.stylesheet); +- let result = cssText; +- if (selectors.length > 0) { +- const selectorMatcher = new RegExp(selectors +- .filter((selector, index) => selectors.indexOf(selector) === index) +- .sort((a, b) => b.length - a.length) +- .map((selector) => { +- return escapeRegExp(selector); +- }) +- .join('|'), 'g'); +- result = result.replace(selectorMatcher, (selector) => { +- const newSelector = selector.replace(HOVER_SELECTOR_GLOBAL, '$1.\\:hover'); +- return `${selector}, ${newSelector}`; +- }); +- } +- if (medias.length > 0) { +- const mediaMatcher = new RegExp(medias +- .filter((media, index) => medias.indexOf(media) === index) +- .sort((a, b) => b.length - a.length) +- .map((media) => { +- return escapeRegExp(media); +- }) +- .join('|'), 'g'); +- result = result.replace(mediaMatcher, (media) => { +- return media.replace(MEDIA_SELECTOR_GLOBAL, '$1-$2'); +- }); +- } ++ const result = parse(cssText) + cache === null || cache === void 0 ? void 0 : cache.stylesWithHoverClass.set(cssText, result); + return result; + } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a2c4d1630f5fe..41d88f8548668 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,7 +12,7 @@ patchedDependencies: hash: gydrxrztd4ruyhouu6tu7zh43e path: patches/heatmap.js@2.0.5.patch rrweb@2.0.0-alpha.13: - hash: g7jnlxuwyxkja3n62yb6xpim6q + hash: ppwwvd5inagbkghbte76s24nnq path: patches/rrweb@2.0.0-alpha.13.patch dependencies: @@ -333,7 +333,7 @@ dependencies: version: 1.5.1 rrweb: specifier: 2.0.0-alpha.13 - version: 2.0.0-alpha.13(patch_hash=g7jnlxuwyxkja3n62yb6xpim6q) + version: 2.0.0-alpha.13(patch_hash=ppwwvd5inagbkghbte76s24nnq) sass: specifier: ^1.26.2 version: 1.56.0 @@ -19277,7 +19277,7 @@ packages: resolution: {integrity: sha512-slbhNBCYjxLGCeH95a67ECCy5a22nloXp1F5wF7DCzUNw80FN7tF9Lef1sRGLNo32g3mNqTc2sWLATlKejMxYw==} dev: false - /rrweb@2.0.0-alpha.13(patch_hash=g7jnlxuwyxkja3n62yb6xpim6q): + /rrweb@2.0.0-alpha.13(patch_hash=ppwwvd5inagbkghbte76s24nnq): resolution: {integrity: sha512-a8GXOCnzWHNaVZPa7hsrLZtNZ3CGjiL+YrkpLo0TfmxGLhjNZbWY2r7pE06p+FcjFNlgUVTmFrSJbK3kO7yxvw==} dependencies: '@rrweb/types': 2.0.0-alpha.13