poops 1.9.8 β 2.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/README.md +122 -112
- package/lib/markup/collections.js +4 -1
- package/lib/markup/engines/liquid.js +2 -2
- package/lib/markup/engines/nunjucks.js +2 -2
- package/lib/markup/image-cache.js +30 -2
- package/lib/markup/indexer.js +46 -23
- package/lib/markups.js +38 -21
- package/lib/reactor.js +3 -2
- package/lib/scripts.js +3 -2
- package/lib/server.js +96 -2
- package/lib/styles.js +10 -1
- package/lib/utils/helpers.js +6 -0
- package/package.json +11 -10
- package/poops.js +47 -48
package/README.md
CHANGED
|
@@ -54,6 +54,7 @@ It uses a simple config file where you define your input and output paths and it
|
|
|
54
54
|
- [Live Reload (optional)](#live-reload-optional)
|
|
55
55
|
- [Watch (optional)](#watch-optional)
|
|
56
56
|
- [Include Paths (optional)](#include-paths-optional)
|
|
57
|
+
- [Contributing](#contributing)
|
|
57
58
|
- [Why?](#why)
|
|
58
59
|
|
|
59
60
|
## Features
|
|
@@ -81,6 +82,8 @@ It uses a simple config file where you define your input and output paths and it
|
|
|
81
82
|
|
|
82
83
|
> For a superfast start, you can use the Poops template repository: [π©πͺοΈShitstorm](https://github.com/stamat/shitstorm)
|
|
83
84
|
|
|
85
|
+
Poops requires **Node.js 22 or newer**.
|
|
86
|
+
|
|
84
87
|
You can install Poops globally:
|
|
85
88
|
|
|
86
89
|
```bash
|
|
@@ -108,7 +111,6 @@ or pass a custom config. This is useful when you have multiple environments:
|
|
|
108
111
|
| `--build` | `-b` | Build the project and exit |
|
|
109
112
|
| `--config <path>` | `-c` | Specify the config file |
|
|
110
113
|
| `--port <number>` | `-p` | Specify the server port, overrides config |
|
|
111
|
-
| `--livereload-port <number>` | `-l` | Specify the livereload port, overrides config |
|
|
112
114
|
| `--base-url <path>` | `-u` | Set the base URL prefix for markup, overrides config |
|
|
113
115
|
| `--quiet` | `-q` | Hide the header and the server/livereload info lines |
|
|
114
116
|
|
|
@@ -118,7 +120,7 @@ The `--base-url` flag is particularly useful for CI/CD pipelines where the deplo
|
|
|
118
120
|
poops --build --base-url /blog
|
|
119
121
|
```
|
|
120
122
|
|
|
121
|
-
The `--quiet` flag drops the `π© Poops β vX.Y.Z` header (and its terminal bell) plus the `Local server` / `Network` / `
|
|
123
|
+
The `--quiet` flag drops the `π© Poops β vX.Y.Z` header (and its terminal bell) plus the `Local server` / `Network` / `Live reload` lines. Handy when you run several Poops instances side by side and only want to see which one is compiling:
|
|
122
124
|
|
|
123
125
|
```bash
|
|
124
126
|
poops -q & poops -q -c site/poops.json
|
|
@@ -181,15 +183,17 @@ Just create a `poops.json` file in the root of your project and add the followin
|
|
|
181
183
|
}
|
|
182
184
|
],
|
|
183
185
|
"markup": {
|
|
184
|
-
"engine": "nunjucks",
|
|
185
186
|
"in": "example/src/markup",
|
|
186
187
|
"out": "/",
|
|
187
|
-
"
|
|
188
|
-
"
|
|
189
|
-
"
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
188
|
+
"options": {
|
|
189
|
+
"engine": "nunjucks",
|
|
190
|
+
"site": {
|
|
191
|
+
"title": "Poops",
|
|
192
|
+
"description": "A super simple bundler for simple web projects."
|
|
193
|
+
},
|
|
194
|
+
"data": ["data/links.json", "data/poops.yaml"],
|
|
195
|
+
"includePaths": ["_layouts", "_partials"]
|
|
196
|
+
}
|
|
193
197
|
},
|
|
194
198
|
"copy": [
|
|
195
199
|
{
|
|
@@ -226,7 +230,7 @@ Scripts are bundled with [esbuild](https://esbuild.github.io/). Supports `.js`,
|
|
|
226
230
|
- `minify` - whether to minify the output or not, minification is performed by `esbuild` and is only applied to non-minified files. Default is `false`
|
|
227
231
|
- `justMinified` - whether you want to have a minified file as output only. Removes the non-minified file from the output. Useful for production builds. Default is `false`
|
|
228
232
|
- `format` - the output format, can be `iife` or `esm` or `cjs` - this is a direct esbuild option
|
|
229
|
-
- `target` - the target for the output, can be `es2018` or `es2019` or `es2020` or `esnext` for instance - this is a direct esbuild option
|
|
233
|
+
- `target` - the target for the output, can be `es2018` or `es2019` or `es2020` or `esnext` for instance - this is a direct esbuild option. Default is `es2020`
|
|
230
234
|
- `jsx` - the JSX transform mode, can be `transform` (default) or `automatic`. Use `automatic` for React 17+ JSX runtime which doesn't require importing React in every file - this is a direct esbuild option
|
|
231
235
|
|
|
232
236
|
`scripts` property can accept an array of script configurations or just a single script configuration. If you want to bundle multiple scripts, just add them to the `scripts` array:
|
|
@@ -313,8 +317,6 @@ Each reactor entry has the following properties:
|
|
|
313
317
|
}
|
|
314
318
|
```
|
|
315
319
|
|
|
316
|
-
For backwards compatibility, `"ssg"` is also accepted as a config key β it is treated as an alias for `"reactor"`.
|
|
317
|
-
|
|
318
320
|
In your templates, use the `inject` name to insert the rendered HTML:
|
|
319
321
|
|
|
320
322
|
```html
|
|
@@ -526,11 +528,13 @@ npm i -D postcss @tailwindcss/postcss tailwindcss
|
|
|
526
528
|
"markup": {
|
|
527
529
|
"in": "src/markup",
|
|
528
530
|
"out": "dist",
|
|
529
|
-
"
|
|
530
|
-
"
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
531
|
+
"options": {
|
|
532
|
+
"site": {
|
|
533
|
+
"title": "Poops + Tailwind",
|
|
534
|
+
"description": "A Tailwind CSS example for Poops"
|
|
535
|
+
},
|
|
536
|
+
"includePaths": ["_layouts", "_partials"]
|
|
537
|
+
}
|
|
534
538
|
},
|
|
535
539
|
"serve": { "port": 4040, "base": "/dist" },
|
|
536
540
|
"livereload": true,
|
|
@@ -550,9 +554,31 @@ Then use Tailwind utility classes directly in your markup templates. Tailwind v4
|
|
|
550
554
|
|
|
551
555
|
### Markups
|
|
552
556
|
|
|
557
|
+
`markup` has the same shape as a `scripts` or `styles` entry: `in`, `out`, and
|
|
558
|
+
everything else under `options`.
|
|
559
|
+
|
|
560
|
+
```json
|
|
561
|
+
{
|
|
562
|
+
"markup": {
|
|
563
|
+
"in": "src/markup",
|
|
564
|
+
"out": "dist",
|
|
565
|
+
"options": {
|
|
566
|
+
"engine": "nunjucks",
|
|
567
|
+
"site": { "title": "My Awesome Site" }
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
> **Deprecated:** Poops 1.x also read these keys directly on `markup`
|
|
574
|
+
> (`"markup": { "site": β¦ }`). That still works in 2.x and warns; it stops
|
|
575
|
+
> working in 3.0. Move them into `options`.
|
|
576
|
+
|
|
577
|
+
**Options:**
|
|
578
|
+
|
|
553
579
|
- `engine` (optional) - the template engine to use. Can be `"nunjucks"` (default) or `"liquid"`. [Nunjucks](https://mozilla.github.io/nunjucks/) is a Mozilla template engine inspired by Jinja2. [Liquid](https://liquidjs.com/) is a Shopify-compatible template engine. Both engines support the same tags, filters, collections, search index, sitemap, and navigation tree features documented below.
|
|
554
|
-
- `in` - the input path, can be a directory or a file path, but please just use it as a directory path for now. All files in this directory will be processed and the structure of the directory will be preserved in the output directory with exception to directories that begin with an underscore `_` will be ignored.
|
|
555
|
-
- `out` - the output path, can be only a directory path (for now)
|
|
580
|
+
- `in` (entry, not an option) - the input path, can be a directory or a file path, but please just use it as a directory path for now. All files in this directory will be processed and the structure of the directory will be preserved in the output directory with exception to directories that begin with an underscore `_` will be ignored.
|
|
581
|
+
- `out` (entry, not an option) - the output path, can be only a directory path (for now)
|
|
556
582
|
- `site` (optional) - global data that will be available to all templates in the markup directory. Like site title, description, social media links, etc. You can then use this data in your templates `{{ site.title }}` for instance.
|
|
557
583
|
- `data` (optional) - is an array of JSON or YAML data files, that once loaded will be available to all templates in the markup directory. If you provide a path to a file for instance `links.json` with a `facebook` property, you can then use this data in your templates `{{ links.facebook }}`. The base name of the file will be used as the variable name, with spaces, dashes and dots replaced with underscores. So `the awesome-links.json` will be available as `{{ the_awesome_links.facebook }}` in your templates. The root directory of the data files is `in` directory. So if you have a `data` directory in your `in` directory, you can specify the data files like this `data: ["data/links.json"]`. The same goes for the YAML files.
|
|
558
584
|
- `includePaths` - an array of paths to directories that will be added to the template engine's include paths. Useful if you want to separate template partials and layouts. For instance, if you have a `_includes` directory with a `header.njk` (or `header.liquid`) partial that you want to include in your markup, you can add it to the include paths and then include the templates like this `{% include "header.njk" %}`, without specifying the full path to the partial.
|
|
@@ -578,13 +604,15 @@ Here is a sample markup configuration using the default Nunjucks engine:
|
|
|
578
604
|
"markup": {
|
|
579
605
|
"in": "src/markup",
|
|
580
606
|
"out": "dist",
|
|
581
|
-
"
|
|
582
|
-
"
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
607
|
+
"options": {
|
|
608
|
+
"site": {
|
|
609
|
+
"title": "My Awesome Site",
|
|
610
|
+
"description": "This is my awesome site"
|
|
611
|
+
},
|
|
612
|
+
"data": ["data/links.json", "data/other.yaml"],
|
|
613
|
+
"includePaths": ["_includes"],
|
|
614
|
+
"baseURL": "/blog"
|
|
615
|
+
}
|
|
588
616
|
}
|
|
589
617
|
}
|
|
590
618
|
```
|
|
@@ -594,15 +622,17 @@ To use Liquid instead, set the `engine` property:
|
|
|
594
622
|
```json
|
|
595
623
|
{
|
|
596
624
|
"markup": {
|
|
597
|
-
"engine": "liquid",
|
|
598
625
|
"in": "src/liquid",
|
|
599
626
|
"out": "dist",
|
|
600
|
-
"
|
|
601
|
-
"
|
|
602
|
-
"
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
627
|
+
"options": {
|
|
628
|
+
"engine": "liquid",
|
|
629
|
+
"site": {
|
|
630
|
+
"title": "My Awesome Site",
|
|
631
|
+
"description": "This is my awesome site"
|
|
632
|
+
},
|
|
633
|
+
"data": ["_data/links.json", "_data/other.yaml"],
|
|
634
|
+
"includePaths": ["_layouts", "_partials"]
|
|
635
|
+
}
|
|
606
636
|
}
|
|
607
637
|
}
|
|
608
638
|
```
|
|
@@ -661,7 +691,7 @@ The `engine` option also accepts a module specifier β an npm package name or a
|
|
|
661
691
|
"markup": {
|
|
662
692
|
"in": "src/markup",
|
|
663
693
|
"out": "dist",
|
|
664
|
-
"engine": "poops-shopify"
|
|
694
|
+
"options": { "engine": "poops-shopify" }
|
|
665
695
|
}
|
|
666
696
|
}
|
|
667
697
|
```
|
|
@@ -680,7 +710,7 @@ export default class MyEngine {
|
|
|
680
710
|
get markupExtensions() {
|
|
681
711
|
return "html|liquid|md";
|
|
682
712
|
} // glob alternation of processed extensions
|
|
683
|
-
registerFilters({
|
|
713
|
+
registerFilters({ dateFormat, markupOut }) {}
|
|
684
714
|
registerTags(getOutputDir) {}
|
|
685
715
|
setGlobal(key, value) {}
|
|
686
716
|
removeGlobal(key) {}
|
|
@@ -734,14 +764,16 @@ sort: date
|
|
|
734
764
|
"markup": {
|
|
735
765
|
"in": "src/markup",
|
|
736
766
|
"out": "dist",
|
|
737
|
-
"
|
|
738
|
-
"
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
767
|
+
"options": {
|
|
768
|
+
"collections": [
|
|
769
|
+
"changelog",
|
|
770
|
+
{
|
|
771
|
+
"name": "blog",
|
|
772
|
+
"paginate": 5,
|
|
773
|
+
"sort": { "by": "title", "order": "asc" }
|
|
774
|
+
}
|
|
775
|
+
]
|
|
776
|
+
}
|
|
745
777
|
}
|
|
746
778
|
}
|
|
747
779
|
```
|
|
@@ -806,12 +838,14 @@ Pages 2..N automatically get a distinct `<title>` β `Changelog β Page 2` β
|
|
|
806
838
|
```json
|
|
807
839
|
{
|
|
808
840
|
"markup": {
|
|
809
|
-
"
|
|
810
|
-
"
|
|
811
|
-
"
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
841
|
+
"options": {
|
|
842
|
+
"site": {
|
|
843
|
+
"pagination": {
|
|
844
|
+
"title": "{title} β Seite {n}",
|
|
845
|
+
"prev": "ZurΓΌck",
|
|
846
|
+
"next": "Weiter",
|
|
847
|
+
"of": "von"
|
|
848
|
+
}
|
|
815
849
|
}
|
|
816
850
|
}
|
|
817
851
|
}
|
|
@@ -1049,7 +1083,7 @@ All filters are available in both engines. The only syntax difference is how arg
|
|
|
1049
1083
|
|
|
1050
1084
|
- `markdown` β renders a markdown string to HTML with GitHub Flavored Markdown extras: emoji shortcodes (e.g. `:rocket:` β π), alert callouts (`> [!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]`, `[!CAUTION]`, `[!INFO]`) and footnotes (`[^1]`). Code fences are syntax-highlighted and headings get slug `id`s plus permalink anchors. Usage: `{{ "**bold** :rocket:" | markdown }}`
|
|
1051
1085
|
|
|
1052
|
-
- `date` β formats a date string. Uses [dayjs](https://day.js.org/) format tokens. A default format can be set via the `
|
|
1086
|
+
- `date` β formats a date string. Uses [dayjs](https://day.js.org/) format tokens. A default format can be set via the `dateFormat` config option.
|
|
1053
1087
|
- Nunjucks: `{{ "2024-01-15" | date("MMMM D, YYYY") }}`
|
|
1054
1088
|
- Liquid: `{{ "2024-01-15" | date: "MMMM D, YYYY" }}`
|
|
1055
1089
|
|
|
@@ -1111,8 +1145,8 @@ All filters are available in both engines. The only syntax difference is how arg
|
|
|
1111
1145
|
|
|
1112
1146
|
```json
|
|
1113
1147
|
"markup": {
|
|
1114
|
-
"
|
|
1115
|
-
"jsonld": { "@type": "TechArticle" }
|
|
1148
|
+
"options": {
|
|
1149
|
+
"site": { "jsonld": { "@type": "TechArticle" } }
|
|
1116
1150
|
}
|
|
1117
1151
|
}
|
|
1118
1152
|
```
|
|
@@ -1249,17 +1283,17 @@ The string shorthand sets the output filename with default options. For more con
|
|
|
1249
1283
|
```json
|
|
1250
1284
|
{
|
|
1251
1285
|
"searchIndex": {
|
|
1252
|
-
"
|
|
1286
|
+
"out": "search-index.json",
|
|
1253
1287
|
"minWordLength": 3,
|
|
1254
1288
|
"maxKeywords": 20,
|
|
1255
1289
|
"globalFrequencyCeiling": 0.8,
|
|
1256
1290
|
"stopWords": "path/to/custom-stop-words.json"
|
|
1257
1291
|
},
|
|
1258
1292
|
"sitemap": {
|
|
1259
|
-
"
|
|
1293
|
+
"out": "sitemap.xml"
|
|
1260
1294
|
},
|
|
1261
1295
|
"llms": {
|
|
1262
|
-
"
|
|
1296
|
+
"out": "llms.txt",
|
|
1263
1297
|
"title": "My Site",
|
|
1264
1298
|
"description": "One-line summary of the site.",
|
|
1265
1299
|
"intro": "src/llms-intro.md",
|
|
@@ -1270,7 +1304,7 @@ The string shorthand sets the output filename with default options. For more con
|
|
|
1270
1304
|
|
|
1271
1305
|
**Search Index options:**
|
|
1272
1306
|
|
|
1273
|
-
- `
|
|
1307
|
+
- `out` β output filename, written to the markup output directory
|
|
1274
1308
|
- `minWordLength` β minimum word length to consider as a keyword (default: `3`)
|
|
1275
1309
|
- `maxKeywords` β maximum keywords per page (default: `20`)
|
|
1276
1310
|
- `globalFrequencyCeiling` β drop words appearing in more than this fraction of all pages (default: `0.8`, meaning words found in 80%+ of pages are dropped as non-discriminating)
|
|
@@ -1302,14 +1336,14 @@ All front matter fields are passed through to the index automatically. Internal
|
|
|
1302
1336
|
|
|
1303
1337
|
**llms.txt** generates an [`llms.txt`](https://llmstxt.org) β a Markdown index of your pages that LLMs and generative engines (GEO) read to understand your site. It has an `# H1` title, a `> ` blockquote summary, then `- [title](url): description` links grouped by URL path: the first folder is a `## section`, a second folder nests as a `### subsection` under it, and root-level pages fall under a lead "Pages" section. So `docs/config-reference.html` lands directly under `## Docs` while `docs/quick-start/x.html` lands under `### Quick Start` inside it. Collection items (which live under `collection/β¦`) group the same way and are ordered newest-first by their `date`; other sections keep file order. Set `intro` to a Markdown file path (relative to the project root) to insert free-form context between the blockquote and the link sections β a file authored for LLMs, e.g. `llms-intro.md`. Avoid `##` headings in it; they read as sections. (A raw README is a poor fit β badges, install noise and its own headings collide.) `title` and `description` default to your `site.title`/`site.description`; override them (and the lead section name via `sectionTitle`) with the object form. `site.url` makes the links absolute. Collection index/pagination pages are skipped, like the search index.
|
|
1304
1338
|
|
|
1305
|
-
Set `full` to also write a companion full-content file β every page's Markdown body concatenated into one file an LLM can ingest whole (the index is the link map; this is the corpus). `true` names it after `
|
|
1339
|
+
Set `full` to also write a companion full-content file β every page's Markdown body concatenated into one file an LLM can ingest whole (the index is the link map; this is the corpus). `true` names it after `out` with a `-full` suffix (`llms.txt` β `llms-full.txt`, `ai.txt` β `ai-full.txt`); pass a string to set the path yourself. The file opens with a `# Full Documentation Archive for {title}` header, a one-line intro naming the site and a `> ` blockquote of the `description` so a whole-file ingest starts with context, then each page becomes an `# title` (its own leading H1 if it has one) + `URL:` line + body, joined by `---`. Set `fullIntro` to a Markdown file path (from the project root) to insert your own preamble after that header β the `full` counterpart to `intro` (inserted verbatim; a missing file warns and is skipped). Only `.md`/`.markdown` sources qualify (a `.njk`/`.liquid` source is template code, not prose); `noindex` and collection-index pages are dropped. Content is the Markdown **source**, so unrendered `{% raw %}{% β¦ %}{% endraw %}` tags or shortcodes in a body pass through verbatim.
|
|
1306
1340
|
|
|
1307
|
-
**robots.txt** generates a `robots.txt`. The string shorthand writes an allow-all file (`User-agent: *`, empty `Disallow:`) with a `Sitemap:` line pointing at your generated sitemap β absolute when `site.url` is set. The object form takes `
|
|
1341
|
+
**robots.txt** generates a `robots.txt`. The string shorthand writes an allow-all file (`User-agent: *`, empty `Disallow:`) with a `Sitemap:` line pointing at your generated sitemap β absolute when `site.url` is set. The object form takes `out`, `userAgent`, `allow`/`disallow` (a path or array of paths), and `sitemap` (an explicit URL, or `false` to omit the line):
|
|
1308
1342
|
|
|
1309
1343
|
```json
|
|
1310
1344
|
{
|
|
1311
1345
|
"robots": {
|
|
1312
|
-
"
|
|
1346
|
+
"out": "robots.txt",
|
|
1313
1347
|
"disallow": ["/admin", "/drafts"],
|
|
1314
1348
|
"sitemap": false
|
|
1315
1349
|
}
|
|
@@ -1344,7 +1378,7 @@ The string shorthand sets the output filename. For docs sites, use the object fo
|
|
|
1344
1378
|
```json
|
|
1345
1379
|
{
|
|
1346
1380
|
"nav": {
|
|
1347
|
-
"
|
|
1381
|
+
"out": "nav.json",
|
|
1348
1382
|
"root": "docs",
|
|
1349
1383
|
"collections": "index",
|
|
1350
1384
|
"home": false
|
|
@@ -1354,7 +1388,7 @@ The string shorthand sets the output filename. For docs sites, use the object fo
|
|
|
1354
1388
|
|
|
1355
1389
|
**Navigation options:**
|
|
1356
1390
|
|
|
1357
|
-
- `
|
|
1391
|
+
- `out` β output filename, written to the markup output directory
|
|
1358
1392
|
- `collections` β how to treat collection pages (default `true`):
|
|
1359
1393
|
- `true` β include every collection page, nested under its collection
|
|
1360
1394
|
- `false` β exclude all collection pages (drops a blog's posts from the sidebar)
|
|
@@ -1445,7 +1479,7 @@ Generate a subscription feed for a [collection](#collections) β no hand-author
|
|
|
1445
1479
|
"in": "src/markup",
|
|
1446
1480
|
"out": "dist",
|
|
1447
1481
|
"options": {
|
|
1448
|
-
"feed": { "collection": "blog", "
|
|
1482
|
+
"feed": { "collection": "blog", "out": "blog/feed.rss" }
|
|
1449
1483
|
}
|
|
1450
1484
|
}
|
|
1451
1485
|
}
|
|
@@ -1454,7 +1488,7 @@ Generate a subscription feed for a [collection](#collections) β no hand-author
|
|
|
1454
1488
|
**Feed options:**
|
|
1455
1489
|
|
|
1456
1490
|
- `collection` β the collection to build the feed from. Omit it to emit a feed for **every** collection.
|
|
1457
|
-
- `
|
|
1491
|
+
- `out` β the file to write. A bare filename (`feed.xml`, the default) is placed inside the collection's own folder (`blog/feed.xml`); a value with a slash is used as-is under the output directory.
|
|
1458
1492
|
- `type` β `"rss"` (default) or `"atom"`.
|
|
1459
1493
|
- `limit` β max items, newest first (default `20`).
|
|
1460
1494
|
- `title` β channel title (default `"<Collection> | <site.title>"`).
|
|
@@ -1597,69 +1631,37 @@ Sets up a local server for your project.
|
|
|
1597
1631
|
Server options:
|
|
1598
1632
|
|
|
1599
1633
|
- `port` - the port on which the server will run
|
|
1600
|
-
- `base` - the base path of the server, where your HTML files are located
|
|
1634
|
+
- `base` - the base path of the server, where your HTML files are located. Defaults to the markup `out` directory, so most configs can leave it out
|
|
1601
1635
|
|
|
1602
1636
|
If you don't want to run a local server, just remove the `serve` property from the config.
|
|
1603
1637
|
|
|
1604
1638
|
### Live Reload (optional)
|
|
1605
1639
|
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
Live reload options:
|
|
1609
|
-
|
|
1610
|
-
- `port` - the port on which the livereload server will run
|
|
1611
|
-
- `exclude` - an array of files and directories to exclude from livereload
|
|
1612
|
-
- `extraExts` - an array of extra file extensions (without the dot) that trigger a browser refresh, added to the defaults
|
|
1613
|
-
- `exts` - an array of file extensions that replaces the default list entirely
|
|
1614
|
-
|
|
1615
|
-
By default a refresh is triggered by changes to: `html`, `css`, `js`, `png`, `gif`, `jpg`, `php`, `php5`, `py`, `rb`, `erb`, `coffee`. If you work with other file types, for example Slim or Nunjucks templates, add them:
|
|
1616
|
-
|
|
1617
|
-
```json
|
|
1618
|
-
{
|
|
1619
|
-
"livereload": {
|
|
1620
|
-
"extraExts": ["slim", "njk"]
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
```
|
|
1624
|
-
|
|
1625
|
-
`livereload` can only be `true`, which means that it will run on the default port (`35729`) or you can specify a port:
|
|
1626
|
-
|
|
1627
|
-
```json
|
|
1628
|
-
{
|
|
1629
|
-
"livereload": {
|
|
1630
|
-
"port": whateverPortYouWant
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
```
|
|
1634
|
-
|
|
1635
|
-
You can also exclude files and directories from livereload:
|
|
1640
|
+
Reloads the browser when a build finishes. It is a switch, nothing more:
|
|
1636
1641
|
|
|
1637
1642
|
```json
|
|
1638
1643
|
{
|
|
1639
|
-
"
|
|
1640
|
-
|
|
1641
|
-
}
|
|
1644
|
+
"serve": { "base": "dist" },
|
|
1645
|
+
"livereload": true
|
|
1642
1646
|
}
|
|
1643
1647
|
```
|
|
1644
1648
|
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
(location.host || "localhost").split(":")[0] +
|
|
1652
|
-
':35729/livereload.js?snipver=1"></' +
|
|
1653
|
-
"script>",
|
|
1654
|
-
);
|
|
1655
|
-
</script>
|
|
1656
|
-
```
|
|
1649
|
+
Live reload rides the `serve` port β there is no second server and no port to
|
|
1650
|
+
configure, so it needs `serve` to be on. With both set, Poops answers
|
|
1651
|
+
`/__poops_reload` as a [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)
|
|
1652
|
+
stream and appends a small client script to every HTML page it serves. **Your
|
|
1653
|
+
templates need no snippet.** Nothing is injected into your build output β the
|
|
1654
|
+
script exists only in the response the dev server writes.
|
|
1657
1655
|
|
|
1658
|
-
|
|
1656
|
+
A save triggers exactly one reload, after the build that follows it has
|
|
1657
|
+
finished. When everything a build wrote is CSS, the stylesheets are swapped in
|
|
1658
|
+
place instead: the page is not reloaded, so scroll position, open dialogs and
|
|
1659
|
+
form state survive a style edit.
|
|
1659
1660
|
|
|
1660
|
-
|
|
1661
|
+
The browser reconnects on its own, so restarting Poops picks the open tabs back
|
|
1662
|
+
up without touching them.
|
|
1661
1663
|
|
|
1662
|
-
|
|
1664
|
+
To turn it off, remove the `livereload` property or set it to `false`.
|
|
1663
1665
|
|
|
1664
1666
|
### Watch (optional)
|
|
1665
1667
|
|
|
@@ -1687,6 +1689,14 @@ Same as `watch` property, `includePaths` accepts an array of paths to include. I
|
|
|
1687
1689
|
}
|
|
1688
1690
|
```
|
|
1689
1691
|
|
|
1692
|
+
## Contributing
|
|
1693
|
+
|
|
1694
|
+
Issues and pull requests are welcome β see [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
1695
|
+
for how to set up, what a PR needs, and how releases are cut.
|
|
1696
|
+
|
|
1697
|
+
Released changes are in [CHANGELOG.md](CHANGELOG.md), written up in full on the
|
|
1698
|
+
[changelog site](https://stamat.info/poops/changelog/).
|
|
1699
|
+
|
|
1690
1700
|
## Why?
|
|
1691
1701
|
|
|
1692
1702
|
Why doesn't anyone maintain GULP anymore? Why does Parcel hate config files? Why are Rollup and Webpack so complex to setup for simple tasks? Vite???? What's going on?
|
|
@@ -33,7 +33,10 @@ export function getSingleCollectionData(markupInDir, collectionName) {
|
|
|
33
33
|
frontMatter.wordcount = wordcount(content)
|
|
34
34
|
frontMatter.excerpt = excerpt(content)
|
|
35
35
|
frontMatter.fileName = path.basename(file)
|
|
36
|
-
|
|
36
|
+
// posix, like the page.filePath a template gets for a non-collection page β
|
|
37
|
+
// both feed "edit this page on GitHub" links, and a Windows backslash in one
|
|
38
|
+
// would produce a broken URL for collection items only
|
|
39
|
+
frontMatter.filePath = toPosix(path.relative(process.cwd(), file))
|
|
37
40
|
frontMatter.collection = collectionName
|
|
38
41
|
frontMatter.url = toPosix(path.join(collectionName, path.basename(frontMatter.filePath)))
|
|
39
42
|
|
|
@@ -83,7 +83,7 @@ export default class LiquidEngine {
|
|
|
83
83
|
get indexableExtensions() { return new Set(['.html', '.md', '.liquid']) }
|
|
84
84
|
get markupExtensions() { return 'html|xml|rss|atom|json|liquid|md' }
|
|
85
85
|
|
|
86
|
-
registerFilters({
|
|
86
|
+
registerFilters({ dateFormat, markupOut }) {
|
|
87
87
|
const engine = this.engine
|
|
88
88
|
engine.registerFilter('slugify', (str) => slugify(str))
|
|
89
89
|
engine.registerFilter('humanize', (str) => humanize(str))
|
|
@@ -91,7 +91,7 @@ export default class LiquidEngine {
|
|
|
91
91
|
engine.registerFilter('markdown', (str) => marked.parse(String(str || '')))
|
|
92
92
|
engine.registerFilter('toc', (html) => renderToc(String(html || '')))
|
|
93
93
|
engine.registerFilter('date', (str, template) => {
|
|
94
|
-
const fmt = template ||
|
|
94
|
+
const fmt = template || dateFormat
|
|
95
95
|
if (!fmt) return str
|
|
96
96
|
const date = !str || (typeof str === 'string' && str.trim() === '') ? new Date() : new Date(str)
|
|
97
97
|
return dayjs(date).format(fmt)
|
|
@@ -144,7 +144,7 @@ export default class NunjucksEngine {
|
|
|
144
144
|
get indexableExtensions() { return new Set(['.html', '.md', '.njk']) }
|
|
145
145
|
get markupExtensions() { return 'html|xml|rss|atom|json|njk|md' }
|
|
146
146
|
|
|
147
|
-
registerFilters({
|
|
147
|
+
registerFilters({ dateFormat, markupOut }) {
|
|
148
148
|
const env = this.env
|
|
149
149
|
env.addFilter('slugify', slugify)
|
|
150
150
|
env.addFilter('humanize', humanize)
|
|
@@ -173,7 +173,7 @@ export default class NunjucksEngine {
|
|
|
173
173
|
return new nunjucks.runtime.SafeString(content)
|
|
174
174
|
})
|
|
175
175
|
env.addFilter('date', (str, template) => {
|
|
176
|
-
const fmt = template ||
|
|
176
|
+
const fmt = template || dateFormat
|
|
177
177
|
if (!fmt) return str
|
|
178
178
|
const date = !str || str.trim() === '' ? new Date() : new Date(str)
|
|
179
179
|
return dayjs(date).format(fmt)
|
|
@@ -28,10 +28,38 @@ function readCacheFile(cachePath) {
|
|
|
28
28
|
return null
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// Every lookup below keys on posix paths (the cache is keyed by path relative
|
|
32
|
+
// to the cache dir). A cache written on Windows carries backslash keys and
|
|
33
|
+
// then nothing ever matches β normalize once here rather than at each site.
|
|
34
|
+
// Not toPosix: that is a no-op on a posix host, and a cache generated on
|
|
35
|
+
// Windows can be read on one β committed alongside the images it describes,
|
|
36
|
+
// or built in CI. A literal backslash in an image filename is not a thing.
|
|
37
|
+
if (data && data.entries) data.entries = posixKeys(data.entries)
|
|
38
|
+
|
|
31
39
|
cacheFileCache.set(cachePath, { mtimeMs: stat.mtimeMs, size: stat.size, data })
|
|
32
40
|
return data
|
|
33
41
|
}
|
|
34
42
|
|
|
43
|
+
function posixKeys(entries) {
|
|
44
|
+
const toSlash = (p) => String(p).replace(/\\/g, '/')
|
|
45
|
+
const out = {}
|
|
46
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
47
|
+
// Variant paths land in srcset attributes, so they need the same treatment
|
|
48
|
+
out[toSlash(key)] = Array.isArray(value.outputs)
|
|
49
|
+
? { ...value, outputs: value.outputs.map((o) => ({ ...o, path: toSlash(o.path) })) }
|
|
50
|
+
: value
|
|
51
|
+
}
|
|
52
|
+
return out
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Containment, the way lib/server.js does it: a plain startsWith(root) also
|
|
56
|
+
// accepts a sibling whose name merely starts with root's β `dist-old` for
|
|
57
|
+
// `dist` β so a `../` in a template's path argument reads a cache outside the
|
|
58
|
+
// output dir.
|
|
59
|
+
function isInside(dir, root) {
|
|
60
|
+
return dir === root || dir.startsWith(root + path.sep)
|
|
61
|
+
}
|
|
62
|
+
|
|
35
63
|
export function clearImageCache() {
|
|
36
64
|
cacheFileCache.clear()
|
|
37
65
|
}
|
|
@@ -45,7 +73,7 @@ export function clearImageCache() {
|
|
|
45
73
|
export function getImageEntry(imagePath, outputDir) {
|
|
46
74
|
const root = path.resolve(outputDir)
|
|
47
75
|
let dir = path.resolve(root, path.dirname(imagePath))
|
|
48
|
-
if (!dir
|
|
76
|
+
if (!isInside(dir, root)) return null
|
|
49
77
|
|
|
50
78
|
const target = toPosix(path.relative(root, path.resolve(root, imagePath)))
|
|
51
79
|
const targetNoExt = target.replace(/\.[^./]+$/, '')
|
|
@@ -85,7 +113,7 @@ export function listImages(dirPath, outputDir) {
|
|
|
85
113
|
const root = path.resolve(outputDir)
|
|
86
114
|
const clean = toPosix(dirPath || '').replace(/^\/+|\/+$/g, '')
|
|
87
115
|
let dir = path.resolve(root, clean)
|
|
88
|
-
if (!dir
|
|
116
|
+
if (!isInside(dir, root)) return []
|
|
89
117
|
|
|
90
118
|
while (true) {
|
|
91
119
|
const data = readCacheFile(path.join(dir, CACHE_FILENAME))
|