@maizzle/framework 4.8.7 → 5.0.0-beta.0
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/bin/maizzle +3 -1
- package/package.json +65 -58
- package/src/commands/build.js +244 -19
- package/src/commands/serve.js +2 -197
- package/src/generators/plaintext.js +192 -91
- package/src/generators/render.js +128 -0
- package/src/index.js +46 -14
- package/src/{generators/posthtml → posthtml}/defaultComponentsConfig.js +6 -4
- package/src/{generators/posthtml → posthtml}/defaultConfig.js +1 -1
- package/src/posthtml/index.js +74 -0
- package/src/posthtml/plugins/expandLinkTag.js +36 -0
- package/src/server/client.js +181 -0
- package/src/server/index.js +383 -0
- package/src/server/routes/hmr.js +24 -0
- package/src/server/routes/index.js +38 -0
- package/src/server/views/error.html +83 -0
- package/src/server/views/index.html +24 -0
- package/src/server/websockets.js +27 -0
- package/src/transformers/addAttributes.js +30 -0
- package/src/transformers/attributeToStyle.js +30 -36
- package/src/transformers/baseUrl.js +52 -23
- package/src/transformers/comb.js +51 -0
- package/src/transformers/core.js +20 -0
- package/src/transformers/filters/defaultFilters.js +90 -70
- package/src/transformers/filters/index.js +14 -78
- package/src/transformers/index.js +268 -63
- package/src/transformers/inline.js +240 -0
- package/src/transformers/markdown.js +13 -14
- package/src/transformers/minify.js +21 -16
- package/src/transformers/posthtmlMso.js +13 -8
- package/src/transformers/prettify.js +16 -15
- package/src/transformers/preventWidows.js +32 -26
- package/src/transformers/removeAttributes.js +17 -17
- package/src/transformers/replaceStrings.js +30 -9
- package/src/transformers/safeClassNames.js +24 -24
- package/src/transformers/shorthandCss.js +22 -0
- package/src/transformers/sixHex.js +15 -15
- package/src/transformers/urlParameters.js +18 -16
- package/src/transformers/useAttributeSizes.js +65 -0
- package/src/utils/getConfigByFilePath.js +124 -0
- package/src/utils/node.js +68 -0
- package/src/utils/string.js +117 -0
- package/types/build.d.ts +117 -57
- package/types/components.d.ts +130 -112
- package/types/config.d.ts +454 -242
- package/types/css/inline.d.ts +234 -0
- package/types/css/purge.d.ts +125 -0
- package/types/events.d.ts +5 -105
- package/types/index.d.ts +148 -116
- package/types/markdown.d.ts +20 -18
- package/types/minify.d.ts +122 -120
- package/types/plaintext.d.ts +46 -52
- package/types/posthtml.d.ts +103 -136
- package/types/render.d.ts +0 -117
- package/types/urlParameters.d.ts +21 -20
- package/types/widowWords.d.ts +9 -7
- package/src/functions/plaintext.js +0 -5
- package/src/functions/render.js +0 -5
- package/src/generators/config.js +0 -52
- package/src/generators/output/index.js +0 -4
- package/src/generators/output/to-disk.js +0 -254
- package/src/generators/output/to-string.js +0 -73
- package/src/generators/postcss.js +0 -23
- package/src/generators/posthtml/index.js +0 -75
- package/src/generators/tailwindcss.js +0 -157
- package/src/transformers/extraAttributes.js +0 -33
- package/src/transformers/inlineCss.js +0 -42
- package/src/transformers/removeInlineBackgroundColor.js +0 -56
- package/src/transformers/removeInlineSizes.js +0 -43
- package/src/transformers/removeInlinedSelectors.js +0 -100
- package/src/transformers/removeUnusedCss.js +0 -48
- package/src/transformers/shorthandInlineCSS.js +0 -26
- package/src/utils/helpers.js +0 -13
- package/types/baseUrl.d.ts +0 -79
- package/types/fetch.d.ts +0 -143
- package/types/inlineCss.d.ts +0 -207
- package/types/layouts.d.ts +0 -39
- package/types/removeUnusedCss.d.ts +0 -115
- package/types/tailwind.d.ts +0 -22
- package/types/templates.d.ts +0 -181
package/bin/maizzle
CHANGED
package/package.json
CHANGED
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maizzle/framework",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0-beta.0",
|
|
4
4
|
"description": "Maizzle is a framework that helps you quickly build HTML emails with Tailwind CSS.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./src/index.js",
|
|
10
|
+
"types": "./types/index.d.ts"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
8
13
|
"bin": {
|
|
9
14
|
"maizzle": "bin/maizzle"
|
|
10
15
|
},
|
|
16
|
+
"files": [
|
|
17
|
+
"bin",
|
|
18
|
+
"src",
|
|
19
|
+
"types"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"dev": "vitest",
|
|
26
|
+
"lint": "biome lint ./src ./test",
|
|
27
|
+
"pretest": "npm run lint",
|
|
28
|
+
"test": "vitest run --coverage",
|
|
29
|
+
"release": "npx np"
|
|
30
|
+
},
|
|
11
31
|
"repository": {
|
|
12
32
|
"type": "git",
|
|
13
33
|
"url": "https://github.com/maizzle/framework.git"
|
|
@@ -27,72 +47,59 @@
|
|
|
27
47
|
"email-boilerplate",
|
|
28
48
|
"html-emails"
|
|
29
49
|
],
|
|
30
|
-
"publishConfig": {
|
|
31
|
-
"access": "public"
|
|
32
|
-
},
|
|
33
|
-
"scripts": {
|
|
34
|
-
"test": "c8 ava",
|
|
35
|
-
"pretest": "xo",
|
|
36
|
-
"style": "xo",
|
|
37
|
-
"release": "np"
|
|
38
|
-
},
|
|
39
|
-
"files": [
|
|
40
|
-
"src",
|
|
41
|
-
"types",
|
|
42
|
-
"bin"
|
|
43
|
-
],
|
|
44
50
|
"dependencies": {
|
|
45
|
-
"@
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
51
|
+
"@csstools/css-calc": "^1.2.2",
|
|
52
|
+
"@maizzle/cli": "next",
|
|
53
|
+
"cheerio": "^1.0.0-rc.12",
|
|
54
|
+
"chokidar": "^3.6.0",
|
|
55
|
+
"cli-table3": "^0.6.5",
|
|
56
|
+
"color-shorthand-hex-to-six-digit": "^5.0.16",
|
|
57
|
+
"defu": "^6.1.4",
|
|
58
|
+
"email-comb": "^7.0.21",
|
|
59
|
+
"express": "^4.19.2",
|
|
50
60
|
"fast-glob": "^3.3.2",
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
61
|
+
"gray-matter": "^4.0.3",
|
|
62
|
+
"html-crush": "^6.0.18",
|
|
63
|
+
"is-url-superb": "^6.1.0",
|
|
64
|
+
"istextorbinary": "^9.5.0",
|
|
55
65
|
"juice": "^10.0.0",
|
|
56
|
-
"lodash": "^4.17.
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"postcss
|
|
66
|
+
"lodash-es": "^4.17.21",
|
|
67
|
+
"morphdom": "^2.7.2",
|
|
68
|
+
"ora": "^8.0.1",
|
|
69
|
+
"pathe": "^1.1.2",
|
|
70
|
+
"postcss": "^8.4.38",
|
|
71
|
+
"postcss-custom-properties": "^13.3.10",
|
|
72
|
+
"postcss-import": "^16.1.0",
|
|
73
|
+
"postcss-safe-parser": "^7.0.0",
|
|
61
74
|
"posthtml": "^0.16.6",
|
|
62
75
|
"posthtml-attrs-parser": "^1.1.0",
|
|
63
|
-
"posthtml-base-url": "^
|
|
76
|
+
"posthtml-base-url": "^3.1.2",
|
|
64
77
|
"posthtml-component": "^1.1.0",
|
|
65
|
-
"posthtml-content": "^
|
|
66
|
-
"posthtml-
|
|
67
|
-
"posthtml-
|
|
68
|
-
"posthtml-
|
|
69
|
-
"posthtml-
|
|
70
|
-
"posthtml-
|
|
71
|
-
"posthtml-mso": "^2.0.1",
|
|
78
|
+
"posthtml-content": "^2.0.1",
|
|
79
|
+
"posthtml-extra-attributes": "^3.0.0",
|
|
80
|
+
"posthtml-markdownit": "^3.0.1",
|
|
81
|
+
"posthtml-mso": "^3.0.0",
|
|
82
|
+
"posthtml-parser": "^0.12.0",
|
|
83
|
+
"posthtml-postcss": "^1.0.0",
|
|
72
84
|
"posthtml-postcss-merge-longhand": "^3.1.0",
|
|
73
|
-
"posthtml-
|
|
74
|
-
"posthtml-
|
|
85
|
+
"posthtml-render": "^3.0.0",
|
|
86
|
+
"posthtml-safe-class-names": "^4.0.2",
|
|
87
|
+
"posthtml-url-parameters": "^3.0.0",
|
|
75
88
|
"pretty": "^2.0.0",
|
|
76
|
-
"
|
|
77
|
-
"string-
|
|
78
|
-
"
|
|
79
|
-
"
|
|
89
|
+
"string-remove-widows": "^4.0.22",
|
|
90
|
+
"string-strip-html": "^13.4.8",
|
|
91
|
+
"tailwindcss": "^3.4.4",
|
|
92
|
+
"ws": "^8.17.0"
|
|
80
93
|
},
|
|
81
94
|
"devDependencies": {
|
|
82
|
-
"@
|
|
83
|
-
"@types/js-beautify": "^1.14.
|
|
84
|
-
"@types/markdown-it": "^14.
|
|
85
|
-
"
|
|
86
|
-
"
|
|
87
|
-
"
|
|
88
|
-
"xo": "0.39.1"
|
|
95
|
+
"@biomejs/biome": "^1.8.3",
|
|
96
|
+
"@types/js-beautify": "^1.14.3",
|
|
97
|
+
"@types/markdown-it": "^14.1.1",
|
|
98
|
+
"@vitest/coverage-v8": "^2.0.1",
|
|
99
|
+
"supertest": "^7.0.0",
|
|
100
|
+
"vitest": "^2.0.1"
|
|
89
101
|
},
|
|
90
102
|
"engines": {
|
|
91
|
-
"node": ">=
|
|
92
|
-
},
|
|
93
|
-
"ava": {
|
|
94
|
-
"files": [
|
|
95
|
-
"test/**/test*.js"
|
|
96
|
-
]
|
|
103
|
+
"node": ">=18.0.0"
|
|
97
104
|
}
|
|
98
105
|
}
|
package/src/commands/build.js
CHANGED
|
@@ -1,27 +1,252 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import {
|
|
2
|
+
readFile,
|
|
3
|
+
writeFile,
|
|
4
|
+
copyFile,
|
|
5
|
+
lstat,
|
|
6
|
+
mkdir,
|
|
7
|
+
rm
|
|
8
|
+
} from 'node:fs/promises'
|
|
9
|
+
import path from 'pathe'
|
|
10
|
+
import fg from 'fast-glob'
|
|
11
|
+
import { defu as merge } from 'defu'
|
|
5
12
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
13
|
+
import get from 'lodash/get.js'
|
|
14
|
+
import isEmpty from 'lodash-es/isEmpty.js'
|
|
15
|
+
import { isBinary } from 'istextorbinary'
|
|
9
16
|
|
|
10
|
-
|
|
17
|
+
import ora from 'ora'
|
|
18
|
+
import pico from 'picocolors'
|
|
19
|
+
import cliTable from 'cli-table3'
|
|
11
20
|
|
|
12
|
-
|
|
21
|
+
import { render } from '../generators/render.js'
|
|
22
|
+
import { formatTime } from '../utils/string.js'
|
|
23
|
+
import { getColorizedFileSize } from '../utils/node.js'
|
|
24
|
+
import { readFileConfig } from '../utils/getConfigByFilePath.js'
|
|
25
|
+
import {
|
|
26
|
+
generatePlaintext,
|
|
27
|
+
handlePlaintextTags,
|
|
28
|
+
writePlaintextFile
|
|
29
|
+
} from '../generators/plaintext.js'
|
|
13
30
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Compile templates and output to the build directory.
|
|
33
|
+
* Returns a promise containing an object with files output and the config object.
|
|
34
|
+
*
|
|
35
|
+
* @param {object|string} config - The Maizzle config object, or path to a config file.
|
|
36
|
+
* @returns {Promise<object>} The build output, containing the files and config.
|
|
37
|
+
*/
|
|
38
|
+
export default async (config = {}) => {
|
|
39
|
+
const spinner = ora()
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const startTime = Date.now()
|
|
43
|
+
|
|
44
|
+
// Compute config
|
|
45
|
+
config = await readFileConfig(config).catch(() => { throw new Error('Could not compute config') })
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Support customizing the spinner
|
|
49
|
+
*/
|
|
50
|
+
const spinnerConfig = get(config, 'build.spinner')
|
|
51
|
+
|
|
52
|
+
if (spinnerConfig === false) {
|
|
53
|
+
// Show only 'Building...' text
|
|
54
|
+
spinner.isEnabled = false
|
|
55
|
+
} else {
|
|
56
|
+
spinner.spinner = get(config, 'build.spinner', 'circleHalves')
|
|
17
57
|
}
|
|
18
58
|
|
|
19
|
-
spinner.
|
|
20
|
-
} else {
|
|
21
|
-
spinner.succeed(`Built ${parsed.length} templates in ${elapsedSeconds}s`)
|
|
22
|
-
}
|
|
59
|
+
spinner.start('Building...')
|
|
23
60
|
|
|
24
|
-
|
|
25
|
-
|
|
61
|
+
// Run beforeCreate event
|
|
62
|
+
if (typeof config.beforeCreate === 'function') {
|
|
63
|
+
await config.beforeCreate({ config })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const buildOutputPath = get(config, 'build.output.path', 'build_local')
|
|
67
|
+
|
|
68
|
+
// Remove output directory
|
|
69
|
+
await rm(buildOutputPath, { recursive: true, force: true })
|
|
70
|
+
|
|
71
|
+
const table = new cliTable({
|
|
72
|
+
head: ['File name', 'File size', 'Build time'].map(item => pico.bold(item)),
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
// Determine paths of templates to build
|
|
76
|
+
const userFilePaths = get(config, 'build.content', 'src/templates/**/*.html')
|
|
77
|
+
const templateFolders = Array.isArray(userFilePaths) ? userFilePaths : [userFilePaths]
|
|
78
|
+
const templatePaths = await fg.glob([...new Set(templateFolders)])
|
|
79
|
+
|
|
80
|
+
// If there are no templates to build, throw error
|
|
81
|
+
if (templatePaths.length === 0) {
|
|
82
|
+
throw new Error(`No templates found in ${pico.inverse(templateFolders)}`)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const baseDirs = templateFolders.filter(p => !p.startsWith('!')).map(p => {
|
|
86
|
+
const parts = p.split('/')
|
|
87
|
+
// remove the glob part (e.g., **/*.html):
|
|
88
|
+
return parts.filter(part => !part.includes('*')).join('/')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Check for binary files
|
|
93
|
+
*
|
|
94
|
+
* We store paths to binary files in a separate array, because we don't want
|
|
95
|
+
* to render them. These files will be treated as static files and will
|
|
96
|
+
* be copied directly to the output directory, just like the
|
|
97
|
+
* `build.static` folders.
|
|
98
|
+
*/
|
|
99
|
+
const binaryPaths = await fg.glob([...new Set(baseDirs.map(base => `${base}/**/*.*`))])
|
|
100
|
+
.then(paths => paths.filter(file => isBinary(file)))
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Render templates
|
|
104
|
+
*
|
|
105
|
+
* Render each template and write the output to the output directory,
|
|
106
|
+
* preserving the relative path.
|
|
107
|
+
*/
|
|
108
|
+
for await (const templatePath of templatePaths) {
|
|
109
|
+
const templateBuildStartTime = Date.now()
|
|
110
|
+
|
|
111
|
+
// Determine the base directory the template belongs to
|
|
112
|
+
const baseDir = baseDirs.find(base => templatePath.startsWith(base))
|
|
113
|
+
|
|
114
|
+
// Compute the relative path
|
|
115
|
+
const relativePath = path.relative(baseDir, templatePath)
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Add the current template path to the config
|
|
119
|
+
*
|
|
120
|
+
* Can be used in events like `beforeRender` to determine
|
|
121
|
+
* which template file is being rendered.
|
|
122
|
+
*/
|
|
123
|
+
config.build.current = {
|
|
124
|
+
path: path.parse(templatePath),
|
|
125
|
+
baseDir,
|
|
126
|
+
relativePath,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const html = await readFile(templatePath, 'utf8')
|
|
130
|
+
|
|
131
|
+
const rendered = await render(html, config)
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Generate plaintext
|
|
135
|
+
*
|
|
136
|
+
* We do this first so that we can remove the <plaintext>
|
|
137
|
+
* tags from the markup before outputting the file.
|
|
138
|
+
*/
|
|
139
|
+
const plaintextConfig = get(rendered.config, 'plaintext')
|
|
140
|
+
|
|
141
|
+
if (Boolean(plaintextConfig) || !isEmpty(plaintextConfig)) {
|
|
142
|
+
const posthtmlOptions = get(rendered.config, 'posthtml.options', {})
|
|
143
|
+
|
|
144
|
+
const plaintext = await generatePlaintext(rendered.html, merge(plaintextConfig, posthtmlOptions))
|
|
145
|
+
rendered.html = await handlePlaintextTags(rendered.html, posthtmlOptions)
|
|
146
|
+
await writePlaintextFile(plaintext, rendered.config)
|
|
147
|
+
.catch(error => {
|
|
148
|
+
throw new Error(`Error writing plaintext file: ${error}`)
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Determine output path, creating directories if needed
|
|
154
|
+
*
|
|
155
|
+
* Prioritize `permalink` path from Front Matter,
|
|
156
|
+
* fallback to the current template path.
|
|
157
|
+
*
|
|
158
|
+
* We do this before generating plaintext, so that
|
|
159
|
+
* any paths will already have been created.
|
|
160
|
+
*/
|
|
161
|
+
const outputPathFromConfig = get(rendered.config, 'permalink', path.join(buildOutputPath, relativePath))
|
|
162
|
+
const parsedOutputPath = path.parse(outputPathFromConfig)
|
|
163
|
+
const extension = get(rendered.config, 'build.output.extension', parsedOutputPath.ext.slice(1))
|
|
164
|
+
const outputPath = `${parsedOutputPath.dir}/${parsedOutputPath.name}.${extension}`
|
|
26
165
|
|
|
27
|
-
|
|
166
|
+
const pathExists = await lstat(path.dirname(outputPath)).catch(() => false)
|
|
167
|
+
|
|
168
|
+
if (!pathExists) {
|
|
169
|
+
await mkdir(path.dirname(outputPath), { recursive: true })
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Write the rendered HTML to disk, creating directories if needed
|
|
174
|
+
*/
|
|
175
|
+
await writeFile(outputPath, rendered.html)
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Add file to CLI table for build summary logging
|
|
179
|
+
*/
|
|
180
|
+
if (config.build.summary) {
|
|
181
|
+
table.push([
|
|
182
|
+
path.relative(get(rendered.config, 'build.output.path'), outputPath),
|
|
183
|
+
getColorizedFileSize(rendered.html),
|
|
184
|
+
formatTime(Date.now() - templateBuildStartTime)
|
|
185
|
+
])
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Copy static files
|
|
191
|
+
*
|
|
192
|
+
* Copy binary files that are alongside templates as well as
|
|
193
|
+
* files from `build.static`, to the output directory.
|
|
194
|
+
*
|
|
195
|
+
* TODO: support an array of objects with source and destination, i.e. static: [{ source: 'src/assets', destination: 'assets' }, ...]
|
|
196
|
+
*/
|
|
197
|
+
|
|
198
|
+
// Copy binary files that are alongside templates
|
|
199
|
+
for await (const binaryPath of binaryPaths) {
|
|
200
|
+
const relativePath = path.relative(get(config, 'build.current.baseDir'), binaryPath)
|
|
201
|
+
const outputPath = path.join(get(config, 'build.output.path'), get(config, 'build.static.destination'), relativePath)
|
|
202
|
+
|
|
203
|
+
await mkdir(path.dirname(outputPath), { recursive: true })
|
|
204
|
+
await copyFile(binaryPath, outputPath)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Copy files from `build.static`
|
|
208
|
+
const staticSourcePaths = await fg.glob([...new Set(get(config, 'build.static.source', []))])
|
|
209
|
+
.then(paths => paths.filter(file => isBinary(file)))
|
|
210
|
+
|
|
211
|
+
if (!isEmpty(staticSourcePaths)) {
|
|
212
|
+
for await (const staticPath of staticSourcePaths) {
|
|
213
|
+
const relativePath = path.relative(get(config, 'build.current.baseDir'), staticPath)
|
|
214
|
+
const outputPath = path.join(get(config, 'build.output.path'), get(config, 'build.static.destination'), relativePath)
|
|
215
|
+
|
|
216
|
+
await mkdir(path.dirname(outputPath), { recursive: true })
|
|
217
|
+
await copyFile(staticPath, outputPath)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const compiledFiles = await fg.glob(path.join(config.build.output.path, '**/*'))
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Run `afterBuild` event
|
|
225
|
+
*/
|
|
226
|
+
if (typeof config.afterBuild === 'function') {
|
|
227
|
+
await config.afterBuild({ files: compiledFiles, config, render })
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Log a build summary if enabled in the config
|
|
232
|
+
*
|
|
233
|
+
* Need to first clear the spinner
|
|
234
|
+
*/
|
|
235
|
+
|
|
236
|
+
spinner.clear()
|
|
237
|
+
|
|
238
|
+
if (config.build.summary) {
|
|
239
|
+
console.log(table.toString() + '\n')
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
spinner.succeed(`Build completed in ${formatTime(Date.now() - startTime)}`)
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
files: compiledFiles,
|
|
246
|
+
config
|
|
247
|
+
}
|
|
248
|
+
} catch (error) {
|
|
249
|
+
spinner.fail('Build failed')
|
|
250
|
+
throw error
|
|
251
|
+
}
|
|
252
|
+
}
|
package/src/commands/serve.js
CHANGED
|
@@ -1,198 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
const path = require('path')
|
|
3
|
-
const fs = require('fs-extra')
|
|
1
|
+
import server from '../server/index.js'
|
|
4
2
|
|
|
5
|
-
|
|
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
|
-
/**
|
|
13
|
-
* Initialize Browsersync on-demand
|
|
14
|
-
* https://github.com/maizzle/framework/issues/605
|
|
15
|
-
*/
|
|
16
|
-
const browsersync = () => {
|
|
17
|
-
if (!global.cachedBrowserSync) {
|
|
18
|
-
const bs = require('browser-sync')
|
|
19
|
-
global.cachedBrowserSync = bs.create()
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return global.cachedBrowserSync
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const getConfig = async (env = 'local', config = {}) => merge(
|
|
26
|
-
config,
|
|
27
|
-
await Config.getMerged(env)
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
const serve = async (env = 'local', config = {}) => {
|
|
31
|
-
config = await getConfig(env, merge(config, {
|
|
32
|
-
build: {
|
|
33
|
-
command: 'serve'
|
|
34
|
-
}
|
|
35
|
-
}))
|
|
36
|
-
|
|
37
|
-
const spinner = ora()
|
|
38
|
-
|
|
39
|
-
// Build all emails first
|
|
40
|
-
await buildToFile(env, config)
|
|
41
|
-
|
|
42
|
-
// Set some paths to watch
|
|
43
|
-
let templates = get(config, 'build.templates')
|
|
44
|
-
templates = Array.isArray(templates) ? templates : [templates]
|
|
45
|
-
|
|
46
|
-
const templatePaths = [...new Set(templates.map(config => `${get(config, 'source', 'src')}/**`))]
|
|
47
|
-
const tailwindConfig = get(config, 'build.tailwind.config', 'tailwind.config.js')
|
|
48
|
-
const globalPaths = [
|
|
49
|
-
'src/**',
|
|
50
|
-
...new Set(get(config, 'build.browsersync.watch', []))
|
|
51
|
-
]
|
|
52
|
-
|
|
53
|
-
if (typeof tailwindConfig === 'string') {
|
|
54
|
-
globalPaths.push(tailwindConfig)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Watch for Template file changes
|
|
58
|
-
browsersync()
|
|
59
|
-
.watch(templatePaths)
|
|
60
|
-
.on('change', async file => {
|
|
61
|
-
config = await getConfig(env, config)
|
|
62
|
-
|
|
63
|
-
if (config.events && typeof config.events.beforeCreate === 'function') {
|
|
64
|
-
await config.events.beforeCreate(config)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Don't render if file type is not configured
|
|
68
|
-
// eslint-disable-next-line
|
|
69
|
-
const filetypes = templates.reduce((acc, template) => {
|
|
70
|
-
return [...acc, ...get(template, 'filetypes', ['html'])]
|
|
71
|
-
}, [])
|
|
72
|
-
|
|
73
|
-
if (!filetypes.includes(path.extname(file).slice(1))) {
|
|
74
|
-
return
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Clear console if enabled
|
|
78
|
-
if (get(config, 'build.console.clear')) {
|
|
79
|
-
clearConsole()
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Start the spinner
|
|
83
|
-
const start = new Date()
|
|
84
|
-
spinner.start('Building email...')
|
|
85
|
-
|
|
86
|
-
// Render the template
|
|
87
|
-
renderToString(
|
|
88
|
-
await fs.readFile(file.replace(/\\/g, '/'), 'utf8'),
|
|
89
|
-
{
|
|
90
|
-
maizzle: merge(
|
|
91
|
-
config,
|
|
92
|
-
{
|
|
93
|
-
build: {
|
|
94
|
-
current: {
|
|
95
|
-
path: path.parse(file)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
),
|
|
100
|
-
...config.events
|
|
101
|
-
}
|
|
102
|
-
)
|
|
103
|
-
.then(async ({html, config}) => {
|
|
104
|
-
// Write the file to disk
|
|
105
|
-
let source = ''
|
|
106
|
-
let dest = ''
|
|
107
|
-
let ext = ''
|
|
108
|
-
|
|
109
|
-
if (Array.isArray(config.build.templates)) {
|
|
110
|
-
const match = config.build.templates.find(template => path.parse(file).dir.includes(path.normalize(template.source)))
|
|
111
|
-
source = path.normalize(get(match, 'source'))
|
|
112
|
-
dest = path.normalize(get(match, 'destination.path', 'build_local'))
|
|
113
|
-
ext = get(match, 'destination.ext', 'html')
|
|
114
|
-
} else if (isObject(config.build.templates)) {
|
|
115
|
-
source = path.normalize(get(config, 'build.templates.source'))
|
|
116
|
-
dest = path.normalize(get(config, 'build.templates.destination.path', 'build_local'))
|
|
117
|
-
ext = get(config, 'build.templates.destination.ext', 'html')
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const fileDir = path.parse(file).dir.replace(source, '')
|
|
121
|
-
const finalDestination = path.join(dest, fileDir, `${path.parse(file).name}.${ext}`)
|
|
122
|
-
|
|
123
|
-
await fs.outputFile(config.permalink || finalDestination, html)
|
|
124
|
-
})
|
|
125
|
-
.then(() => {
|
|
126
|
-
browsersync().reload()
|
|
127
|
-
spinner.succeed(`Compiled in ${(Date.now() - start) / 1000}s [${file}]`)
|
|
128
|
-
})
|
|
129
|
-
.catch(error => {
|
|
130
|
-
throw error
|
|
131
|
-
})
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
// Watch for changes in all other files
|
|
135
|
-
browsersync()
|
|
136
|
-
.watch(globalPaths, {ignored: templatePaths})
|
|
137
|
-
.on('change', () => buildToFile(env, config)
|
|
138
|
-
.then(() => browsersync().reload())
|
|
139
|
-
.catch(error => {
|
|
140
|
-
throw error
|
|
141
|
-
})
|
|
142
|
-
)
|
|
143
|
-
.on('unlink', () => buildToFile(env, config)
|
|
144
|
-
.then(() => browsersync().reload())
|
|
145
|
-
.catch(error => {
|
|
146
|
-
throw error
|
|
147
|
-
})
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
// Watch for changes in config files
|
|
151
|
-
browsersync()
|
|
152
|
-
.watch('{maizzle.config*,config*}.{js,cjs}')
|
|
153
|
-
.on('change', async file => {
|
|
154
|
-
const fileName = path.parse(file).base
|
|
155
|
-
const match = fileName.match(/\.?config\.(.+?)\./)
|
|
156
|
-
|
|
157
|
-
const parsedEnv = match ? match[1] : env || 'local'
|
|
158
|
-
|
|
159
|
-
Config
|
|
160
|
-
.getMerged(parsedEnv)
|
|
161
|
-
.then(config => buildToFile(parsedEnv, config)
|
|
162
|
-
.then(() => browsersync().reload())
|
|
163
|
-
.catch(error => {
|
|
164
|
-
throw error
|
|
165
|
-
})
|
|
166
|
-
)
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
// Browsersync options
|
|
170
|
-
const baseDir = templates.map(t => t.destination.path)
|
|
171
|
-
|
|
172
|
-
// Initialize Browsersync
|
|
173
|
-
browsersync()
|
|
174
|
-
.init(
|
|
175
|
-
merge(
|
|
176
|
-
{
|
|
177
|
-
notify: false,
|
|
178
|
-
open: false,
|
|
179
|
-
port: 3000,
|
|
180
|
-
server: {
|
|
181
|
-
baseDir,
|
|
182
|
-
directory: true
|
|
183
|
-
},
|
|
184
|
-
tunnel: false,
|
|
185
|
-
ui: {port: 3001},
|
|
186
|
-
logFileChanges: false,
|
|
187
|
-
watchOptions: {
|
|
188
|
-
awaitWriteFinish: {
|
|
189
|
-
stabilityThreshold: 150,
|
|
190
|
-
pollInterval: 25
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
},
|
|
194
|
-
get(config, 'build.browsersync', {})
|
|
195
|
-
), () => {})
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
module.exports = serve
|
|
3
|
+
export default server
|