@maizzle/framework 6.0.0-1 → 6.0.0-3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/src/posthtml/index.js +4 -0
- package/src/posthtml/plugins/postcss/cleanupTailwindArtifacts.js +78 -0
- package/src/posthtml/plugins/postcss/removeDuplicateSelectors.js +35 -0
- package/src/transformers/inline.js +17 -12
- package/src/transformers/purge.js +15 -11
- package/src/transformers/safeClassNames.js +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [6.0.0-2] - 2025-07-11
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- fixed an issue with duplicate CSS selectors for utilities that cannot be disabled in Tailwind CSS v4, like `text-decoration`
|
|
12
|
+
- fixed an issue where some Tailwind directives like `@layer` or `@property` were still present in the final build, even though they were not used
|
|
13
|
+
|
|
7
14
|
## [6.0.0-1] - 2025-07-11
|
|
8
15
|
|
|
9
16
|
### Added
|
package/package.json
CHANGED
package/src/posthtml/index.js
CHANGED
|
@@ -18,6 +18,8 @@ import tailwindcss from '@tailwindcss/postcss'
|
|
|
18
18
|
import postcssCalc from 'postcss-calc'
|
|
19
19
|
import cssVariables from 'postcss-css-variables'
|
|
20
20
|
import postcssSafeParser from 'postcss-safe-parser'
|
|
21
|
+
import removeDuplicateSelectors from './plugins/postcss/removeDuplicateSelectors.js'
|
|
22
|
+
import cleanupTailwindArtifacts from './plugins/postcss/cleanupTailwindArtifacts.js'
|
|
21
23
|
|
|
22
24
|
import defaultComponentsConfig from './defaultComponentsConfig.js'
|
|
23
25
|
|
|
@@ -36,6 +38,8 @@ export async function process(html = '', config = {}) {
|
|
|
36
38
|
tailwindcss(get(config, 'css.tailwind', {})),
|
|
37
39
|
resolveCSSProps !== false && cssVariables(resolveCSSProps),
|
|
38
40
|
resolveCalc !== false && postcssCalc(resolveCalc),
|
|
41
|
+
removeDuplicateSelectors(),
|
|
42
|
+
cleanupTailwindArtifacts(get(config, 'css.cleanup', {})),
|
|
39
43
|
...get(config, 'postcss.plugins', []),
|
|
40
44
|
],
|
|
41
45
|
merge(
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostCSS plugin to clean up Tailwind CSS artifacts and leftovers.
|
|
3
|
+
*
|
|
4
|
+
* This plugin safely removes unused Tailwind-specific CSS that
|
|
5
|
+
* may be left behind after processing, while preserving
|
|
6
|
+
* any CSS that might be intentionally used.
|
|
7
|
+
*/
|
|
8
|
+
export default function cleanupTailwindArtifacts(options = {}) {
|
|
9
|
+
const opts = {
|
|
10
|
+
removeEmptyLayers: true,
|
|
11
|
+
removeUnusedTwProperties: true,
|
|
12
|
+
removeEmptyRules: false,
|
|
13
|
+
preserveCustomProperties: [], // Array of custom property names to preserve
|
|
14
|
+
...options
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
postcssPlugin: 'cleanup-tailwind-artifacts',
|
|
19
|
+
OnceExit(root) {
|
|
20
|
+
const usedCustomProperties = new Set()
|
|
21
|
+
const rulesToRemove = []
|
|
22
|
+
|
|
23
|
+
// First pass: collect all custom properties usage
|
|
24
|
+
root.walkDecls(decl => {
|
|
25
|
+
// Check if any declaration uses custom properties
|
|
26
|
+
if (decl.value.includes('var(--')) {
|
|
27
|
+
const matches = decl.value.match(/var\(--[\w-]+\)/g)
|
|
28
|
+
if (matches) {
|
|
29
|
+
matches.forEach(match => {
|
|
30
|
+
const propName = match.replace(/var\(|-|\)/g, '')
|
|
31
|
+
usedCustomProperties.add(propName)
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
// Second pass: find unused @property declarations and empty @layer rules
|
|
38
|
+
root.walkAtRules(rule => {
|
|
39
|
+
// Handle @property declarations
|
|
40
|
+
if (rule.name === 'property' && opts.removeUnusedTwProperties) {
|
|
41
|
+
const propertyName = rule.params.replace(/^--/, '')
|
|
42
|
+
|
|
43
|
+
// Only remove Tailwind-specific custom properties that aren't used
|
|
44
|
+
if (propertyName.startsWith('tw-') && !usedCustomProperties.has(propertyName)) {
|
|
45
|
+
// Check if it's in the preserve list
|
|
46
|
+
if (!opts.preserveCustomProperties.includes(propertyName)) {
|
|
47
|
+
rulesToRemove.push(rule)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Handle @layer rules
|
|
53
|
+
if (rule.name === 'layer' && opts.removeEmptyLayers) {
|
|
54
|
+
// Only remove @layer rules that have no nodes
|
|
55
|
+
if (!rule.nodes || rule.nodes.length === 0) {
|
|
56
|
+
rulesToRemove.push(rule)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// Third pass: remove empty rules (optional, off by default)
|
|
62
|
+
if (opts.removeEmptyRules) {
|
|
63
|
+
root.walkRules(rule => {
|
|
64
|
+
if (!rule.nodes || rule.nodes.length === 0) {
|
|
65
|
+
rulesToRemove.push(rule)
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Remove all identified artifacts
|
|
71
|
+
rulesToRemove.forEach(rule => {
|
|
72
|
+
rule.remove()
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
cleanupTailwindArtifacts.postcss = true
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostCSS plugin to remove duplicate selectors, keeping only the last occurrence.
|
|
3
|
+
* This is useful when CSS contains multiple rules with the same selector,
|
|
4
|
+
* and we want to keep only the most recent one.
|
|
5
|
+
*/
|
|
6
|
+
export default function removeDuplicateSelectors() {
|
|
7
|
+
return {
|
|
8
|
+
postcssPlugin: 'remove-duplicate-selectors',
|
|
9
|
+
OnceExit(root) {
|
|
10
|
+
const selectorMap = new Map()
|
|
11
|
+
const rulesToRemove = []
|
|
12
|
+
|
|
13
|
+
// First pass: collect all rules and their selectors
|
|
14
|
+
root.walkRules(rule => {
|
|
15
|
+
const selector = rule.selector
|
|
16
|
+
|
|
17
|
+
// If we've seen this selector before, mark the previous one for removal
|
|
18
|
+
if (selectorMap.has(selector)) {
|
|
19
|
+
const previousRule = selectorMap.get(selector)
|
|
20
|
+
rulesToRemove.push(previousRule)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Update the map with the current rule (latest occurrence)
|
|
24
|
+
selectorMap.set(selector, rule)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// Second pass: remove all the duplicate rules
|
|
28
|
+
rulesToRemove.forEach(rule => {
|
|
29
|
+
rule.remove()
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
removeDuplicateSelectors.postcss = true
|
|
@@ -32,21 +32,26 @@ export async function inline(html = '', options = {}) {
|
|
|
32
32
|
options.safelist = new Set([
|
|
33
33
|
...get(options, 'safelist', []),
|
|
34
34
|
...[
|
|
35
|
-
'
|
|
36
|
-
'
|
|
37
|
-
'
|
|
38
|
-
'
|
|
39
|
-
'
|
|
40
|
-
'
|
|
35
|
+
'body', // Gmail
|
|
36
|
+
'gmail', // Gmail
|
|
37
|
+
'apple', // Apple Mail
|
|
38
|
+
'ios', // Mail on iOS
|
|
39
|
+
'ox-', // Open-Xchange
|
|
40
|
+
'yahoo', // Yahoo! Mail
|
|
41
|
+
'outlook', // Outlook Mac and Android
|
|
41
42
|
'[data-ogs', // Outlook.com
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
'
|
|
43
|
+
'bloop_container', // Airmail
|
|
44
|
+
'Singleton', // Apple Mail 10
|
|
45
|
+
'unused', // Notes 8
|
|
46
|
+
'moz-text-html', // Thunderbird
|
|
47
|
+
'mail-detail-content', // Comcast, Libero webmail
|
|
48
|
+
'mail-content', // Notion
|
|
47
49
|
'edo', // Edison (all)
|
|
48
50
|
'#msgBody', // Freenet uses #msgBody
|
|
49
|
-
'
|
|
51
|
+
'lang', // Fenced code blocks
|
|
52
|
+
'ShadowHTML', // Superhuman
|
|
53
|
+
'spark', // Spark
|
|
54
|
+
'at-', // Safe class names for container queries
|
|
50
55
|
],
|
|
51
56
|
])
|
|
52
57
|
|
|
@@ -9,20 +9,24 @@ import { getPosthtmlOptions } from '../posthtml/defaultConfig.js'
|
|
|
9
9
|
const posthtmlPlugin = options => tree => {
|
|
10
10
|
const defaultSafelist = [
|
|
11
11
|
'*body*', // Gmail
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
12
|
+
'*gmail*', // Gmail
|
|
13
|
+
'*apple*', // Apple Mail
|
|
14
|
+
'*ios*', // Mail on iOS
|
|
15
|
+
'*ox-*', // Open-Xchange
|
|
16
|
+
'*outlook*', // Outlook.com
|
|
17
17
|
'[data-ogs*', // Outlook.com
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
18
|
+
'*bloop_container*', // Airmail
|
|
19
|
+
'*Singleton*', // Apple Mail 10
|
|
20
|
+
'*unused', // Notes 8
|
|
21
|
+
'*moz-text-html*', // Thunderbird
|
|
22
|
+
'*mail-detail-content*', // Comcast, Libero webmail
|
|
23
|
+
'*mail-content-*', // Notion
|
|
23
24
|
'*edo*', // Edison (all)
|
|
24
25
|
'#*', // Freenet uses #msgBody
|
|
25
|
-
'
|
|
26
|
+
'*lang*', // Fenced code blocks
|
|
27
|
+
'*ShadowHTML*', // Superhuman
|
|
28
|
+
'*spark*', // Spark
|
|
29
|
+
'*at-*', // Safe class names for container queries
|
|
26
30
|
]
|
|
27
31
|
|
|
28
32
|
const defaultOptions = {
|