@maizzle/framework 4.0.0-alpha.1 → 4.0.0-alpha.10

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 (67) hide show
  1. package/.github/workflows/nodejs.yml +1 -1
  2. package/bin/maizzle +3 -0
  3. package/package.json +16 -12
  4. package/src/commands/build.js +32 -0
  5. package/src/commands/serve.js +156 -0
  6. package/src/functions/plaintext.js +5 -0
  7. package/src/functions/render.js +5 -0
  8. package/src/generators/output/to-disk.js +90 -77
  9. package/src/generators/output/to-string.js +2 -6
  10. package/src/generators/plaintext.js +49 -52
  11. package/src/generators/postcss.js +29 -0
  12. package/src/generators/posthtml.js +11 -6
  13. package/src/generators/tailwindcss.js +116 -115
  14. package/src/index.js +14 -148
  15. package/src/transformers/baseUrl.js +33 -9
  16. package/src/transformers/filters/defaultFilters.js +126 -0
  17. package/src/transformers/filters/index.js +55 -0
  18. package/src/transformers/index.js +13 -7
  19. package/src/transformers/inlineCss.js +1 -14
  20. package/src/transformers/minify.js +1 -1
  21. package/src/transformers/prettify.js +16 -9
  22. package/src/transformers/removeInlineBackgroundColor.js +1 -1
  23. package/src/transformers/removeInlinedSelectors.js +70 -0
  24. package/src/transformers/removeUnusedCss.js +40 -20
  25. package/src/transformers/shorthandInlineCSS.js +19 -0
  26. package/src/transformers/sixHex.js +24 -1
  27. package/test/expected/posthtml/component.html +13 -0
  28. package/test/expected/{inheritance.html → posthtml/extend-template.html} +2 -2
  29. package/test/expected/posthtml/fetch.html +5 -0
  30. package/test/expected/posthtml/layout.html +3 -0
  31. package/test/expected/transformers/atimport-in-style.html +15 -0
  32. package/test/expected/transformers/{base-image-url.html → base-url.html} +18 -2
  33. package/test/expected/transformers/filters.html +81 -0
  34. package/test/expected/transformers/preserve-transform-css.html +36 -0
  35. package/test/expected/useConfig.html +9 -9
  36. package/test/fixtures/basic.html +9 -9
  37. package/test/fixtures/posthtml/component.html +19 -0
  38. package/test/fixtures/{inheritance.html → posthtml/extend-template.html} +7 -7
  39. package/test/fixtures/posthtml/fetch.html +9 -0
  40. package/test/fixtures/posthtml/layout.html +11 -0
  41. package/test/fixtures/transformers/atimport-in-style.html +11 -0
  42. package/test/fixtures/transformers/{base-image-url.html → base-url.html} +18 -2
  43. package/test/fixtures/transformers/filters.html +87 -0
  44. package/test/fixtures/transformers/preserve-transform-css.html +25 -0
  45. package/test/fixtures/useConfig.html +9 -9
  46. package/test/stubs/components/component.html +5 -0
  47. package/test/stubs/data.json +14 -0
  48. package/test/stubs/layouts/basic.html +1 -0
  49. package/test/stubs/{layout.html → layouts/full.html} +0 -0
  50. package/test/stubs/{layout-basic.html → layouts/template.html} +5 -5
  51. package/test/stubs/post.css +6 -0
  52. package/test/stubs/tailwind/{preserve.html → content-source.html} +0 -0
  53. package/test/stubs/tailwind/tailwind.css +3 -0
  54. package/test/stubs/template.html +10 -10
  55. package/test/stubs/templates/1.html +1 -1
  56. package/test/stubs/templates/2.test +1 -0
  57. package/test/test-config.js +19 -19
  58. package/test/test-postcss.js +8 -0
  59. package/test/test-posthtml.js +72 -0
  60. package/test/{test-tailwind.js → test-tailwindcss.js} +117 -100
  61. package/test/test-todisk.js +79 -28
  62. package/test/test-tostring.js +148 -132
  63. package/test/test-transformers.js +216 -49
  64. package/src/transformers/transform.js +0 -22
  65. package/test/expected/transformers/transform-postcss.html +0 -19
  66. package/test/stubs/templates/2.html +0 -1
  67. package/test/stubs/templates/3.mzl +0 -1
