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:
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user