Skip to content

Commit 8d6d09d

Browse files
committed
feat: add strict mode
Strict mode significantly speeds up runtime and limits false positives by restricting the notion of a used token to entire strings only, i.e. substrings and combinations of strings are not considered used.
1 parent c367770 commit 8d6d09d

File tree

6 files changed

+73
-3
lines changed

6 files changed

+73
-3
lines changed

lib/css-nuker.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const DISABLE_COMMENT_REGEX = /\bnukecss:disable\b/
66
class CssNuker {
77
constructor(sources, parser, options = {}) {
88
this._options = Object.assign({
9+
strict: false,
910
simple: false,
1011
amalgamate: false,
1112
whitelist: undefined,

lib/sources/html-source.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,19 @@ class HtmlSource extends ParsedSource {
2828
onopentag(name, attributes) {
2929
const attributeValues = Array.isArray(attributeKeys) ?
3030
attributeKeys.map(key => attributes[key]) : []
31-
attributeValues.concat([name])
31+
_(attributeValues)
32+
.concat([name])
3233
.filter(candidate => typeof candidate === 'string')
34+
.map(value => value.split(' '))
35+
.flatten()
3336
.forEach(candidate => tokens.add(candidate.toLowerCase()))
3437
},
3538
ontext(value) {
3639
innerText = value
3740
},
3841
onclosetag(name) {
3942
if (name === 'script' && innerText) {
40-
children.push(JsSource.from(innerText))
43+
children.push(JsSource.from(innerText, options))
4144
}
4245
},
4346
onerror(err) {

lib/sources/js-source.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ class JsSource extends ParsedSource {
99
}
1010

1111
_findToken(selector) {
12-
if (selector.length === 0) {
12+
if (this._options.strict) {
13+
return false
14+
} else if (selector.length === 0) {
1315
return true
1416
}
1517

lib/sources/parsed-source.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ class ParsedSource {
1616
}
1717

1818
_findWholeSelectorInTokens(selector) {
19+
if (this._options.strict) {
20+
return false
21+
}
22+
1923
return this._tokensArray.find(token => ParsedSource.textContains(token, selector))
2024
}
2125

test/sources/html-source.test.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,5 +97,27 @@ describe('sources/html-source.js', () => {
9797
expect(source).to.not.contain('myJsVar')
9898
})
9999
})
100+
101+
context('when options.strict=true', () => {
102+
const html = `
103+
<html>
104+
<div class="first-class second-class"></div>
105+
<script>const hello = 'my-class'</script>
106+
</html>
107+
`
108+
109+
const source = HtmlSource.from(html, {strict: true})
110+
111+
it('should find tokens as multiple classes', () => {
112+
expect(source).to.contain('first-class')
113+
expect(source).to.contain('second-class')
114+
})
115+
116+
it('should find tokens in script', () => {
117+
expect(source).to.contain('my-class')
118+
expect(source).to.not.contain('my')
119+
expect(source).to.not.contain('hello')
120+
})
121+
})
100122
})
101123
})

test/sources/js-source.test.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,44 @@ describe('sources/js-source.js', () => {
8787
})
8888
})
8989

90+
context('when options.strict=true', () => {
91+
const script = `
92+
const myVar = 'the-class'
93+
const otherVar = 'the-OtHer-cLass'
94+
const html = '<div class="inner-class">Content</div>'
95+
const dynamicVar = ['fa', 'icon'].join('-')
96+
const obj = {inVisible: true, BLOCK__element: 1}
97+
obj.additional_class = false
98+
`
99+
100+
const source = JsSource.from(script, {strict: true})
101+
102+
it('should find tokens as strings', () => {
103+
expect(source).to.contain('the-class')
104+
expect(source).to.contain('the-other-class')
105+
expect(source).to.contain('fa')
106+
expect(source).to.contain('tHe-cLaSs')
107+
})
108+
109+
it('should not find tokens within strings', () => {
110+
expect(source).to.not.contain('div')
111+
expect(source).to.not.contain('inner-class')
112+
})
113+
114+
it('should not find tokens as combinations of strings', () => {
115+
expect(source).to.not.contain('fa-icon')
116+
})
117+
118+
it('should find tokens as object keys', () => {
119+
expect(source).to.contain('invisible')
120+
expect(source).to.contain('block__element')
121+
})
122+
123+
it('should find tokens as object key assignment', () => {
124+
expect(source).to.contain('additional_class')
125+
})
126+
})
127+
90128
context('when script is malformed', () => {
91129
it('should not throw', () => {
92130
expect(() => JsSource.from('const foo != "whaaaa')).to.not.throw()

0 commit comments

Comments
 (0)