poops 1.1.0 → 1.2.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 +515 -55
- package/lib/copy.js +7 -9
- package/lib/markup/collections.js +247 -0
- package/lib/markup/engines/liquid.js +240 -0
- package/lib/markup/engines/nunjucks.js +261 -0
- package/lib/markup/helpers.js +170 -0
- package/lib/markup/highlight.js +77 -0
- package/lib/markup/indexer.js +154 -0
- package/lib/markup/stop-words-en.json +25 -0
- package/lib/markups.js +223 -459
- package/lib/postcss.js +127 -0
- package/lib/{ssg.js → reactor.js} +36 -25
- package/lib/scripts.js +12 -13
- package/lib/styles.js +20 -12
- package/lib/utils/helpers.js +0 -56
- package/lib/utils/log.js +64 -0
- package/package.json +21 -3
- package/poops.js +87 -107
- package/lib/utils/print-style.js +0 -72
package/README.md
CHANGED
|
@@ -28,15 +28,18 @@ It uses a simple config file where you define your input and output paths and it
|
|
|
28
28
|
|
|
29
29
|
- Bundles SCSS/SASS to CSS
|
|
30
30
|
- Uses [dart-sass](https://sass-lang.com/dart-sass) for SCSS/SASS bundling
|
|
31
|
+
- Design token support — import JSON tokens (W3C DTCG & Style Dictionary) as SCSS variables or maps
|
|
32
|
+
- PostCSS pipeline — use any PostCSS plugin including [Tailwind CSS](https://tailwindcss.com/)
|
|
31
33
|
- Bundles JS/TS/JSX/TSX to IIFE/ESM/CJS
|
|
32
34
|
- Uses [esbuild](https://esbuild.github.io/) for bundling and transpiling JS/TS/JSX/TSX to IIFE/ESM/CJS
|
|
35
|
+
- React pre-rendering (Reactor) — renders React components to HTML at build time for static sites with optional hydration
|
|
33
36
|
- Optional JS and CSS minification using [esbuild](https://esbuild.github.io/)
|
|
34
37
|
- Can produce minified code simultaneously with non-minified code! (cause I always forget to minify my code for production)
|
|
35
38
|
- Supports source maps only for non minified - non production code (optional)
|
|
36
39
|
- Supports multiple input and output paths
|
|
37
40
|
- Resolves node modules
|
|
38
41
|
- Can add a templatable banner to output files (optional)
|
|
39
|
-
- Static site generation with [
|
|
42
|
+
- Static site generation with swappable template engines: [Nunjucks](https://mozilla.github.io/nunjucks/) (default) or [Liquid](https://liquidjs.com/) — with blogging option (optional)
|
|
40
43
|
- Has a configurable local server (optional)
|
|
41
44
|
- Rebuilds on file changes (optional)
|
|
42
45
|
- Live reloads on file changes (optional)
|
|
@@ -77,7 +80,7 @@ If you have installed Poops locally you can run it with `npx poops` or `npx 💩
|
|
|
77
80
|
|
|
78
81
|
## Configuration
|
|
79
82
|
|
|
80
|
-
Configuring Poops is simple 😌. Let's presume that we have a `example/src/scss` and `example/src/js` directories and we want to bundle the files into `example/dist/css` and `example/dist/js`. If you also have markup files, you can use [
|
|
83
|
+
Configuring Poops is simple 😌. Let's presume that we have a `example/src/scss` and `example/src/js` directories and we want to bundle the files into `example/dist/css` and `example/dist/js`. If you also have markup files, you can use [Nunjucks](https://mozilla.github.io/nunjucks/) (default) or [Liquid](https://liquidjs.com/) templating engine to generate HTML files from your templates. Let's presume that we have a `example/src/markup` directory and we want to generate HTML files in the root of the your directory.
|
|
81
84
|
|
|
82
85
|
Just create a `poops.json` file in the root of your project and add the following (you can see this sample config in this repo's root):
|
|
83
86
|
|
|
@@ -96,6 +99,18 @@ Just create a `poops.json` file in the root of your project and add the followin
|
|
|
96
99
|
}
|
|
97
100
|
}
|
|
98
101
|
],
|
|
102
|
+
"reactor": [
|
|
103
|
+
{
|
|
104
|
+
"component": "example/src/js/App.jsx",
|
|
105
|
+
"inject": "app_html",
|
|
106
|
+
"in": "example/src/js/app-hydrate.jsx",
|
|
107
|
+
"out": "example/dist/js/app-hydrate.js",
|
|
108
|
+
"options": {
|
|
109
|
+
"minify": true,
|
|
110
|
+
"target": "es2019"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
],
|
|
99
114
|
"styles": [
|
|
100
115
|
{
|
|
101
116
|
"in": "example/src/scss/index.scss",
|
|
@@ -108,22 +123,21 @@ Just create a `poops.json` file in the root of your project and add the followin
|
|
|
108
123
|
}
|
|
109
124
|
],
|
|
110
125
|
"markup": {
|
|
126
|
+
"engine": "nunjucks",
|
|
111
127
|
"in": "example/src/markup",
|
|
112
128
|
"out": "/",
|
|
113
|
-
"
|
|
114
|
-
"
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
"data"
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
"
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
]
|
|
126
|
-
}
|
|
129
|
+
"site": {
|
|
130
|
+
"title": "Poops",
|
|
131
|
+
"description": "A super simple bundler for simple web projects."
|
|
132
|
+
},
|
|
133
|
+
"data": [
|
|
134
|
+
"data/links.json",
|
|
135
|
+
"data/poops.yaml"
|
|
136
|
+
],
|
|
137
|
+
"includePaths": [
|
|
138
|
+
"_layouts",
|
|
139
|
+
"_partials"
|
|
140
|
+
]
|
|
127
141
|
},
|
|
128
142
|
"copy": [
|
|
129
143
|
{
|
|
@@ -142,7 +156,7 @@ Just create a `poops.json` file in the root of your project and add the followin
|
|
|
142
156
|
}
|
|
143
157
|
```
|
|
144
158
|
|
|
145
|
-
All config properties are optional except `scripts`, `styles` or `markups`. You have to specify at least one of them. If you don't have anything to consume, you won't poop. 💩
|
|
159
|
+
All config properties are optional except `scripts`, `styles`, `postcss` or `markups`. You have to specify at least one of them. If you don't have anything to consume, you won't poop. 💩
|
|
146
160
|
|
|
147
161
|
You can freely remove the properties that you don't need. For example, if you don't want to run a local server, just remove the `serve` property from the config.
|
|
148
162
|
|
|
@@ -218,26 +232,26 @@ Setting `jsx` to `automatic` uses React's JSX runtime (React 17+), so you don't
|
|
|
218
232
|
|
|
219
233
|
As noted earlier, if you don't want to bundle scripts, just remove the `scripts` property from the config.
|
|
220
234
|
|
|
221
|
-
###
|
|
235
|
+
### Reactor (React Pre-rendering)
|
|
222
236
|
|
|
223
|
-
|
|
237
|
+
The `reactor` config key defines React components that are pre-rendered to HTML at build time (SSG) and optionally hydrated on the client. This is a separate pipeline from `scripts` — reactor entries have their own build step, watcher path, and logging tag.
|
|
224
238
|
|
|
225
|
-
Each
|
|
239
|
+
Each reactor entry has the following properties:
|
|
226
240
|
|
|
227
|
-
- `component` — the file that default-exports a React component (rendered
|
|
228
|
-
- `
|
|
229
|
-
- `
|
|
230
|
-
- `
|
|
231
|
-
- `options` — esbuild options
|
|
241
|
+
- `component` — the file that default-exports a React component (rendered at build time with `renderToString`)
|
|
242
|
+
- `inject` — template global variable name for the rendered HTML (available in both Nunjucks and Liquid)
|
|
243
|
+
- `in` (optional) — client entry file for hydration (bundled for the browser)
|
|
244
|
+
- `out` (optional) — output path for the client bundle
|
|
245
|
+
- `options` (optional) — esbuild options for the client bundle (same as script entries: `minify`, `format`, `target`, `sourcemap`, etc.)
|
|
232
246
|
|
|
233
247
|
```json
|
|
234
248
|
{
|
|
235
|
-
"
|
|
249
|
+
"reactor": [
|
|
236
250
|
{
|
|
237
251
|
"component": "src/js/App.jsx",
|
|
252
|
+
"inject": "app_html",
|
|
238
253
|
"in": "src/js/app-hydrate.jsx",
|
|
239
254
|
"out": "dist/js/app-hydrate.js",
|
|
240
|
-
"inject": "app_html",
|
|
241
255
|
"options": {
|
|
242
256
|
"minify": true,
|
|
243
257
|
"target": "es2019"
|
|
@@ -247,23 +261,38 @@ Each SSG entry has the following properties:
|
|
|
247
261
|
}
|
|
248
262
|
```
|
|
249
263
|
|
|
250
|
-
|
|
264
|
+
For backwards compatibility, `"ssg"` is also accepted as a config key — it is treated as an alias for `"reactor"`.
|
|
265
|
+
|
|
266
|
+
In your templates, use the `inject` name to insert the rendered HTML:
|
|
251
267
|
|
|
252
268
|
```html
|
|
253
269
|
<div id="root">{{ app_html | safe }}</div>
|
|
254
270
|
<script src="js/app-hydrate.min.js"></script>
|
|
255
271
|
```
|
|
256
272
|
|
|
273
|
+
If you only need server-side rendering without client hydration, omit `in` and `out`:
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
{
|
|
277
|
+
"reactor": [
|
|
278
|
+
{
|
|
279
|
+
"component": "src/js/App.jsx",
|
|
280
|
+
"inject": "app_html"
|
|
281
|
+
}
|
|
282
|
+
]
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
257
286
|
**How it works:**
|
|
258
287
|
|
|
259
288
|
1. Poops bundles the component with `react-dom/server` for Node.js and calls `renderToString`
|
|
260
|
-
2. The rendered HTML is stored and made available as a
|
|
261
|
-
3.
|
|
289
|
+
2. The rendered HTML is stored and made available as a template global variable
|
|
290
|
+
3. If `in`/`out` are specified, the client entry is bundled for the browser
|
|
262
291
|
4. At runtime, React hydrates the pre-rendered HTML, making it interactive
|
|
263
292
|
|
|
264
|
-
|
|
293
|
+
Poops does not need `react` or `react-dom` as its own dependency — they are resolved from your project's `node_modules`. In watch mode, changes to files in the reactor component's directory trigger re-rendering and client re-bundling. Markup is recompiled only when the rendered output actually changes. Changes to other JS/TS files only trigger the scripts pipeline — the two are independent.
|
|
265
294
|
|
|
266
|
-
|
|
295
|
+
**Note:** If you don't need server-side pre-rendering, you can bundle a React app entirely through the regular `scripts` pipeline — just point `in` to your `.jsx`/`.tsx` entry file and use `createRoot` on the client. The `reactor` config is only needed when you want build-time HTML rendering with optional hydration.
|
|
267
296
|
|
|
268
297
|
### Styles
|
|
269
298
|
|
|
@@ -278,6 +307,9 @@ Styles are bundled with [Dart Sass](https://sass-lang.com/dart-sass). You can sp
|
|
|
278
307
|
- `sourcemap` - whether to generate sourcemaps or not, sourcemaps are generated only for non-minified files since they are useful for debugging. Default is `false`
|
|
279
308
|
- `minify` - whether to minify the output or not, minification is performed by `esbuild`. Default is `false`
|
|
280
309
|
- `justMinified` - whether you want to have a minified file as output only. Removes the non-minified file from the output. Useful for production builds. Defaults to `false`.
|
|
310
|
+
- `tokenPaths` - a string or array of directory paths containing JSON design token files. Enables the [`sass-token-importer`](https://github.com/stamat/sass-token-importer) which lets you `@use` JSON tokens directly in SCSS. Supports [W3C DTCG](https://design-tokens.github.io/community-group/format/) and [Style Dictionary](https://amzn.github.io/style-dictionary/) formats with auto-detection.
|
|
311
|
+
- `tokenOutput` - output mode for design tokens: `"variables"` (default) generates flat SCSS variables, `"map"` generates nested Sass maps.
|
|
312
|
+
- `resolveAliases` - whether to resolve `{path.to.token}` alias references in design tokens. Default is `true`.
|
|
281
313
|
|
|
282
314
|
`styles` property can accept an array of style configurations or just a single style configuration. If you want to bundle multiple styles, just add them to the `styles` array:
|
|
283
315
|
|
|
@@ -306,49 +338,476 @@ Styles are bundled with [Dart Sass](https://sass-lang.com/dart-sass). You can sp
|
|
|
306
338
|
}
|
|
307
339
|
```
|
|
308
340
|
|
|
341
|
+
#### Design Tokens
|
|
342
|
+
|
|
343
|
+
You can import JSON design token files directly into your SCSS using the `token:` prefix. Define your tokens in JSON once and use them as SCSS variables — no manual variable files to keep in sync.
|
|
344
|
+
|
|
345
|
+
Given a token file `src/tokens/colors.json`:
|
|
346
|
+
|
|
347
|
+
```json
|
|
348
|
+
{
|
|
349
|
+
"color": {
|
|
350
|
+
"$type": "color",
|
|
351
|
+
"primary": { "$value": "#0066cc" },
|
|
352
|
+
"secondary": { "$value": "#ff6600" },
|
|
353
|
+
"link": { "$value": "{color.primary}" }
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Add `tokenPaths` to your styles config:
|
|
359
|
+
|
|
360
|
+
```json
|
|
361
|
+
{
|
|
362
|
+
"styles": [{
|
|
363
|
+
"in": "src/scss/index.scss",
|
|
364
|
+
"out": "dist/css/styles.css",
|
|
365
|
+
"options": {
|
|
366
|
+
"tokenPaths": ["src/tokens"]
|
|
367
|
+
}
|
|
368
|
+
}]
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Then use the `token:` prefix in your SCSS:
|
|
373
|
+
|
|
374
|
+
```scss
|
|
375
|
+
@use "token:colors" as c;
|
|
376
|
+
|
|
377
|
+
.btn {
|
|
378
|
+
color: c.$color-primary;
|
|
379
|
+
}
|
|
380
|
+
.btn:hover {
|
|
381
|
+
color: c.$color-secondary;
|
|
382
|
+
}
|
|
383
|
+
a {
|
|
384
|
+
color: c.$color-link; // resolved from {color.primary} → #0066cc
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
For Sass maps instead of flat variables, set `"tokenOutput": "map"`:
|
|
389
|
+
|
|
390
|
+
```scss
|
|
391
|
+
@use "sass:map";
|
|
392
|
+
@use "token:colors" as c;
|
|
393
|
+
|
|
394
|
+
.btn {
|
|
395
|
+
color: map.get(c.$color, primary);
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
309
399
|
As noted earlier, if you don't want to bundle styles, just remove the `styles` property from the config.
|
|
310
400
|
|
|
311
|
-
###
|
|
401
|
+
### PostCSS (optional)
|
|
402
|
+
|
|
403
|
+
Process CSS files with [PostCSS](https://postcss.org/) and any PostCSS plugins. This is a separate pipeline from Styles (Sass) — use it for tools like [Tailwind CSS](https://tailwindcss.com/), [Autoprefixer](https://github.com/postcss/autoprefixer), or any other PostCSS plugin.
|
|
312
404
|
|
|
313
|
-
|
|
405
|
+
PostCSS and its plugins are **not** bundled with Poops. You need to install them in your project:
|
|
314
406
|
|
|
407
|
+
```bash
|
|
408
|
+
npm i -D postcss
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Each PostCSS entry has the following properties:
|
|
412
|
+
|
|
413
|
+
- `in` - the input CSS file path
|
|
414
|
+
- `out` - the output path, can be a directory or a file path
|
|
415
|
+
- `options` - options for the pipeline
|
|
416
|
+
|
|
417
|
+
**Options:**
|
|
418
|
+
|
|
419
|
+
- `plugins` - an array of PostCSS plugin names to load. Each entry can be a string (plugin name) or a tuple `["plugin-name", { options }]` for passing options to the plugin.
|
|
420
|
+
- `minify` - whether to minify the output using `esbuild`. Default is `false`
|
|
421
|
+
- `justMinified` - output only the minified file. Default is `false`
|
|
422
|
+
|
|
423
|
+
`postcss` property can accept an array of configurations or a single configuration:
|
|
424
|
+
|
|
425
|
+
```json
|
|
426
|
+
{
|
|
427
|
+
"postcss": {
|
|
428
|
+
"in": "src/css/main.css",
|
|
429
|
+
"out": "dist/css/main.css",
|
|
430
|
+
"options": {
|
|
431
|
+
"plugins": ["@tailwindcss/postcss"],
|
|
432
|
+
"minify": true
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
You can also pass options to plugins using the tuple form:
|
|
439
|
+
|
|
440
|
+
```json
|
|
441
|
+
{
|
|
442
|
+
"postcss": {
|
|
443
|
+
"in": "src/css/main.css",
|
|
444
|
+
"out": "dist/css/main.css",
|
|
445
|
+
"options": {
|
|
446
|
+
"plugins": [
|
|
447
|
+
["autoprefixer", { "grid": true }]
|
|
448
|
+
]
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
**Build order:** PostCSS runs after Styles and Markups in the build pipeline. This means PostCSS plugins can reference the compiled markup output (e.g. Tailwind scanning HTML for utility classes). In watch mode, PostCSS is re-triggered after Styles or Markups recompile.
|
|
455
|
+
|
|
456
|
+
#### Tailwind CSS Example
|
|
457
|
+
|
|
458
|
+
The `example-tailwind/` directory demonstrates using Tailwind CSS v4 with Poops. To run it:
|
|
459
|
+
|
|
460
|
+
```bash
|
|
461
|
+
npm i -D postcss @tailwindcss/postcss tailwindcss
|
|
462
|
+
node poops.js -c example-tailwind/poops.json
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
The example config (`example-tailwind/poops.json`):
|
|
466
|
+
|
|
467
|
+
```json
|
|
468
|
+
{
|
|
469
|
+
"postcss": {
|
|
470
|
+
"in": "example-tailwind/src/css/main.css",
|
|
471
|
+
"out": "example-tailwind/dist/css/main.css",
|
|
472
|
+
"options": {
|
|
473
|
+
"plugins": ["@tailwindcss/postcss"],
|
|
474
|
+
"minify": true
|
|
475
|
+
}
|
|
476
|
+
},
|
|
477
|
+
"markup": {
|
|
478
|
+
"in": "example-tailwind/src/markup",
|
|
479
|
+
"out": "example-tailwind/dist",
|
|
480
|
+
"site": {
|
|
481
|
+
"title": "Poops + Tailwind",
|
|
482
|
+
"description": "A Tailwind CSS example for Poops"
|
|
483
|
+
},
|
|
484
|
+
"includePaths": ["_layouts", "_partials"]
|
|
485
|
+
},
|
|
486
|
+
"serve": { "port": 4041, "base": "/example-tailwind/dist" },
|
|
487
|
+
"livereload": true,
|
|
488
|
+
"watch": ["example-tailwind/src"]
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
The CSS entry file (`src/css/main.css`) simply imports Tailwind:
|
|
493
|
+
|
|
494
|
+
```css
|
|
495
|
+
@import "tailwindcss";
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
Then use Tailwind utility classes directly in your markup templates. Tailwind v4 auto-detects content sources, so no `tailwind.config.js` is needed.
|
|
499
|
+
|
|
500
|
+
**Using Sass + Tailwind together:** If you want both Sass and Tailwind, keep them as separate pipelines writing to separate output files. The Sass pipeline compiles `.scss` to CSS, while the PostCSS pipeline handles Tailwind independently. They don't need to chain into each other unless you want PostCSS to post-process the Sass output (e.g. with Autoprefixer) — in that case, point `postcss.in` to the Sass output file and `postcss.out` to a different file so the original Sass output is preserved for re-processing.
|
|
501
|
+
|
|
502
|
+
### Markups
|
|
503
|
+
|
|
504
|
+
- `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, and sitemap features documented below.
|
|
315
505
|
- `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.
|
|
316
506
|
- `out` - the output path, can be only a directory path (for now)
|
|
317
507
|
- `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.
|
|
318
508
|
- `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.
|
|
319
|
-
- `includePaths` - an array of paths to directories that will be added to the
|
|
509
|
+
- `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.
|
|
320
510
|
|
|
321
511
|
**💡 NOTE:** If, for instance, you are building a simple static onepager for your library, and want to pass a version variable from your `package.json`, Poops automatically reads your `package.json` if it exists in your working directory and sets the global variable `package` to the parsed JSON. So you can use it in your markup files, for example like this: `{{ package.version }}`.
|
|
322
512
|
|
|
323
|
-
Here is a sample markup configuration:
|
|
513
|
+
Here is a sample markup configuration using the default Nunjucks engine:
|
|
324
514
|
|
|
325
|
-
```
|
|
515
|
+
```json
|
|
516
|
+
{
|
|
517
|
+
"markup": {
|
|
518
|
+
"in": "src/markup",
|
|
519
|
+
"out": "dist",
|
|
520
|
+
"site": {
|
|
521
|
+
"title": "My Awesome Site",
|
|
522
|
+
"description": "This is my awesome site"
|
|
523
|
+
},
|
|
524
|
+
"data": [
|
|
525
|
+
"data/links.json",
|
|
526
|
+
"data/other.yaml"
|
|
527
|
+
],
|
|
528
|
+
"includePaths": [
|
|
529
|
+
"_includes"
|
|
530
|
+
]
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
To use Liquid instead, set the `engine` property:
|
|
536
|
+
|
|
537
|
+
```json
|
|
326
538
|
{
|
|
327
|
-
"
|
|
539
|
+
"markup": {
|
|
540
|
+
"engine": "liquid",
|
|
541
|
+
"in": "src/liquid",
|
|
542
|
+
"out": "dist",
|
|
543
|
+
"site": {
|
|
544
|
+
"title": "My Awesome Site",
|
|
545
|
+
"description": "This is my awesome site"
|
|
546
|
+
},
|
|
547
|
+
"data": [
|
|
548
|
+
"_data/links.json",
|
|
549
|
+
"_data/other.yaml"
|
|
550
|
+
],
|
|
551
|
+
"includePaths": [
|
|
552
|
+
"_layouts",
|
|
553
|
+
"_partials"
|
|
554
|
+
]
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
If your project doesn't have markups, you can remove the `markup` property from the config entirely. No code will be executed for this property.
|
|
560
|
+
|
|
561
|
+
#### Nunjucks vs Liquid
|
|
562
|
+
|
|
563
|
+
Both engines support the same feature set (collections, pagination, search index, sitemap, custom tags, and filters). The main differences are in template syntax:
|
|
564
|
+
|
|
565
|
+
| Feature | Nunjucks | Liquid |
|
|
566
|
+
|---------|----------|--------|
|
|
567
|
+
| File extension | `.njk` | `.liquid` |
|
|
568
|
+
| Inheritance | `{% extends "base.html" %}` | `{% layout "base.liquid" %}` |
|
|
569
|
+
| Default values | `{{ x or "fallback" }}` | `{{ x \| default: "fallback" }}` |
|
|
570
|
+
| Contains check | `{% if "x" in items %}` | `{% if items contains "x" %}` |
|
|
571
|
+
| Safe output | `{{ html \| safe }}` | `{{ html }}` (no escaping by default) |
|
|
572
|
+
| Includes | `{% include "partial.njk" %}` | `{% render "partial.liquid" %}` |
|
|
573
|
+
|
|
574
|
+
Both engines process `.html` and `.md` files in addition to their native extension.
|
|
575
|
+
|
|
576
|
+
#### Custom Tags
|
|
577
|
+
|
|
578
|
+
##### image
|
|
579
|
+
|
|
580
|
+
Poops can generate responsive `<img>` elements with `srcset` attributes. Image processing (resize, format conversion) is handled externally — Poops discovers the generated variants on disk and produces the correct HTML markup.
|
|
581
|
+
|
|
582
|
+
**Naming convention:** Your image tool should output variants as `{name}-{width}w.{ext}`. For example, given `photo.jpg`, the expected variants are: `photo-320w.jpg`, `photo-640w.jpg`, `photo-320w.webp`, `photo-640w.webp`, etc.
|
|
583
|
+
|
|
584
|
+
**`{% image %}` tag** — generates a full `<img>` element:
|
|
585
|
+
|
|
586
|
+
Nunjucks:
|
|
587
|
+
```nunjucks
|
|
588
|
+
{% image 'static/photo.jpg', alt='Hero', class='hero-img', sizes='(max-width: 640px) 100vw, 50vw' %}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
Liquid:
|
|
592
|
+
```liquid
|
|
593
|
+
{% image 'static/photo.jpg', alt: 'Hero', class: 'hero-img', sizes: '(max-width: 640px) 100vw, 50vw' %}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
Output:
|
|
597
|
+
|
|
598
|
+
```html
|
|
599
|
+
<img
|
|
600
|
+
src="static/photo-640w.jpg"
|
|
601
|
+
srcset="
|
|
602
|
+
static/photo-320w.webp 320w,
|
|
603
|
+
static/photo-640w.webp 640w,
|
|
604
|
+
static/photo-960w.webp 960w
|
|
605
|
+
"
|
|
606
|
+
sizes="(max-width: 640px) 100vw, 50vw"
|
|
607
|
+
alt="Hero"
|
|
608
|
+
class="hero-img"
|
|
609
|
+
loading="lazy"
|
|
610
|
+
/>
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
- Scans the output directory for files matching `{name}-{width}w.{ext}`
|
|
614
|
+
- Groups by format, prefers `avif` > `webp` > original format for srcset
|
|
615
|
+
- Uses the middle-sized variant as `src` fallback
|
|
616
|
+
- Prepends `relativePathPrefix` automatically
|
|
617
|
+
- Defaults: `sizes="100vw"`, `loading="lazy"`
|
|
618
|
+
- Falls back to a plain `<img src="...">` if no variants are found
|
|
619
|
+
|
|
620
|
+
##### googleFonts
|
|
621
|
+
|
|
622
|
+
Generates Google Fonts `<link>` tags with preconnect hints. Accepts an array of font names (strings) or font objects with weight/italic options.
|
|
623
|
+
|
|
624
|
+
Nunjucks (supports inline arrays):
|
|
625
|
+
```nunjucks
|
|
626
|
+
{% googleFonts ["Open Sans", "Roboto"] %}
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
Liquid (pass a variable — inline arrays are not supported in Liquid syntax):
|
|
630
|
+
```liquid
|
|
631
|
+
{% googleFonts fonts %}
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
Where `fonts` is defined in a data file (e.g. `fonts.json`):
|
|
635
|
+
```json
|
|
636
|
+
["Open Sans", "Roboto"]
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
Output:
|
|
640
|
+
|
|
641
|
+
```html
|
|
642
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
643
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
644
|
+
<link
|
|
645
|
+
href="https://fonts.googleapis.com/css2?family=Open+Sans&family=Roboto&display=swap"
|
|
646
|
+
rel="stylesheet"
|
|
647
|
+
/>
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
With specific weights and italics (Nunjucks):
|
|
651
|
+
|
|
652
|
+
```nunjucks
|
|
653
|
+
{% googleFonts ["DM Sans", {name: "Poppins", weights: [400, 700], ital: true}] %}
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
With specific weights and italics (Liquid — via data file):
|
|
657
|
+
|
|
658
|
+
```json
|
|
659
|
+
["DM Sans", {"name": "Poppins", "weights": [400, 700], "ital": true}]
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
Font object options:
|
|
663
|
+
|
|
664
|
+
- `name` — font family name
|
|
665
|
+
- `weights` — array of weight values (e.g. `[400, 700]`)
|
|
666
|
+
- `ital` — set to `true` to include italic variants
|
|
667
|
+
- `display` — font-display strategy, defaults to `swap` (Nunjucks only, as a keyword argument)
|
|
668
|
+
|
|
669
|
+
##### highlight
|
|
670
|
+
|
|
671
|
+
Syntax-highlights code blocks at build time using [highlight.js](https://highlightjs.org/), eliminating layout shift caused by client-side highlighting. Code is pre-highlighted in the HTML output — you only need the highlight.js CSS theme on the client, not the JS.
|
|
672
|
+
|
|
673
|
+
**`{% highlight %}` tag** — wraps a code block with syntax highlighting (same syntax in both engines):
|
|
674
|
+
|
|
675
|
+
```
|
|
676
|
+
{% highlight 'javascript' %}
|
|
677
|
+
const greet = (name) => {
|
|
678
|
+
return `Hello, ${name}!`;
|
|
679
|
+
};
|
|
680
|
+
{% endhighlight %}
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
Output:
|
|
684
|
+
|
|
685
|
+
```html
|
|
686
|
+
<pre><code class="hljs language-javascript"><span class="hljs-keyword">const</span> greet = <span class="hljs-function">...</span></code></pre>
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
The language argument is optional. If omitted, highlight.js will attempt to auto-detect the language.
|
|
690
|
+
|
|
691
|
+
**Markdown code fences** are also highlighted automatically at build time:
|
|
692
|
+
|
|
693
|
+
````md
|
|
694
|
+
```json
|
|
695
|
+
{ "name": "poops" }
|
|
696
|
+
```
|
|
697
|
+
````
|
|
698
|
+
|
|
699
|
+
Registered languages: `javascript`/`js`, `typescript`/`ts`, `css`, `scss`, `html`, `xml`, `json`, `bash`/`sh`, `shell`, `python`/`py`, `ruby`/`rb`, `php`, `java`, `c`, `cpp`, `csharp`/`cs`, `go`, `rust`/`rs`, `yaml`/`yml`, `markdown`/`md`, `sql`, `diff`.
|
|
700
|
+
|
|
701
|
+
#### Custom Filters
|
|
702
|
+
|
|
703
|
+
All filters are available in both engines. The only syntax difference is how arguments are passed: Nunjucks uses parentheses `| filter("arg")`, Liquid uses a colon `| filter: "arg"`.
|
|
704
|
+
|
|
705
|
+
- `slugify` — slugifies a string. Usage: `{{ "My Awesome Title" | slugify }}` will output `my-awesome-title`
|
|
706
|
+
|
|
707
|
+
- `jsonify` — serializes a value to JSON. Usage: `{{ myObject | jsonify }}`
|
|
708
|
+
|
|
709
|
+
- `markdown` — renders a markdown string to HTML. Usage: `{{ "**bold**" | markdown }}`
|
|
710
|
+
|
|
711
|
+
- `date` — formats a date string. Uses [dayjs](https://day.js.org/) format tokens. A default format can be set via the `timeDateFormat` config option.
|
|
712
|
+
- Nunjucks: `{{ "2024-01-15" | date("MMMM D, YYYY") }}`
|
|
713
|
+
- Liquid: `{{ "2024-01-15" | date: "MMMM D, YYYY" }}`
|
|
714
|
+
|
|
715
|
+
- `concat` — returns a new array with the value appended (does not mutate the original):
|
|
716
|
+
- Nunjucks: `{{ items | concat("c") }}`
|
|
717
|
+
- Liquid: `{{ items | concat: "c" }}`
|
|
718
|
+
|
|
719
|
+
- `push` — appends a value to an array in place (mutates the original):
|
|
720
|
+
- Nunjucks: `{{ items | push("c") }}`
|
|
721
|
+
- Liquid: `{{ items | push: "c" }}`
|
|
722
|
+
|
|
723
|
+
- `svg` — reads an SVG file and injects it inline. The path is resolved relative to the project root. Returns empty string if the file doesn't exist or isn't an SVG. Usage: `{{ 'src/icons/logo.svg' | svg }}`
|
|
724
|
+
|
|
725
|
+
- `highlight` — syntax-highlights a code string at build time using highlight.js. Takes an optional language argument. If the language is omitted, highlight.js will auto-detect it. Returns a `<pre><code class="hljs">` block with highlighted markup.
|
|
726
|
+
- Nunjucks: `{{ someCodeVariable | highlight('javascript') }}`
|
|
727
|
+
- Liquid: `{{ someCodeVariable | highlight: 'javascript' }}`
|
|
728
|
+
|
|
729
|
+
- `srcset` — returns just the srcset attribute value:
|
|
730
|
+
|
|
731
|
+
```html
|
|
732
|
+
<img
|
|
733
|
+
src="static/photo-640w.jpg"
|
|
734
|
+
srcset="{{ 'static/photo.jpg' | srcset }}"
|
|
735
|
+
sizes="100vw"
|
|
736
|
+
alt="Hero"
|
|
737
|
+
/>
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
Returns: `static/photo-320w.webp 320w, static/photo-640w.webp 640w, static/photo-960w.webp 960w`
|
|
741
|
+
|
|
742
|
+
#### Search Index & Sitemap
|
|
743
|
+
|
|
744
|
+
Poops can automatically generate a JSON search index and/or an XML sitemap from your compiled pages. Both are generated in a single pass during the markup compilation phase.
|
|
745
|
+
|
|
746
|
+
To enable, add `searchIndex` and/or `sitemap` to your markup config:
|
|
747
|
+
|
|
748
|
+
```json
|
|
749
|
+
{
|
|
750
|
+
"markup": {
|
|
328
751
|
"in": "src/markup",
|
|
329
752
|
"out": "dist",
|
|
330
753
|
"options": {
|
|
331
|
-
"
|
|
332
|
-
|
|
333
|
-
"description": "This is my awesome site"
|
|
334
|
-
},
|
|
335
|
-
"data": [
|
|
336
|
-
"data/links.json",
|
|
337
|
-
"data/other.yaml"
|
|
338
|
-
],
|
|
339
|
-
"includePaths": [
|
|
340
|
-
"_includes"
|
|
341
|
-
]
|
|
754
|
+
"searchIndex": "search-index.json",
|
|
755
|
+
"sitemap": "sitemap.xml"
|
|
342
756
|
}
|
|
343
757
|
}
|
|
344
758
|
}
|
|
345
759
|
```
|
|
346
760
|
|
|
347
|
-
|
|
761
|
+
The string shorthand sets the output filename with default options. For more control, use the object form:
|
|
348
762
|
|
|
349
|
-
|
|
763
|
+
```json
|
|
764
|
+
{
|
|
765
|
+
"searchIndex": {
|
|
766
|
+
"output": "search-index.json",
|
|
767
|
+
"minWordLength": 3,
|
|
768
|
+
"maxKeywords": 20,
|
|
769
|
+
"globalFrequencyCeiling": 0.8,
|
|
770
|
+
"stopWords": "path/to/custom-stop-words.json"
|
|
771
|
+
},
|
|
772
|
+
"sitemap": {
|
|
773
|
+
"output": "sitemap.xml"
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
**Search Index options:**
|
|
779
|
+
|
|
780
|
+
- `output` — output filename, written to the markup output directory
|
|
781
|
+
- `minWordLength` — minimum word length to consider as a keyword (default: `3`)
|
|
782
|
+
- `maxKeywords` — maximum keywords per page (default: `20`)
|
|
783
|
+
- `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)
|
|
784
|
+
- `stopWords` — customise stop word filtering:
|
|
785
|
+
- omit or `undefined` — uses the bundled English stop words
|
|
786
|
+
- `false` — disables stop word filtering entirely
|
|
787
|
+
- `["word1", "word2"]` — inline array of stop words
|
|
788
|
+
- `"path/to/file.json"` — path to a JSON array file (relative to project root)
|
|
789
|
+
|
|
790
|
+
**Search Index output format:**
|
|
791
|
+
|
|
792
|
+
All front matter fields are passed through to the index automatically. Internal fields (`content`, `isIndex`, `layout`, `published`) are stripped. If a page defines `keywords` in its front matter, those are used as-is instead of auto-extracted ones.
|
|
793
|
+
|
|
794
|
+
```json
|
|
795
|
+
[
|
|
796
|
+
{
|
|
797
|
+
"title": "My Post",
|
|
798
|
+
"date": "2024-01-15",
|
|
799
|
+
"description": "A great post about things.",
|
|
800
|
+
"collection": "blog",
|
|
801
|
+
"tags": ["javascript", "bundler"],
|
|
802
|
+
"url": "blog/my-post.html",
|
|
803
|
+
"keywords": ["javascript", "bundler", "webpack", "esbuild"]
|
|
804
|
+
}
|
|
805
|
+
]
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
**Sitemap** generates a standard `sitemap.xml` with `<loc>` and `<lastmod>` (from front matter `date`). If `site.url` is set in your markup config, it is prepended to all URLs. Collection index/pagination pages are included in the sitemap but excluded from the search index.
|
|
350
809
|
|
|
351
|
-
|
|
810
|
+
Pages with `published: false` in their front matter are excluded from both outputs.
|
|
352
811
|
|
|
353
812
|
### Copy
|
|
354
813
|
|
|
@@ -525,15 +984,16 @@ Same as `watch` property, `includePaths` accepts an array of paths to include. I
|
|
|
525
984
|
- [ ] Styles `in` should be able to support array of inputs like we have it on scripts
|
|
526
985
|
- [ ] Build a cli config creation helper tool. If the user doesn't have a config file, we can ask them a few questions and create a config file for them. Create Yeoman generator for poops projects.
|
|
527
986
|
- [x] Add nunjucks static templating
|
|
528
|
-
- [
|
|
529
|
-
- [
|
|
987
|
+
- [x] Refactor nunjucks implementation
|
|
988
|
+
- [x] Complete documentation for nunjucks
|
|
530
989
|
- [x] Add markdown support
|
|
531
990
|
- [x] Front Matter support
|
|
532
991
|
- [x] Future implementation: posts and custom collections, so we can have a real static site generator
|
|
533
992
|
- [x] Collection pagination system
|
|
534
993
|
- [x] Post published toggle
|
|
535
994
|
- [x] RSS and ATOM generation for collections
|
|
536
|
-
- [
|
|
995
|
+
- [x] Support for images and creating srcsets
|
|
996
|
+
- [x] Add Liquid template engine as a swappable alternative to Nunjucks
|
|
537
997
|
|
|
538
998
|
## Why?
|
|
539
999
|
|