@maizzle/framework 4.8.8 → 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.
Files changed (80) hide show
  1. package/bin/maizzle +3 -1
  2. package/package.json +64 -55
  3. package/src/commands/build.js +244 -19
  4. package/src/commands/serve.js +2 -197
  5. package/src/generators/plaintext.js +192 -91
  6. package/src/generators/render.js +128 -0
  7. package/src/index.js +45 -13
  8. package/src/{generators/posthtml → posthtml}/defaultComponentsConfig.js +6 -4
  9. package/src/{generators/posthtml → posthtml}/defaultConfig.js +1 -1
  10. package/src/posthtml/index.js +74 -0
  11. package/src/posthtml/plugins/expandLinkTag.js +36 -0
  12. package/src/server/client.js +181 -0
  13. package/src/server/index.js +383 -0
  14. package/src/server/routes/hmr.js +24 -0
  15. package/src/server/routes/index.js +38 -0
  16. package/src/server/views/error.html +83 -0
  17. package/src/server/views/index.html +24 -0
  18. package/src/server/websockets.js +27 -0
  19. package/src/transformers/addAttributes.js +30 -0
  20. package/src/transformers/attributeToStyle.js +30 -36
  21. package/src/transformers/baseUrl.js +56 -27
  22. package/src/transformers/comb.js +51 -0
  23. package/src/transformers/core.js +20 -0
  24. package/src/transformers/filters/defaultFilters.js +90 -71
  25. package/src/transformers/filters/index.js +14 -78
  26. package/src/transformers/index.js +268 -63
  27. package/src/transformers/inline.js +240 -0
  28. package/src/transformers/markdown.js +13 -14
  29. package/src/transformers/minify.js +21 -16
  30. package/src/transformers/posthtmlMso.js +13 -8
  31. package/src/transformers/prettify.js +16 -15
  32. package/src/transformers/preventWidows.js +32 -28
  33. package/src/transformers/removeAttributes.js +19 -19
  34. package/src/transformers/replaceStrings.js +30 -9
  35. package/src/transformers/safeClassNames.js +24 -24
  36. package/src/transformers/shorthandCss.js +22 -0
  37. package/src/transformers/sixHex.js +17 -17
  38. package/src/transformers/urlParameters.js +18 -16
  39. package/src/transformers/useAttributeSizes.js +65 -0
  40. package/src/utils/getConfigByFilePath.js +124 -0
  41. package/src/utils/node.js +68 -0
  42. package/src/utils/string.js +117 -0
  43. package/types/build.d.ts +117 -57
  44. package/types/components.d.ts +130 -112
  45. package/types/config.d.ts +454 -242
  46. package/types/css/inline.d.ts +234 -0
  47. package/types/css/purge.d.ts +125 -0
  48. package/types/events.d.ts +5 -105
  49. package/types/index.d.ts +148 -116
  50. package/types/markdown.d.ts +20 -18
  51. package/types/minify.d.ts +122 -120
  52. package/types/plaintext.d.ts +46 -52
  53. package/types/posthtml.d.ts +103 -136
  54. package/types/render.d.ts +0 -117
  55. package/types/urlParameters.d.ts +21 -20
  56. package/types/widowWords.d.ts +9 -7
  57. package/src/functions/plaintext.js +0 -5
  58. package/src/functions/render.js +0 -5
  59. package/src/generators/config.js +0 -52
  60. package/src/generators/output/index.js +0 -4
  61. package/src/generators/output/to-disk.js +0 -254
  62. package/src/generators/output/to-string.js +0 -73
  63. package/src/generators/postcss.js +0 -23
  64. package/src/generators/posthtml/index.js +0 -75
  65. package/src/generators/tailwindcss.js +0 -157
  66. package/src/transformers/extraAttributes.js +0 -33
  67. package/src/transformers/inlineCss.js +0 -42
  68. package/src/transformers/removeInlineBackgroundColor.js +0 -57
  69. package/src/transformers/removeInlineSizes.js +0 -45
  70. package/src/transformers/removeInlinedSelectors.js +0 -100
  71. package/src/transformers/removeUnusedCss.js +0 -48
  72. package/src/transformers/shorthandInlineCSS.js +0 -26
  73. package/src/utils/helpers.js +0 -13
  74. package/types/baseUrl.d.ts +0 -79
  75. package/types/fetch.d.ts +0 -143
  76. package/types/inlineCss.d.ts +0 -207
  77. package/types/layouts.d.ts +0 -39
  78. package/types/removeUnusedCss.d.ts +0 -115
  79. package/types/tailwind.d.ts +0 -22
  80. package/types/templates.d.ts +0 -181
