poops 1.2.2 → 1.2.4
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/README.md +263 -58
- package/lib/copy.js +4 -2
- package/lib/images.js +57 -0
- package/lib/markup/collections.js +38 -14
- package/lib/markup/engines/liquid.js +17 -28
- package/lib/markup/engines/nunjucks.js +22 -34
- package/lib/markup/helpers.js +180 -37
- package/lib/markup/image-cache.js +114 -0
- package/lib/markup/indexer.js +6 -2
- package/lib/markups.js +56 -11
- package/lib/postcss.js +16 -30
- package/lib/reactor.js +11 -29
- package/lib/scripts.js +37 -34
- package/lib/styles.js +17 -31
- package/lib/utils/helpers.js +20 -16
- package/lib/utils/log.js +15 -1
- package/lib/utils/minify.js +41 -0
- package/package.json +7 -3
- package/poops.js +114 -23
package/poops.js
CHANGED
|
@@ -9,11 +9,12 @@ import os from 'node:os'
|
|
|
9
9
|
import fs from 'node:fs'
|
|
10
10
|
import livereload from 'livereload'
|
|
11
11
|
import Markups from './lib/markups.js'
|
|
12
|
+
import Images from './lib/images.js'
|
|
12
13
|
import path from 'node:path'
|
|
13
14
|
import serveStatic from 'serve-static'
|
|
14
15
|
import Reactor from './lib/reactor.js'
|
|
15
16
|
import Scripts from './lib/scripts.js'
|
|
16
|
-
import log, { styledLog } from './lib/utils/log.js'
|
|
17
|
+
import log, { styledLog, hasLoggedErrors } from './lib/utils/log.js'
|
|
17
18
|
import Styles from './lib/styles.js'
|
|
18
19
|
import PostCSS from './lib/postcss.js'
|
|
19
20
|
import Argoyle from 'argoyle'
|
|
@@ -28,6 +29,7 @@ const cli = new Argoyle(pkg.version)
|
|
|
28
29
|
.option('config', { short: 'c', value: '<path>', description: 'Specify the config file' })
|
|
29
30
|
.option('port', { short: 'p', value: '<number>', description: 'Specify the port for the server, overrides the config file' })
|
|
30
31
|
.option('livereload-port', { short: 'l', value: '<number>', description: 'Specify the port for the livereload server, overrides the config file' })
|
|
32
|
+
.option('base-url', { short: 'u', value: '<path>', description: 'Set the base URL prefix for markup, overrides the config file' })
|
|
31
33
|
|
|
32
34
|
let flags, positionals
|
|
33
35
|
try {
|
|
@@ -41,6 +43,7 @@ const build = flags.build
|
|
|
41
43
|
const defaultConfigPath = flags.config || positionals[0] || 'poops.json'
|
|
42
44
|
const overridePort = flags.port
|
|
43
45
|
const overrideLivereloadPort = flags['livereload-port']
|
|
46
|
+
const overrideBaseURL = flags['base-url']
|
|
44
47
|
|
|
45
48
|
let configPath = path.join(cwd, defaultConfigPath)
|
|
46
49
|
if (!pathExists(configPath)) configPath = path.join(cwd, '💩.json') // TODO: Ok dude, I know it's late, but you can do better than this.
|
|
@@ -70,7 +73,9 @@ function setupLiveReloadServer(config) {
|
|
|
70
73
|
|
|
71
74
|
const liveReloadServer = livereload.createServer({
|
|
72
75
|
exclusions: [...new Set(liveReloadExcludes)],
|
|
73
|
-
port: config.livereload_port
|
|
76
|
+
port: config.livereload_port,
|
|
77
|
+
exts: config.livereload.exts, // replaces the default watched extensions
|
|
78
|
+
extraExts: config.livereload.extraExts // adds to the defaults
|
|
74
79
|
})
|
|
75
80
|
styledLog(`🔃 {dim}LiveReload :{/} ${liveReloadServer.config.port}`)
|
|
76
81
|
console.log()
|
|
@@ -82,7 +87,15 @@ function setupWatchers(config, modules) {
|
|
|
82
87
|
|
|
83
88
|
// TODO: think about watching the updates of the config file itself, we can reload the config and recompile everything.
|
|
84
89
|
// TODO: ability to automatically create a watch list of directories if watch is set to true. The list will be generated from the `in` property of each task.
|
|
85
|
-
|
|
90
|
+
// awaitWriteFinish: wait for saves to finish writing before recompiling, so a
|
|
91
|
+
// mid-write (truncated/partial) file is never read. Fixes intermittent broken
|
|
92
|
+
// builds on editor save. Bump thresholds if slow disks flake.
|
|
93
|
+
// Shared by 'change' and 'add': editors with atomic saves (rename-write)
|
|
94
|
+
// fire unlink+add instead of change, so both events must trigger the same
|
|
95
|
+
// rebuilds. 'add' also covers genuinely new files (e.g. a new markup page).
|
|
96
|
+
// Rebuild branches shared by change/add/deletion: a deletion needs the same
|
|
97
|
+
// rebuilds (a deleted-but-still-imported file must surface the error).
|
|
98
|
+
const rebuild = (file) => {
|
|
86
99
|
if (/(\.m?jsx?|\.tsx?)$/i.test(file)) {
|
|
87
100
|
modules.scripts.compile().catch(err => console.error(err))
|
|
88
101
|
|
|
@@ -102,26 +115,79 @@ function setupWatchers(config, modules) {
|
|
|
102
115
|
modules.markups.compile().then(() => modules.postcss.compile()).catch(err => console.error(err))
|
|
103
116
|
}
|
|
104
117
|
|
|
105
|
-
// TODO: We can actually reload the page only if the data file from data has changed.
|
|
106
118
|
if (/(\.json|\.ya?ml)$/i.test(file)) {
|
|
107
119
|
modules.markups.reloadDataFiles().then(() => modules.markups.compile()).catch(err => console.error(err))
|
|
108
120
|
}
|
|
121
|
+
}
|
|
109
122
|
|
|
123
|
+
// Source image extensions handled by poops-images. The doesFileBelongToPath
|
|
124
|
+
// guard (against config.images.in) also breaks the feedback loop: generated
|
|
125
|
+
// variants land in the `out` dir, never in `in`, so they don't retrigger.
|
|
126
|
+
const imageExtRe = /(\.jpe?g|\.png|\.tiff?|\.webp|\.heic|\.heif|\.svg|\.gif)$/i
|
|
127
|
+
const belongsToImages = (file) => imageExtRe.test(file) && doesFileBelongToPath(file, config.images)
|
|
128
|
+
|
|
129
|
+
const compileChanged = (file) => {
|
|
130
|
+
rebuild(file)
|
|
131
|
+
if (belongsToImages(file)) {
|
|
132
|
+
modules.images.compile()
|
|
133
|
+
.then(() => modules.markups.compile())
|
|
134
|
+
.then(() => modules.postcss.compile())
|
|
135
|
+
.catch(err => console.error(err))
|
|
136
|
+
}
|
|
110
137
|
doesFileBelongToPath(file, config.copy) && modules.copy.execute().catch(err => console.error(err))
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Atomic-save editors (rename-write) fire unlink+add for every save, so an
|
|
141
|
+
// unlink only counts as a real deletion if no add for the same path follows
|
|
142
|
+
// within the settle window. Must outlive the 150ms awaitWriteFinish delay
|
|
143
|
+
// on 'add'. Fixed 300ms; make configurable if slow disks flake.
|
|
144
|
+
const UNLINK_SETTLE_MS = 300
|
|
145
|
+
const pendingUnlinks = new Map()
|
|
146
|
+
|
|
147
|
+
const scheduleUnlink = (target, handler) => {
|
|
148
|
+
clearTimeout(pendingUnlinks.get(target))
|
|
149
|
+
pendingUnlinks.set(target, setTimeout(() => {
|
|
150
|
+
pendingUnlinks.delete(target)
|
|
151
|
+
handler(target)
|
|
152
|
+
}, UNLINK_SETTLE_MS))
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const handleDeleted = (file) => {
|
|
156
|
+
modules.markups.removeOutput(file)
|
|
157
|
+
if (belongsToImages(file)) {
|
|
158
|
+
// Deleted source: drop its variants + cache entry, then recompile markup
|
|
159
|
+
// so galleries/srcsets reading the image cache no longer reference it.
|
|
160
|
+
modules.images.remove(file)
|
|
161
|
+
.then(() => modules.markups.compile())
|
|
162
|
+
.then(() => modules.postcss.compile())
|
|
163
|
+
.catch(err => console.error(err))
|
|
114
164
|
}
|
|
165
|
+
rebuild(file)
|
|
115
166
|
modules.copy.unlink(file, doesFileBelongToPath(file, config.copy))
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (
|
|
121
|
-
modules.markups.
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const handleDeletedDir = (dirPath) => {
|
|
170
|
+
modules.markups.removeOutput(dirPath)
|
|
171
|
+
if (doesFileBelongToPath(dirPath, config.markup)) {
|
|
172
|
+
modules.markups.compile().then(() => modules.postcss.compile()).catch(err => console.error(err))
|
|
122
173
|
}
|
|
123
|
-
|
|
124
|
-
}
|
|
174
|
+
modules.copy.unlink(dirPath, doesFileBelongToPath(dirPath, config.copy))
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
chokidar.watch(config.watch, {
|
|
178
|
+
ignoreInitial: true,
|
|
179
|
+
awaitWriteFinish: { stabilityThreshold: 150, pollInterval: 50 }
|
|
180
|
+
}).on('change', compileChanged)
|
|
181
|
+
.on('add', (file) => {
|
|
182
|
+
const pending = pendingUnlinks.get(file)
|
|
183
|
+
if (pending) {
|
|
184
|
+
clearTimeout(pending)
|
|
185
|
+
pendingUnlinks.delete(file)
|
|
186
|
+
}
|
|
187
|
+
compileChanged(file)
|
|
188
|
+
})
|
|
189
|
+
.on('unlink', (file) => scheduleUnlink(file, handleDeleted))
|
|
190
|
+
.on('unlinkDir', (dirPath) => scheduleUnlink(dirPath, handleDeletedDir))
|
|
125
191
|
}
|
|
126
192
|
|
|
127
193
|
// Main function 💩
|
|
@@ -130,22 +196,32 @@ async function poops() {
|
|
|
130
196
|
const postcss = new PostCSS(config)
|
|
131
197
|
const reactor = new Reactor(config)
|
|
132
198
|
const scripts = new Scripts(config)
|
|
199
|
+
const images = new Images(config)
|
|
133
200
|
const markups = new Markups(config)
|
|
134
201
|
const copy = new Copy(config)
|
|
135
202
|
|
|
136
|
-
|
|
137
|
-
|
|
203
|
+
// Thrown errors are caught so one failing step doesn't stop the rest;
|
|
204
|
+
// `failed` + hasLoggedErrors() (module-internal, swallowed errors) decide
|
|
205
|
+
// the build exit code, so a broken compile can't ship as a green build.
|
|
206
|
+
let failed = false
|
|
207
|
+
const step = async(task) => {
|
|
208
|
+
try { await task() } catch (err) { failed = true; console.error(err) }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
await step(() => styles.compile())
|
|
212
|
+
await step(() => reactor.compile())
|
|
138
213
|
config.reactorData = reactor.getRendered()
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
214
|
+
await step(() => scripts.compile())
|
|
215
|
+
await step(() => images.compile()) // before markups: engines read the poops-images cache
|
|
216
|
+
await step(() => markups.compile())
|
|
217
|
+
await step(() => postcss.compile())
|
|
218
|
+
await step(() => copy.execute())
|
|
143
219
|
|
|
144
220
|
if (build || (!config.watch && !config.livereload && !config.serve)) {
|
|
145
|
-
process.exit(0)
|
|
221
|
+
process.exit(failed || hasLoggedErrors() ? 1 : 0)
|
|
146
222
|
}
|
|
147
223
|
|
|
148
|
-
setupWatchers(config, { styles, postcss, reactor, scripts, markups, copy })
|
|
224
|
+
setupWatchers(config, { styles, postcss, reactor, scripts, images, markups, copy })
|
|
149
225
|
}
|
|
150
226
|
|
|
151
227
|
// CLI Header
|
|
@@ -179,6 +255,16 @@ if (!config.reactor && config.ssg) {
|
|
|
179
255
|
config.reactor = config.ssg
|
|
180
256
|
}
|
|
181
257
|
|
|
258
|
+
if (overrideBaseURL && config.markup) {
|
|
259
|
+
config.markup.baseURL = overrideBaseURL
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// poops-images resolves custom handlers/composites relative to the config file;
|
|
263
|
+
// without this it would default to cwd. Matches poops-images' own loadConfig.
|
|
264
|
+
if (config.images && typeof config.images === 'object' && config.images.configDir === undefined) {
|
|
265
|
+
config.images.configDir = path.dirname(configPath)
|
|
266
|
+
}
|
|
267
|
+
|
|
182
268
|
async function getAvailablePort(port, max) {
|
|
183
269
|
while (port < max) {
|
|
184
270
|
const status = await portscanner.checkPortStatus(port, 'localhost')
|
|
@@ -238,6 +324,11 @@ async function startServer() {
|
|
|
238
324
|
// Start the webserver
|
|
239
325
|
if (!build && config.serve) {
|
|
240
326
|
startServer()
|
|
327
|
+
} else if (!build && config.livereload) {
|
|
328
|
+
// livereload without serve: still needs the livereload server
|
|
329
|
+
resolveLiveReloadPort(config)
|
|
330
|
+
.then(poops)
|
|
331
|
+
.then(() => setupLiveReloadServer(config))
|
|
241
332
|
} else {
|
|
242
333
|
poops()
|
|
243
334
|
}
|