@maizzle/framework 5.0.0-beta.8 → 5.0.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.
- package/package.json +33 -31
- package/src/commands/build.js +106 -30
- package/src/generators/plaintext.js +22 -18
- package/src/generators/render.js +16 -18
- package/src/index.js +3 -3
- package/src/posthtml/defaultComponentsConfig.js +10 -2
- package/src/posthtml/defaultConfig.js +13 -3
- package/src/posthtml/index.js +59 -14
- package/src/server/index.js +171 -98
- package/src/server/routes/index.js +51 -13
- package/src/server/views/404.html +59 -0
- package/src/server/views/index.html +162 -14
- package/src/transformers/addAttributes.js +2 -3
- package/src/transformers/attributeToStyle.js +1 -3
- package/src/transformers/baseUrl.js +6 -6
- package/src/transformers/filters/index.js +1 -2
- package/src/transformers/index.js +57 -95
- package/src/transformers/inline.js +44 -48
- package/src/transformers/markdown.js +14 -7
- package/src/transformers/minify.js +4 -3
- package/src/transformers/posthtmlMso.js +1 -3
- package/src/transformers/prettify.js +4 -3
- package/src/transformers/preventWidows.js +15 -65
- package/src/transformers/{comb.js → purge.js} +9 -8
- package/src/transformers/removeAttributes.js +3 -4
- package/src/transformers/replaceStrings.js +7 -5
- package/src/transformers/safeClassNames.js +1 -2
- package/src/transformers/shorthandCss.js +1 -3
- package/src/transformers/sixHex.js +1 -3
- package/src/transformers/template.js +9 -9
- package/src/transformers/urlParameters.js +1 -3
- package/src/transformers/useAttributeSizes.js +1 -3
- package/src/utils/getConfigByFilePath.js +10 -0
- package/src/utils/node.js +0 -23
- package/src/utils/string.js +34 -12
- package/types/build.d.ts +51 -28
- package/types/config.d.ts +127 -64
- package/types/css/inline.d.ts +10 -26
- package/types/css/purge.d.ts +3 -3
- package/types/events.d.ts +153 -5
- package/types/index.d.ts +4 -3
- package/types/posthtml.d.ts +3 -3
- package/types/urlParameters.d.ts +1 -1
- package/types/widowWords.d.ts +16 -36
- package/types/components.d.ts +0 -195
- package/types/expressions.d.ts +0 -100
package/src/posthtml/index.js
CHANGED
|
@@ -3,27 +3,39 @@ 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
|
-
import defaultPosthtmlConfig from './defaultConfig.js'
|
|
9
10
|
import expandLinkTag from './plugins/expandLinkTag.js'
|
|
10
11
|
import envAttributes from './plugins/envAttributes.js'
|
|
11
|
-
import
|
|
12
|
+
import { getPosthtmlOptions } from './defaultConfig.js'
|
|
12
13
|
|
|
13
14
|
// PostCSS
|
|
14
15
|
import tailwindcss from 'tailwindcss'
|
|
16
|
+
import postcssCalc from 'postcss-calc'
|
|
15
17
|
import postcssImport from 'postcss-import'
|
|
18
|
+
import cssVariables from 'postcss-css-variables'
|
|
16
19
|
import postcssSafeParser from 'postcss-safe-parser'
|
|
17
|
-
import customProperties from 'postcss-custom-properties'
|
|
18
20
|
|
|
19
21
|
import defaultComponentsConfig from './defaultComponentsConfig.js'
|
|
20
22
|
|
|
21
23
|
export async function process(html = '', config = {}) {
|
|
24
|
+
/**
|
|
25
|
+
* Configure PostCSS pipeline. Plugins defined and added here
|
|
26
|
+
* will apply to all `<style>` tags in the HTML.
|
|
27
|
+
*/
|
|
28
|
+
const resolveCSSProps = get(config, 'css.resolveProps')
|
|
29
|
+
const resolveCalc = get(config, 'css.resolveCalc') !== false
|
|
30
|
+
? get(config, 'css.resolveCalc', { precision: 2 }) // it's true by default, use default precision 2
|
|
31
|
+
: false
|
|
32
|
+
|
|
22
33
|
const postcssPlugin = posthtmlPostcss(
|
|
23
34
|
[
|
|
24
35
|
postcssImport(),
|
|
25
36
|
tailwindcss(get(config, 'css.tailwind', {})),
|
|
26
|
-
|
|
37
|
+
resolveCSSProps !== false && cssVariables(resolveCSSProps),
|
|
38
|
+
resolveCalc !== false && postcssCalc(resolveCalc),
|
|
27
39
|
...get(config, 'postcss.plugins', []),
|
|
28
40
|
],
|
|
29
41
|
merge(
|
|
@@ -35,12 +47,16 @@ export async function process(html = '', config = {}) {
|
|
|
35
47
|
)
|
|
36
48
|
)
|
|
37
49
|
|
|
38
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Define PostHTML options by merging user-provided ones
|
|
52
|
+
* on top of a default configuration.
|
|
53
|
+
*/
|
|
54
|
+
const posthtmlOptions = getPosthtmlOptions(get(config, 'posthtml.options', {}))
|
|
39
55
|
|
|
40
56
|
const componentsUserOptions = get(config, 'components', {})
|
|
41
57
|
|
|
42
58
|
const expressionsOptions = merge(
|
|
43
|
-
get(config, '
|
|
59
|
+
get(config, 'expressions', get(config, 'posthtml.expressions', {})),
|
|
44
60
|
get(componentsUserOptions, 'expressions', {}),
|
|
45
61
|
)
|
|
46
62
|
|
|
@@ -50,26 +66,55 @@ export async function process(html = '', config = {}) {
|
|
|
50
66
|
{ page: config },
|
|
51
67
|
)
|
|
52
68
|
|
|
69
|
+
const fetchPlugin = posthtmlFetch(
|
|
70
|
+
merge(
|
|
71
|
+
{
|
|
72
|
+
expressions: merge(
|
|
73
|
+
{ locals },
|
|
74
|
+
expressionsOptions,
|
|
75
|
+
{
|
|
76
|
+
missingLocal: '{local}',
|
|
77
|
+
strictMode: false,
|
|
78
|
+
},
|
|
79
|
+
),
|
|
80
|
+
},
|
|
81
|
+
get(config, 'fetch', {})
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
const componentsConfig = merge(
|
|
86
|
+
{
|
|
87
|
+
expressions: merge(
|
|
88
|
+
{ locals },
|
|
89
|
+
expressionsOptions,
|
|
90
|
+
)
|
|
91
|
+
},
|
|
92
|
+
componentsUserOptions,
|
|
93
|
+
defaultComponentsConfig
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
// Ensure `fileExtension` is array and has no duplicates
|
|
97
|
+
componentsConfig.fileExtension = Array.from(new Set(
|
|
98
|
+
[].concat(componentsConfig.fileExtension)
|
|
99
|
+
))
|
|
100
|
+
|
|
53
101
|
return posthtml([
|
|
54
102
|
...get(config, 'posthtml.plugins.before', []),
|
|
55
103
|
envTags(config.env),
|
|
56
104
|
envAttributes(config.env),
|
|
57
105
|
expandLinkTag,
|
|
58
106
|
postcssPlugin,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
expressions: {
|
|
62
|
-
locals,
|
|
63
|
-
}
|
|
64
|
-
}, defaultComponentsConfig)
|
|
65
|
-
),
|
|
107
|
+
fetchPlugin,
|
|
108
|
+
components(componentsConfig),
|
|
66
109
|
expandLinkTag,
|
|
67
110
|
postcssPlugin,
|
|
111
|
+
envTags(config.env),
|
|
112
|
+
envAttributes(config.env),
|
|
68
113
|
...get(config, 'posthtml.plugins.after', get(config, 'posthtml.plugins', []))
|
|
69
114
|
])
|
|
70
115
|
.process(html, posthtmlOptions)
|
|
71
116
|
.then(result => ({
|
|
72
|
-
config,
|
|
117
|
+
config: merge(config, { page: config }),
|
|
73
118
|
html: result.html,
|
|
74
119
|
}))
|
|
75
120
|
.catch(error => {
|
package/src/server/index.js
CHANGED
|
@@ -40,6 +40,77 @@ app.use(hmrRoute)
|
|
|
40
40
|
|
|
41
41
|
let viewing = ''
|
|
42
42
|
const spinner = ora()
|
|
43
|
+
let templatePaths = []
|
|
44
|
+
const serverStartTime = Date.now()
|
|
45
|
+
|
|
46
|
+
function getTemplateFolders(config) {
|
|
47
|
+
return Array.isArray(get(config, 'build.content'))
|
|
48
|
+
? config.build.content
|
|
49
|
+
: [config.build.content]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function getTemplatePaths(templateFolders) {
|
|
53
|
+
return await fg.glob([...new Set(templateFolders)])
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function getUpdatedRoutes(app, config) {
|
|
57
|
+
return getTemplatePaths(getTemplateFolders(config))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function renderUpdatedFile(file, config) {
|
|
61
|
+
try {
|
|
62
|
+
const startTime = Date.now()
|
|
63
|
+
spinner.start('Building...')
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Add current template path info to the config
|
|
67
|
+
*
|
|
68
|
+
* Can be used in events like `beforeRender` to determine
|
|
69
|
+
* which template file is being rendered.
|
|
70
|
+
*/
|
|
71
|
+
config.build.current = {
|
|
72
|
+
path: path.parse(file),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Read the file
|
|
76
|
+
const fileContent = await fs.readFile(file, 'utf8')
|
|
77
|
+
|
|
78
|
+
// Set a `dev` flag on the config
|
|
79
|
+
config._dev = true
|
|
80
|
+
|
|
81
|
+
// Render the file with PostHTML
|
|
82
|
+
let { html } = await render(fileContent, config)
|
|
83
|
+
|
|
84
|
+
// Update console message
|
|
85
|
+
const shouldReportFileSize = get(config, 'server.reportFileSize', false)
|
|
86
|
+
|
|
87
|
+
spinner.succeed(
|
|
88
|
+
`Done in ${formatTime(Date.now() - startTime)}`
|
|
89
|
+
+ `${pico.gray(` [${path.relative(cwd(), file)}]`)}`
|
|
90
|
+
+ `${shouldReportFileSize ? ' · ' + getColorizedFileSize(html) : ''}`
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Inject HMR script
|
|
95
|
+
*/
|
|
96
|
+
html = injectScript(html, '<script src="/hmr.js"></script>')
|
|
97
|
+
|
|
98
|
+
// Notify connected websocket clients about the change
|
|
99
|
+
wss.clients.forEach(client => {
|
|
100
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
101
|
+
client.send(JSON.stringify({
|
|
102
|
+
type: 'change',
|
|
103
|
+
content: html,
|
|
104
|
+
scrollSync: get(config, 'server.scrollSync', false),
|
|
105
|
+
hmr: get(config, 'server.hmr', true),
|
|
106
|
+
}))
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
} catch (error) {
|
|
110
|
+
spinner.fail('Failed to render template.')
|
|
111
|
+
throw error
|
|
112
|
+
}
|
|
113
|
+
}
|
|
43
114
|
|
|
44
115
|
export default async (config = {}) => {
|
|
45
116
|
// Read the Maizzle config file
|
|
@@ -47,7 +118,10 @@ export default async (config = {}) => {
|
|
|
47
118
|
|
|
48
119
|
/**
|
|
49
120
|
* Dev server settings
|
|
50
|
-
|
|
121
|
+
*/
|
|
122
|
+
spinner.spinner = get(config, 'server.spinner', 'circleHalves')
|
|
123
|
+
spinner.start('Starting server...')
|
|
124
|
+
|
|
51
125
|
const shouldScroll = get(config, 'server.scrollSync', false)
|
|
52
126
|
const useHmr = get(config, 'server.hmr', true)
|
|
53
127
|
|
|
@@ -60,18 +134,20 @@ export default async (config = {}) => {
|
|
|
60
134
|
* Initialize WebSocket server
|
|
61
135
|
* Used to send messages between the server and the browser
|
|
62
136
|
*/
|
|
63
|
-
initWebSockets(wss, {
|
|
64
|
-
|
|
65
|
-
// Get a list of all template paths
|
|
66
|
-
const templateFolders = Array.isArray(get(config, 'build.content'))
|
|
67
|
-
? config.build.content
|
|
68
|
-
: [config.build.content]
|
|
137
|
+
initWebSockets(wss, { shouldScroll, useHmr })
|
|
69
138
|
|
|
70
|
-
|
|
139
|
+
// Register routes
|
|
140
|
+
templatePaths = await getUpdatedRoutes(app, config)
|
|
71
141
|
|
|
72
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Store template paths on the request object
|
|
144
|
+
*
|
|
145
|
+
* We use it in the index view to list all templates.
|
|
146
|
+
* */
|
|
73
147
|
app.request.templatePaths = templatePaths
|
|
74
148
|
|
|
149
|
+
app.request.maizzleConfig = config
|
|
150
|
+
|
|
75
151
|
/**
|
|
76
152
|
* Create route pattern
|
|
77
153
|
* Only allow files with the following extensions
|
|
@@ -83,7 +159,7 @@ export default async (config = {}) => {
|
|
|
83
159
|
)
|
|
84
160
|
].join('|')
|
|
85
161
|
|
|
86
|
-
const routePattern = Array.isArray(
|
|
162
|
+
const routePattern = Array.isArray(getTemplateFolders(config))
|
|
87
163
|
? `*/:file.(${extensions})`
|
|
88
164
|
: `:file.(${extensions})`
|
|
89
165
|
|
|
@@ -94,15 +170,20 @@ export default async (config = {}) => {
|
|
|
94
170
|
app.get(routePattern, async (req, res, next) => {
|
|
95
171
|
// Run beforeCreate event
|
|
96
172
|
if (typeof config.beforeCreate === 'function') {
|
|
97
|
-
config.beforeCreate(config)
|
|
173
|
+
await config.beforeCreate({ config })
|
|
98
174
|
}
|
|
99
175
|
|
|
100
176
|
try {
|
|
101
|
-
const filePath = templatePaths.find(t => t.endsWith(req.url.slice(1)))
|
|
177
|
+
const filePath = templatePaths.find(t => t.endsWith(decodeURI(req.url.slice(1))))
|
|
102
178
|
|
|
103
179
|
// Set the file being viewed
|
|
104
180
|
viewing = filePath
|
|
105
181
|
|
|
182
|
+
// Add current template path info to the config
|
|
183
|
+
config.build.current = {
|
|
184
|
+
path: path.parse(filePath),
|
|
185
|
+
}
|
|
186
|
+
|
|
106
187
|
// Read the file
|
|
107
188
|
const fileContent = await fs.readFile(filePath, 'utf8')
|
|
108
189
|
|
|
@@ -125,99 +206,82 @@ export default async (config = {}) => {
|
|
|
125
206
|
})
|
|
126
207
|
})
|
|
127
208
|
|
|
128
|
-
// Error-handling middleware
|
|
129
|
-
app.use(async (error, req, res, next) => { // eslint-disable-line
|
|
130
|
-
console.error(error)
|
|
131
|
-
|
|
132
|
-
const view = await fs.readFile(path.join(__dirname, 'views', 'error.html'), 'utf8')
|
|
133
|
-
const { html } = await render(view, {
|
|
134
|
-
method: req.method,
|
|
135
|
-
url: req.url,
|
|
136
|
-
error
|
|
137
|
-
})
|
|
138
|
-
|
|
139
|
-
res.status(500).send(html)
|
|
140
|
-
})
|
|
141
|
-
|
|
142
209
|
/**
|
|
143
210
|
* Components watcher
|
|
144
211
|
*
|
|
145
212
|
* Watches for changes in the configured Templates and Components paths
|
|
146
213
|
*/
|
|
214
|
+
let isWatcherReady = false
|
|
147
215
|
chokidar
|
|
148
|
-
.watch(
|
|
216
|
+
.watch(
|
|
217
|
+
[
|
|
218
|
+
...templatePaths,
|
|
219
|
+
...get(config, 'components.folders', defaultComponentsConfig.folders)
|
|
220
|
+
],
|
|
221
|
+
{
|
|
222
|
+
ignoreInitial: true,
|
|
223
|
+
awaitWriteFinish: {
|
|
224
|
+
stabilityThreshold: 150,
|
|
225
|
+
pollInterval: 25,
|
|
226
|
+
},
|
|
227
|
+
}
|
|
228
|
+
)
|
|
149
229
|
.on('change', async () => {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
return
|
|
230
|
+
if (viewing) {
|
|
231
|
+
await renderUpdatedFile(viewing, config)
|
|
153
232
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
// Update console message
|
|
174
|
-
const shouldReportFileSize = get(config, 'server.reportFileSize', false)
|
|
175
|
-
|
|
176
|
-
spinner.succeed(
|
|
177
|
-
`Done in ${formatTime(Date.now() - startTime)}`
|
|
178
|
-
+ `${pico.gray(` [${path.relative(cwd(), viewing)}]`)}`
|
|
179
|
-
+ `${ shouldReportFileSize ? ' · ' + getColorizedFileSize(html) : ''}`
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Inject HMR script
|
|
184
|
-
*/
|
|
185
|
-
html = injectScript(html, '<script src="/hmr.js"></script>')
|
|
186
|
-
|
|
187
|
-
// Notify connected websocket clients about the change
|
|
188
|
-
wss.clients.forEach(client => {
|
|
189
|
-
if (client.readyState === WebSocket.OPEN) {
|
|
190
|
-
client.send(JSON.stringify({
|
|
191
|
-
type: 'change',
|
|
192
|
-
content: html,
|
|
193
|
-
scrollSync: get(config, 'server.scrollSync', false),
|
|
194
|
-
hmr: get(config, 'server.hmr', true),
|
|
195
|
-
}))
|
|
196
|
-
}
|
|
197
|
-
})
|
|
198
|
-
} catch (error) {
|
|
199
|
-
spinner.fail('Failed to render template.')
|
|
200
|
-
throw error
|
|
233
|
+
})
|
|
234
|
+
.on('ready', () => {
|
|
235
|
+
/**
|
|
236
|
+
* `add` fires immediately when the watcher is created,
|
|
237
|
+
* so we use this trick to detect new files added
|
|
238
|
+
* after it has started.
|
|
239
|
+
*/
|
|
240
|
+
isWatcherReady = true
|
|
241
|
+
})
|
|
242
|
+
.on('add', async () => {
|
|
243
|
+
if (isWatcherReady) {
|
|
244
|
+
templatePaths = await getUpdatedRoutes(app, config)
|
|
245
|
+
app.request.templatePaths = templatePaths
|
|
246
|
+
}
|
|
247
|
+
})
|
|
248
|
+
.on('unlink', async () => {
|
|
249
|
+
if (isWatcherReady) {
|
|
250
|
+
templatePaths = await getUpdatedRoutes(app, config)
|
|
251
|
+
app.request.templatePaths = templatePaths
|
|
201
252
|
}
|
|
202
253
|
})
|
|
203
254
|
|
|
204
255
|
/**
|
|
205
256
|
* Global watcher
|
|
206
257
|
*
|
|
207
|
-
* Watch for changes in the config
|
|
258
|
+
* Watch for changes in the config files, Tailwind CSS config, CSS files,
|
|
259
|
+
* configured static assets, and user-defined watch paths.
|
|
208
260
|
*/
|
|
209
261
|
const globalWatchedPaths = new Set([
|
|
210
|
-
'config*.js',
|
|
211
|
-
'maizzle.config*.js',
|
|
212
|
-
'tailwind*.config.js',
|
|
262
|
+
'config*.{js,cjs,ts}',
|
|
263
|
+
'maizzle.config*.{js,cjs,ts}',
|
|
264
|
+
'tailwind*.config.{js,ts}',
|
|
213
265
|
'**/*.css',
|
|
214
|
-
...get(config, '
|
|
266
|
+
...get(config, 'build.static.source', []),
|
|
267
|
+
...get(config, 'server.watch', []),
|
|
215
268
|
])
|
|
216
269
|
|
|
217
270
|
async function globalPathsHandler(file, eventType) {
|
|
271
|
+
// Update express.static to serve new files
|
|
272
|
+
if (eventType === 'add') {
|
|
273
|
+
app.use(express.static(path.dirname(file)))
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Stop serving deleted files
|
|
277
|
+
if (eventType === 'unlink') {
|
|
278
|
+
app._router.stack = app._router.stack.filter(
|
|
279
|
+
layer => layer.regexp.source !== path.dirname(file).replace(/\\/g, '/')
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
|
|
218
283
|
// Not viewing a component in the browser, no need to rebuild
|
|
219
284
|
if (!viewing) {
|
|
220
|
-
spinner.info(`file ${eventType}: ${file}`)
|
|
221
285
|
return
|
|
222
286
|
}
|
|
223
287
|
|
|
@@ -235,7 +299,7 @@ export default async (config = {}) => {
|
|
|
235
299
|
|
|
236
300
|
// Run beforeCreate event
|
|
237
301
|
if (typeof config.beforeCreate === 'function') {
|
|
238
|
-
await config.beforeCreate(config)
|
|
302
|
+
await config.beforeCreate({ config })
|
|
239
303
|
}
|
|
240
304
|
|
|
241
305
|
// Read the file
|
|
@@ -254,7 +318,7 @@ export default async (config = {}) => {
|
|
|
254
318
|
spinner.succeed(
|
|
255
319
|
`Done in ${formatTime(Date.now() - startTime)}`
|
|
256
320
|
+ `${pico.gray(` [${path.relative(cwd(), filePath)}]`)}`
|
|
257
|
-
+ `${
|
|
321
|
+
+ `${shouldReportFileSize ? ' · ' + getColorizedFileSize(html) : ''}`
|
|
258
322
|
)
|
|
259
323
|
|
|
260
324
|
/**
|
|
@@ -274,7 +338,7 @@ export default async (config = {}) => {
|
|
|
274
338
|
}
|
|
275
339
|
})
|
|
276
340
|
} catch (error) {
|
|
277
|
-
spinner.fail(
|
|
341
|
+
spinner.fail(`Failed to render template: ${file}`)
|
|
278
342
|
throw error
|
|
279
343
|
}
|
|
280
344
|
}
|
|
@@ -286,6 +350,10 @@ export default async (config = {}) => {
|
|
|
286
350
|
get(config, 'build.output.path', 'build_production'),
|
|
287
351
|
],
|
|
288
352
|
ignoreInitial: true,
|
|
353
|
+
awaitWriteFinish: {
|
|
354
|
+
stabilityThreshold: 150,
|
|
355
|
+
pollInterval: 25,
|
|
356
|
+
},
|
|
289
357
|
})
|
|
290
358
|
.on('change', async file => await globalPathsHandler(file, 'change'))
|
|
291
359
|
.on('add', async file => await globalPathsHandler(file, 'add'))
|
|
@@ -293,25 +361,33 @@ export default async (config = {}) => {
|
|
|
293
361
|
|
|
294
362
|
/**
|
|
295
363
|
* Serve all folders in the cwd as static files
|
|
296
|
-
*
|
|
297
|
-
* TODO: change to include build.assets or build.static, which may be outside cwd
|
|
298
364
|
*/
|
|
299
365
|
const srcFoldersList = await fg.glob(
|
|
300
366
|
[
|
|
301
367
|
'**/*/',
|
|
302
368
|
...get(config, 'build.static.source', [])
|
|
303
369
|
], {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
370
|
+
onlyFiles: false,
|
|
371
|
+
ignore: [
|
|
372
|
+
'node_modules',
|
|
373
|
+
get(config, 'build.output.path', 'build_*'),
|
|
374
|
+
]
|
|
375
|
+
})
|
|
310
376
|
|
|
311
377
|
srcFoldersList.forEach(folder => {
|
|
312
378
|
app.use(express.static(path.join(config.cwd, folder)))
|
|
313
379
|
})
|
|
314
380
|
|
|
381
|
+
// Error-handling middleware
|
|
382
|
+
app.use(async (req, res) => {
|
|
383
|
+
const view = await fs.readFile(path.join(__dirname, 'views', '404.html'), 'utf8')
|
|
384
|
+
const { html } = await render(view, {
|
|
385
|
+
url: req.url,
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
res.status(404).send(html)
|
|
389
|
+
})
|
|
390
|
+
|
|
315
391
|
/**
|
|
316
392
|
* Start the server
|
|
317
393
|
*/
|
|
@@ -320,9 +396,6 @@ export default async (config = {}) => {
|
|
|
320
396
|
const maxRetries = get(config, 'server.maxRetries', 10)
|
|
321
397
|
|
|
322
398
|
function startServer(port) {
|
|
323
|
-
const serverStartTime = Date.now()
|
|
324
|
-
spinner.start('Starting server...')
|
|
325
|
-
|
|
326
399
|
const server = createServer(app)
|
|
327
400
|
|
|
328
401
|
/**
|
|
@@ -343,7 +416,7 @@ export default async (config = {}) => {
|
|
|
343
416
|
)
|
|
344
417
|
|
|
345
418
|
spinner.stopAndPersist({
|
|
346
|
-
text:
|
|
419
|
+
text: `\n${pico.bgBlue(` Maizzle v${version} `)} ready in ${pico.bold(formatTime(Date.now() - serverStartTime))}`
|
|
347
420
|
+ '\n\n'
|
|
348
421
|
+ ` → Local: http://localhost:${port}`
|
|
349
422
|
+ '\n'
|
|
@@ -7,27 +7,65 @@ import { fileURLToPath } from 'node:url'
|
|
|
7
7
|
import expressions from 'posthtml-expressions'
|
|
8
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
const
|
|
10
|
+
function groupFilesByDirectories(globs, files) {
|
|
11
|
+
const result = {}
|
|
12
|
+
let current = {}
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
globs.forEach(glob => {
|
|
15
|
+
const rootPath = glob.split(/[\*\!\{\}]/)[0].replace(/\/+$/, '')
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
files.forEach(file => {
|
|
18
|
+
if (file.startsWith(rootPath)) {
|
|
19
|
+
const relativePath = file.slice(rootPath.length + 1)
|
|
20
|
+
const parts = relativePath.split('/')
|
|
21
|
+
current = result[rootPath] = result[rootPath] || {}
|
|
22
|
+
|
|
23
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
24
|
+
current = current[parts[i]] = current[parts[i]] || {}
|
|
25
|
+
}
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
const fileName = parts[parts.length - 1]
|
|
28
|
+
current[fileName] = {
|
|
29
|
+
name: fileName,
|
|
30
|
+
href: encodeURI(file),
|
|
31
|
+
}
|
|
32
|
+
}
|
|
25
33
|
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
return result
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function flattenPaths(paths, parentPath = '', currentDepth = 0) {
|
|
40
|
+
const flatArray = []
|
|
41
|
+
|
|
42
|
+
for (const [key, value] of Object.entries(paths)) {
|
|
43
|
+
const fullPath = parentPath ? `${parentPath}/${key}` : key
|
|
44
|
+
|
|
45
|
+
if (value && typeof value === 'object' && !value.name) {
|
|
46
|
+
// If it's a folder, add it with the current depth and recurse into its contents
|
|
47
|
+
flatArray.push({ name: key, path: fullPath, depth: currentDepth, type: 'folder' })
|
|
48
|
+
flatArray.push(...flattenPaths(value, fullPath, currentDepth + 1))
|
|
49
|
+
} else if (value && typeof value === 'object' && value.name) {
|
|
50
|
+
// If it's a file, add it with the current depth
|
|
51
|
+
flatArray.push({ name: value.name, href: value.href, path: fullPath, depth: currentDepth, type: 'file' })
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return flatArray
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
route.get(['/', '/index.html'], async (req, res) => {
|
|
59
|
+
const view = await fs.readFile(path.join(__dirname, '../views', 'index.html'), 'utf8')
|
|
60
|
+
|
|
61
|
+
const content = new Set(req.maizzleConfig.build.content)
|
|
62
|
+
|
|
63
|
+
const groupedByDir = groupFilesByDirectories(content, req.templatePaths)
|
|
26
64
|
|
|
27
65
|
const { html } = await posthtml()
|
|
28
66
|
.use(expressions({
|
|
29
67
|
locals: {
|
|
30
|
-
|
|
68
|
+
paths: flattenPaths(groupedByDir)
|
|
31
69
|
}
|
|
32
70
|
}))
|
|
33
71
|
.process(view)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>404 - Template not found</title>
|
|
7
|
+
<style>
|
|
8
|
+
html, body {
|
|
9
|
+
font-family: Helvetica, Arial, sans-serif;
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
height: 100%;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.container {
|
|
17
|
+
box-sizing: border-box;
|
|
18
|
+
height: 100vh;
|
|
19
|
+
display: flex;
|
|
20
|
+
align-items: center;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
position: relative;
|
|
23
|
+
z-index: 1;
|
|
24
|
+
padding: 24px;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.error-code {
|
|
28
|
+
font-size: 25rem;
|
|
29
|
+
font-weight: 700;
|
|
30
|
+
color: #f1f5f9;
|
|
31
|
+
position: fixed;
|
|
32
|
+
top: -1.5rem;
|
|
33
|
+
left: -3rem;
|
|
34
|
+
user-select: none;
|
|
35
|
+
}
|
|
36
|
+
</style>
|
|
37
|
+
</head>
|
|
38
|
+
<body>
|
|
39
|
+
<span class="error-code">404</span>
|
|
40
|
+
|
|
41
|
+
<div class="container">
|
|
42
|
+
<div style="text-align: center;">
|
|
43
|
+
<h1 style="font-size: 3rem; color: #0F172A; margin: 2.25rem 0">
|
|
44
|
+
Template Not Found
|
|
45
|
+
</h1>
|
|
46
|
+
<p style="margin: 0 0 2.25rem; font-size: 1.25rem; line-height: 1.5; color: #64748B;">
|
|
47
|
+
The Template you are looking for was not found:
|
|
48
|
+
</p>
|
|
49
|
+
<p style="margin: 1rem 0 0; font-size: 1rem; line-height: 1.5; font-weight: 600; color: #334155;">
|
|
50
|
+
{{ page.url }}
|
|
51
|
+
</p>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div style="position: fixed; bottom: 0; right: 0; pointer-events: none; user-select: none;">
|
|
56
|
+
<svg width="883" height="536" fill="none" xmlns="http://www.w3.org/2000/svg"><mask id="a" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="1100" height="536"><path fill="#D9D9D9" d="M0 .955h1100V536H0z"/></mask><g mask="url(#a)" stroke="#94A3B8" stroke-miterlimit="10"><path d="M1056.93 92.587c0-50.03-43.95-90.587-98.168-90.587-54.22 0-98.174 40.557-98.174 90.587v483.125c0 50.029 43.954 90.586 98.174 90.586 54.218 0 98.168-40.557 98.168-90.586V92.587ZM646.241 92.587C646.241 42.556 602.287 2 548.067 2c-54.219 0-98.173 40.557-98.173 90.587v483.125c0 50.029 43.954 90.586 98.173 90.586 54.22 0 98.174-40.557 98.174-90.586V92.587Z"/><path d="M1036.18 148.383c33.41-39.402 25.88-96.336-16.82-127.164C976.657-9.61 914.955-2.66 881.544 36.742L471.586 520.215c-33.411 39.402-25.879 96.336 16.824 127.164 42.702 30.829 104.404 23.879 137.815-15.523l409.955-483.473ZM625.093 148.396c33.411-39.403 25.878-96.336-16.824-127.164C565.567-9.597 503.865-2.647 470.454 36.755L60.495 520.228c-33.41 39.402-25.878 96.336 16.825 127.164 42.702 30.829 104.404 23.879 137.815-15.523l409.958-483.473Z"/></g></svg>
|
|
57
|
+
</div>
|
|
58
|
+
</body>
|
|
59
|
+
</html>
|