Comparing version 1.3.1 to 2.0.0
@@ -1,5 +0,4 @@ | ||
export declare const CLASS_IDENT_REGEX: RegExp; | ||
export * from './CheckCSS.js'; | ||
export declare function ignoreCSS(re: RegExp | undefined): void; | ||
export declare function extractClasses(sel: string): string[]; | ||
export default function checkCSS(): void; | ||
export declare function monitorCSS(): void; | ||
export default function checkCSS(): void; |
@@ -1,105 +0,30 @@ | ||
// Regex for identifying class names in CSS selectors | ||
// REF: https://www.w3.org/TR/selectors-3/#lex | ||
export const CLASS_IDENT_REGEX = /\.-?(?:[_a-z]|[^\0-\x7f]|\\[0-9a-f]{1,6}\s?|\\[^\s0-9a-f])(?:[_a-z0-9-]|[^\0-\x7f]|\\[0-9a-f]{1,6}\s?|\\[^\s0-9a-f])*/gi; | ||
const seen = new Set(); | ||
let defined; | ||
import { CheckCSS } from './CheckCSS.js'; | ||
export * from './CheckCSS.js'; | ||
let ignoreRE; | ||
let checkcss; | ||
const warned = new Set(); | ||
function warn(msg) { | ||
if (warned.has(msg)) | ||
return; | ||
warned.add(msg); | ||
console.warn(msg); | ||
} | ||
// Legacy API support | ||
export function ignoreCSS(re) { | ||
ignoreRE = re; | ||
} | ||
function checkClassNames(node, includeChildren = false) { | ||
if (node?.classList) { | ||
for (const cl of node.classList) { | ||
// Ignore defined and already-seen classes | ||
if (defined.has(cl) || seen.has(cl)) | ||
continue; | ||
// Mark as seen | ||
seen.add(cl); | ||
// Ignore classes that mathc the ignore regex | ||
if (ignoreRE?.test(cl)) | ||
continue; | ||
console.warn(`Undefined CSS class: ${cl}`, node); | ||
} | ||
export default function checkCSS() { | ||
warn('checkCSS() is deprecated. Use CheckCSS#scan() instead'); | ||
if (!checkcss) { | ||
checkcss = new CheckCSS(document); | ||
checkcss.onClassnameDetected = (classname, el) => { | ||
return ignoreRE?.test(classname) ?? true; | ||
}; | ||
} | ||
if (includeChildren) { | ||
for (const el of node.querySelectorAll('*')) { | ||
checkClassNames(el); | ||
} | ||
} | ||
checkcss.scan(); | ||
} | ||
function isGroupingRule(rule) { | ||
return 'cssRules' in rule; | ||
} | ||
function isCSSStyleRule(rule) { | ||
return 'selectorText' in rule; | ||
} | ||
export function extractClasses(sel) { | ||
const classnames = sel.match(CLASS_IDENT_REGEX) ?? []; | ||
return classnames.map(c => { | ||
// Strip '.' | ||
c = c.substring(1); | ||
// Unescape numeric escape sequences (\###) | ||
c = c.replaceAll(/\\[0-9a-f]{1,6}\s?/gi, escape => { | ||
return String.fromCodePoint(parseInt(escape.substring(1), 16)); | ||
}); | ||
// Unescape character escape sequences (\[some char]) | ||
c = c.replaceAll(/\\[^\s0-9a-f]/g, c => c.substring(1)); | ||
return c; | ||
}); | ||
} | ||
function ingestRules(rules) { | ||
for (const rule of rules) { | ||
if (isGroupingRule(rule)) { | ||
// Some rules are groups of rules (e.g. CSSMediaRule), so we need to | ||
// recurse into them | ||
ingestRules(rule.cssRules); | ||
} | ||
else if (isCSSStyleRule(rule)) { | ||
// Add each classname to the defined set | ||
for (const classname of extractClasses(rule.selectorText)) { | ||
defined.add(classname); | ||
} | ||
} | ||
} | ||
} | ||
export function monitorCSS() { | ||
const observer = new MutationObserver(mutationsList => { | ||
for (const mut of mutationsList) { | ||
if (mut.type === 'childList' && mut?.addedNodes) { | ||
for (const el of mut.addedNodes) { | ||
// Ignore text nodes | ||
if (el.nodeType == 3) | ||
continue; | ||
if (!(el instanceof HTMLElement)) | ||
return; | ||
// Sweep DOM fragment | ||
checkClassNames(el); | ||
for (const cel of el.querySelectorAll('*')) { | ||
checkClassNames(cel); | ||
} | ||
} | ||
} | ||
else if (mut?.attributeName == 'class') { | ||
// ... if the element 'class' changed | ||
checkClassNames(mut.target); | ||
} | ||
} | ||
}); | ||
observer.observe(document, { | ||
attributes: true, | ||
childList: true, | ||
subtree: true, | ||
}); | ||
warn('monitorCSS() is deprecated. Use CheckCSS#watch() instead'); | ||
checkcss.watch(); | ||
} | ||
export default function checkCSS() { | ||
if (defined) | ||
return; | ||
defined = new Set(); | ||
// Ingest rules from all stylesheets | ||
for (const sheet of document.styleSheets) { | ||
ingestRules(sheet.cssRules); | ||
} | ||
// Do a sweep of the existing DOM | ||
checkClassNames(document.documentElement, true); | ||
} | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "checkcss", | ||
"version": "1.3.1", | ||
"version": "2.0.0", | ||
"type": "module", | ||
"description": "Utility method for warning when elements have a `class` attribute that refers to an undefined CSS class", | ||
"description": "Detect references to undefined CSS classes", | ||
"main": "dist/index.js", | ||
"types": "dist/index.d.js", | ||
"exports": { | ||
".": "./dist/index.js" | ||
}, | ||
"scripts": { | ||
"test": "node test/test.js", | ||
"test:browser": "npx http-server -o /test/browser-test.htm", | ||
"prepare": "rm -fr dist && yarn build", | ||
@@ -31,3 +34,6 @@ "build": "tsc", | ||
"typescript": "^4.7.3" | ||
}, | ||
"dependencies": { | ||
"uuid": "^9.0.0" | ||
} | ||
} |
@@ -1,19 +0,13 @@ | ||
# checkcss | ||
Utility method for warning any time the `class` attribute of an element references an undefined CSS class. | ||
Logic for detecting when DOM elements reference undefined CSS classes. | ||
This module provides two methods: | ||
Note: The `scan()` method uses the [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) API to monitor DOM changes. While this should be pretty efficient, it's probably not something you want to be running in production. | ||
* `checkCSS()` performs a one-time sweep of the DOM. | ||
* `monitorCSS()` Sets up a | ||
[MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) to continuously monitor DOM changes, looking for elements that reference undefined CSS classes. | ||
(Note: `monitorCSS()` is fairly efficient but is probably not something you want to be | ||
running in production.) | ||
## Installation | ||
``` | ||
npm i checkcss | ||
npm install checkcss | ||
# or | ||
yarn add checkcss | ||
``` | ||
@@ -24,12 +18,23 @@ | ||
```javascript | ||
import checkCSS, { ignoreCSS, monitorCSS } from 'checkcss'; | ||
import { CheckCSS } from 'checkcss'; | ||
// (optional) Set a regex for classnames to ignore | ||
ignoreCSS(/^license-|^maintainer-/); | ||
// Create CheckCSS instance | ||
const checkcss = new CheckCSS(); | ||
// Check current DOM | ||
checkCSS(); | ||
// OPTIONAL: Set hook to filter classnames. | ||
// Return false for classnames that should be ignored. | ||
checkcss.onClassnameDetected = function (classname, element) { | ||
return /^license-|^maintainer-/.test(); | ||
}; | ||
// ... and setup monitor to check DOM as it changes | ||
monitorCSS(); | ||
// OPTIONAL: Set hook for custom logging (replaces default log method) | ||
checkcss.onUndefinedClassname = function (classname) { | ||
// Custom logging goes here | ||
}; | ||
// Scan current DOM for undefined classes | ||
checkcss.scan(); | ||
// Monitor DOM as it changes | ||
checkcss.watch(); | ||
``` |
124
src/index.ts
@@ -1,117 +0,37 @@ | ||
// Regex for identifying class names in CSS selectors | ||
// REF: https://www.w3.org/TR/selectors-3/#lex | ||
export const CLASS_IDENT_REGEX = | ||
/\.-?(?:[_a-z]|[^\0-\x7f]|\\[0-9a-f]{1,6}\s?|\\[^\s0-9a-f])(?:[_a-z0-9-]|[^\0-\x7f]|\\[0-9a-f]{1,6}\s?|\\[^\s0-9a-f])*/gi; | ||
import { CheckCSS } from './CheckCSS.js'; | ||
export * from './CheckCSS.js'; | ||
const seen = new Set(); | ||
let defined: Set<any>; | ||
let ignoreRE: RegExp | undefined; | ||
export function ignoreCSS(re: RegExp | undefined) { | ||
ignoreRE = re; | ||
} | ||
function checkClassNames(node: Element, includeChildren = false) { | ||
if (node?.classList) { | ||
for (const cl of node.classList) { | ||
// Ignore defined and already-seen classes | ||
if (defined.has(cl) || seen.has(cl)) continue; | ||
let checkcss: CheckCSS; | ||
// Mark as seen | ||
seen.add(cl); | ||
// Ignore classes that mathc the ignore regex | ||
if (ignoreRE?.test(cl)) continue; | ||
console.warn(`Undefined CSS class: ${cl}`, node); | ||
} | ||
} | ||
if (includeChildren) { | ||
for (const el of node.querySelectorAll('*')) { | ||
checkClassNames(el); | ||
} | ||
} | ||
const warned = new Set(); | ||
function warn(msg: string) { | ||
if (warned.has(msg)) return; | ||
warned.add(msg); | ||
console.warn(msg); | ||
} | ||
function isGroupingRule(rule: CSSRule): rule is CSSGroupingRule { | ||
return 'cssRules' in rule; | ||
// Legacy API support | ||
export function ignoreCSS(re: RegExp | undefined) { | ||
ignoreRE = re; | ||
} | ||
function isCSSStyleRule(rule: CSSRule): rule is CSSStyleRule { | ||
return 'selectorText' in rule; | ||
} | ||
export default function checkCSS() { | ||
warn('checkCSS() is deprecated. Use CheckCSS#scan() instead'); | ||
export function extractClasses(sel: string) { | ||
const classnames = sel.match(CLASS_IDENT_REGEX) ?? []; | ||
return classnames.map(c => { | ||
// Strip '.' | ||
c = c.substring(1); | ||
if (!checkcss) { | ||
checkcss = new CheckCSS(document); | ||
// Unescape numeric escape sequences (\###) | ||
c = c.replaceAll(/\\[0-9a-f]{1,6}\s?/gi, escape => { | ||
return String.fromCodePoint(parseInt(escape.substring(1), 16)); | ||
}); | ||
checkcss.onClassnameDetected = (classname, el) => { | ||
return ignoreRE?.test(classname) ?? true; | ||
}; | ||
} | ||
// Unescape character escape sequences (\[some char]) | ||
c = c.replaceAll(/\\[^\s0-9a-f]/g, c => c.substring(1)); | ||
return c; | ||
}); | ||
checkcss.scan(); | ||
} | ||
function ingestRules(rules: CSSRuleList) { | ||
for (const rule of rules) { | ||
if (isGroupingRule(rule)) { | ||
// Some rules are groups of rules (e.g. CSSMediaRule), so we need to | ||
// recurse into them | ||
ingestRules(rule.cssRules); | ||
} else if (isCSSStyleRule(rule)) { | ||
// Add each classname to the defined set | ||
for (const classname of extractClasses(rule.selectorText)) { | ||
defined.add(classname); | ||
} | ||
} | ||
} | ||
} | ||
export function monitorCSS() { | ||
const observer = new MutationObserver(mutationsList => { | ||
for (const mut of mutationsList) { | ||
if (mut.type === 'childList' && mut?.addedNodes) { | ||
for (const el of mut.addedNodes) { | ||
// Ignore text nodes | ||
if (el.nodeType == 3) continue; | ||
if (!(el instanceof HTMLElement)) return; | ||
// Sweep DOM fragment | ||
checkClassNames(el); | ||
for (const cel of el.querySelectorAll('*')) { | ||
checkClassNames(cel); | ||
} | ||
} | ||
} else if (mut?.attributeName == 'class') { | ||
// ... if the element 'class' changed | ||
checkClassNames(mut.target as Element); | ||
} | ||
} | ||
}); | ||
observer.observe(document, { | ||
attributes: true, | ||
childList: true, | ||
subtree: true, | ||
}); | ||
warn('monitorCSS() is deprecated. Use CheckCSS#watch() instead'); | ||
checkcss.watch(); | ||
} | ||
export default function checkCSS() { | ||
if (defined) return; | ||
defined = new Set(); | ||
// Ingest rules from all stylesheets | ||
for (const sheet of document.styleSheets) { | ||
ingestRules(sheet.cssRules); | ||
} | ||
// Do a sweep of the existing DOM | ||
checkClassNames(document.documentElement, true); | ||
} |
@@ -8,6 +8,10 @@ import assert from 'assert'; | ||
// Sampling of selector patterns that show up in the `tailwind` framework. "X" = | ||
// text of some sort, "0" = numeric digits of some sort. | ||
// Sampling of selector patterns that show up in the Tailwind framework | ||
// "X" = text of some sort, "0" = numeric digits of some sort. | ||
const TAILWIND_SELECTORS = { | ||
'.w-[32px]': ['w-[32px]'], | ||
'.left-1/2': ['left-1/2'], | ||
'.-X\\.X': ['-X.X'], | ||
'.-X\\.X > :X([X]) ~ :X([X])': ['-X.X'], | ||
@@ -14,0 +18,0 @@ '.X, .X, .X': ['X', 'X', 'X'], |
{ | ||
"compilerOptions": { | ||
"declaration": true, | ||
"moduleResolution": "node", | ||
"outDir": "./dist", | ||
@@ -5,0 +6,0 @@ "sourceMap": true, |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
31323
18
529
40
1
1
+ Addeduuid@^9.0.0
+ Addeduuid@9.0.1(transitive)