@maizzle/framework 4.0.0-alpha.4 → 4.0.0-alpha.7
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/package.json +86 -86
- package/src/commands/serve.js +5 -1
- package/src/generators/output/to-disk.js +208 -208
- package/src/generators/tailwindcss.js +116 -114
- package/src/transformers/transform.js +2 -2
- package/test/test-tailwind.js +1 -1
package/package.json
CHANGED
|
@@ -1,86 +1,86 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@maizzle/framework",
|
|
3
|
-
"version": "4.0.0-alpha.
|
|
4
|
-
"description": "Maizzle is a framework that helps you quickly build HTML emails with Tailwind CSS.",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"main": "src/index.js",
|
|
7
|
-
"repository": {
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "https://github.com/maizzle/framework.git"
|
|
10
|
-
},
|
|
11
|
-
"bugs": "https://github.com/maizzle/framework/issues",
|
|
12
|
-
"homepage": "https://maizzle.com",
|
|
13
|
-
"author": "Cosmin Popovici (https://github.com/cossssmin)",
|
|
14
|
-
"keywords": [
|
|
15
|
-
"maizzle",
|
|
16
|
-
"tailwindcss",
|
|
17
|
-
"responsive-email",
|
|
18
|
-
"email-framework",
|
|
19
|
-
"email-template",
|
|
20
|
-
"email-marketing",
|
|
21
|
-
"email-campaigns",
|
|
22
|
-
"email-newsletter",
|
|
23
|
-
"email-boilerplate",
|
|
24
|
-
"html-emails"
|
|
25
|
-
],
|
|
26
|
-
"publishConfig": {
|
|
27
|
-
"access": "public"
|
|
28
|
-
},
|
|
29
|
-
"scripts": {
|
|
30
|
-
"test": "c8 ava -s",
|
|
31
|
-
"pretest": "xo",
|
|
32
|
-
"style": "xo",
|
|
33
|
-
"release": "np"
|
|
34
|
-
},
|
|
35
|
-
"dependencies": {
|
|
36
|
-
"autoprefixer": "^10.4.0",
|
|
37
|
-
"browser-sync": "^2.26.13",
|
|
38
|
-
"color-shorthand-hex-to-six-digit": "^3.0.2",
|
|
39
|
-
"email-comb": "^5.0.0",
|
|
40
|
-
"front-matter": "^4.0.0",
|
|
41
|
-
"fs-extra": "^10.0.0",
|
|
42
|
-
"glob-promise": "^4.1.0",
|
|
43
|
-
"html-crush": "^4.0.0",
|
|
44
|
-
"is-url-superb": "^5.0.0",
|
|
45
|
-
"juice": "^8.0.0",
|
|
46
|
-
"lodash": "^4.17.20",
|
|
47
|
-
"ora": "^5.1.0",
|
|
48
|
-
"postcss": "^8.4.4",
|
|
49
|
-
"postcss-import": "^14.0.0",
|
|
50
|
-
"postcss-merge-longhand": "^5.0.1",
|
|
51
|
-
"posthtml": "^0.16.6",
|
|
52
|
-
"posthtml-attrs-parser": "^0.1.1",
|
|
53
|
-
"posthtml-base-url": "^1.0.1",
|
|
54
|
-
"posthtml-content": "^0.0.3",
|
|
55
|
-
"posthtml-expressions": "^1.8.1",
|
|
56
|
-
"posthtml-extend": "^0.6.0",
|
|
57
|
-
"posthtml-extra-attributes": "^1.0.0",
|
|
58
|
-
"posthtml-fetch": "^2.0.0",
|
|
59
|
-
"posthtml-markdownit": "^1.3.0",
|
|
60
|
-
"posthtml-modules": "^0.8.
|
|
61
|
-
"posthtml-mso": "^1.0.4",
|
|
62
|
-
"posthtml-postcss-merge-longhand": "^1.0.2",
|
|
63
|
-
"posthtml-remove-attributes": "^1.0.0",
|
|
64
|
-
"posthtml-safe-class-names": "^1.0.4",
|
|
65
|
-
"posthtml-url-parameters": "^1.0.4",
|
|
66
|
-
"pretty": "^2.0.0",
|
|
67
|
-
"prevent-widows": "^1.0.2",
|
|
68
|
-
"query-string": "^7.1.0",
|
|
69
|
-
"string-strip-html": "^8.2.0",
|
|
70
|
-
"tailwindcss": "^3.0.0"
|
|
71
|
-
},
|
|
72
|
-
"devDependencies": {
|
|
73
|
-
"ava": "^4.0.1",
|
|
74
|
-
"c8": "^7.11.0",
|
|
75
|
-
"np": "*",
|
|
76
|
-
"xo": "0.39.1"
|
|
77
|
-
},
|
|
78
|
-
"engines": {
|
|
79
|
-
"node": ">=12.13.0"
|
|
80
|
-
},
|
|
81
|
-
"ava": {
|
|
82
|
-
"files": [
|
|
83
|
-
"test/**/test*.js"
|
|
84
|
-
]
|
|
85
|
-
}
|
|
86
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@maizzle/framework",
|
|
3
|
+
"version": "4.0.0-alpha.7",
|
|
4
|
+
"description": "Maizzle is a framework that helps you quickly build HTML emails with Tailwind CSS.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/maizzle/framework.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": "https://github.com/maizzle/framework/issues",
|
|
12
|
+
"homepage": "https://maizzle.com",
|
|
13
|
+
"author": "Cosmin Popovici (https://github.com/cossssmin)",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"maizzle",
|
|
16
|
+
"tailwindcss",
|
|
17
|
+
"responsive-email",
|
|
18
|
+
"email-framework",
|
|
19
|
+
"email-template",
|
|
20
|
+
"email-marketing",
|
|
21
|
+
"email-campaigns",
|
|
22
|
+
"email-newsletter",
|
|
23
|
+
"email-boilerplate",
|
|
24
|
+
"html-emails"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"test": "c8 ava -s",
|
|
31
|
+
"pretest": "xo",
|
|
32
|
+
"style": "xo",
|
|
33
|
+
"release": "np"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"autoprefixer": "^10.4.0",
|
|
37
|
+
"browser-sync": "^2.26.13",
|
|
38
|
+
"color-shorthand-hex-to-six-digit": "^3.0.2",
|
|
39
|
+
"email-comb": "^5.0.0",
|
|
40
|
+
"front-matter": "^4.0.0",
|
|
41
|
+
"fs-extra": "^10.0.0",
|
|
42
|
+
"glob-promise": "^4.1.0",
|
|
43
|
+
"html-crush": "^4.0.0",
|
|
44
|
+
"is-url-superb": "^5.0.0",
|
|
45
|
+
"juice": "^8.0.0",
|
|
46
|
+
"lodash": "^4.17.20",
|
|
47
|
+
"ora": "^5.1.0",
|
|
48
|
+
"postcss": "^8.4.4",
|
|
49
|
+
"postcss-import": "^14.0.0",
|
|
50
|
+
"postcss-merge-longhand": "^5.0.1",
|
|
51
|
+
"posthtml": "^0.16.6",
|
|
52
|
+
"posthtml-attrs-parser": "^0.1.1",
|
|
53
|
+
"posthtml-base-url": "^1.0.1",
|
|
54
|
+
"posthtml-content": "^0.0.3",
|
|
55
|
+
"posthtml-expressions": "^1.8.1",
|
|
56
|
+
"posthtml-extend": "^0.6.0",
|
|
57
|
+
"posthtml-extra-attributes": "^1.0.0",
|
|
58
|
+
"posthtml-fetch": "^2.0.0",
|
|
59
|
+
"posthtml-markdownit": "^1.3.0",
|
|
60
|
+
"posthtml-modules": "^0.8.1",
|
|
61
|
+
"posthtml-mso": "^1.0.4",
|
|
62
|
+
"posthtml-postcss-merge-longhand": "^1.0.2",
|
|
63
|
+
"posthtml-remove-attributes": "^1.0.0",
|
|
64
|
+
"posthtml-safe-class-names": "^1.0.4",
|
|
65
|
+
"posthtml-url-parameters": "^1.0.4",
|
|
66
|
+
"pretty": "^2.0.0",
|
|
67
|
+
"prevent-widows": "^1.0.2",
|
|
68
|
+
"query-string": "^7.1.0",
|
|
69
|
+
"string-strip-html": "^8.2.0",
|
|
70
|
+
"tailwindcss": "^3.0.0"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"ava": "^4.0.1",
|
|
74
|
+
"c8": "^7.11.0",
|
|
75
|
+
"np": "*",
|
|
76
|
+
"xo": "0.39.1"
|
|
77
|
+
},
|
|
78
|
+
"engines": {
|
|
79
|
+
"node": ">=12.13.0"
|
|
80
|
+
},
|
|
81
|
+
"ava": {
|
|
82
|
+
"files": [
|
|
83
|
+
"test/**/test*.js"
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
}
|
package/src/commands/serve.js
CHANGED
|
@@ -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
|
|
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
|
})
|
|
@@ -1,208 +1,208 @@
|
|
|
1
|
-
const path = require('path')
|
|
2
|
-
const fs = require('fs-extra')
|
|
3
|
-
const glob = require('glob-promise')
|
|
4
|
-
const {get, isEmpty, merge} = require('lodash')
|
|
5
|
-
const {asyncForEach} = require('../../utils/helpers')
|
|
6
|
-
const removePlaintextTags = require('../../transformers/plaintext')
|
|
7
|
-
|
|
8
|
-
const Config = require('../config')
|
|
9
|
-
const Tailwind = require('../tailwindcss')
|
|
10
|
-
const Plaintext = require('../plaintext')
|
|
11
|
-
|
|
12
|
-
const render = require('./to-string')
|
|
13
|
-
|
|
14
|
-
module.exports = async (env, spinner, config) => {
|
|
15
|
-
process.env.NODE_ENV = env || 'local'
|
|
16
|
-
|
|
17
|
-
if (isEmpty(config)) {
|
|
18
|
-
config = await Config.getMerged(env).catch(error => {
|
|
19
|
-
spinner.fail('Build failed')
|
|
20
|
-
throw error
|
|
21
|
-
})
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const buildTemplates = get(config, 'build.templates')
|
|
25
|
-
const templatesConfig = Array.isArray(buildTemplates) ? buildTemplates : [buildTemplates]
|
|
26
|
-
|
|
27
|
-
const parsed = []
|
|
28
|
-
let files = []
|
|
29
|
-
|
|
30
|
-
const css = (typeof get(config, 'tailwind.compiled') === 'string')
|
|
31
|
-
? config.tailwind.compiled
|
|
32
|
-
: await Tailwind.compile('
|
|
33
|
-
|
|
34
|
-
// Parse each template config object
|
|
35
|
-
await asyncForEach(templatesConfig, async templateConfig => {
|
|
36
|
-
const outputDir = get(templateConfig, 'destination.path', `build_${env}`)
|
|
37
|
-
|
|
38
|
-
await fs.remove(outputDir)
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Get all files in the template config's source directory
|
|
42
|
-
* Supports `source` defined as:
|
|
43
|
-
* - string
|
|
44
|
-
* - array of strings
|
|
45
|
-
* - function that returns either of the above
|
|
46
|
-
*
|
|
47
|
-
* */
|
|
48
|
-
const templateSource = []
|
|
49
|
-
const templateTypeErrorMessage = 'Invalid template source: expected string or array of strings, got '
|
|
50
|
-
|
|
51
|
-
if (typeof templateConfig.source === 'function') {
|
|
52
|
-
const sources = templateConfig.source(config)
|
|
53
|
-
|
|
54
|
-
if (Array.isArray(sources)) {
|
|
55
|
-
templateSource.push(...sources)
|
|
56
|
-
} else if (typeof sources === 'string') {
|
|
57
|
-
templateSource.push(sources)
|
|
58
|
-
} else {
|
|
59
|
-
throw new TypeError(templateTypeErrorMessage + typeof sources)
|
|
60
|
-
}
|
|
61
|
-
} else {
|
|
62
|
-
if (Array.isArray(templateConfig.source)) {
|
|
63
|
-
templateSource.push(...templateConfig.source)
|
|
64
|
-
} else if (typeof templateConfig.source === 'string') {
|
|
65
|
-
templateSource.push(templateConfig.source)
|
|
66
|
-
} else {
|
|
67
|
-
throw new TypeError(templateTypeErrorMessage + typeof templateConfig.source)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Parse each template source
|
|
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
|
-
|
|
80
|
-
await fs
|
|
81
|
-
.copy(source, out)
|
|
82
|
-
.then(async () => {
|
|
83
|
-
const extensions = Array.isArray(templateConfig.filetypes)
|
|
84
|
-
? templateConfig.filetypes.join('|')
|
|
85
|
-
: templateConfig.filetypes || get(templateConfig, 'filetypes', 'html')
|
|
86
|
-
|
|
87
|
-
const templates = await glob(`${outputDir}/**/*.+(${extensions})`)
|
|
88
|
-
|
|
89
|
-
if (templates.length === 0) {
|
|
90
|
-
spinner.warn(`Error: no files with the .${extensions} extension found in ${templateConfig.source}`)
|
|
91
|
-
return
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (config.events && typeof config.events.beforeCreate === 'function') {
|
|
95
|
-
await config.events.beforeCreate(config)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
await asyncForEach(templates, async file => {
|
|
99
|
-
const html = await fs.readFile(file, 'utf8')
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
const compiled = await render(html, {
|
|
103
|
-
maizzle: {
|
|
104
|
-
...config,
|
|
105
|
-
env
|
|
106
|
-
},
|
|
107
|
-
tailwind: {
|
|
108
|
-
compiled: css
|
|
109
|
-
},
|
|
110
|
-
...config.events
|
|
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
|
-
}
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
const assets = {source: '', destination: 'assets', ...get(templateConfig, 'assets')}
|
|
177
|
-
|
|
178
|
-
if (Array.isArray(assets.source)) {
|
|
179
|
-
await asyncForEach(assets.source, async source => {
|
|
180
|
-
if (fs.existsSync(source)) {
|
|
181
|
-
await fs.copy(source, path.join(templateConfig.destination.path, assets.destination)).catch(error => spinner.warn(error.message))
|
|
182
|
-
}
|
|
183
|
-
})
|
|
184
|
-
} else {
|
|
185
|
-
if (fs.existsSync(assets.source)) {
|
|
186
|
-
await fs.copy(assets.source, path.join(templateConfig.destination.path, assets.destination)).catch(error => spinner.warn(error.message))
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
await glob(path.join(templateConfig.destination.path, '/**/*.*'))
|
|
191
|
-
.then(contents => {
|
|
192
|
-
files = [...new Set([...files, ...contents])]
|
|
193
|
-
})
|
|
194
|
-
})
|
|
195
|
-
.catch(error => spinner.warn(error.message))
|
|
196
|
-
})
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
if (config.events && typeof config.events.afterBuild === 'function') {
|
|
200
|
-
await config.events.afterBuild(files)
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return {
|
|
204
|
-
files,
|
|
205
|
-
parsed,
|
|
206
|
-
css
|
|
207
|
-
}
|
|
208
|
-
}
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const fs = require('fs-extra')
|
|
3
|
+
const glob = require('glob-promise')
|
|
4
|
+
const {get, isEmpty, merge} = require('lodash')
|
|
5
|
+
const {asyncForEach} = require('../../utils/helpers')
|
|
6
|
+
const removePlaintextTags = require('../../transformers/plaintext')
|
|
7
|
+
|
|
8
|
+
const Config = require('../config')
|
|
9
|
+
const Tailwind = require('../tailwindcss')
|
|
10
|
+
const Plaintext = require('../plaintext')
|
|
11
|
+
|
|
12
|
+
const render = require('./to-string')
|
|
13
|
+
|
|
14
|
+
module.exports = async (env, spinner, config) => {
|
|
15
|
+
process.env.NODE_ENV = env || 'local'
|
|
16
|
+
|
|
17
|
+
if (isEmpty(config)) {
|
|
18
|
+
config = await Config.getMerged(env).catch(error => {
|
|
19
|
+
spinner.fail('Build failed')
|
|
20
|
+
throw error
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const buildTemplates = get(config, 'build.templates')
|
|
25
|
+
const templatesConfig = Array.isArray(buildTemplates) ? buildTemplates : [buildTemplates]
|
|
26
|
+
|
|
27
|
+
const parsed = []
|
|
28
|
+
let files = []
|
|
29
|
+
|
|
30
|
+
const css = (typeof get(config, 'tailwind.compiled') === 'string')
|
|
31
|
+
? config.tailwind.compiled
|
|
32
|
+
: await Tailwind.compile('', '', {}, config, spinner)
|
|
33
|
+
|
|
34
|
+
// Parse each template config object
|
|
35
|
+
await asyncForEach(templatesConfig, async templateConfig => {
|
|
36
|
+
const outputDir = get(templateConfig, 'destination.path', `build_${env}`)
|
|
37
|
+
|
|
38
|
+
await fs.remove(outputDir)
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get all files in the template config's source directory
|
|
42
|
+
* Supports `source` defined as:
|
|
43
|
+
* - string
|
|
44
|
+
* - array of strings
|
|
45
|
+
* - function that returns either of the above
|
|
46
|
+
*
|
|
47
|
+
* */
|
|
48
|
+
const templateSource = []
|
|
49
|
+
const templateTypeErrorMessage = 'Invalid template source: expected string or array of strings, got '
|
|
50
|
+
|
|
51
|
+
if (typeof templateConfig.source === 'function') {
|
|
52
|
+
const sources = templateConfig.source(config)
|
|
53
|
+
|
|
54
|
+
if (Array.isArray(sources)) {
|
|
55
|
+
templateSource.push(...sources)
|
|
56
|
+
} else if (typeof sources === 'string') {
|
|
57
|
+
templateSource.push(sources)
|
|
58
|
+
} else {
|
|
59
|
+
throw new TypeError(templateTypeErrorMessage + typeof sources)
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
if (Array.isArray(templateConfig.source)) {
|
|
63
|
+
templateSource.push(...templateConfig.source)
|
|
64
|
+
} else if (typeof templateConfig.source === 'string') {
|
|
65
|
+
templateSource.push(templateConfig.source)
|
|
66
|
+
} else {
|
|
67
|
+
throw new TypeError(templateTypeErrorMessage + typeof templateConfig.source)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Parse each template source
|
|
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
|
+
|
|
80
|
+
await fs
|
|
81
|
+
.copy(source, out)
|
|
82
|
+
.then(async () => {
|
|
83
|
+
const extensions = Array.isArray(templateConfig.filetypes)
|
|
84
|
+
? templateConfig.filetypes.join('|')
|
|
85
|
+
: templateConfig.filetypes || get(templateConfig, 'filetypes', 'html')
|
|
86
|
+
|
|
87
|
+
const templates = await glob(`${outputDir}/**/*.+(${extensions})`)
|
|
88
|
+
|
|
89
|
+
if (templates.length === 0) {
|
|
90
|
+
spinner.warn(`Error: no files with the .${extensions} extension found in ${templateConfig.source}`)
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (config.events && typeof config.events.beforeCreate === 'function') {
|
|
95
|
+
await config.events.beforeCreate(config)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
await asyncForEach(templates, async file => {
|
|
99
|
+
const html = await fs.readFile(file, 'utf8')
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const compiled = await render(html, {
|
|
103
|
+
maizzle: {
|
|
104
|
+
...config,
|
|
105
|
+
env
|
|
106
|
+
},
|
|
107
|
+
tailwind: {
|
|
108
|
+
compiled: css
|
|
109
|
+
},
|
|
110
|
+
...config.events
|
|
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
|
+
}
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
const assets = {source: '', destination: 'assets', ...get(templateConfig, 'assets')}
|
|
177
|
+
|
|
178
|
+
if (Array.isArray(assets.source)) {
|
|
179
|
+
await asyncForEach(assets.source, async source => {
|
|
180
|
+
if (fs.existsSync(source)) {
|
|
181
|
+
await fs.copy(source, path.join(templateConfig.destination.path, assets.destination)).catch(error => spinner.warn(error.message))
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
} else {
|
|
185
|
+
if (fs.existsSync(assets.source)) {
|
|
186
|
+
await fs.copy(assets.source, path.join(templateConfig.destination.path, assets.destination)).catch(error => spinner.warn(error.message))
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
await glob(path.join(templateConfig.destination.path, '/**/*.*'))
|
|
191
|
+
.then(contents => {
|
|
192
|
+
files = [...new Set([...files, ...contents])]
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
.catch(error => spinner.warn(error.message))
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
if (config.events && typeof config.events.afterBuild === 'function') {
|
|
200
|
+
await config.events.afterBuild(files)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
files,
|
|
205
|
+
parsed,
|
|
206
|
+
css
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -1,114 +1,116 @@
|
|
|
1
|
-
const path = require('path')
|
|
2
|
-
const fs = require('fs-extra')
|
|
3
|
-
const postcss = require('postcss')
|
|
4
|
-
const tailwindcss = require('tailwindcss')
|
|
5
|
-
const postcssImport = require('postcss-import')
|
|
6
|
-
const postcssNested = require('tailwindcss/nesting')
|
|
7
|
-
const {requireUncached} = require('../utils/helpers')
|
|
8
|
-
const mergeLonghand = require('postcss-merge-longhand')
|
|
9
|
-
const {get, isObject, isEmpty, merge} = require('lodash')
|
|
10
|
-
|
|
11
|
-
module.exports = {
|
|
12
|
-
compile: async (css = '', html = '', tailwindConfig = {}, maizzleConfig = {}, spinner = null) => {
|
|
13
|
-
tailwindConfig = (isObject(tailwindConfig) && !isEmpty(tailwindConfig)) ? tailwindConfig : get(maizzleConfig, 'build.tailwind.config', 'tailwind.config.js')
|
|
14
|
-
|
|
15
|
-
// Compute the Tailwind config to use
|
|
16
|
-
const userConfig = () => {
|
|
17
|
-
// If a custom config object was passed, use that
|
|
18
|
-
if (isObject(tailwindConfig) && !isEmpty(tailwindConfig)) {
|
|
19
|
-
return tailwindConfig
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Try loading a fresh tailwind.config.js, with fallback to an empty object.
|
|
24
|
-
* This will use the default Tailwind config (with rem units etc)
|
|
25
|
-
*/
|
|
26
|
-
try {
|
|
27
|
-
return requireUncached(path.resolve(process.cwd(), tailwindConfig))
|
|
28
|
-
} catch {
|
|
29
|
-
return {}
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Merge user's Tailwind config on top of a 'base' config
|
|
34
|
-
const config = merge({
|
|
35
|
-
important: true,
|
|
36
|
-
content: {
|
|
37
|
-
files: [
|
|
38
|
-
'./src/**/*.*',
|
|
39
|
-
{raw: html, extension: 'html'}
|
|
40
|
-
]
|
|
41
|
-
}
|
|
42
|
-
}, userConfig())
|
|
43
|
-
|
|
44
|
-
// Add back the `{raw: html}` option if user provided own config
|
|
45
|
-
if (Array.isArray(config.content)) {
|
|
46
|
-
config.content = {
|
|
47
|
-
files: [
|
|
48
|
-
...config.content,
|
|
49
|
-
'./src/**/*.*',
|
|
50
|
-
{raw: html, extension: 'html'}
|
|
51
|
-
]
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Include all `build.templates.source` paths when scanning for selectors to preserve
|
|
56
|
-
const buildTemplates = get(maizzleConfig, 'build.templates')
|
|
57
|
-
|
|
58
|
-
if (buildTemplates) {
|
|
59
|
-
const templateObjects = Array.isArray(buildTemplates) ? buildTemplates : [buildTemplates]
|
|
60
|
-
const templateSources = templateObjects.map(template => {
|
|
61
|
-
const source = get(template, 'source')
|
|
62
|
-
|
|
63
|
-
if (typeof source === 'function') {
|
|
64
|
-
const sources = source(maizzleConfig)
|
|
65
|
-
|
|
66
|
-
if (Array.isArray(sources)) {
|
|
67
|
-
sources.map(s => config.content.files.push(s))
|
|
68
|
-
} else if (typeof sources === 'string') {
|
|
69
|
-
config.content.files.push(sources)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Must return a valid `content` entry
|
|
73
|
-
return {raw: '', extension: 'html'}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Support single-file sources i.e. src/templates/index.html
|
|
77
|
-
if (typeof source === 'string' && Boolean(path.extname(source))) {
|
|
78
|
-
config.content.files.push(source)
|
|
79
|
-
|
|
80
|
-
return {raw: '', extension: 'html'}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return `${source}/**/*.*`
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
config.content.files.push(...templateSources)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const userFilePath = get(maizzleConfig, 'build.tailwind.css', path.join(process.cwd(), 'src/css/tailwind.css'))
|
|
90
|
-
const userFileExists = await fs.pathExists(userFilePath)
|
|
91
|
-
|
|
92
|
-
if (userFileExists) {
|
|
93
|
-
css
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const fs = require('fs-extra')
|
|
3
|
+
const postcss = require('postcss')
|
|
4
|
+
const tailwindcss = require('tailwindcss')
|
|
5
|
+
const postcssImport = require('postcss-import')
|
|
6
|
+
const postcssNested = require('tailwindcss/nesting')
|
|
7
|
+
const {requireUncached} = require('../utils/helpers')
|
|
8
|
+
const mergeLonghand = require('postcss-merge-longhand')
|
|
9
|
+
const {get, isObject, isEmpty, merge} = require('lodash')
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
compile: async (css = '', html = '', tailwindConfig = {}, maizzleConfig = {}, spinner = null) => {
|
|
13
|
+
tailwindConfig = (isObject(tailwindConfig) && !isEmpty(tailwindConfig)) ? tailwindConfig : get(maizzleConfig, 'build.tailwind.config', 'tailwind.config.js')
|
|
14
|
+
|
|
15
|
+
// Compute the Tailwind config to use
|
|
16
|
+
const userConfig = () => {
|
|
17
|
+
// If a custom config object was passed, use that
|
|
18
|
+
if (isObject(tailwindConfig) && !isEmpty(tailwindConfig)) {
|
|
19
|
+
return tailwindConfig
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Try loading a fresh tailwind.config.js, with fallback to an empty object.
|
|
24
|
+
* This will use the default Tailwind config (with rem units etc)
|
|
25
|
+
*/
|
|
26
|
+
try {
|
|
27
|
+
return requireUncached(path.resolve(process.cwd(), tailwindConfig))
|
|
28
|
+
} catch {
|
|
29
|
+
return {}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Merge user's Tailwind config on top of a 'base' config
|
|
34
|
+
const config = merge({
|
|
35
|
+
important: true,
|
|
36
|
+
content: {
|
|
37
|
+
files: [
|
|
38
|
+
'./src/**/*.*',
|
|
39
|
+
{raw: html, extension: 'html'}
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
}, userConfig())
|
|
43
|
+
|
|
44
|
+
// Add back the `{raw: html}` option if user provided own config
|
|
45
|
+
if (Array.isArray(config.content)) {
|
|
46
|
+
config.content = {
|
|
47
|
+
files: [
|
|
48
|
+
...config.content,
|
|
49
|
+
'./src/**/*.*',
|
|
50
|
+
{raw: html, extension: 'html'}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Include all `build.templates.source` paths when scanning for selectors to preserve
|
|
56
|
+
const buildTemplates = get(maizzleConfig, 'build.templates')
|
|
57
|
+
|
|
58
|
+
if (buildTemplates) {
|
|
59
|
+
const templateObjects = Array.isArray(buildTemplates) ? buildTemplates : [buildTemplates]
|
|
60
|
+
const templateSources = templateObjects.map(template => {
|
|
61
|
+
const source = get(template, 'source')
|
|
62
|
+
|
|
63
|
+
if (typeof source === 'function') {
|
|
64
|
+
const sources = source(maizzleConfig)
|
|
65
|
+
|
|
66
|
+
if (Array.isArray(sources)) {
|
|
67
|
+
sources.map(s => config.content.files.push(s))
|
|
68
|
+
} else if (typeof sources === 'string') {
|
|
69
|
+
config.content.files.push(sources)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Must return a valid `content` entry
|
|
73
|
+
return {raw: '', extension: 'html'}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Support single-file sources i.e. src/templates/index.html
|
|
77
|
+
if (typeof source === 'string' && Boolean(path.extname(source))) {
|
|
78
|
+
config.content.files.push(source)
|
|
79
|
+
|
|
80
|
+
return {raw: '', extension: 'html'}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return `${source}/**/*.*`
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
config.content.files.push(...templateSources)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const userFilePath = get(maizzleConfig, 'build.tailwind.css', path.join(process.cwd(), 'src/css/tailwind.css'))
|
|
90
|
+
const userFileExists = await fs.pathExists(userFilePath)
|
|
91
|
+
|
|
92
|
+
if (userFileExists) {
|
|
93
|
+
css = await fs.readFile(path.resolve(userFilePath), 'utf8') + css
|
|
94
|
+
} else {
|
|
95
|
+
css = '@import "tailwindcss/components"; @import "tailwindcss/utilities";' + css
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return postcss([
|
|
99
|
+
postcssImport({path: path.dirname(userFilePath)}),
|
|
100
|
+
postcssNested(),
|
|
101
|
+
tailwindcss(config),
|
|
102
|
+
maizzleConfig.env === 'local' ? () => {} : mergeLonghand(),
|
|
103
|
+
...get(maizzleConfig, 'build.postcss.plugins', [])
|
|
104
|
+
])
|
|
105
|
+
.process(css, {from: undefined})
|
|
106
|
+
.then(result => result.css)
|
|
107
|
+
.catch(error => {
|
|
108
|
+
console.error(error)
|
|
109
|
+
if (spinner) {
|
|
110
|
+
spinner.stop()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
throw new Error(`Tailwind CSS compilation failed`)
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -15,8 +15,8 @@ module.exports = async (html, config = {}, direct = false) => {
|
|
|
15
15
|
|
|
16
16
|
const compileCss = css => Tailwind.compile(css, html, tailwindConfig, maizzleConfig)
|
|
17
17
|
|
|
18
|
-
replacements.tailwindcss = css => compileCss(
|
|
19
|
-
replacements.postcss = css => compileCss(
|
|
18
|
+
replacements.tailwindcss = css => compileCss(css)
|
|
19
|
+
replacements.postcss = css => compileCss(css)
|
|
20
20
|
|
|
21
21
|
return posthtml([posthtmlContent(replacements)]).process(html, posthtmlOptions).then(result => result.html)
|
|
22
22
|
}
|
package/test/test-tailwind.js
CHANGED
|
@@ -113,5 +113,5 @@ test('uses custom postcss plugins from the maizzle config', async t => {
|
|
|
113
113
|
const css = await Tailwind.compile('.test {transform: scale(0.5)}', '<div class="test">Test</a>', {}, maizzleConfig)
|
|
114
114
|
|
|
115
115
|
t.not(css, undefined)
|
|
116
|
-
t.is(css.trim(), '.test {-webkit-transform: scale(0.5);transform: scale(0.5)}')
|
|
116
|
+
t.is(css.trim(), '.inline {display: inline !important} .table {display: table !important} .contents {display: contents !important} .transform {-webkit-transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) !important;transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) !important} .test {-webkit-transform: scale(0.5);transform: scale(0.5)}')
|
|
117
117
|
})
|