@@ -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/bin/maizzle ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ require('@maizzle/cli')
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@maizzle/framework",
3
- "version": "4.0.0-alpha.1",
3
+ "version": "4.0.0-alpha.10",
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",
7
+ "bin": {
8
+ "maizzle": "bin/maizzle"
9
+ },
7
10
  "repository": {
8
11
  "type": "git",
9
12
  "url": "https://github.com/maizzle/framework.git"
@@ -33,10 +36,11 @@
33
36
  "release": "np"
34
37
  },
35
38
  "dependencies": {
39
+ "@maizzle/cli": "^1.4.0",
36
40
  "autoprefixer": "^10.4.0",
37
41
  "browser-sync": "^2.26.13",
38
42
  "color-shorthand-hex-to-six-digit": "^3.0.2",
39
- "email-comb": "^5.0.0",
43
+ "email-comb": "^5.2.0",
40
44
  "front-matter": "^4.0.0",
41
45
  "fs-extra": "^10.0.0",
42
46
  "glob-promise": "^4.1.0",
@@ -48,27 +52,27 @@
48
52
  "postcss": "^8.4.4",
49
53
  "postcss-import": "^14.0.0",
50
54
  "postcss-merge-longhand": "^5.0.1",
51
- "posthtml": "^0.16.4",
55
+ "posthtml": "^0.16.6",
52
56
  "posthtml-attrs-parser": "^0.1.1",
53
57
  "posthtml-base-url": "^1.0.1",
54
- "posthtml-content": "^0.0.3",
58
+ "posthtml-content": "^0.1.0",
55
59
  "posthtml-expressions": "^1.8.1",
56
60
  "posthtml-extend": "^0.6.0",
57
61
  "posthtml-extra-attributes": "^1.0.0",
58
- "posthtml-fetch": "^2.0.0",
59
- "posthtml-markdownit": "^1.2.2",
60
- "posthtml-modules": "^0.8.0",
61
- "posthtml-mso": "^1.0.2",
62
+ "posthtml-fetch": "^2.2.0",
63
+ "posthtml-markdownit": "^1.3.0",
64
+ "posthtml-match-helper": "^1.0.3",
65
+ "posthtml-modules": "^0.9.0",
66
+ "posthtml-mso": "^1.0.4",
62
67
  "posthtml-postcss-merge-longhand": "^1.0.2",
63
68
  "posthtml-remove-attributes": "^1.0.0",
64
- "posthtml-safe-class-names": "^1.0.4",
69
+ "posthtml-safe-class-names": "^1.0.8",
65
70
  "posthtml-url-parameters": "^1.0.4",
66
71
  "pretty": "^2.0.0",
67
72
  "prevent-widows": "^1.0.2",
68
73
  "query-string": "^7.1.0",
69
74
  "string-strip-html": "^8.2.0",
70
- "tailwindcss": "^3.0.0",
71
- "tailwindcss-box-shadow": "^1.0.0"
75
+ "tailwindcss": "^3.0.0"
72
76
  },
73
77
  "devDependencies": {
74
78
  "ava": "^4.0.1",
@@ -77,7 +81,7 @@
77
81
  "xo": "0.39.1"
78
82
  },
79
83
  "engines": {
80
- "node": ">=12.13.0"
84
+ "node": ">=14.0.0"
81
85
  },
