@maizzle/framework 5.0.0-beta.13 → 5.0.0-beta.15

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maizzle/framework",
3
- "version": "5.0.0-beta.13",
3
+ "version": "5.0.0-beta.15",
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",
@@ -77,6 +77,7 @@
77
77
  "posthtml-component": "^1.1.0",
78
78
  "posthtml-content": "^2.0.1",
79
79
  "posthtml-extra-attributes": "^3.0.0",
80
+ "posthtml-fetch": "^4.0.0",
80
81
  "posthtml-markdownit": "^3.0.1",
81
82
  "posthtml-mso": "^3.0.0",
82
83
  "posthtml-parser": "^0.12.0",
@@ -35,8 +35,6 @@ import {
35
35
 
36
36
  import { readFileConfig } from '../utils/getConfigByFilePath.js'
37
37
 
38
- import { transformers } from '../transformers/index.js'
39
-
40
38
  /**
41
39
  * Compile templates and output to the build directory.
42
40
  * Returns a promise containing an object with files output and the config object.
@@ -238,7 +236,6 @@ export default async (config = {}) => {
238
236
  await config.afterBuild({
239
237
  config,
240
238
  files: allOutputFiles,
241
- transform: transformers,
242
239
  })
243
240
  }
244
241
 
@@ -3,12 +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'
10
12
  import envAttributes from './plugins/envAttributes.js'
11
- import envTags from './plugins/envTags.js'
12
13
 
13
14
  // PostCSS
14
15
  import tailwindcss from 'tailwindcss'
@@ -50,12 +51,29 @@ export async function process(html = '', config = {}) {
50
51
  { page: config },
51
52
  )
52
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, 'build.fetch', {})
67
+ )
68
+ )
69
+
53
70
  return posthtml([
54
71
  ...get(config, 'posthtml.plugins.before', []),
55
72
  envTags(config.env),
56
73
  envAttributes(config.env),
57
74
  expandLinkTag,
58
75
  postcssPlugin,
76
+ fetchPlugin,
59
77
  components(
60
78
  merge(
61
79
  {
@@ -40,6 +40,71 @@ app.use(hmrRoute)
40
40
 
41
41
  let viewing = ''
42
42
  const spinner = ora()
43
+ let templatePaths = []
44
+
45
+ function getTemplateFolders(config) {
46
+ return Array.isArray(get(config, 'build.content'))
47
+ ? config.build.content
48
+ : [config.build.content]
49
+ }
50
+
51
+ async function getTemplatePaths(templateFolders) {
52
+ return await fg.glob([...new Set(templateFolders)])
53
+ }
54
+
55
+ async function getUpdatedRoutes(app, config) {
56
+ return getTemplatePaths(getTemplateFolders(config))
57
+ }
58
+
59
+ async function renderUpdatedFile(file, config) {
60
+ try {
61
+ const startTime = Date.now()
62
+ spinner.start('Building...')
63
+
64
+ // beforeCreate event
65
+ if (typeof config.beforeCreate === 'function') {
66
+ await config.beforeCreate(config)
67
+ }
68
+
69
+ // Read the file
70
+ const fileContent = await fs.readFile(file, 'utf8')
71
+
72
+ // Set a `dev` flag on the config
73
+ config._dev = true
74
+
75
+ // Render the file with PostHTML
76
+ let { html } = await render(fileContent, config)
77
+
78
+ // Update console message
79
+ const shouldReportFileSize = get(config, 'server.reportFileSize', false)
80
+
81
+ spinner.succeed(
82
+ `Done in ${formatTime(Date.now() - startTime)}`
83
+ + `${pico.gray(` [${path.relative(cwd(), file)}]`)}`
84
+ + `${shouldReportFileSize ? ' · ' + getColorizedFileSize(html) : ''}`
85
+ )
86
+
87
+ /**
88
+ * Inject HMR script
89
+ */
90
+ html = injectScript(html, '<script src="/hmr.js"></script>')
91
+
92
+ // Notify connected websocket clients about the change
93
+ wss.clients.forEach(client => {
94
+ if (client.readyState === WebSocket.OPEN) {
95
+ client.send(JSON.stringify({
96
+ type: 'change',
97
+ content: html,
98
+ scrollSync: get(config, 'server.scrollSync', false),
99
+ hmr: get(config, 'server.hmr', true),
100
+ }))
101
+ }
102
+ })
103
+ } catch (error) {
104
+ spinner.fail('Failed to render template.')
105
+ throw error
106
+ }
107
+ }
43
108
 
