@maizzle/framework 4.8.8 → 5.0.0-beta.1

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 +63 -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
package/bin/maizzle CHANGED
@@ -1,3 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- require('@maizzle/cli')
3
+ import bootstrap from '@maizzle/cli'
4
+
5
+ await bootstrap()
package/package.json CHANGED
@@ -1,13 +1,32 @@
1
1
  {
2
2
  "name": "@maizzle/framework",
3
- "version": "4.8.8",
3
+ "version": "5.0.0-beta.1",
4
4
  "description": "Maizzle is a framework that helps you quickly build HTML emails with Tailwind CSS.",
5
5
  "license": "MIT",
6
- "main": "src/index.js",
7
- "types": "types/index.d.ts",
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
+ },
11
30
  "repository": {
12
31
  "type": "git",
13
32
  "url": "https://github.com/maizzle/framework.git"
@@ -27,70 +46,59 @@
27
46
  "email-boilerplate",
28
47
  "html-emails"
29
48
  ],
30
- "publishConfig": {
31
- "access": "public"
32
- },
33
- "scripts": {
34
- "lint": "biome lint ./src ./test",
35
- "pretest": "npm run lint",
36
- "test": "c8 ava"
37
- },
38
- "files": [
39
- "src",
40
- "types",
41
- "bin"
42
- ],
43
49
  "dependencies": {
44
- "@maizzle/cli": "^1.5.1",
45
- "autoprefixer": "^10.4.14",
46
- "browser-sync": "^3.0.2",
47
- "color-shorthand-hex-to-six-digit": "^3.0.2",
48
- "email-comb": "^5.3.1",
50
+ "@csstools/css-calc": "^1.2.2",
51
+ "@maizzle/cli": "next",
52
+ "cheerio": "^1.0.0-rc.12",
53
+ "chokidar": "^3.6.0",
54
+ "cli-table3": "^0.6.5",
55
+ "color-shorthand-hex-to-six-digit": "^5.0.16",
56
+ "defu": "^6.1.4",
57
+ "email-comb": "^7.0.21",
58
+ "express": "^4.19.2",
49
59
  "fast-glob": "^3.3.2",
50
- "front-matter": "^4.0.0",
51
- "fs-extra": "^11.2.0",
52
- "html-crush": "^4.0.0",
53
- "is-url-superb": "^5.0.0",
60
+ "gray-matter": "^4.0.3",
61
+ "html-crush": "^6.0.18",
62
+ "is-url-superb": "^6.1.0",
63
+ "istextorbinary": "^9.5.0",
54
64
  "juice": "^10.0.0",
55
- "lodash": "^4.17.20",
56
- "ora": "^5.1.0",
57
- "postcss": "^8.4.21",
58
- "postcss-import": "^15.0.0",
59
- "postcss-merge-longhand": "^6.0.0",
65
+ "lodash-es": "^4.17.21",
66
+ "morphdom": "^2.7.2",
67
+ "ora": "^8.0.1",
68
+ "pathe": "^1.1.2",
69
+ "postcss": "^8.4.38",
70
+ "postcss-custom-properties": "^13.3.10",
71
+ "postcss-import": "^16.1.0",
72
+ "postcss-safe-parser": "^7.0.0",
60
73
  "posthtml": "^0.16.6",
61
74
  "posthtml-attrs-parser": "^1.1.0",
62
- "posthtml-base-url": "^2.0.2",
75
+ "posthtml-base-url": "^3.1.2",
63
76
  "posthtml-component": "^1.1.0",
64
- "posthtml-content": "^1.0.2",
65
- "posthtml-extend": "^0.6.0",
66
- "posthtml-extra-attributes": "^2.0.0",
67
- "posthtml-fetch": "^3.0.0",
68
- "posthtml-markdownit": "^1.3.1",
69
- "posthtml-match-helper": "^1.0.3",
70
- "posthtml-mso": "^2.0.1",
77
+ "posthtml-content": "^2.0.1",
78
+ "posthtml-extra-attributes": "^3.0.0",
79
+ "posthtml-markdownit": "^3.0.1",
80
+ "posthtml-mso": "^3.0.0",
81
+ "posthtml-parser": "^0.12.0",
82
+ "posthtml-postcss": "^1.0.0",
71
83
  "posthtml-postcss-merge-longhand": "^3.1.0",
72
- "posthtml-safe-class-names": "^3.0.0",
73
- "posthtml-url-parameters": "^2.0.0",
84
+ "posthtml-render": "^3.0.0",
85
+ "posthtml-safe-class-names": "^4.0.2",
86
+ "posthtml-url-parameters": "^3.0.0",
74
87
  "pretty": "^2.0.0",
75
- "query-string": "^7.1.3",
76
- "string-remove-widows": "^2.1.0",
77
- "string-strip-html": "^8.2.0",
78
- "tailwindcss": "^3.2.7"
88
+ "string-remove-widows": "^4.0.22",
89
+ "string-strip-html": "^13.4.8",
90
+ "tailwindcss": "^3.4.4",
91
+ "ws": "^8.17.0"
79
92
  },
80
93
  "devDependencies": {
81
94
  "@biomejs/biome": "^1.8.3",
82
- "@types/browser-sync": "^2.29.0",
83
- "@types/js-beautify": "^1.14.0",
84
- "@types/markdown-it": "^14.0.0",
85
- "ava": "^5.2.0",
86
- "c8": "^10.0.0"
95
+ "@types/js-beautify": "^1.14.3",
96
+ "@types/markdown-it": "^14.1.1",
97
+ "@vitest/coverage-v8": "^2.0.1",
98
+ "supertest": "^7.0.0",
99
+ "vitest": "^2.0.1"
87
100
  },
88
101
  "engines": {
89
- "node": ">=14.0.0"
90
- },
91
- "ava": {
92
- "files": [
93
- "test/**/test*.js"
94
- ]
102
+ "node": ">=18.0.0"
95
103
  }
96
104
  }
@@ -1,27 +1,252 @@
1
- const ora = require('ora')
2
- const {get} = require('lodash')
3
- const Output = require('../generators/output')
4
- const {clearConsole} = require('../utils/helpers')
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
- const build = async (env = 'local', config = {}) => {
7
- const start = new Date()
8
- const spinner = ora('Building emails...').start()
13
+ import get from 'lodash/get.js'
14
+ import isEmpty from 'lodash-es/isEmpty.js'
15
+ import { isBinary } from 'istextorbinary'
9
16
 
10
- const {files, parsed} = await Output.toDisk(env, spinner, config)
17
+ import ora from 'ora'
18
+ import pico from 'picocolors'
19
+ import cliTable from 'cli-table3'
11
20
 
12
- const elapsedSeconds = (Date.now() - start) / 1000
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
- if (get(config, 'build.command') === 'serve') {
15
- if (get(config, 'build.console.clear')) {
16
- clearConsole()
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.succeed(`Re-built ${parsed.length} templates in ${elapsedSeconds}s`)
20
- } else {
21
- spinner.succeed(`Built ${parsed.length} templates in ${elapsedSeconds}s`)
22
- }
59
+ spinner.start('Building...')
23
60
 
24
- return {files}
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
- module.exports = build
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
+ }
@@ -1,198 +1,3 @@
1
- const ora = require('ora')
2
- const fs = require('fs-extra')
3
- const path = require('node:path')
1
+ import server from '../server/index.js'
4
2
 
5
- const Config = require('../generators/config')
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