82
86
  "ava": {
83
87
  "files": [
@@ -0,0 +1,32 @@
1
+ const ora = require('ora')
2
+ const {get} = require('lodash')
3
+ const Output = require('../generators/output')
4
+ const {clearConsole} = require('../utils/helpers')
5
+
6
+ const build = async (env = 'local', config = {}) => {
7
+ const start = new Date()
8
+ const spinner = ora('Building emails...').start()
9
+
10
+ try {
11
+ const {files, parsed} = await Output.toDisk(env, spinner, config)
12
+
13
+ const elapsedSeconds = (Date.now() - start) / 1000
14
+
15
+ if (get(config, 'build.command') === 'serve') {
16
+ if (get(config, 'build.console.clear')) {
17
+ clearConsole()
18
+ }
19
+
20
+ spinner.succeed(`Re-built ${parsed.length} templates in ${elapsedSeconds}s`)
21
+ } else {
22
+ spinner.succeed(`Built ${parsed.length} templates in ${elapsedSeconds}s`)
23
+ }
24
+
25
+ return {files}
26
+ } catch (error) {
27
+ spinner.fail(error.message)
28
+ throw error
29
+ }
30
+ }
31
+
32
+ module.exports = build
@@ -0,0 +1,156 @@
1
+ const ora = require('ora')
2
+ const path = require('path')
3
+ const fs = require('fs-extra')
4
+
5
+ const Config = require('../generators/config')
6
+ const buildToFile = require('../commands/build')
7
+ const renderToString = require('../functions/render')
8
+
9
+ const {get, merge, isObject} = require('lodash')
10
+ const {clearConsole} = require('../utils/helpers')
11
+
12
+ const browsersync = () => {
13
+ if (!global.cachedBrowserSync) {
14
+ const bs = require('browser-sync')
15
+ global.cachedBrowserSync = bs.create()
16
+ }
17
+
18
+ return global.cachedBrowserSync
19
+ }
20
+
21
+ const serve = async (env = 'local', config = {}) => {
22
+ config = merge(
23
+ config,
24
+ await Config.getMerged(env),
25
+ {
26
+ build: {
27
+ command: 'serve'
28
+ }
29
+ }
30
+ )
31
+
32
+ const spinner = ora()
33
+
34
+ try {
35
+ await buildToFile(env, config)
36
+
37
+ let templates = get(config, 'build.templates')
38
+ templates = Array.isArray(templates) ? templates : [templates]
39
+
40
+ const templatePaths = [...new Set(templates.map(config => `${get(config, 'source', 'src')}/**`))]
41
+ const globalPaths = [
42
+ 'src/**',
43
+ get(config, 'build.tailwind.config', 'tailwind.config.js'),
44
+ ...new Set(get(config, 'build.browsersync.watch', []))
45
+ ]
46
+
47
+ // Watch for Template file changes
48
+ browsersync()
49
+ .watch(templatePaths)
50
+ .on('change', async file => {
51
+ if (config.events && typeof config.events.beforeCreate === 'function') {
52
+ await config.events.beforeCreate(config)
53
+ }
54
+
55
+ // Don't render if file type is not configured
56
+ // eslint-disable-next-line
57
+ const filetypes = templates.reduce((acc, template) => {
58
+ return [...acc, ...get(template, 'filetypes', ['html'])]
59
+ }, [])
60
+
61
+ if (!filetypes.includes(path.extname(file).slice(1))) {
62
+ return
63
+ }
64
+
65
+ if (get(config, 'build.console.clear')) {
66
+ clearConsole()
67
+ }
68
+
69
+ const start = new Date()
70
+
71
+ spinner.start('Building email...')
72
+
73
+ file = file.replace(/\\/g, '/')
74
+
75
+ const renderOptions = {
76
+ maizzle: config,
77
+ ...config.events
78
+ }
79
+
80
+ renderToString(
81
+ await fs.readFile(file, 'utf8'),
82
+ renderOptions
83
+ )
84
+ .then(async ({html, config}) => {
85
+ let source = ''
86
+ let dest = ''
87
+ let ext = ''
88
+
89
+ if (Array.isArray(config.build.templates)) {
90
+ const match = config.build.templates.find(template => template.source === path.parse(file).dir)
91
+ source = get(match, 'source')
92
+ dest = get(match, 'destination.path', 'build_local')
93
+ ext = get(match, 'destination.ext', 'html')
94
+ } else if (isObject(config.build.templates)) {
95
+ source = get(config, 'build.templates.source')
96
+ dest = get(config, 'build.templates.destination.path', 'build_local')
97
+ ext = get(config, 'build.templates.destination.ext', 'html')
98
+ }
99
+
100
+ const fileDir = path.parse(file).dir.replace(source, '')
101
+ const finalDestination = path.join(dest, fileDir, `${path.parse(file).name}.${ext}`)
102
+
103
+ await fs.outputFile(config.permalink || finalDestination, html)
104
+ })
105
+ .then(() => {
106
+ browsersync().reload()
107
+ spinner.succeed(`Compiled in ${(Date.now() - start) / 1000}s [${file}]`)
108
+ })
109
+ .catch(() => spinner.warn(`Received empty HTML, please save your file again [${file}]`))
110
+ })
111
+
112
+ // Watch for changes in all other files
113
+ browsersync()
114
+ .watch(globalPaths, {ignored: templatePaths})
115
+ .on('change', () => buildToFile(env, config).then(() => browsersync().reload()))
116
+ .on('unlink', () => buildToFile(env, config).then(() => browsersync().reload()))
117
+
118
+ // Watch for changes in config files
119
+ browsersync()
120
+ .watch('config*.js')
121
+ .on('change', async file => {
122
+ const parsedEnv = path.parse(file).name.split('.')[1] || 'local'
123
+
124
+ Config
125
+ .getMerged(parsedEnv)
126
+ .then(config => buildToFile(parsedEnv, config).then(() => browsersync().reload()))
127
+ })
128
+
129
+ // Browsersync options
130
+ const baseDir = templates.map(t => t.destination.path)
131
+
132
+ // Initialize Browsersync
133
+ browsersync()
134
+ .init(
135
+ merge(
136
+ {
137
+ notify: false,
138
+ open: false,
139
+ port: 3000,
140
+ server: {
141
+ baseDir,
142
+ directory: true
143
+ },
144
+ tunnel: false,
145
+ ui: {port: 3001},
146
+ logFileChanges: false
147
+ },
148
+ get(config, 'build.browsersync', {})
149
+ ), () => {})
150
+ } catch (error) {
151
+ spinner.fail(error)
152
+ throw error
153
+ }
154
+ }
155
+
156
+ module.exports = serve
@@ -0,0 +1,5 @@
1
+ const Plaintext = require('../generators/plaintext')
2
+
3
+ const toPlaintext = async (html, config = {}) => Plaintext.generate(html, false, config)
4
+
5
+ module.exports = toPlaintext
@@ -0,0 +1,5 @@
1
+ const Output = require('../generators/output')
2
+
3
+ const render = async (html, options) => Output.toString(html, options)
4
+
5
+ module.exports = render
@@ -29,7 +29,7 @@ module.exports = async (env, spinner, config) => {
29
29
 
30
30
  const css = (typeof get(config, 'tailwind.compiled') === 'string')
31
31
  ? config.tailwind.compiled
32
- : await Tailwind.compile('@tailwind components; @tailwind utilities;', '', {}, config, spinner)
32
+ : await Tailwind.compile('', '', {}, config, spinner)
33
33
 
34
34
  // Parse each template config object
35
35
  await asyncForEach(templatesConfig, async templateConfig => {
@@ -46,26 +46,39 @@ module.exports = async (env, spinner, config) => {
46
46
  *
47
47
  * */
48
48
  const templateSource = []
49
+ const templateTypeErrorMessage = 'Invalid template source: expected string or array of strings, got '
49
50
 
50
51
  if (typeof templateConfig.source === 'function') {
51
- const sources = templateConfig.source()
52
+ const sources = templateConfig.source(config)
53
+
52
54
  if (Array.isArray(sources)) {
53
55
  templateSource.push(...sources)
54
- } else {
56
+ } else if (typeof sources === 'string') {
55
57
  templateSource.push(sources)
58
+ } else {
59
+ throw new TypeError(templateTypeErrorMessage + typeof sources)
56
60
  }
57
61
  } else {
58
62
  if (Array.isArray(templateConfig.source)) {
59
63
  templateSource.push(...templateConfig.source)
60
- } else {
64
+ } else if (typeof templateConfig.source === 'string') {
61
65
  templateSource.push(templateConfig.source)
66
+ } else {
67
+ throw new TypeError(templateTypeErrorMessage + typeof templateConfig.source)
62
68
  }
63
69
  }
64
70
 
65
71
  // Parse each template source
66
72
  await asyncForEach(templateSource, async source => {
73
+ /**
74
+ * Copy single-file sources correctly
75
+ * If `src` is a file, `dest` cannot be a directory
76
+ * https://github.com/jprichardson/node-fs-extra/issues/323
77
+ */
78
+ const out = fs.lstatSync(source).isFile() ? `${outputDir}/${path.basename(source)}` : outputDir
79
+
67
80
  await fs
68
- .copy(source, outputDir)
81
+ .copy(source, out)
69
82
  .then(async () => {
70
83
  const extensions = Array.isArray(templateConfig.filetypes)
71
84
  ? templateConfig.filetypes.join('|')
@@ -78,9 +91,6 @@ module.exports = async (env, spinner, config) => {
78
91
  return
79
92
  }
80
93
 
81
- // Store template config currently being processed
82
- config.build.currentTemplates = templateConfig
83
-
84
94
  if (config.events && typeof config.events.beforeCreate === 'function') {
85
95
  await config.events.beforeCreate(config)
86
96
  }
@@ -88,76 +98,79 @@ module.exports = async (env, spinner, config) => {
88
98
  await asyncForEach(templates, async file => {
89
99
  const html = await fs.readFile(file, 'utf8')
90
100
 
91
- await render(html, {
92
- maizzle: {
93
- ...config,
94
- env
95
- },
96
- tailwind: {
97
- compiled: css
98
- },
99
- ...config.events
100
- })
101
- .then(async ({html, config}) => {
102
- const destination = config.permalink || file
103
-
104
- /**
105
- * Generate plaintext
106
- *
107
- * We do this first so that we can remove the <plaintext>
108
- * tags from the markup before outputting the file.
109
- */
110
-
111
- const plaintextConfig = get(templateConfig, 'plaintext')
112
- const plaintextDestination = get(plaintextConfig, 'destination', config.permalink || file)
113
-
114
- if ((typeof plaintextConfig === 'boolean' && plaintextConfig) || !isEmpty(plaintextConfig)) {
115
- await Plaintext
116
- .generate(html, plaintextDestination, merge(config, {filepath: file}))
117
- .then(({plaintext, destination}) => fs.outputFile(destination, plaintext))
118
- }
119
-
120
- html = removePlaintextTags(html, config)
121
-
122
- /**
123
- * Output file
124
- */
125
- const parts = path.parse(destination)
126
- const extension = get(templateConfig, 'destination.extension', 'html')
127
- const finalDestination = `${parts.dir}/${parts.name}.${extension}`
128
-
129
- await fs.outputFile(finalDestination, html)
130
- .then(async () => {
131
- /**
132
- * Remove original file if its path is different
133
- * from the final destination path.
134
- *
135
- * This ensures non-HTML files do not pollute
136
- * the build destination folder.
137
- */
138
- if (finalDestination !== file) {
139
- await fs.remove(file)
140
- }
141
-
142
- // Keep track of handled files
143
- files.push(file)
144
- parsed.push(file)
145
- })
146
- })
147
- .catch(error => {
148
- switch (config.build.fail) {
149
- case 'silent':
150
- spinner.warn(`Failed to compile template: ${path.basename(file)}`)
151
- break
152
- case 'verbose':
153
- spinner.warn(`Failed to compile template: ${path.basename(file)}`)
154
- console.error(error)
155
- break
156
- default:
157
- spinner.fail(`Failed to compile template: ${path.basename(file)}`)
158
- throw error
159
- }
101
+ try {
102
+ const compiled = await render(html, {
103
+ maizzle: {
104
+ ...config,
105
+ env
106
+ },
107
+ tailwind: {
108
+ compiled: css
109
+ },
110
+ ...config.events
160
111
  })
112
+
113
+ const destination = config.permalink || file
114
+
115
+ /**
116
+ * Generate plaintext
117
+ *
118
+ * We do this first so that we can remove the <plaintext>
119
+ * tags from the markup before outputting the file.
120
+ */
121
+
122
+ const plaintextConfig = get(templateConfig, 'plaintext')
123
+ const plaintextPath = get(plaintextConfig, 'destination.path', config.permalink || file)
124
+
125
+ if (Boolean(plaintextConfig) || !isEmpty(plaintextConfig)) {
126
+ await Plaintext
127
+ .generate(
128
+ compiled.html,
129
+ plaintextPath,
130
+ merge(plaintextConfig, {filepath: file})
131
+ )
132
+ .then(({plaintext, destination}) => fs.outputFile(destination, plaintext))
133
+ }
134
+
135
+ compiled.html = removePlaintextTags(compiled.html, config)
136
+
137
+ /**
138
+ * Output file
139
+ */
140
+ const parts = path.parse(destination)
141
+ const extension = get(templateConfig, 'destination.extension', 'html')
142
+ const finalDestination = `${parts.dir}/${parts.name}.${extension}`
143
+
144
+ await fs.outputFile(finalDestination, compiled.html)
145
+
146
+ /**
147
+ * Remove original file if its path is different
148
+ * from the final destination path.
149
+ *
150
+ * This ensures non-HTML files do not pollute
151
+ * the build destination folder.
152
+ */
153
+ if (finalDestination !== file) {
154
+ await fs.remove(file)
155
+ }
156
+
157
+ // Keep track of handled files
158
+ files.push(file)
159
+ parsed.push(file)
160
+ } catch (error) {
161
+ switch (config.build.fail) {
162
+ case 'silent':
163
+ spinner.warn(`Failed to compile template: ${path.basename(file)}`)
164
+ break
165
+ case 'verbose':
166
+ spinner.warn(`Failed to compile template: ${path.basename(file)}`)
167
+ console.error(error)
168
+ break
169
+ default:
170
+ spinner.fail(`Failed to compile template: ${path.basename(file)}`)
171
+ throw error
172
+ }
173
+ }
161
174
  })
162
175
 
163
176
  const assets = {source: '', destination: 'assets', ...get(templateConfig, 'assets')}
@@ -22,13 +22,9 @@ 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
- let {frontmatter} = fm(html)
28
-
29
- if (frontmatter) {
30
- frontmatter = await posthtml(frontmatter, config)
31
- }
27
+ const {frontmatter} = fm(html)
32
28
 
33
29
  html = `---\n${frontmatter}\n---\n\n${fm(html).body}`
34
30
 
@@ -1,52 +1,49 @@
1
- const path = require('path')
2
- const {get} = require('lodash')
3
- const {stripHtml} = require('string-strip-html')
4
-
5
- module.exports.generate = async (html, destination, config) => {
6
- const options = get(config, 'build.currentTemplates.plaintext', get(config, 'plaintext', {}))
7
- const configDestinationPath = get(options, 'destination.path')
8
- const extension = get(options, 'destination.extension', 'txt')
9
- const plaintext = stripHtml(html, {
10
- dumpLinkHrefsNearby: {
11
- enabled: true
12
- },
13
- ...options
14
- }).result
15
-
16
- // If we set plaintext.destination.path in config/fm
17
- if (configDestinationPath) {
18
- // Naive path checking, this does not support dot folders
19
- const isFilePath = Boolean(path.parse(configDestinationPath).ext)
20
-
21
- /**
22
- * Using a file path will generate a single plaintext file,
23
- * no matter how many templates there are.
24
- *
25
- * It will be based on the last-processed template.
26
- */
27
- if (isFilePath) {
28
- destination = configDestinationPath
29
-
30
- return {plaintext, destination}
31
- }
32
-
33
- /**
34
- * Using a directory-like path for plaintext.destination.path
35
- */
36
- destination = path.join(configDestinationPath, path.basename(config.filepath, path.extname(config.filepath)) + '.' + extension)
37
-
38
- return {plaintext, destination}
39
- }
40
-
41
- /**
42
- * Use template's `permalink` Front Matter key,
43
- * fall back to the original `destination`.
44
- */
45
- destination = get(config, 'permalink', destination)
46
-
47
- if (typeof destination === 'string') {
48
- destination = path.join(path.dirname(destination), path.basename(destination, path.extname(destination)) + '.' + extension)
49
- }
50
-
51
- return {plaintext, destination}
52
- }
1
+ const path = require('path')
2
+ const {get} = require('lodash')
3
+ const {stripHtml} = require('string-strip-html')
4
+
5
+ module.exports.generate = async (html, destination, config) => {
6
+ const configDestinationPath = get(config, 'destination.path')
7
+ const extension = get(config, 'destination.extension', 'txt')
8
+
9
+ const plaintext = stripHtml(html, {
10
+ dumpLinkHrefsNearby: {
11
+ enabled: true
12
+ },
13
+ ...get(config, 'options', {})
14
+ }).result
15
+
16
+ // If we set plaintext.destination.path in config/fm
17
+ if (configDestinationPath) {
18
+ /**
19
+ * Using a file path will generate a single plaintext file,
20
+ * no matter how many templates there are.
21
+ *
22
+ * It will be based on the last-processed template.
23
+ */
24
+ if (path.extname(configDestinationPath)) {
25
+ destination = configDestinationPath
26
+
27
+ return {plaintext, destination}
28
+ }
29
+
30
+ /**
31
+ * Using a directory-like path for plaintext.destination.path
32
+ */
33
+ destination = path.join(configDestinationPath, path.basename(config.filepath, path.extname(config.filepath)) + '.' + extension)
34
+
35
+ return {plaintext, destination}
36
+ }
37
+
38
+ /**
39
+ * Use template's `permalink` Front Matter key,
40
+ * fall back to the original `destination`.
41
+ */
42
+ destination = get(config, 'permalink', destination)
43
+
44
+ if (typeof destination === 'string') {
45
+ destination = path.join(path.dirname(destination), path.basename(destination, path.extname(destination)) + '.' + extension)
46
+ }
47
+
48
+ return {plaintext, destination}
49
+ }
@@ -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
+ }