@maizzle/framework 5.0.0-beta.2 → 5.0.0-beta.21

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.
@@ -2,18 +2,20 @@ import juice from 'juice'
2
2
  import postcss from 'postcss'
3
3
  import get from 'lodash-es/get.js'
4
4
  import has from 'lodash-es/has.js'
5
+ import * as cheerio from 'cheerio/slim'
5
6
  import remove from 'lodash-es/remove.js'
6
7
  import { render } from 'posthtml-render'
7
8
  import { calc } from '@csstools/css-calc'
8
9
  import isEmpty from 'lodash-es/isEmpty.js'
9
- import * as cheerio from 'cheerio/lib/slim'
10
10
  import safeParser from 'postcss-safe-parser'
11
11
  import isObject from 'lodash-es/isObject.js'
12
12
  import { parser as parse } from 'posthtml-parser'
13
+ import { parseCSSRule } from '../utils/string.js'
13
14
  import { useAttributeSizes } from './useAttributeSizes.js'
15
+ import defaultPostHTMLConfig from '../posthtml/defaultConfig.js'
14
16
 
15
17
  const posthtmlPlugin = (options = {}) => tree => {
16
- return inline(render(tree), options).then(html => parse(html))
18
+ return inline(render(tree), options).then(html => parse(html, defaultPostHTMLConfig))
17
19
  }
18
20
 
19
21
  export default posthtmlPlugin