@@ -0,0 +1,124 @@
1
+ import { resolve } from 'pathe'
2
+ import get from 'lodash/get.js'
3
+ import { defu as merge } from 'defu'
4
+ import { lstat } from 'node:fs/promises'
5
+ import isEmpty from 'lodash-es/isEmpty.js'
6
+
7
+ /**
8
+ * Compute the Maizzle config object.
9
+ *
10
+ * If a config file path is provided, that file will be read
11
+ * instead of trying to merge the base and environment configs.
12
+ *
13
+ * If an environment is provided, the config object will be
14
+ * computed based on a base config and the resolved
15
+ * environment config.
16
+ *
17
+ * @param {string} env - The environment name to use.
18
+ * @param {string} path - The path to the config file to use.
19
+ * @returns {Promise<object>} The computed config object.
20
+ */
21
+ export async function readFileConfig(config) {
22
+ try {
23
+ /**
24
+ * If `config` is string, try to read and return
25
+ * the config object from it.
26
+ */
27
+ if (typeof config === 'string' && config) {
28
+ const { default: resolvedConfig } = await import(`file://${resolve(config)}?d=${Date.now()}`)
29
+ .catch(() => { throw new Error('Could not read config file') })
30
+
31
+ return merge(resolvedConfig, { env: config })
32
+ }
33
+
34
+ /**
35
+ * Otherwise, default to the Environment config approach,
36
+ * where we check for config files that follow a
37
+ * specific naming convention.
38
+ *
39
+ * First, we check for a base config file, in this order:
40
+ */
41
+ const baseConfigFileNames = [
42
+ './maizzle.config.js',
43
+ './maizzle.config.local.js',
44
+ './config.js',
45
+ './config.local.js',
46
+ './maizzle.config.cjs',
47
+ './maizzle.config.local.cjs',
48
+ './config.cjs',
49
+ './config.local.cjs',
50
+ ]
51
+
52
+ const env = get(config, 'env', 'local')
53
+ let baseConfig = merge({ env }, config)
54
+ let envConfig = merge({ env }, config)
55
+
56
+ const cwd = env === 'maizzle-ci' ? './test/stubs/config' : process.cwd()
57
+
58
+ // We load the first base config found
59
+ for (const module of baseConfigFileNames) {
60
+ // Check if the file exists, go to next one if not
61
+ const configFileExists = await lstat(resolve(cwd, module)).catch(() => false)
62
+
63
+ if (!configFileExists) {
64
+ continue
65
+ }
66
+
67
+ // Load the config file
68
+ try {
69
+ const { default: baseConfigFile } = await import(`file://${resolve(cwd, module)}?d=${Date.now()}`)
70
+
71
+ // Use the first base config found
72
+ if (!isEmpty(baseConfigFile)) {
73
+ baseConfig = merge(baseConfigFile, baseConfig)
74
+ break
75
+ }
76
+ } catch (error) {
77
+ break
78
+ }
79
+
80
+ }
81
+
82
+ // Then, we load and compute the first Environment config found
83
+ if (env !== 'local') {
84
+ let loaded = false
85
+ const modulesToTry = [
86
+ `./maizzle.config.${env}.js`,
87
+ `./config.${env}.js`,
88
+ `./maizzle.config.${env}.cjs`,
89
+ `./config.${env}.cjs`,
90
+ ]
91
+
92
+ for (const module of modulesToTry) {
93
+ // Check if the file exists, go to next one if not
94
+ const configFileExists = await lstat(resolve(cwd, module)).catch(() => false)
95
+
96
+ if (!configFileExists) {
97
+ continue
98
+ }
99
+
100
+ // Load the config file
101
+ try {
102
+ const { default: envConfigFile } = await import(`file://${resolve(cwd, module)}?d=${Date.now()}`)
103
+
104
+ // If it's not an empty object, merge it with the base config
105
+ if (!isEmpty(envConfigFile)) {
106
+ envConfig = merge(envConfigFile, envConfig)
107
+ loaded = true
108
+ break
109
+ }
110
+ } catch (error) {
111
+ break
112
+ }
113
+ }
114
+
115
+ if (!loaded) {
116
+ throw new Error(`Failed to load the \`${env}\` environment config, do you have one of these files in your project root?\n\n${modulesToTry.join('\n')}`)
117
+ }
118
+ }
119
+
120
+ return merge(envConfig, baseConfig)
121
+ } catch (error) {
122
+ throw new Error('Could not compute config')
123
+ }
124
+ }
@@ -0,0 +1,68 @@
1
+ import os from 'node:os'
2
+ import gm from 'gray-matter'
3
+ import pico from 'picocolors'
4
+ import { humanFileSize } from './string.js'
5
+
6
+ // Return a local IP address
7
+ export function getLocalIP() {
8
+ const interfaces = os.networkInterfaces()
9
+
10
+ for (const iface in interfaces) {
11
+ const ifaceInfo = interfaces[iface]
12
+
13
+ for (const alias of ifaceInfo) {
14
+ if (alias.family === 'IPv4' && !alias.internal) {
15
+ return alias.address
16
+ }
17
+ }
18
+ }
19
+
20
+ return '127.0.0.1' // Default to localhost if no suitable IP is found
21
+ }
22
+
23
+ /**
24
+ * Return the file size of a string
25
+ *
26
+ * @param {string} string The HTML string to calculate the file size of
27
+ * @returns {number} The file size in bytes, a floating-point number
28
+ * */
29
+ export function getFileSize(string) {
30
+ const blob = new Blob([string], { type: 'text/html' })
31
+
32
+ return blob.size.toFixed(2)
33
+ }
34
+
35
+ /**
36
+ * Color-code a formatted file size string depending on the size
37
+ *
38
+ * 0-49 KB: green
39
+ * 50-102 KB: yellow
40
+ * >102 KB: red
41
+ *
42
+ * @param {string} string The HTML string to calculate the file size of
43
+ * @returns {string} The formatted, color-coded file size
44
+ * */
45
+ export function getColorizedFileSize(string) {
46
+ const size = getFileSize(string)
47
+ const formattedSize = humanFileSize(size)
48
+
49
+ if (size / 1024 < 50) {
50
+ return formattedSize
51
+ }
52
+
53
+ if (size / 1024 < 102) {
54
+ return pico.yellow(formattedSize)
55
+ }
56
+
57
+ return pico.red(formattedSize)
58
+ }
59
+
60
+ export function parseFrontMatter(html) {
61
+ /**
62
+ * Need to pass empty options object to gray-matter
63
+ * in order to disable caching.
64
+ * https://github.com/jonschlinkert/gray-matter/issues/43
65
+ */
66
+ const { content, data, matter, stringify } = gm(html, {})
67
+ return { content, data, matter, stringify }
68
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Inject a script into HTML by "prepending" it to the first available closing
3
+ * tag from a list of candidates.
4
+ *
5
+ * @param {string} html The HTML content
6
+ * @param {string} script The script to inject
7
+ * @returns {string} The modified HTML
8
+ */
9
+ export function injectScript(html = '', script = '') {
10
+ if (html.includes('</head>')) {
11
+ return html.replace('</head>', `${script}</head>`)
12
+ }
13
+
14
+ if (html.includes('</title>')) {
15
+ return html.replace('</title>', `</title>${script}`)
16
+ }
17
+
18
+ if (html.includes('</body>')) {
19
+ return html.replace('</body>', `${script}</body>`)
20
+ }
21
+
22
+ if (html.includes('</html>')) {
23
+ return html.replace('</html>', `${script}</html>`)
24
+ }
25
+
26
+ if (html.includes('<!doctype html>')) {
27
+ return html.replace('<!doctype html>', `<!doctype html>${script}`)
28
+ }
29
+
30
+ return script + html
31
+ }
32
+
33
+ /**
34
+ * Find the common prefix among an array of strings.
35
+ *
36
+ * @param {string[]} strings Array of strings
37
+ * @returns {string} Common prefix
38
+ * @throws {TypeError} If the input is not an array
39
+ */
40
+ export function findCommonPrefix(strings) {
41
+ // Must be an array
42
+ if (!Array.isArray(strings)) {
43
+ throw new TypeError('findCommonPrefix expects an array')
44
+ }
45
+
46
+ const sortedStrings = strings.slice().sort()
47
+ const first = sortedStrings[0]
48
+ const last = sortedStrings[sortedStrings.length - 1]
49
+ let i = 0
50
+
51
+ while (i < first.length && first.charAt(i) === last.charAt(i)) {
52
+ i++
53
+ }
54
+
55
+ return first.substring(0, i)
56
+ }
57
+
58
+ export function formatMs(milliseconds) {
59
+ const date = new Date(milliseconds);
60
+
61
+ const hours = date.getUTCHours();
62
+ const minutes = date.getUTCMinutes();
63
+ const seconds = date.getUTCSeconds();
64
+ const formattedTime = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
65
+
66
+ return formattedTime;
67
+ }
68
+
69
+ /**
70
+ * Format milliseconds as human-readable text.
71
+ *
72
+ * @param {number} ms Number of milliseconds.
73
+ * @returns {string} Formatted string.
74
+ */
75
+ export function formatTime(ms) {
76
+ if (ms < 1000) {
77
+ return `${ms} ms`
78
+ }
79
+
80
+ if (ms < 60000) { // Less than 1 minute
81
+ const seconds = ms / 1000
82
+ return `${seconds.toFixed(2)} s`
83
+ }
84
+
85
+ const minutes = ms / 60000
86
+ return `${minutes.toFixed(2)} min`
87
+ }
88
+
89
+ /**
90
+ * Format bytes as human-readable text.
91
+ *
92
+ * @param {number} bytes Number of bytes.
93
+ * @param {boolean} si True to use metric (SI) units, aka powers of 1000. False to use
94
+ * binary (IEC), aka powers of 1024.
95
+ * @param {number} dp Number of decimal places to display.
96
+ *
97
+ * @return {string} Formatted string.
98
+ */
99
+ export function humanFileSize(bytes, si=false, dp=2) {
100
+ const threshold = si ? 1000 : 1024
101
+
102
+ if (Math.abs(bytes) < threshold) {
103
+ return bytes + ' B'
104
+ }
105
+
106
+ const units = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
107
+ let u = -1
108
+ const r = 10**dp
109
+
110
+ do {
111
+ bytes /= threshold
112
+ ++u
113
+ } while (Math.round(Math.abs(bytes) * r) / r >= threshold && u < units.length - 1)
114
+
115
+
116
+ return bytes.toFixed(dp) + ' ' + units[u]
117
+ }
package/types/build.d.ts CHANGED
@@ -1,74 +1,134 @@
1
- import type LayoutsConfig from './layouts';
2
- import type PostHTMLConfig from './posthtml';
3
- import type TailwindConfig from './tailwind';
4
- import type TemplatesConfig from './templates';
5
- import type ComponentsConfig from './components';
6
- import type {Options as BrowserSyncConfig} from 'browser-sync';
1
+ import ComponentsConfig from './components';
2
+ import type { SpinnerName } from 'cli-spinners';
7
3
 
8
4
  export default interface BuildConfig {
9
5
  /**
10
- Templates configuration.
11
- */
12
- templates: TemplatesConfig;
13
-
14
- /**
15
- Tailwind CSS configuration.
16
- */
17
- tailwind?: TailwindConfig;
18
-
19
- /**
20
- [DEPRECATED] Layouts configuration.
21
- */
22
- layouts?: LayoutsConfig;
23
-
24
- /**
25
- Components configuration.
26
- */
6
+ * Components configuration.
7
+ */
27
8
  components?: ComponentsConfig;
28
9
 
29
10
  /**
30
- PostHTML configuration.
31
- */
32
- posthtml?: PostHTMLConfig;
11
+ * Directory where Maizzle should look for Templates to compile.
12
+ *
13
+ * @default ['src/templates/**\/*.html']
14
+ *
15
+ * @example
16
+ * ```
17
+ * export default {
18
+ * build: {
19
+ * files: ['src/templates/**\/*.html']
20
+ * }
21
+ * }
22
+ * ```
23
+ */
24
+ files?: string | string[];
33
25
 
34
26
  /**
35
- Configure PostCSS
27
+ * Define the output path for compiled Templates, and what file extension they should use.
28
+ *
29
+ * @example
30
+ * ```
31
+ * export default {
32
+ * build: {
33
+ * output: {
34
+ * path: 'build_production',
35
+ * extension: 'html'
36
+ * }
37
+ * }
38
+ * }
39
+ * ```
36
40
  */
37
- postcss?: {
41
+ output?: {
38
42
  /**
39
- Additional PostCSS plugins that you would like to use.
40
-
41
- @default []
42
-
43
- @example
44
- ```
45
- const examplePlugin = require('postcss-example-plugin')
46
- module.exports = {
47
- build: {
48
- postcss: {
49
- plugins: [
50
- examplePlugin()
51
- ]
52
- }
53
- }
54
- }
55
- ```
56
- */
57
- plugins?: any[];
43
+ * Directory where Maizzle should output compiled Templates.
44
+ *
45
+ * @default 'build_{env}'
46
+ */
47
+ path?: string;
48
+ /**
49
+ * File extension to be used for compiled Templates.
50
+ *
51
+ * @default 'html'
52
+ */
53
+ extension: string;
58
54
  };
59
55
 
60
56
  /**
61
- Browsersync configuration.
62
-
63
- When you run the `maizzle serve` command, Maizzle uses [Browsersync](https://browsersync.io/)
64
- to start a local development server and open a directory listing of your emails in your default browser.
65
- */
66
- browsersync?: BrowserSyncConfig;
57
+ * Source and destination directories for static files.
58
+ *
59
+ * @example
60
+ * ```
61
+ * export default {
62
+ * build: {
63
+ * static: {
64
+ * source: ['src/images/**\/*.*'],
65
+ * destination: 'images'
66
+ * }
67
+ * }
68
+ * }
69
+ * ```
70
+ */
71
+ static?: {
72
+ /**
73
+ * Array of paths where Maizzle should look for static files.
74
+ *
75
+ * @default undefined
76
+ */
77
+ source?: string[];
78
+ /**
79
+ * Directory where static files should be copied to,
80
+ * relative to the `build.output` path.
81
+ *
82
+ * @default undefined
83
+ */
84
+ 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
+ };
67
101
 
68
102
  /**
69
- Configure how build errors are handled when developing with the Maizzle CLI.
103
+ * Type of spinner to show in the console.
104
+ *
105
+ * @default 'dots'
106
+ *
107
+ * @example
108
+ * ```
109
+ * export default {
110
+ * build: {
111
+ * spinner: 'bounce'
112
+ * }
113
+ * }
114
+ * ```
115
+ */
116
+ spinner?: SpinnerName;
70
117
 
71
- @default undefined
72
- */
73
- fail?: 'silent' | 'verbose';
118
+ /**
119
+ * Show a summary of files that were compiled, along with their
120
+ * size and the time it took to compile them.
121
+ *
122
+ * @default false
123
+ *
124
+ * @example
125
+ * ```
126
+ * export default {
127
+ * build: {
128
+ * summary: true
129
+ * }
130
+ * }
131
+ * ```
132
+ */
133
+ summary?: boolean;
74
134
  }