@maizzle/framework 4.0.0-alpha.5 → 4.0.0-alpha.8

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.
Files changed (46) hide show
  1. package/.github/workflows/nodejs.yml +1 -1
  2. package/package.json +6 -5
  3. package/src/commands/serve.js +21 -14
  4. package/src/generators/output/to-string.js +1 -1
  5. package/src/generators/postcss.js +29 -0
  6. package/src/generators/posthtml.js +66 -61
  7. package/src/generators/tailwindcss.js +2 -2
  8. package/src/index.js +17 -13
  9. package/src/transformers/index.js +60 -57
  10. package/src/transformers/inlineCss.js +1 -1
  11. package/src/transformers/minify.js +1 -1
  12. package/src/transformers/prettify.js +1 -1
  13. package/src/transformers/removeInlineBackgroundColor.js +1 -1
  14. package/src/transformers/removeInlinedSelectors.js +71 -0
  15. package/src/transformers/removeUnusedCss.js +17 -2
  16. package/src/transformers/sixHex.js +24 -1
  17. package/src/transformers/transform.js +35 -6
  18. package/test/expected/posthtml/component.html +13 -0
  19. package/test/expected/{inheritance.html → posthtml/extend-template.html} +2 -2
  20. package/test/expected/posthtml/fetch.html +5 -0
  21. package/test/expected/posthtml/layout.html +3 -0
  22. package/test/expected/transformers/atimport-in-style.html +14 -0
  23. package/test/expected/transformers/base-image-url.html +83 -83
  24. package/test/expected/transformers/preserve-transform-css.html +27 -0
  25. package/test/fixtures/posthtml/component.html +19 -0
  26. package/test/fixtures/{inheritance.html → posthtml/extend-template.html} +7 -7
  27. package/test/fixtures/posthtml/fetch.html +9 -0
  28. package/test/fixtures/posthtml/layout.html +11 -0
  29. package/test/fixtures/transformers/atimport-in-style.html +11 -0
  30. package/test/fixtures/transformers/base-image-url.html +85 -85
  31. package/test/fixtures/transformers/preserve-transform-css.html +25 -0
  32. package/test/stubs/components/component.html +5 -0
  33. package/test/stubs/data.json +14 -0
  34. package/test/stubs/layouts/basic.html +1 -0
  35. package/test/stubs/{layout.html → layouts/full.html} +0 -0
  36. package/test/stubs/{layout-basic.html → layouts/template.html} +5 -5
  37. package/test/stubs/post.css +6 -0
  38. package/test/stubs/tailwind/{preserve.html → content-source.html} +0 -0
  39. package/test/stubs/tailwind/tailwind.css +3 -0
  40. package/test/stubs/template.html +10 -10
  41. package/test/test-postcss.js +8 -0
  42. package/test/test-posthtml.js +66 -0
  43. package/test/{test-tailwind.js → test-tailwindcss.js} +3 -3
  44. package/test/test-tostring.js +142 -132
  45. package/test/test-transformers.js +479 -343
  46. package/test/expected/transformers/transform-postcss.html +0 -19
@@ -14,7 +14,7 @@ jobs:
14
14
 
15
15
  strategy:
16
16
  matrix:
17
- node-version: [12, 14, 16]
17
+ node-version: [14, 16, 17]
18
18
 
19
19
  steps:
20
20
  - uses: actions/checkout@v2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maizzle/framework",
3
- "version": "4.0.0-alpha.5",
3
+ "version": "4.0.0-alpha.8",
4
4
  "description": "Maizzle is a framework that helps you quickly build HTML emails with Tailwind CSS.",
5
5
  "license": "MIT",
6
6
  "main": "src/index.js",
@@ -36,7 +36,7 @@
36
36
  "autoprefixer": "^10.4.0",
37
37
  "browser-sync": "^2.26.13",
38
38
  "color-shorthand-hex-to-six-digit": "^3.0.2",
39
- "email-comb": "^5.0.0",
39
+ "email-comb": "^5.2.0",
40
40
  "front-matter": "^4.0.0",
41
41
  "fs-extra": "^10.0.0",
42
42
  "glob-promise": "^4.1.0",