@@ -116,7 +118,7 @@ export async function inline(html = '', options = {}) {
116
118
  rule.walkDecls(decl => {
117
119
  // Resolve calc() values to static values
118
120
  if (options.resolveCalc) {
119
- decl.value = decl.value.includes('calc(') ? calc(decl.value) : decl.value
121
+ decl.value = decl.value.includes('calc(') ? calc(decl.value, {precision: 2}) : decl.value
120
122
  }
121
123
 
122
124
  declarations.add(decl)
@@ -179,10 +181,10 @@ export async function inline(html = '', options = {}) {
179
181
 
180
182
  if (styleAttr) {
181
183
  inlineStyles = styleAttr.split(';').reduce((acc, i) => {
182
- let [property, value] = i.split(':').map(i => i.trim())
184
+ let { property, value } = parseCSSRule(i)
183
185
 
184
186
  if (value && options.resolveCalc) {
185
- value = value.includes('calc') ? calc(value) : value
187
+ value = value.includes('calc') ? calc(value, {precision: 2}) : value
186
188
  }
187
189
 
188
190
  if (value && options.preferUnitlessValues) {
@@ -4,6 +4,7 @@ import { defu as merge } from 'defu'
4
4
  import { render } from 'posthtml-render'
5
5
  import { parser as parse } from 'posthtml-parser'
6
6
  import posthtmlConfig from '../posthtml/defaultConfig.js'
7
+ import defaultPostHTMLConfig from '../posthtml/defaultConfig.js'
7
8
 
8
9
  const posthtmlPlugin = (options = {}) => tree => {
9
10
  options = merge(options, {
@@ -12,7 +13,7 @@ const posthtmlPlugin = (options = {}) => tree => {
12
13
 
13
14
  const { result: html } = crush(render(tree), options)
14
15
 
15
- return parse(html)
16
+ return parse(html, defaultPostHTMLConfig)
16
17
  }
17
18
 
18
19
  export default posthtmlPlugin
@@ -4,6 +4,7 @@ import { render } from 'posthtml-render'
4
4
  import isEmpty from 'lodash-es/isEmpty.js'
5
5
  import { parser as parse } from 'posthtml-parser'
6
6
  import posthtmlConfig from '../posthtml/defaultConfig.js'
7
+ import defaultPostHTMLConfig from '../posthtml/defaultConfig.js'
7
8
 
8
9
  const posthtmlPlugin = (replacements = {}) => tree => {
9
10
  if (!isEmpty(replacements)) {
@@ -14,10 +15,13 @@ const posthtmlPlugin = (replacements = {}) => tree => {
14
15
  render(tree).replace(patterns, matched => {
15
16
  for (const [regex, replacement] of regexes) {
16
17
  if (regex.test(matched)) {
17
- return replacement
18
+ return matched.replace(regex, replacement)
18
19
  }
19
20
  }
20
- })
21
+
22
+ return matched
23
+ }),
24
+ defaultPostHTMLConfig
21
25
  )
22
26
  }
23
27
 
@@ -0,0 +1,26 @@
1
+ const posthtmlPlugin = (() => tree => {
2
+ const process = node => {
3
+ // Return the original node if it doesn't have a tag
4
+ if (!node.tag) {
5
+ return node
6
+ }
7
+
8
+ // Preserve <template> tags marked as such
9
+ if (node.tag === 'template' && node.attrs?.preserve) {
10
+ node.attrs.preserve = false
11
+
12
+ return node
13
+ }
14
+
15
+ // Replace <template> tags with their content
16
+ if (node.tag === 'template') {
17
+ node.tag = false
18
+ }
19
+
20
+ return node
21
+ }
22
+
23
+ return tree.walk(process)
24
+ })
25
+
26
+ export default posthtmlPlugin
@@ -115,3 +115,82 @@ export function humanFileSize(bytes, si=false, dp=2) {
115
115
 
116
116
  return bytes.toFixed(dp) + ' ' + units[u]
117
117
  }
118
+
119
+ /**
120
+ * Get the root directories from a list of glob patterns.
121
+ *
122
+ * @param {array} patterns List of glob patterns.
123
+ * @returns {array} List of root directories.
124
+ */
125
+ export function getRootDirectories(patterns = []) {
126
+ if (!Array.isArray(patterns)) {
127
+ return []
128
+ }
129
+
130
+ if (patterns.length === 0) {
131
+ return []
132
+ }
133
+
134
+ return [...new Set(
135
+ patterns
136
+ .filter(pattern => !pattern.startsWith('!'))
137
+ .map(pattern => {
138
+ // If the pattern doesn't include wildcards, use it as is
139
+ if (!pattern.includes('*')) {
140
+ return pattern.replace(/\/$/, '') // Remove trailing slash if present
141
+ }
142
+ // For patterns with wildcards, get the part before the first wildcard
143
+ const parts = pattern.split(/[*{]/)[0].split('/')
144
+ return parts.slice(0, -1).join('/')
145
+ })
146
+ .filter(Boolean)
147
+ )]
148
+ }
149
+
150
+ /**
151
+ * Get the file extensions from a glob pattern.
152
+ * @param {*} pattern
153
+ * @returns
154
+ */
155
+ export function getFileExtensionsFromPattern(pattern) {
156
+ const starExtPattern = /\.([^\*\{\}]+)$/ // Matches .ext but not .* or .{ext}
157
+ const bracePattern = /\.{([^}]+)}$/ // Matches .{ext} or .{ext,ext}
158
+ const wildcardPattern = /\.\*$/ // Matches .*
159
+
160
+ if (wildcardPattern.test(pattern)) {
161
+ return ['html'] // We default to 'html' if the pattern is a wildcard
162
+ }
163
+
164
+ const braceMatch = pattern.match(bracePattern);
165
+ if (braceMatch) {
166
+ return braceMatch[1].split(',') // Split and return extensions inside braces
167
+ }
168
+
169
+ const starExtMatch = pattern.match(starExtPattern)
170
+ if (starExtMatch) {
171
+ return [starExtMatch[1]] // Return single extension
172
+ }
173
+
174
+ return ['html'] // No recognizable extension pattern, default to 'html'
175
+ }
176
+
177
+ export function parseCSSRule(rule) {
178
+ // Step 1: Trim the input string
179
+ rule = rule.trim()
180
+
181
+ // Step 2: Find the index of the first colon
182
+ const colonIndex = rule.indexOf(':')
183
+
184
+ // Step 3: Extract property and value parts
185
+ if (colonIndex === -1) {
186
+ return {
187
+ property: '',
188
+ value: ''
189
+ }
190
+ }
191
+
192
+ const property = rule.slice(0, colonIndex).trim()
193
+ const value = rule.slice(colonIndex + 1).trim()
194
+
195
+ return { property, value }
196
+ }
package/types/build.d.ts CHANGED
@@ -1,14 +1,9 @@
1
- import ComponentsConfig from './components';
2
1
  import type { SpinnerName } from 'cli-spinners';
2
+ import type { PostHTMLExpressions } from 'posthtml-expressions';
3
3
 
4
4
  export default interface BuildConfig {
5
5
  /**
6
- * Components configuration.
7
- */
8
- components?: ComponentsConfig;
9
-
10
- /**
11
- * Directory where Maizzle should look for Templates to compile.
6
+ * Paths where Maizzle should look for Templates to compile.
12
7
  *
13
8
  * @default ['src/templates/**\/*.html']
14
9
  *
@@ -16,12 +11,17 @@ export default interface BuildConfig {
16
11
  * ```
17
12
  * export default {
18
13
  * build: {
19
- * files: ['src/templates/**\/*.html']
14
+ * content: ['src/templates/**\/*.html']
20
15
  * }
21
16
  * }
22
17
  * ```
23
18
  */
24
- files?: string | string[];
19
+ content?: string[];
20
+
21
+ /**
22
+ Configure [posthtml-expressions](https://github.com/posthtml/posthtml-expressions) options.
23
+ */
24
+ expressions?: PostHTMLExpressions;
25
25
 
26
26
  /**
27
27
  * Define the output path for compiled Templates, and what file extension they should use.
@@ -51,6 +51,29 @@ export default interface BuildConfig {
51
51
  * @default 'html'
52
52
  */
53
53
  extension: string;
54
+ /**
55
+ * Path or array of paths that will be unwrapped.
56
+ * Everything inside them will be copied to
57
+ * the root of the output directory.
58
+ *
59
+ * @example
60
+ *
61
+ * ```
62
+ * export default {
63
+ * build: {
64
+ * content: ['test/fixtures/**\/*.html'],
65
+ * output: {
66
+ * from: ['test/fixtures'],
67
+ * }
68
+ * }
69
+ * ```
70
+ *
71
+ * This will copy everything inside `test/fixtures` to the root
72
+ * of the output directory, not creating the `test/fixtures`
73
+ * directory.
74
+ *
75
+ */
76
+ from: string;
54
77
  };
55
78
 
56
79
  /**
@@ -82,21 +105,6 @@ export default interface BuildConfig {
82
105
  * @default undefined
83
106
  */
84
107
  destination?: string;
85
- } | {
86
- /**
87
- * An array of objects specifying source and destination directories for static files.
88
- */
89
- static: Array<{
90
- /**
91
- * Array of paths where Maizzle should look for static files.
92
- */
93
- source: string[];
94
- /**
95
- * Directory where static files should be copied to,
96
- * relative to the `build.output` path.
97
- */
98
- destination: string;
99
- }>;
100
108
  };
101
109
 
102
110
  /**
@@ -131,4 +139,31 @@ export default interface BuildConfig {
131
139
  * ```
132
140
  */
133
141
  summary?: boolean;
142
+
143
+ /**
144
+ * Information about the Template currently being compiled.
145
+ *
146
+ * @example
147
+ *
148
+ * ```
149
+ * {
150
+ path: {
151
+ root: 'build_production',
152
+ dir: 'build_production',
153
+ base: 'transactional.html',
154
+ ext: '.html',
155
+ name: 'transactional'
156
+ }
157
+ }
158
+ * ```
159
+ */
160
+ current?: {
161
+ path?: {
162
+ root: string;
163
+ dir: string;
164
+ base: string;
165
+ ext: string;
166
+ name: string;
167
+ };
168
+ };
134
169
  }
package/types/config.d.ts CHANGED
@@ -1,29 +1,23 @@
1
+ import type Events from './events';
1
2
  import type BuildConfig from './build';
2
3
  import type MinifyConfig from './minify';
3
4
  import type PostHTMLConfig from './posthtml';
4
5
  import type MarkdownConfig from './markdown';
5
6
  import type { ProcessOptions } from 'postcss';
6
7
  import type PurgeCSSConfig from './css/purge';
8
+ import type PlaintextConfig from './plaintext';
7
9
  import type CSSInlineConfig from './css/inline';
8
- import type ComponentsConfig from './components';
10
+ import type { SpinnerName } from 'cli-spinners';
9
11
  import type WidowWordsConfig from './widowWords';
10
12
  import type { CoreBeautifyOptions } from 'js-beautify';
11
13
  import type { BaseURLConfig } from 'posthtml-base-url';
12
14
  import type URLParametersConfig from './urlParameters';
13
- import type {
14
- beforeCreateType,
15
- beforeRenderType,
16
- afterRenderType,
17
- afterTransformersType,
18
- afterBuildType
19
- } from './events';
20
- import type PlaintextConfig from './plaintext';
15
+ import type { PostHTMLFetchConfig } from 'posthtml-fetch';
16
+ import type { PostHTMLComponents } from 'posthtml-component';
21
17
 
22
18
  import type { Config as TailwindConfig } from 'tailwindcss';
23
19
 
24
20
  export default interface Config {
25
- [key: string]: any; // eslint-disable-line
26
-
27
21
  /**
28
22
  * Add or remove attributes from elements.
29
23
  */
@@ -67,21 +61,9 @@ export default interface Config {
67
61
  }
68
62
 
69
63
  /**
70
- Configure build settings.
71
-
72
- @example
73
- ```
74
- export default {
75
- build: {
76
- components: ComponentsConfig,
77
- posthtml: PostHTMLConfig,
78
- tailwind: TailwindConfig,
79
- templates: TemplatesConfig,
80
- }
81
- }
82
- ```
83
- */
84
- build: BuildConfig;
64
+ * Configure build settings.
65
+ */
66
+ build?: BuildConfig;
85
67
 
86
68
  /**
87
69
  Define a string that will be prepended to sources and hrefs in your HTML and CSS.
@@ -112,12 +94,12 @@ export default interface Config {
112
94
  /**
113
95
  * Configure components.
114
96
  */
115
- components?: ComponentsConfig;
97
+ components?: PostHTMLComponents;
116
98
 
117
99
  /**
118
100
  * Configure how CSS is handled.
119
101
  */
120
- css: {
102
+ css?: {
121
103
  /**
122
104
  * Configure CSS inlining.
123
105
  */
@@ -136,7 +118,7 @@ export default interface Config {
136
118
  * ```
137
119
  * export default {
138
120
  * css: {
139
- * safeClassNames: {
121
+ * safe: {
140
122
  * ':': '__',
141
123
  * '!': 'i-',
142
124
  * }
@@ -144,7 +126,7 @@ export default interface Config {
144
126
  * }
145
127
  * ```
146
128
  */
147
- safeClassNames?: boolean | Record<string, string>;
129
+ safe?: boolean | Record<string, string>;
148
130
 
149
131
  /**
150
132
  * Ensure that all your HEX colors inside `bgcolor` and `color` attributes are defined with six digits.
@@ -184,11 +166,15 @@ export default interface Config {
184
166
 
185
167
  /**
186
168
  * Use a custom Tailwind CSS configuration object.
187
- *
188
169
  */
189
170
  tailwind?: TailwindConfig;
190
171
  }
191
172
 
173
+ /**
174
+ * Configure the [`<fetch>`](https://maizzle.com/docs/tags#fetch) tag.
175
+ */
176
+ fetch?: PostHTMLFetchConfig;
177
+
192
178
  /**
193
179
  * Transform text inside elements marked with custom attributes.
194
180
  * Filters work only on elements that contain only text.
@@ -204,7 +190,7 @@ export default interface Config {
204
190
  * }
205
191
  * ```
206
192
  */
207
- filters: boolean | Record<string, (str: string) => string>;
193
+ filters?: boolean | Record<string, (str: string) => string>;
208
194
 
209
195
  /**
210
196
  * Define variables outside of the `page` object.
@@ -228,7 +214,7 @@ export default interface Config {
228
214
  * <p>{{ company.name }}</p>
229
215
  * ```
230
216
  */
231
- locals?: Record<string, any>; // eslint-disable-line
217
+ locals?: Record<string, any>;
232
218
 
233
219
  /**
234
220
  * Configure the Markdown parser.
@@ -404,6 +390,22 @@ export default interface Config {
404
390
  * @default false
405
391
  */
406
392
  reportFileSize?: boolean;
393
+
394
+ /**
395
+ * Type of spinner to show in the console.
396
+ *
397
+ * @default 'circleHalves'
398
+ *
399
+ * @example
400
+ * ```
401
+ * export default {
402
+ * server: {
403
+ * spinner: 'bounce'
404
+ * }
405
+ * }
406
+ * ```
407
+ */
408
+ spinner?: SpinnerName;
407
409
  }
408
410
 
409
411
  /**
@@ -471,83 +473,87 @@ export default interface Config {
471
473
  * @example
472
474
  * ```
473
475
  * export default {
474
- * beforeCreate: async (config) => {
476
+ * beforeCreate: async ({config}) => {
475
477
  * // do something with `config`
476
478
  * }
477
479
  * }
478
480
  * ```
479
481
  */
480
- beforeCreate: beforeCreateType;
482
+ beforeCreate?: Events['beforeCreate'];
481
483
 
482
484
  /**
483
485
  * Runs after the Template's config has been computed, but just before it is compiled.
484
- * It exposes the Template's HTML, its config, and the Maizzle `render` function.
485
- * Must return the `html` string.
486
+ *
487
+ * Must return the `html` string, otherwise the original will be used.
486
488
  *
487
489
  * @default undefined
488
490
  *
489
491
  * @example
490
492
  * ```
491
493
  * export default {
492
- * beforeRender: async ({html, config, render}) => {
494
+ * beforeRender: async ({html, matter, config, posthtml, transform}) => {
493
495
  * // do something...
494
496
  * return html;
495
497
  * }
496
498
  * }
497
499
  * ```
498
500
  */
499
- beforeRender: beforeRenderType;
501
+ beforeRender?: Events['beforeRender'];
500
502
 
501
503
  /**
502
504
  * Runs after the Template has been compiled, but before any Transformers have been applied.
503
- * Exposes the rendered `html` string and the `config`. Must return the `html` string.
505
+ *
506
+ * Must return the `html` string, otherwise the original will be used.
504
507
  *
505
508
  * @default undefined
506
509
  *
507
510
  * @example
508
511
  * ```
509
512
  * export default {
510
- * afterRender: async ({html, config}) => {
513
+ * afterRender: async ({html, matter, config, posthtml, transform}) => {
511
514
  * // do something...
512
515
  * return html;
513
516
  * }
514
517
  * }
515
518
  * ```
516
519
  */
517
- afterRender: afterRenderType;
520
+ afterRender?: Events['afterRender'];
518
521
 
519
522
  /**
520
523
  * Runs after all Transformers have been applied, just before the final HTML is returned.
521
- * Exposes the rendered `html` string and the `config`. Must return the `html` string.
524
+ *
525
+ * Must return the `html` string, otherwise the original will be used.
522
526
  *
523
527
  * @default undefined
524
528
  *
525
529
  * @example
526
530
  * ```
527
531
  * export default {
528
- * afterTransformers: async ({html, config, render}) => {
532
+ * afterTransformers: async ({html, matter, config, posthtml, transform}) => {
529
533
  * // do something...
530
534
  * return html;
531
535
  * }
532
536
  * }
533
537
  * ```
534
538
  */
535
- afterTransformers: afterTransformersType;
539
+ afterTransformers?: Events['afterTransformers'];
536
540
 
537
541
  /**
538
542
  * Runs after all Templates have been compiled and output to disk.
539
- * The files parameter will contain the paths to all the files inside the `build.templates.destination.path` directory.
543
+ * `files` will contain the paths to all the files inside the `build.output.path` directory.
540
544
  *
541
545
  * @default undefined
542
546
  *
543
547
  * @example
544
548
  * ```
545
549
  * export default {
546
- * afterBuild: async ({files, config, render}) => {
550
+ * afterBuild: async ({files, config, transform}) => {
547
551
  * // do something...
548
552
  * }
549
553
  * }
550
554
  * ```
551
555
  */
552
- afterBuild: afterBuildType;
556
+ afterBuild?: Events['afterBuild'];
557
+
558
+ [key: string]: any;
553
559
  }
@@ -102,7 +102,7 @@ export default interface PurgeCSSConfig {
102
102
  * }
103
103
  * ```
104
104
  */
105
- doNotRemoveHTMLCommentsWhoseOpeningTagContains: Opts['doNotRemoveHTMLCommentsWhoseOpeningTagContains'];
105
+ doNotRemoveHTMLCommentsWhoseOpeningTagContains?: Opts['doNotRemoveHTMLCommentsWhoseOpeningTagContains'];
106
106
 
107
107
  /**
108
108
  * Rename all classes and IDs in both your `<style>` tags and your body HTML elements,