44
109
  export default async (config = {}) => {
45
110
  // Read the Maizzle config file
@@ -63,16 +128,18 @@ export default async (config = {}) => {
63
128
  */
64
129
  initWebSockets(wss, { scrollSync: shouldScroll, hmr: useHmr })
65
130
 
66
- // Get a list of all template paths
67
- const templateFolders = Array.isArray(get(config, 'build.content'))
68
- ? config.build.content
69
- : [config.build.content]
131
+ // Register routes
132
+ templatePaths = await getUpdatedRoutes(app, config)
70
133
 
71
- const templatePaths = await fg.glob([...new Set(templateFolders)])
72
-
73
- // Set the template paths on the app, we use them in the index view
134
+ /**
135
+ * Store template paths on the request object
136
+ *
137
+ * We use it in the index view to list all templates.
138
+ * */
74
139
  app.request.templatePaths = templatePaths
75
140
 
141
+ // await updateRoutes(app, config)
142
+
76
143
  /**
77
144
  * Create route pattern
78
145
  * Only allow files with the following extensions
@@ -84,7 +151,7 @@ export default async (config = {}) => {
84
151
  )
85
152
  ].join('|')
86
153
 
87
- const routePattern = Array.isArray(templateFolders)
154
+ const routePattern = Array.isArray(getTemplateFolders(config))
88
155
  ? `*/:file.(${extensions})`
89
156
  : `:file.(${extensions})`
90
157
 
@@ -127,7 +194,7 @@ export default async (config = {}) => {
127
194
  })
128
195
 
129
196
  // Error-handling middleware
130
- app.use(async (error, req, res, next) => { // eslint-disable-line
197
+ app.use(async (error, req, res, next) => {
131
198
  console.error(error)
132
199
 
133
200
  const view = await fs.readFile(path.join(__dirname, 'views', 'error.html'), 'utf8')
@@ -145,60 +212,32 @@ export default async (config = {}) => {
145
212
  *
146
213
  * Watches for changes in the configured Templates and Components paths
147
214
  */
215
+ let isWatcherReady = false
148
216
  chokidar
149
- .watch([...templatePaths, ...get(config, 'components.folders', defaultComponentsConfig.folders) ])
217
+ .watch([...templatePaths, ...get(config, 'components.folders', defaultComponentsConfig.folders)])
150
218
  .on('change', async () => {
151
- // Not viewing a component in the browser, no need to rebuild
152
- if (!viewing) {
153
- return
219
+ if (viewing) {
220
+ await renderUpdatedFile(viewing, config)
154
221
  }
155
-
156
- try {
157
- const startTime = Date.now()
158
- spinner.start('Building...')
159
-
160
- // beforeCreate event
161
- if (typeof config.beforeCreate === 'function') {
162
- await config.beforeCreate(config)
163
- }
164
-
165
- // Read the file
166
- const fileContent = await fs.readFile(viewing, 'utf8')
167
-
168
- // Set a `dev` flag on the config
169
- config._dev = true
170
-
171
- // Render the file with PostHTML
172
- let { html } = await render(fileContent, config)
173
-
174
- // Update console message
175
- const shouldReportFileSize = get(config, 'server.reportFileSize', false)
176
-
177
- spinner.succeed(
178
- `Done in ${formatTime(Date.now() - startTime)}`
179
- + `${pico.gray(` [${path.relative(cwd(), viewing)}]`)}`
180
- + `${ shouldReportFileSize ? ' · ' + getColorizedFileSize(html) : ''}`
181
- )
182
-
183
- /**
184
- * Inject HMR script
185
- */
186
- html = injectScript(html, '<script src="/hmr.js"></script>')
187
-
188
- // Notify connected websocket clients about the change
189
- wss.clients.forEach(client => {
190
- if (client.readyState === WebSocket.OPEN) {
191
- client.send(JSON.stringify({
192
- type: 'change',
193
- content: html,
194
- scrollSync: get(config, 'server.scrollSync', false),
195
- hmr: get(config, 'server.hmr', true),
196
- }))
197
- }
198
- })
199
- } catch (error) {
200
- spinner.fail('Failed to render template.')
201
- throw error
222
+ })
223
+ .on('ready', () => {
224
+ /**
225
+ * `add` fires immediately when the watcher is created,
226
+ * so we use this trick to detect new files added
227
+ * after it has started.
228
+ */
229
+ isWatcherReady = true
230
+ })
231
+ .on('add', async () => {
232
+ if (isWatcherReady) {
233
+ templatePaths = await getUpdatedRoutes(app, config)
234
+ app.request.templatePaths = templatePaths
235
+ }
236
+ })
237
+ .on('unlink', async () => {
238
+ if (isWatcherReady) {
239
+ templatePaths = await getUpdatedRoutes(app, config)
240
+ app.request.templatePaths = templatePaths
202
241
  }
203
242
  })
204
243
 
@@ -255,7 +294,7 @@ export default async (config = {}) => {
255
294
  spinner.succeed(
256
295
  `Done in ${formatTime(Date.now() - startTime)}`
257
296
  + `${pico.gray(` [${path.relative(cwd(), filePath)}]`)}`
258
- + `${ shouldReportFileSize ? ' · ' + getColorizedFileSize(html) : ''}`
297
+ + `${shouldReportFileSize ? ' · ' + getColorizedFileSize(html) : ''}`
259
298
  )
260
299
 
261
300
  /**
@@ -302,12 +341,12 @@ export default async (config = {}) => {
302
341
  '**/*/',
303
342
  ...get(config, 'build.static.source', [])
304
343
  ], {
305
- onlyFiles: false,
306
- ignore: [
307
- 'node_modules',
308
- get(config, 'build.output.path', 'build_*'),
309
- ]
310
- })
344
+ onlyFiles: false,
345
+ ignore: [
346
+ 'node_modules',
347
+ get(config, 'build.output.path', 'build_*'),
348
+ ]
349
+ })
311
350
 
312
351
  srcFoldersList.forEach(folder => {
313
352
  app.use(express.static(path.join(config.cwd, folder)))
package/types/config.d.ts CHANGED
@@ -62,7 +62,7 @@ export default interface Config {
62
62
  /**
63
63
  * Configure build settings.
64
64
  */
65
- build: BuildConfig;
65
+ build?: BuildConfig;
66
66
 
67
67
  /**
68
68
  Define a string that will be prepended to sources and hrefs in your HTML and CSS.
@@ -98,7 +98,7 @@ export default interface Config {
98
98
  /**
99
99
  * Configure how CSS is handled.
100
100
  */
101
- css: {
101
+ css?: {
102
102
  /**
103
103
  * Configure CSS inlining.
104
104
  */
@@ -184,7 +184,7 @@ export default interface Config {
184
184
  * }
185
185
  * ```
186
186
  */
187
- filters: boolean | Record<string, (str: string) => string>;
187
+ filters?: boolean | Record<string, (str: string) => string>;
188
188
 
189
189
  /**
190
190
  * Define variables outside of the `page` object.
@@ -473,7 +473,7 @@ export default interface Config {
473
473
  * }
474
474
  * ```
475
475
  */
476
- beforeCreate: Events['beforeCreate'];
476
+ beforeCreate?: Events['beforeCreate'];
477
477
 
478
478
  /**
479
479
  * Runs after the Template's config has been computed, but just before it is compiled.
@@ -492,7 +492,7 @@ export default interface Config {
492
492
  * }
493
493
  * ```
494
494
  */
495
- beforeRender: Events['beforeRender'];
495
+ beforeRender?: Events['beforeRender'];
496
496
 
497
497
  /**
498
498
  * Runs after the Template has been compiled, but before any Transformers have been applied.
@@ -511,7 +511,7 @@ export default interface Config {
511
511
  * }
512
512
  * ```
513
513
  */
514
- afterRender: Events['afterRender'];
514
+ afterRender?: Events['afterRender'];
515
515
 
516
516
  /**
517
517
  * Runs after all Transformers have been applied, just before the final HTML is returned.
@@ -530,7 +530,7 @@ export default interface Config {
530
530
  * }
531
531
  * ```
532
532
  */
533
- afterTransformers: Events['afterTransformers'];
533
+ afterTransformers?: Events['afterTransformers'];
534
534
 
535
535
  /**
536
536
  * Runs after all Templates have been compiled and output to disk.
@@ -547,7 +547,7 @@ export default interface Config {
547
547
  * }
548
548
  * ```
549
549
  */
550
- afterBuild: Events['afterBuild'];
550
+ afterBuild?: Events['afterBuild'];
551
551
 
552
552
  [key: string]: any;
553
553
  }
@@ -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,