feat: add new css parser - postcss (#1458)

* feat: add new css parser

* make selectors change

* selectors and tests

* media changes

* remove old css references

* better variable name

* use postcss and port tests

* fix media test

* inline plugins

* fix failing multiline selector

* correct test result

* move tests to correct file

* cleanup all tests

* remove unused css-tree

* update bundle

* cleanup dependencies

* revert config files to master

* remove d.ts files

* update snapshot

* reset rebuilt test

* apply fuzzy css matching

* remove extra test

* Fix imports

* Newer versions of nswapi break rrdom-nodejs tests.
Example:
 FAIL  test/document-nodejs.test.ts > RRDocument for nodejs environment > RRDocument API > querySelectorAll
TypeError: e[api] is not a function
 ❯ byTag ../../node_modules/nwsapi/src/nwsapi.js:390:37
 ❯ Array.<anonymous> ../../node_modules/nwsapi/src/nwsapi.js:327:113
 ❯ collect ../../node_modules/nwsapi/src/nwsapi.js:1578:32
 ❯ Object._querySelectorAll [as select] ../../node_modules/nwsapi/src/nwsapi.js:1533:36
 ❯ RRDocument.querySelectorAll src/document-nodejs.ts:96:24

* Migrate from jest to vitest

* Order of selectors has changed with postcss

* Remove unused eslint

---------

Co-authored-by: Justin Halsall <Juice10@users.noreply.github.com>
This commit is contained in:
David Newell
2024-06-27 13:34:08 +01:00
committed by GitHub
parent db201841ac
commit 89ae4d2bad
9 changed files with 3742 additions and 5764 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
import { type Rule, type Media, type NodeWithRules, parse } from './css';
import { mediaSelectorPlugin, pseudoClassPlugin } from './css';
import {
type serializedNodeWithId,
NodeType,
@@ -8,6 +8,7 @@ import {
type legacyAttributes,
} from './types';
import { isElement, Mirror, isNodeMetaEqual } from './utils';
import postcss from 'postcss';
const tagMap: tagMap = {
script: 'noscript',
@@ -57,83 +58,15 @@ function getTagName(n: elementNode): string {
return tagName;
}
// based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
function escapeRegExp(str: string) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
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');
export function adaptCssForReplay(cssText: string, cache: BuildCache): string {
const cachedStyle = cache?.stylesWithHoverClass.get(cssText);
if (cachedStyle) return cachedStyle;
const ast = parse(cssText, {
silent: true,
});
if (!ast.stylesheet) {
return cssText;
}
const selectors: string[] = [];
const medias: string[] = [];
function getSelectors(rule: Rule | Media | NodeWithRules) {
if ('selectors' in rule && rule.selectors) {
rule.selectors.forEach((selector: string) => {
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) => {
// not attempting to maintain min-device-width along with min-width
// (it's non standard)
return media.replace(MEDIA_SELECTOR_GLOBAL, '$1-$2');
});
}
const ast: { css: string } = postcss([
mediaSelectorPlugin,
pseudoClassPlugin,
]).process(cssText);
const result = ast.css;
cache?.stylesWithHoverClass.set(cssText, result);
return result;
}