@maizzle/framework 5.0.0-beta.2 → 5.0.0-beta.20
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 +23 -20
- package/src/commands/build.js +138 -70
- package/src/generators/plaintext.js +13 -14
- package/src/generators/render.js +8 -7
- package/src/posthtml/index.js +34 -5
- package/src/posthtml/plugins/envAttributes.js +32 -0
- package/src/posthtml/plugins/envTags.js +33 -0
- package/src/server/index.js +155 -92
- package/src/server/views/404.html +59 -0
- package/src/transformers/addAttributes.js +1 -1
- package/src/transformers/comb.js +1 -1
- package/src/transformers/core.js +12 -0
- package/src/transformers/index.js +32 -40
- package/src/transformers/inline.js +6 -4
- package/src/transformers/minify.js +2 -1
- package/src/transformers/replaceStrings.js +6 -2
- package/src/transformers/template.js +26 -0
- package/src/utils/string.js +79 -0
- package/types/build.d.ts +59 -24
- package/types/config.d.ts +54 -48
- package/types/css/purge.d.ts +1 -1
- package/types/events.d.ts +153 -5
- package/types/posthtml.d.ts +3 -3
- package/types/urlParameters.d.ts +1 -1
- package/types/components.d.ts +0 -195
- package/types/expressions.d.ts +0 -100
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maizzle/framework",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.20",
|
|
4
4
|
"description": "Maizzle is a framework that helps you quickly build HTML emails with Tailwind CSS.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,9 +23,10 @@
|
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"dev": "vitest",
|
|
26
|
-
"
|
|
26
|
+
"release": "npx np",
|
|
27
27
|
"pretest": "npm run lint",
|
|
28
|
-
"test": "vitest run --coverage"
|
|
28
|
+
"test": "vitest run --coverage",
|
|
29
|
+
"lint": "biome lint ./src ./test"
|
|
29
30
|
},
|
|
30
31
|
"repository": {
|
|
31
32
|
"type": "git",
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
"html-emails"
|
|
48
49
|
],
|
|
49
50
|
"dependencies": {
|
|
50
|
-
"@csstools/css-calc": "^1.2.
|
|
51
|
+
"@csstools/css-calc": "^1.2.4",
|
|
51
52
|
"@maizzle/cli": "next",
|
|
52
53
|
"cheerio": "^1.0.0-rc.12",
|
|
53
54
|
"chokidar": "^3.6.0",
|
|
@@ -63,35 +64,37 @@
|
|
|
63
64
|
"istextorbinary": "^9.5.0",
|
|
64
65
|
"juice": "^10.0.0",
|
|
65
66
|
"lodash-es": "^4.17.21",
|
|
66
|
-
"morphdom": "^2.7.
|
|
67
|
+
"morphdom": "^2.7.4",
|
|
67
68
|
"ora": "^8.0.1",
|
|
68
69
|
"pathe": "^1.1.2",
|
|
69
|
-
"postcss": "^8.4.
|
|
70
|
-
"postcss-custom-properties": "^13.3.
|
|
70
|
+
"postcss": "^8.4.39",
|
|
71
|
+
"postcss-custom-properties": "^13.3.12",
|
|
71
72
|
"postcss-import": "^16.1.0",
|
|
72
73
|
"postcss-safe-parser": "^7.0.0",
|
|
73
74
|
"posthtml": "^0.16.6",
|
|
74
|
-
"posthtml-attrs-parser": "^1.1.
|
|
75
|
-
"posthtml-base-url": "^3.1.
|
|
76
|
-
"posthtml-component": "^
|
|
77
|
-
"posthtml-content": "^2.0
|
|
78
|
-
"posthtml-
|
|
79
|
-
"posthtml-
|
|
80
|
-
"posthtml-
|
|
75
|
+
"posthtml-attrs-parser": "^1.1.1",
|
|
76
|
+
"posthtml-base-url": "^3.1.4",
|
|
77
|
+
"posthtml-component": "^2.0.0",
|
|
78
|
+
"posthtml-content": "^2.1.0",
|
|
79
|
+
"posthtml-expressions": "^1.11.4",
|
|
80
|
+
"posthtml-extra-attributes": "^3.1.0",
|
|
81
|
+
"posthtml-fetch": "^4.0.0",
|
|
82
|
+
"posthtml-markdownit": "^3.1.0",
|
|
83
|
+
"posthtml-mso": "^3.1.0",
|
|
81
84
|
"posthtml-parser": "^0.12.0",
|
|
82
|
-
"posthtml-postcss": "^1.0.
|
|
83
|
-
"posthtml-postcss-merge-longhand": "^3.1.
|
|
85
|
+
"posthtml-postcss": "^1.0.2",
|
|
86
|
+
"posthtml-postcss-merge-longhand": "^3.1.2",
|
|
84
87
|
"posthtml-render": "^3.0.0",
|
|
85
|
-
"posthtml-safe-class-names": "^4.0
|
|
86
|
-
"posthtml-url-parameters": "^3.
|
|
88
|
+
"posthtml-safe-class-names": "^4.1.0",
|
|
89
|
+
"posthtml-url-parameters": "^3.1.0",
|
|
87
90
|
"pretty": "^2.0.0",
|
|
88
91
|
"string-remove-widows": "^4.0.22",
|
|
89
92
|
"string-strip-html": "^13.4.8",
|
|
90
|
-
"tailwindcss": "^3.4.
|
|
93
|
+
"tailwindcss": "^3.4.7",
|
|
91
94
|
"ws": "^8.17.0"
|
|
92
95
|
},
|
|
93
96
|
"devDependencies": {
|
|
94
|
-
"@biomejs/biome": "
|
|
97
|
+
"@biomejs/biome": "1.8.3",
|
|
95
98
|
"@types/js-beautify": "^1.14.3",
|
|
96
99
|
"@types/markdown-it": "^14.1.1",
|
|
97
100
|
"@vitest/coverage-v8": "^2.0.1",
|
package/src/commands/build.js
CHANGED
|
@@ -4,7 +4,8 @@ import {
|
|
|
4
4
|
copyFile,
|
|
5
5
|
lstat,
|
|
6
6
|
mkdir,
|
|
7
|
-
rm
|
|
7
|
+
rm,
|
|
8
|
+
cp,
|
|
8
9
|
} from 'node:fs/promises'
|
|
9
10
|
import path from 'pathe'
|
|
10
11
|
import fg from 'fast-glob'
|
|
@@ -12,22 +13,50 @@ import { defu as merge } from 'defu'
|
|
|
12
13
|
|
|
13
14
|
import get from 'lodash/get.js'
|
|
14
15
|
import isEmpty from 'lodash-es/isEmpty.js'
|
|
15
|
-
import { isBinary } from 'istextorbinary'
|
|
16
16
|
|
|
17
17
|
import ora from 'ora'
|
|
18
18
|
import pico from 'picocolors'
|
|
19
19
|
import cliTable from 'cli-table3'
|
|
20
20
|
|
|
21
21
|
import { render } from '../generators/render.js'
|
|
22
|
-
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
formatTime,
|
|
25
|
+
getRootDirectories,
|
|
26
|
+
getFileExtensionsFromPattern,
|
|
27
|
+
} from '../utils/string.js'
|
|
28
|
+
|
|
23
29
|
import { getColorizedFileSize } from '../utils/node.js'
|
|
24
|
-
|
|
30
|
+
|
|
25
31
|
import {
|
|
26
32
|
generatePlaintext,
|
|
27
33
|
handlePlaintextTags,
|
|
28
34
|
writePlaintextFile
|
|
29
35
|
} from '../generators/plaintext.js'
|
|
30
36
|
|
|
37
|
+
import { readFileConfig } from '../utils/getConfigByFilePath.js'
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Ensures that a directory exists, creating it if needed.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} filePath - The path to the file to check.
|
|
43
|
+
*/
|
|
44
|
+
async function ensureDirectoryExistence(filePath) {
|
|
45
|
+
const dirname = path.dirname(filePath)
|
|
46
|
+
await mkdir(dirname, { recursive: true })
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Copy a file from source to target.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} source - The source file path.
|
|
53
|
+
* @param {string} target - The target file path.
|
|
54
|
+
*/
|
|
55
|
+
async function copyFileAsync(source, target) {
|
|
56
|
+
await ensureDirectoryExistence(target)
|
|
57
|
+
await copyFile(source, target)
|
|
58
|
+
}
|
|
59
|
+
|
|
31
60
|
/**
|
|
32
61
|
* Compile templates and output to the build directory.
|
|
33
62
|
* Returns a promise containing an object with files output and the config object.
|
|
@@ -41,7 +70,10 @@ export default async (config = {}) => {
|
|
|
41
70
|
try {
|
|
42
71
|
const startTime = Date.now()
|
|
43
72
|
|
|
44
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Read the config file for this environment,
|
|
75
|
+
* merging it with the default config.
|
|
76
|
+
*/
|
|
45
77
|
config = await readFileConfig(config).catch(() => { throw new Error('Could not compute config') })
|
|
46
78
|
|
|
47
79
|
/**
|
|
@@ -72,9 +104,12 @@ export default async (config = {}) => {
|
|
|
72
104
|
head: ['File name', 'File size', 'Build time'].map(item => pico.bold(item)),
|
|
73
105
|
})
|
|
74
106
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
107
|
+
/**
|
|
108
|
+
* Check that templates to be built, actually exist
|
|
109
|
+
*/
|
|
110
|
+
const contentPaths = get(config, 'build.content', ['src/templates/**/*.html'])
|
|
111
|
+
|
|
112
|
+
const templateFolders = Array.isArray(contentPaths) ? contentPaths : [contentPaths]
|
|
78
113
|
const templatePaths = await fg.glob([...new Set(templateFolders)])
|
|
79
114
|
|
|
80
115
|
// If there are no templates to build, throw error
|
|
@@ -82,37 +117,84 @@ export default async (config = {}) => {
|
|
|
82
117
|
throw new Error(`No templates found in ${pico.inverse(templateFolders)}`)
|
|
83
118
|
}
|
|
84
119
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
120
|
+
/**
|
|
121
|
+
* Copy source directories to destination
|
|
122
|
+
*
|
|
123
|
+
* Copies each `build.content` path to the `build.output.path` directory.
|
|
124
|
+
*/
|
|
125
|
+
let from = get(config, 'build.output.from', ['src/templates', 'src'])
|
|
126
|
+
|
|
127
|
+
const globPathsToCopy = contentPaths.map(glob => {
|
|
128
|
+
// Keep negated paths as they are
|
|
129
|
+
if (glob.startsWith('!')) {
|
|
130
|
+
return glob
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Keep single-file sources as they are
|
|
134
|
+
if (!/\*/.test(glob)) {
|
|
135
|
+
return glob
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Update non-negated paths to target all files, avoiding duplication
|
|
139
|
+
return glob.replace(/\/\*\*\/\*\.\{.*?\}$|\/\*\*\/\*\.[^/]*$|\/*\.[^/]*$/, '/**/*')
|
|
89
140
|
})
|
|
90
141
|
|
|
142
|
+
try {
|
|
143
|
+
from = Array.isArray(from) ? from : [from]
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Copy files from source to destination
|
|
147
|
+
*
|
|
148
|
+
* The array/set conversion is to remove duplicates
|
|
149
|
+
*/
|
|
150
|
+
for (const file of await fg(Array.from(new Set(globPathsToCopy)))) {
|
|
151
|
+
let relativePath
|
|
152
|
+
for (const dir of from) {
|
|
153
|
+
if (file.startsWith(dir)) {
|
|
154
|
+
relativePath = path.relative(dir, file)
|
|
155
|
+
break
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (!relativePath) {
|
|
159
|
+
relativePath = path.relative('.', file)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const targetPath = path.join(config.build.output.path, relativePath)
|
|
163
|
+
await copyFileAsync(file, targetPath)
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.error(`Error while processing pattern ${pattern}: `, err);
|
|
167
|
+
}
|
|
168
|
+
|
|
91
169
|
/**
|
|
92
|
-
*
|
|
170
|
+
* Get a list of files to render, from the output directory
|
|
93
171
|
*
|
|
94
|
-
*
|
|
95
|
-
* to
|
|
96
|
-
* be copied directly to the output directory, just like the
|
|
97
|
-
* `build.static` folders.
|
|
172
|
+
* Uses all file extensions from non-negated glob paths in `build.content`
|
|
173
|
+
* to determine which files to render from the output directory.
|
|
98
174
|
*/
|
|
99
|
-
const
|
|
100
|
-
|
|
175
|
+
const outputExtensions = new Set()
|
|
176
|
+
|
|
177
|
+
for (const pattern of contentPaths) {
|
|
178
|
+
outputExtensions.add(...getFileExtensionsFromPattern(pattern))
|
|
179
|
+
}
|
|
101
180
|
|
|
102
181
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* Render each template and write the output to the output directory,
|
|
106
|
-
* preserving the relative path.
|
|
182
|
+
* Create a list of templates to compile
|
|
107
183
|
*/
|
|
108
|
-
|
|
109
|
-
const templateBuildStartTime = Date.now()
|
|
184
|
+
const extensions = outputExtensions.size > 1 ? `{${[...outputExtensions].join(',')}}` : 'html'
|
|
110
185
|
|
|
111
|
-
|
|
112
|
-
|
|
186
|
+
const templatesToCompile = await fg.glob(
|
|
187
|
+
path.join(
|
|
188
|
+
buildOutputPath,
|
|
189
|
+
`**/*.${extensions}`
|
|
190
|
+
)
|
|
191
|
+
)
|
|
113
192
|
|
|
114
|
-
|
|
115
|
-
|
|
193
|
+
/**
|
|
194
|
+
* Render templates
|
|
195
|
+
*/
|
|
196
|
+
for await (const templatePath of templatesToCompile) {
|
|
197
|
+
const templateBuildStartTime = Date.now()
|
|
116
198
|
|
|
117
199
|
/**
|
|
118
200
|
* Add the current template path to the config
|
|
@@ -122,8 +204,6 @@ export default async (config = {}) => {
|
|
|
122
204
|
*/
|
|
123
205
|
config.build.current = {
|
|
124
206
|
path: path.parse(templatePath),
|
|
125
|
-
baseDir,
|
|
126
|
-
relativePath,
|
|
127
207
|
}
|
|
128
208
|
|
|
129
209
|
const html = await readFile(templatePath, 'utf8')
|
|
@@ -158,8 +238,9 @@ export default async (config = {}) => {
|
|
|
158
238
|
* We do this before generating plaintext, so that
|
|
159
239
|
* any paths will already have been created.
|
|
160
240
|
*/
|
|
161
|
-
const outputPathFromConfig = get(rendered.config, 'permalink',
|
|
241
|
+
const outputPathFromConfig = get(rendered.config, 'permalink', templatePath)
|
|
162
242
|
const parsedOutputPath = path.parse(outputPathFromConfig)
|
|
243
|
+
// This keeps original file extension if no output extension is set
|
|
163
244
|
const extension = get(rendered.config, 'build.output.extension', parsedOutputPath.ext.slice(1))
|
|
164
245
|
const outputPath = `${parsedOutputPath.dir}/${parsedOutputPath.name}.${extension}`
|
|
165
246
|
|
|
@@ -175,62 +256,49 @@ export default async (config = {}) => {
|
|
|
175
256
|
await writeFile(outputPath, rendered.html)
|
|
176
257
|
|
|
177
258
|
/**
|
|
178
|
-
*
|
|
259
|
+
* Remove original file if its path is different
|
|
260
|
+
* from the final destination path.
|
|
179
261
|
*/
|
|
180
|
-
if (
|
|
181
|
-
|
|
182
|
-
path.relative(get(rendered.config, 'build.output.path'), outputPath),
|
|
183
|
-
getColorizedFileSize(rendered.html),
|
|
184
|
-
formatTime(Date.now() - templateBuildStartTime)
|
|
185
|
-
])
|
|
262
|
+
if (outputPath !== templatePath) {
|
|
263
|
+
await rm(templatePath)
|
|
186
264
|
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Add file to CLI table for build summary logging
|
|
268
|
+
*/
|
|
269
|
+
table.push([
|
|
270
|
+
path.relative(get(rendered.config, 'build.output.path'), outputPath),
|
|
271
|
+
getColorizedFileSize(rendered.html),
|
|
272
|
+
formatTime(Date.now() - templateBuildStartTime)
|
|
273
|
+
])
|
|
187
274
|
}
|
|
188
275
|
|
|
189
276
|
/**
|
|
190
277
|
* Copy static files
|
|
191
278
|
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
* TODO: support an array of objects with source and destination, i.e. static: [{ source: 'src/assets', destination: 'assets' }, ...]
|
|
279
|
+
* TODO: support an array of objects with source and destination,
|
|
280
|
+
* i.e. static: [{ source: 'src/assets', destination: 'assets' }, ...]
|
|
196
281
|
*/
|
|
282
|
+
const staticSourcePaths = getRootDirectories([...new Set(get(config, 'build.static.source', []))])
|
|
197
283
|
|
|
198
|
-
|
|
199
|
-
|
|
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)
|
|
284
|
+
for await (const rootDir of staticSourcePaths) {
|
|
285
|
+
await cp(rootDir, path.join(buildOutputPath, get(config, 'build.static.destination')), { recursive: true })
|
|
205
286
|
}
|
|
206
287
|
|
|
207
|
-
|
|
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, '**/*'))
|
|
288
|
+
const allOutputFiles = await fg.glob(path.join(buildOutputPath, '**/*'))
|
|
222
289
|
|
|
223
290
|
/**
|
|
224
291
|
* Run `afterBuild` event
|
|
225
292
|
*/
|
|
226
293
|
if (typeof config.afterBuild === 'function') {
|
|
227
|
-
await config.afterBuild({
|
|
294
|
+
await config.afterBuild({
|
|
295
|
+
config,
|
|
296
|
+
files: allOutputFiles,
|
|
297
|
+
})
|
|
228
298
|
}
|
|
229
299
|
|
|
230
300
|
/**
|
|
231
301
|
* Log a build summary if enabled in the config
|
|
232
|
-
*
|
|
233
|
-
* Need to first clear the spinner
|
|
234
302
|
*/
|
|
235
303
|
|
|
236
304
|
spinner.clear()
|
|
@@ -239,10 +307,10 @@ export default async (config = {}) => {
|
|
|
239
307
|
console.log(table.toString() + '\n')
|
|
240
308
|
}
|
|
241
309
|
|
|
242
|
-
spinner.succeed(`
|
|
310
|
+
spinner.succeed(`Built ${table.length} template${table.length > 1 ? 's' : ''} in ${formatTime(Date.now() - startTime)}`)
|
|
243
311
|
|
|
244
312
|
return {
|
|
245
|
-
files:
|
|
313
|
+
files: allOutputFiles,
|
|
246
314
|
config
|
|
247
315
|
}
|
|
248
316
|
} catch (error) {
|
|
@@ -134,7 +134,7 @@ export async function generatePlaintext(html = '', config = {}) {
|
|
|
134
134
|
).result
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
export async function writePlaintextFile(plaintext = '',
|
|
137
|
+
export async function writePlaintextFile(plaintext = '', config = {}) {
|
|
138
138
|
if (!plaintext) {
|
|
139
139
|
throw new Error('Missing plaintext content.')
|
|
140
140
|
}
|
|
@@ -149,8 +149,8 @@ export async function writePlaintextFile(plaintext = '', templateConfig = {}) {
|
|
|
149
149
|
* Fall back to template's build output path and extension, for example:
|
|
150
150
|
* `config.build.output.path`
|
|
151
151
|
*/
|
|
152
|
-
const plaintextConfig = get(
|
|
153
|
-
let plaintextOutputPath = get(plaintextConfig, 'output.path', get(
|
|
152
|
+
const plaintextConfig = get(config, 'plaintext')
|
|
153
|
+
let plaintextOutputPath = get(plaintextConfig, 'output.path', get(config, 'build.output.path'))
|
|
154
154
|
const plaintextExtension = get(plaintextConfig, 'output.extension', 'txt')
|
|
155
155
|
|
|
156
156
|
/**
|
|
@@ -158,15 +158,12 @@ export async function writePlaintextFile(plaintext = '', templateConfig = {}) {
|
|
|
158
158
|
*/
|
|
159
159
|
if (plaintextConfig === true) {
|
|
160
160
|
// If the template has a `permalink` key set in the FM
|
|
161
|
-
if (typeof
|
|
161
|
+
if (typeof config.permalink === 'string') {
|
|
162
162
|
// Output plaintext at the `permalink` path
|
|
163
|
-
plaintextOutputPath =
|
|
163
|
+
plaintextOutputPath = config.permalink
|
|
164
164
|
} else {
|
|
165
165
|
// Output plaintext at the same directory as the HTML file
|
|
166
|
-
plaintextOutputPath = path
|
|
167
|
-
get(templateConfig, 'build.output.path'),
|
|
168
|
-
get(templateConfig, 'build.current.relativePath')
|
|
169
|
-
)
|
|
166
|
+
plaintextOutputPath = get(config, 'build.output.path')
|
|
170
167
|
}
|
|
171
168
|
}
|
|
172
169
|
|
|
@@ -186,8 +183,8 @@ export async function writePlaintextFile(plaintext = '', templateConfig = {}) {
|
|
|
186
183
|
*/
|
|
187
184
|
if (path.extname(plaintextOutputPath)) {
|
|
188
185
|
// Ensure the target directory exists
|
|
189
|
-
await lstat(
|
|
190
|
-
await mkdir(
|
|
186
|
+
await lstat(plaintextOutputPath).catch(async () => {
|
|
187
|
+
await mkdir(plaintextOutputPath, { recursive: true })
|
|
191
188
|
})
|
|
192
189
|
|
|
193
190
|
// Ensure correct extension is used
|
|
@@ -196,17 +193,19 @@ export async function writePlaintextFile(plaintext = '', templateConfig = {}) {
|
|
|
196
193
|
path.basename(plaintextOutputPath, path.extname(plaintextOutputPath)) + '.' + plaintextExtension
|
|
197
194
|
)
|
|
198
195
|
|
|
196
|
+
console.log('plaintextOutputPath', plaintextOutputPath);
|
|
197
|
+
|
|
199
198
|
return writeFile(plaintextOutputPath, plaintext)
|
|
200
199
|
}
|
|
201
200
|
|
|
202
201
|
/**
|
|
203
202
|
* If `plaintextOutputPath` is a directory path, output file there, using the template's name
|
|
204
203
|
*/
|
|
205
|
-
const templateFileName = get(
|
|
204
|
+
const templateFileName = get(config, 'build.current.path.name')
|
|
206
205
|
|
|
207
206
|
plaintextOutputPath = path.join(
|
|
208
|
-
plaintextOutputPath,
|
|
209
|
-
get(
|
|
207
|
+
path.dirname(plaintextOutputPath),
|
|
208
|
+
get(config, 'build.current.path.dir'),
|
|
210
209
|
templateFileName + '.' + plaintextExtension
|
|
211
210
|
)
|
|
212
211
|
|
package/src/generators/render.js
CHANGED
|
@@ -4,6 +4,7 @@ import { cwd } from 'node:process'
|
|
|
4
4
|
import { defu as merge } from 'defu'
|
|
5
5
|
import expressions from 'posthtml-expressions'
|
|
6
6
|
import { parseFrontMatter } from '../utils/node.js'
|
|
7
|
+
import defaultConfig from '../posthtml/defaultConfig.js'
|
|
7
8
|
import { process as compilePostHTML } from '../posthtml/index.js'
|
|
8
9
|
import { run as useTransformers } from '../transformers/index.js'
|
|
9
10
|
|
|
@@ -40,7 +41,7 @@ export async function render(html = '', config = {}) {
|
|
|
40
41
|
})
|
|
41
42
|
]
|
|
42
43
|
)
|
|
43
|
-
.process(matter)
|
|
44
|
+
.process(matter, defaultConfig)
|
|
44
45
|
.then(({ html }) => parseFrontMatter(`---${html}\n---`))
|
|
45
46
|
|
|
46
47
|
const templateConfig = merge(matterData, config)
|
|
@@ -57,15 +58,15 @@ export async function render(html = '', config = {}) {
|
|
|
57
58
|
*
|
|
58
59
|
* @param {Object} options
|
|
59
60
|
* @param {string} options.html - The HTML to be transformed
|
|
61
|
+
* @param {Object} options.matter - The front matter data
|
|
60
62
|
* @param {Object} options.config - The current template config
|
|
61
|
-
* @param {function} options.render - The render function
|
|
62
63
|
* @returns {string} - The transformed HTML, or the original one if nothing was returned
|
|
63
64
|
*/
|
|
64
65
|
if (typeof templateConfig.beforeRender === 'function') {
|
|
65
66
|
content = await templateConfig.beforeRender(({
|
|
66
67
|
html: content,
|
|
68
|
+
matter: matterData,
|
|
67
69
|
config: templateConfig,
|
|
68
|
-
render
|
|
69
70
|
})) ?? content
|
|
70
71
|
}
|
|
71
72
|
|
|
@@ -77,15 +78,15 @@ export async function render(html = '', config = {}) {
|
|
|
77
78
|
*
|
|
78
79
|
* @param {Object} options
|
|
79
80
|
* @param {string} options.html - The HTML to be transformed
|
|
81
|
+
* @param {Object} options.matter - The front matter data
|
|
80
82
|
* @param {Object} options.config - The current template config
|
|
81
|
-
* @param {function} options.render - The render function
|
|
82
83
|
* @returns {string} - The transformed HTML, or the original one if nothing was returned
|
|
83
84
|
*/
|
|
84
85
|
if (typeof templateConfig.afterRender === 'function') {
|
|
85
86
|
compiled.html = await templateConfig.afterRender(({
|
|
86
87
|
html: compiled.html,
|
|
88
|
+
matter: matterData,
|
|
87
89
|
config: templateConfig,
|
|
88
|
-
render
|
|
89
90
|
})) ?? compiled.html
|
|
90
91
|
}
|
|
91
92
|
|
|
@@ -109,15 +110,15 @@ export async function render(html = '', config = {}) {
|
|
|
109
110
|
*
|
|
110
111
|
* @param {Object} options
|
|
111
112
|
* @param {string} options.html - The HTML to be transformed
|
|
113
|
+
* @param {Object} options.matter - The front matter data
|
|
112
114
|
* @param {Object} options.config - The current template config
|
|
113
|
-
* @param {function} options.render - The render function
|
|
114
115
|
* @returns {string} - The transformed HTML, or the original one if nothing was returned
|
|
115
116
|
*/
|
|
116
117
|
if (typeof templateConfig.afterTransformers === 'function') {
|
|
117
118
|
compiled.html = await templateConfig.afterTransformers(({
|
|
118
119
|
html: compiled.html,
|
|
120
|
+
matter: matterData,
|
|
119
121
|
config: templateConfig,
|
|
120
|
-
render
|
|
121
122
|
})) ?? compiled.html
|
|
122
123
|
}
|
|
123
124
|
|
package/src/posthtml/index.js
CHANGED
|
@@ -3,10 +3,13 @@ import { defu as merge } from 'defu'
|
|
|
3
3
|
|
|
4
4
|
// PostHTML
|
|
5
5
|
import posthtml from 'posthtml'
|
|
6
|
+
import posthtmlFetch from 'posthtml-fetch'
|
|
7
|
+
import envTags from './plugins/envTags.js'
|
|
6
8
|
import components from 'posthtml-component'
|
|
7
9
|
import posthtmlPostcss from 'posthtml-postcss'
|
|
8
10
|
import defaultPosthtmlConfig from './defaultConfig.js'
|
|
9
11
|
import expandLinkTag from './plugins/expandLinkTag.js'
|
|
12
|
+
import envAttributes from './plugins/envAttributes.js'
|
|
10
13
|
|
|
11
14
|
// PostCSS
|
|
12
15
|
import tailwindcss from 'tailwindcss'
|
|
@@ -48,19 +51,45 @@ export async function process(html = '', config = {}) {
|
|
|
48
51
|
{ page: config },
|
|
49
52
|
)
|
|
50
53
|
|
|
54
|
+
const fetchPlugin = posthtmlFetch(
|
|
55
|
+
merge(
|
|
56
|
+
{
|
|
57
|
+
expressions: merge(
|
|
58
|
+
{ locals },
|
|
59
|
+
expressionsOptions,
|
|
60
|
+
{
|
|
61
|
+
missingLocal: '{local}',
|
|
62
|
+
strictMode: false,
|
|
63
|
+
},
|
|
64
|
+
),
|
|
65
|
+
},
|
|
66
|
+
get(config, 'fetch', {})
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
|
|
51
70
|
return posthtml([
|
|
52
71
|
...get(config, 'posthtml.plugins.before', []),
|
|
72
|
+
envTags(config.env),
|
|
73
|
+
envAttributes(config.env),
|
|
53
74
|
expandLinkTag,
|
|
54
75
|
postcssPlugin,
|
|
76
|
+
fetchPlugin,
|
|
55
77
|
components(
|
|
56
|
-
merge(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
78
|
+
merge(
|
|
79
|
+
{
|
|
80
|
+
expressions: merge(
|
|
81
|
+
{ locals },
|
|
82
|
+
expressionsOptions,
|
|
83
|
+
)
|
|
84
|
+
},
|
|
85
|
+
componentsUserOptions,
|
|
86
|
+
defaultComponentsConfig
|
|
87
|
+
)
|
|
61
88
|
),
|
|
62
89
|
expandLinkTag,
|
|
63
90
|
postcssPlugin,
|
|
91
|
+
envTags(config.env),
|
|
92
|
+
envAttributes(config.env),
|
|
64
93
|
...get(config, 'posthtml.plugins.after', get(config, 'posthtml.plugins', []))
|
|
65
94
|
])
|
|
66
95
|
.process(html, posthtmlOptions)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const plugin = (env => tree => {
|
|
2
|
+
const process = node => {
|
|
3
|
+
// Return the original node if no environment is set
|
|
4
|
+
if (!env) {
|
|
5
|
+
return node
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (node.attrs) {
|
|
9
|
+
for (const attr in node.attrs) {
|
|
10
|
+
const suffix = `-${env}`
|
|
11
|
+
|
|
12
|
+
// Find attributes on this node that have this suffix
|
|
13
|
+
if (attr.endsWith(suffix)) {
|
|
14
|
+
const key = attr.slice(0, -suffix.length)
|
|
15
|
+
const value = node.attrs[attr]
|
|
16
|
+
|
|
17
|
+
// Change the attribute without the suffix to have the value of the suffixed attribute
|
|
18
|
+
node.attrs[key] = value
|
|
19
|
+
|
|
20
|
+
// Remove the attribute with the suffix
|
|
21
|
+
node.attrs[attr] = false
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return node
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return tree.walk(process)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
export default plugin
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const plugin = (env => tree => {
|
|
2
|
+
const process = node => {
|
|
3
|
+
env = env || 'local'
|
|
4
|
+
|
|
5
|
+
// Return the original node if it doesn't have a tag
|
|
6
|
+
if (!node.tag) {
|
|
7
|
+
return node
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const tagEnv = node.tag.split(':').pop()
|
|
11
|
+
|
|
12
|
+
// Tag targets current env, remove it and return its content
|
|
13
|
+
if (node.tag === `env:${env}`) {
|
|
14
|
+
node.tag = false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Tag doesn't target current env, remove it completely
|
|
18
|
+
if (
|
|
19
|
+
typeof node.tag === 'string'
|
|
20
|
+
&& node.tag.startsWith('env:')
|
|
21
|
+
&& tagEnv !== env
|
|
22
|
+
) {
|
|
23
|
+
node.content = []
|
|
24
|
+
node.tag = false
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return node
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return tree.walk(process)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
export default plugin
|