@@ -55,9 +55,10 @@
55
55
  "posthtml-expressions": "^1.8.1",
56
56
  "posthtml-extend": "^0.6.0",
57
57
  "posthtml-extra-attributes": "^1.0.0",
58
- "posthtml-fetch": "^2.0.0",
58
+ "posthtml-fetch": "^2.2.0",
59
59
  "posthtml-markdownit": "^1.3.0",
60
- "posthtml-modules": "^0.8.0",
60
+ "posthtml-match-helper": "^1.0.3",
61
+ "posthtml-modules": "^0.9.0",
61
62
  "posthtml-mso": "^1.0.4",
62
63
  "posthtml-postcss-merge-longhand": "^1.0.2",
63
64
  "posthtml-remove-attributes": "^1.0.0",
@@ -76,7 +77,7 @@
76
77
  "xo": "0.39.1"
77
78
  },
78
79
  "engines": {
79
- "node": ">=12.13.0"
80
+ "node": ">=14.19.1"
80
81
  },
81
82
  "ava": {
82
83
  "files": [
@@ -72,19 +72,23 @@ const serve = async (env = 'local', config = {}) => {
72
72
  renderOptions
73
73
  )
74
74
  .then(async ({html, config}) => {
75
+ let source = ''
75
76
  let dest = ''
76
77
  let ext = ''
77
78
 
78
79
  if (Array.isArray(config.build.templates)) {
79
80
  const match = config.build.templates.find(template => template.source === path.parse(file).dir)
81
+ source = get(match, 'source')
80
82
  dest = get(match, 'destination.path', 'build_local')
81
83
  ext = get(match, 'destination.ext', 'html')
82
84
  } else if (isObject(config.build.templates)) {
85
+ source = get(config, 'build.templates.source')
83
86
  dest = get(config, 'build.templates.destination.path', 'build_local')
84
87
  ext = get(config, 'build.templates.destination.ext', 'html')
85
88
  }
86
89
 
87
- const finalDestination = path.join(dest, `${path.parse(file).name}.${ext}`)
90
+ const fileDir = path.parse(file).dir.replace(source, '')
91
+ const finalDestination = path.join(dest, fileDir, `${path.parse(file).name}.${ext}`)
88
92
 
89
93
  await fs.outputFile(config.permalink || finalDestination, html)
90
94
  })
@@ -117,19 +121,22 @@ const serve = async (env = 'local', config = {}) => {
117
121
 
118
122
  // Initialize Browsersync
119
123
  browsersync()
120
- .init({
121
- notify: false,
122
- open: false,
123
- port: 3000,
124
- server: {
125
- baseDir,
126
- directory: true
127
- },
128
- tunnel: false,
129
- ui: {port: 3001},
130
- logFileChanges: false,
131
- ...get(config, 'build.browsersync', {})
132
- }, () => {})
124
+ .init(
125
+ merge(
126
+ {
127
+ notify: false,
128
+ open: false,
129
+ port: 3000,
130
+ server: {
131
+ baseDir,
132
+ directory: true
133
+ },
134
+ tunnel: false,
135
+ ui: {port: 3001},
136
+ logFileChanges: false
137
+ },
138
+ get(config, 'build.browsersync', {})
139
+ ), () => {})
133
140
  } catch (error) {
134
141
  spinner.fail(error)
135
142
  throw error
@@ -22,7 +22,7 @@ module.exports = async (html, options) => {
22
22
  let config = merge(fileConfig, get(options, 'maizzle', {}))
23
23
 
24
24
  const tailwindConfig = get(options, 'tailwind.config', {})
25
- const cssString = get(options, 'tailwind.css', '@tailwind components; @tailwind utilities;')
25
+ const cssString = get(options, 'tailwind.css', '')
26
26
 
27
27
  let {frontmatter} = fm(html)
28
28
 
@@ -0,0 +1,29 @@
1
+ const path = require('path')
2
+ const {get} = require('lodash')
3
+ const postcss = require('postcss')
4
+ const postcssImport = require('postcss-import')
5
+ const postcssNested = require('tailwindcss/nesting')
6
+ const mergeLonghand = require('postcss-merge-longhand')
7
+
8
+ module.exports = {
9
+ process: async (css = '', maizzleConfig = {}, spinner = null) => {
10
+ const userFilePath = get(maizzleConfig, 'build.tailwind.css', path.join(process.cwd(), 'src/css/tailwind.css'))
11
+
12
+ return postcss([
13
+ postcssImport({path: path.dirname(userFilePath)}),
14
+ postcssNested(),
15
+ maizzleConfig.env === 'local' ? () => {} : mergeLonghand(),
16
+ ...get(maizzleConfig, 'build.postcss.plugins', [])
17
+ ])
18
+ .process(css, {from: undefined})
19
+ .then(result => result.css)
20
+ .catch(error => {
21
+ console.error(error)
22
+ if (spinner) {
23
+ spinner.stop()
24
+ }
25
+
26
+ throw new Error(`PostCSS processing failed`)
27
+ })
28
+ }
29
+ }
@@ -1,61 +1,66 @@
1
- const fm = require('front-matter')
2
- const posthtml = require('posthtml')
3
- const {get, merge} = require('lodash')
4
- const fetch = require('posthtml-fetch')
5
- const layouts = require('posthtml-extend')
6
- const modules = require('posthtml-modules')
7
- const expressions = require('posthtml-expressions')
8
-
9
- module.exports = async (html, config) => {
10
- const layoutsOptions = get(config, 'build.layouts', {})
11
-
12
- const fetchOptions = get(config, 'build.posthtml.fetch', {})
13
- const fetchPlugin = fetch({...fetchOptions})
14
-
15
- const modulesOptions = get(config, 'build.components', {})
16
- // Fake `from` option so we can reference modules relatively
17
- const modulesRoot = modulesOptions.root || './'
18
- const modulesFrom = modulesOptions.from || `${modulesRoot}/fake`
19
-
20
- const posthtmlOptions = get(config, 'build.posthtml.options', {})
21
- const posthtmlPlugins = get(config, 'build.posthtml.plugins', [])
22
-
23
- const expressionsOptions = merge({strictMode: false}, get(config, 'build.posthtml.expressions', {}))
24
-
25
- const locals = merge(
26
- get(expressionsOptions, 'locals', {}),
27
- get(config, 'locals', {}),
28
- {page: config}
29
- )
30
-
31
- return posthtml([
32
- fetchPlugin,
33
- layouts(
34
- merge(
35
- {
36
- strict: false,
37
- plugins: [
38
- expressions({...expressionsOptions, locals})
39
- ]
40
- },
41
- layoutsOptions
42
- )
43
- ),
44
- modules({
45
- parser: posthtmlOptions,
46
- attributeAsLocals: true,
47
- from: modulesFrom,
48
- root: modulesRoot,
49
- tag: 'component',
50
- attribute: 'src',
51
- plugins: [
52
- fetchPlugin
53
- ],
54
- locals,
55
- ...modulesOptions
56
- }),
57
- ...posthtmlPlugins
58
- ])
59
- .process(html, {...posthtmlOptions})
60
- .then(result => fm(result.html).body)
61
- }
1
+ const fm = require('front-matter')
2
+ const posthtml = require('posthtml')
3
+ const {get, merge} = require('lodash')
4
+ const fetch = require('posthtml-fetch')
5
+ const layouts = require('posthtml-extend')
6
+ const modules = require('posthtml-modules')
7
+ const expressions = require('posthtml-expressions')
8
+
9
+ module.exports = async (html, config) => {
10
+ const layoutsOptions = get(config, 'build.layouts', {})
11
+
12
+ const modulesOptions = get(config, 'build.components', {})
13
+ // Fake `from` option so we can reference modules relatively
14
+ const modulesRoot = modulesOptions.root || './'
15
+ const modulesFrom = modulesOptions.from || `${modulesRoot}/fake`
16
+
17
+ const posthtmlOptions = get(config, 'build.posthtml.options', {})
18
+ const posthtmlPlugins = get(config, 'build.posthtml.plugins', [])
19
+
20
+ const expressionsOptions = merge({strictMode: false}, get(config, 'build.posthtml.expressions', {}))
21
+
22
+ const locals = merge(
23
+ get(expressionsOptions, 'locals', {}),
24
+ get(config, 'locals', {}),
25
+ {page: config}
26
+ )
27
+
28
+ const fetchPlugin = fetch(
29
+ merge(
30
+ {
31
+ expressions: merge({...expressionsOptions, locals})
32
+ },
33
+ get(config, 'build.posthtml.fetch', {})
34
+ )
35
+ )
36
+
37
+ return posthtml([
38
+ fetchPlugin,
39
+ expressions({...expressionsOptions, locals}),
40
+ layouts(
41
+ merge(
42
+ {
43
+ strict: false,
44
+ expressions: merge({...expressionsOptions, locals})
45
+ },
46
+ layoutsOptions
47
+ )
48
+ ),
49
+ modules({
50
+ parser: posthtmlOptions,
51
+ attributeAsLocals: true,
52
+ from: modulesFrom,
53
+ root: modulesRoot,
54
+ tag: 'component',
55
+ attribute: 'src',
56
+ plugins: [
57
+ fetchPlugin
58
+ ],
59
+ locals,
60
+ ...modulesOptions
61
+ }),
62
+ ...posthtmlPlugins
63
+ ])
64
+ .process(html, {...posthtmlOptions})
65
+ .then(result => fm(result.html).body)
66
+ }
@@ -90,9 +90,9 @@ module.exports = {
90
90
  const userFileExists = await fs.pathExists(userFilePath)
91
91
 
92
92
  if (userFileExists) {
93
- css += await fs.readFile(path.resolve(userFilePath), 'utf8')
93
+ css = await fs.readFile(path.resolve(userFilePath), 'utf8') + css
94
94
  } else {
95
- css = '@tailwind components; @tailwind utilities;' + css
95
+ css = `@import "tailwindcss/components"; @import "tailwindcss/utilities"; ${css}`
96
96
  }
97
97
 
98
98
  return postcss([
package/src/index.js CHANGED
@@ -1,13 +1,17 @@
1
- const serve = require('./commands/serve')
2
- const toFile = require('./commands/build')
3
- const transformers = require('./transformers')
4
- const toString = require('./functions/render')
5
- const toPlaintext = require('./functions/plaintext')
6
-
7
- module.exports = {
8
- serve,
9
- build: toFile,
10
- ...transformers,
11
- render: toString,
12
- plaintext: toPlaintext
13
- }
1
+ const serve = require('./commands/serve')
2
+ const toFile = require('./commands/build')
3
+ const transformers = require('./transformers')
4
+ const toString = require('./functions/render')
5
+ const PostCSS = require('./generators/postcss')
6
+ const toPlaintext = require('./functions/plaintext')
7
+ const TailwindCSS = require('./generators/tailwindcss')
8
+
9
+ module.exports = {
10
+ serve,
11
+ build: toFile,
12
+ ...transformers,
13
+ render: toString,
14
+ postcss: PostCSS,
15
+ plaintext: toPlaintext,
16
+ tailwindcss: TailwindCSS
17
+ }
@@ -1,57 +1,60 @@
1
- const inline = require('./inlineCss')
2
- const minify = require('./minify')
3
- const markdown = require('./markdown')
4
- const prettify = require('./prettify')
5
- const ensureSixHEX = require('./sixHex')
6
- const applyBaseImageUrl = require('./baseUrl')
7
- const addURLParams = require('./urlParameters')
8
- const transformContents = require('./transform')
9
- const preventWidows = require('./preventWidows')
10
- const replaceStrings = require('./replaceStrings')
11
- const safeClassNames = require('./safeClassNames')
12
- const removeUnusedCSS = require('./removeUnusedCss')
13
- const removeAttributes = require('./removeAttributes')
14
- const attributeToStyle = require('./attributeToStyle')
15
- const removeInlineSizes = require('./removeInlineSizes')
16
- const applyExtraAttributes = require('./extraAttributes')
17
- const removeInlineBgColor = require('./removeInlineBackgroundColor')
18
-
19
- exports.process = async (html, config) => {
20
- html = await safeClassNames(html, config)
21
- html = await transformContents(html, config)
22
- html = await markdown(html, config)
23
- html = await preventWidows(html, config)
24
- html = await attributeToStyle(html, config)
25
- html = await inline(html, config)
26
- html = await removeUnusedCSS(html, config)
27
- html = await removeInlineSizes(html, config)
28
- html = await removeInlineBgColor(html, config)
29
- html = await removeAttributes(html, config)
30
- html = await applyExtraAttributes(html, config)
31
- html = await applyBaseImageUrl(html, config)
32
- html = await addURLParams(html, config)
33
- html = await ensureSixHEX(html, config)
34
- html = await prettify(html, config)
35
- html = await minify(html, config)
36
- html = await replaceStrings(html, config)
37
-
38
- return html
39
- }
40
-
41
- exports.inlineCSS = (html, config) => inline(html, config, true)
42
- exports.minify = (html, config) => minify(html, config, true)
43
- exports.markdown = (html, config) => markdown(html, config, true)
44
- exports.prettify = (html, config) => prettify(html, config, true)
45
- exports.ensureSixHEX = (html, config) => ensureSixHEX(html, config)
46
- exports.addURLParams = (html, config) => addURLParams(html, config, true)
47
- exports.transformContents = (html, config) => transformContents(html, config, true)
48
- exports.preventWidows = (html, config) => preventWidows(html, config, true)
49
- exports.replaceStrings = (html, config) => replaceStrings(html, config, true)
50
- exports.safeClassNames = (html, config) => safeClassNames(html, config, true)
51
- exports.applyBaseImageUrl = (html, config) => applyBaseImageUrl(html, config, true)
52
- exports.removeUnusedCSS = (html, config) => removeUnusedCSS(html, config, true)
53
- exports.removeAttributes = (html, config) => removeAttributes(html, config, true)
54
- exports.removeInlineSizes = (html, config) => removeInlineSizes(html, config, true)
55
- exports.applyExtraAttributes = (html, config) => applyExtraAttributes(html, config, true)
56
- exports.removeInlineBgColor = (html, config) => removeInlineBgColor(html, config, true)
57
- exports.attributeToStyle = (html, config) => attributeToStyle(html, config, true)
1
+ const inline = require('./inlineCss')
2
+ const minify = require('./minify')
3
+ const markdown = require('./markdown')
4
+ const prettify = require('./prettify')
5
+ const ensureSixHEX = require('./sixHex')
6
+ const applyBaseImageUrl = require('./baseUrl')
7
+ const addURLParams = require('./urlParameters')
8
+ const transformContents = require('./transform')
9
+ const preventWidows = require('./preventWidows')
10
+ const replaceStrings = require('./replaceStrings')
11
+ const safeClassNames = require('./safeClassNames')
12
+ const removeUnusedCSS = require('./removeUnusedCss')
13
+ const removeAttributes = require('./removeAttributes')
14
+ const attributeToStyle = require('./attributeToStyle')
15
+ const removeInlineSizes = require('./removeInlineSizes')
16
+ const applyExtraAttributes = require('./extraAttributes')
17
+ const removeInlinedClasses = require('./removeInlinedSelectors')
18
+ const removeInlineBgColor = require('./removeInlineBackgroundColor')
19
+
20
+ exports.process = async (html, config) => {
21
+ html = await safeClassNames(html, config)
22
+ html = await transformContents(html, config)
23
+ html = await markdown(html, config)
24
+ html = await preventWidows(html, config)
25
+ html = await attributeToStyle(html, config)
26
+ html = await inline(html, config)
27
+ html = await removeInlinedClasses(html, config)
28
+ html = await removeUnusedCSS(html, config)
29
+ html = await removeInlineSizes(html, config)
30
+ html = await removeInlineBgColor(html, config)
31
+ html = await removeAttributes(html, config)
32
+ html = await applyExtraAttributes(html, config)
33
+ html = await applyBaseImageUrl(html, config)
34
+ html = await addURLParams(html, config)
35
+ html = await ensureSixHEX(html, config)
36
+ html = await prettify(html, config)
37
+ html = await minify(html, config)
38
+ html = await replaceStrings(html, config)
39
+
40
+ return html
41
+ }
42
+
43
+ exports.minify = (html, config) => minify(html, config, true)
44
+ exports.inlineCSS = (html, config) => inline(html, config, true)
45
+ exports.markdown = (html, config) => markdown(html, config, true)
46
+ exports.prettify = (html, config) => prettify(html, config, true)
47
+ exports.ensureSixHEX = (html, config) => ensureSixHEX(html, config)
48
+ exports.addURLParams = (html, config) => addURLParams(html, config, true)
49
+ exports.preventWidows = (html, config) => preventWidows(html, config, true)
50
+ exports.replaceStrings = (html, config) => replaceStrings(html, config, true)
51
+ exports.safeClassNames = (html, config) => safeClassNames(html, config, true)
52
+ exports.removeUnusedCSS = (html, config) => removeUnusedCSS(html, config, true)
53
+ exports.removeAttributes = (html, config) => removeAttributes(html, config, true)
54
+ exports.attributeToStyle = (html, config) => attributeToStyle(html, config, true)
55
+ exports.removeInlineSizes = (html, config) => removeInlineSizes(html, config, true)
56
+ exports.applyBaseImageUrl = (html, config) => applyBaseImageUrl(html, config, true)
57
+ exports.transformContents = (html, config) => transformContents(html, config, true)
58
+ exports.removeInlineBgColor = (html, config) => removeInlineBgColor(html, config, true)
59
+ exports.applyExtraAttributes = (html, config) => applyExtraAttributes(html, config, true)
60
+ exports.removeInlinedClasses = (html, config) => removeInlinedClasses(html, config)
@@ -9,7 +9,7 @@ module.exports = async (html, config = {}, direct = false) => {
9
9
  }
10
10
 
11
11
  const options = direct ? config : get(config, 'inlineCSS', {})
12
- const removeStyleTags = get(options, 'removeStyleTags', true)
12
+ const removeStyleTags = get(options, 'removeStyleTags', false)
13
13
  const css = get(config, 'customCSS', false)
14
14
 
15
15
  if (get(config, 'inlineCSS') === true || !isEmpty(options)) {
@@ -1,5 +1,5 @@
1
- const {get, isEmpty} = require('lodash')
2
1
  const {crush} = require('html-crush')
2
+ const {get, isEmpty} = require('lodash')
3
3
 
4
4
  module.exports = async (html, config = {}, direct = false) => {
5
5
  if (get(config, 'minify') === false) {
@@ -1,5 +1,5 @@
1
- const {get, isEmpty} = require('lodash')
2
1
  const pretty = require('pretty')
2
+ const {get, isEmpty} = require('lodash')
3
3
 
4
4
  module.exports = async (html, config = {}, direct = false) => {
5
5
  if (get(config, 'prettify') === false) {
@@ -1,5 +1,5 @@
1
- const {get, isEmpty} = require('lodash')
2
1
  const posthtml = require('posthtml')
2
+ const {get, isEmpty} = require('lodash')
3
3
  const parseAttrs = require('posthtml-attrs-parser')
4
4
 
5
5
  module.exports = async (html, config = {}, direct = false) => {
@@ -0,0 +1,71 @@
1
+ const {get, has, remove} = require('lodash')
2
+ const postcss = require('postcss')
3
+ const posthtml = require('posthtml')
4
+ const parseAttrs = require('posthtml-attrs-parser')
5
+ const matchHelper = require('posthtml-match-helper')
6
+
7
+ module.exports = async (html, config = {}) => {
8
+ if (get(config, 'removeInlinedClasses') === false) {
9
+ return html
10
+ }
11
+
12
+ const posthtmlOptions = get(config, 'build.posthtml.options', {})
13
+ return posthtml([plugin()]).process(html, posthtmlOptions).then(result => result.html)
14
+ }
15
+
16
+ const plugin = () => tree => {
17
+ const process = node => {
18
+ // For each style tag...
19
+ if (node.tag === 'style') {
20
+ const {root} = postcss().process(node.content)
21
+
22
+ root.walkRules(rule => {
23
+ // Skip media queries and such...
24
+ if (rule.parent.type === 'atrule') {
25
+ return
26
+ }
27
+
28
+ const {selector} = rule
29
+ const prop = get(rule.nodes[0], 'prop')
30
+
31
+ // If we find the selector in the HTML...
32
+ tree.match(matchHelper(selector), n => {
33
+ const parsedAttrs = parseAttrs(n.attrs)
34
+ const classAttr = get(parsedAttrs, 'class', [])
35
+ const styleAttr = get(parsedAttrs, 'style', {})
36
+
37
+ // If the class is in the style attribute (inlined), remove it
38
+ if (has(styleAttr, prop)) {
39
+ // Remove the class attribute
40
+ // console.log(styleAttr, prop, classAttr)
41
+ remove(classAttr, s => selector.includes(s))
42
+
43
+ // Remove the rule in the <style> tag
44
+ rule.remove()
45
+ }
46
+
47
+ /**
48
+ * Remove from <style> selectors that were used to create shorthand declarations
49
+ * like `margin: 0 0 0 16px` (transformed with mergeLonghand when inlining).
50
+ */
51
+ Object.keys(styleAttr).forEach(key => {
52
+ if (prop.includes(key)) {
53
+ rule.remove()
54
+ remove(classAttr, s => selector.includes(s))
55
+ }
56
+ })
57
+
58
+ n.attrs = parsedAttrs.compose()
59
+
60
+ return n
61
+ })
62
+ })
63
+
64
+ node.content = root.toString()
65
+ }
66
+
67
+ return node
68
+ }
69
+
70
+ return tree.walk(process)
71
+ }
@@ -1,5 +1,5 @@
1
- const {get, isEmpty} = require('lodash')
2
1
  const {comb} = require('email-comb')
2
+ const {get, isEmpty} = require('lodash')
3
3
 
4
4
  module.exports = async (html, config = {}, direct = false) => {
5
5
  if (get(config, 'removeUnusedCSS') === false) {
@@ -8,11 +8,26 @@ module.exports = async (html, config = {}, direct = false) => {
8
8
 
9
9
  const options = direct ? config : get(config, 'removeUnusedCSS', {})
10
10
 
11
+ const safelist = [
12
+ '*body*', // Gmail
13
+ '.outlook*', // Outlook.com
14
+ '.bloop_container', // Airmail
15
+ '.Singleton', // Apple Mail 10
16
+ '.unused', // Notes 8
17
+ '.moz-text-html', // Thunderbird
18
+ '.mail-detail-content', // Comcast, Libero webmail
19
+ '*edo*', // Edison (all)
20
+ '#*', // Freenet uses #msgBody
21
+ '.lang*' // Fenced code blocks
22
+ ]
23
+
11
24
  if (typeof options === 'boolean' && options) {
12
- return comb(html).result
25
+ return comb(html, {whitelist: safelist}).result
13
26
  }
14
27
 
15
28
  if (!isEmpty(options)) {
29
+ options.whitelist = [...get(options, 'whitelist', []), ...safelist]
30
+
16
31
  return comb(html, options).result
17
32
  }
18
33
 
@@ -1,4 +1,6 @@
1
1
  const {get} = require('lodash')
2
+ const posthtml = require('posthtml')
3
+ const parseAttrs = require('posthtml-attrs-parser')
2
4
  const {conv} = require('color-shorthand-hex-to-six-digit')
3
5
 
4
6
  module.exports = async (html, config = {}) => {
@@ -6,5 +8,26 @@ module.exports = async (html, config = {}) => {
6
8
  return html
7
9
  }
8
10
 
9
- return conv(html)
11
+ const posthtmlOptions = get(config, 'build.posthtml.options', {})
12
+ return posthtml([sixHex()]).process(html, posthtmlOptions).then(result => result.html)
13
+ }
14
+
15
+ const sixHex = () => tree => {
16
+ const process = node => {
17
+ const attrs = parseAttrs(node.attrs)
18
+
19
+ const targets = ['bgcolor', 'color']
20
+
21
+ targets.forEach(attribute => {
22
+ if (attrs[attribute]) {
23
+ attrs[attribute] = conv(attrs[attribute])
24
+ }
25
+ })
26
+
27
+ node.attrs = attrs.compose()
28
+
29
+ return node
30
+ }
31
+
32
+ return tree.walk(process)
10
33
  }