nibula 1.5.3 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/README.md +2 -0
- package/bin/create.js +1 -1
- package/bin/delegate.js +58 -0
- package/bin/nibula.js +4 -19
- package/docs/Assistant CLI.md +3 -3
- package/docs/Components.md +68 -12
- package/docs/Creating pages.md +18 -13
- package/docs/Head and SEO.md +7 -6
- package/docs/Javascript.md +11 -9
- package/docs/Styling with SCSS.md +9 -11
- package/package.json +17 -22
- package/src/frontend/components/welcome.njk +1 -0
- package/src/frontend/data/pages.json +27 -0
- package/src/frontend/data/site.json +1 -28
- package/src/frontend/js/global.js +11 -0
- package/src/frontend/js/pages/404.js +2 -1
- package/src/frontend/js/pages/homepage.js +2 -1
- package/src/frontend/layouts/base.njk +23 -19
- package/src/frontend/scss/{modules/_global.scss → _global.scss} +9 -10
- package/src/frontend/scss/_root.scss +14 -0
- package/src/frontend/scss/modules/_animations.scss +1 -1
- package/src/frontend/scss/modules/_footer.scss +2 -1
- package/src/frontend/scss/modules/_header.scss +1 -2
- package/src/frontend/scss/modules/_mobile.scss +1 -1
- package/src/frontend/scss/modules/_typography.scss +5 -1
- package/src/frontend/scss/pages/404.scss +2 -2
- package/src/frontend/scss/pages/homepage.scss +2 -2
- package/src/frontend/sitemap.njk +1 -1
- package/src/frontend/ts/global.ts +12 -0
- package/src/frontend/ts/pages/404.ts +2 -1
- package/src/frontend/ts/pages/homepage.ts +2 -1
- package/tools/cli/ui.js +17 -3
- package/tools/config/messages.json +10 -8
- package/tools/config/settings.json +2 -6
- package/tools/lib/pageActions.js +8 -5
- package/tools/lib/pagesData.js +88 -0
- package/tools/lib/paths.js +8 -5
- package/tools/lib/validation.js +1 -0
- package/tools/res/templates/template.js +1 -1
- package/tools/res/templates/template.scss +3 -3
- package/tools/res/templates/template.ts +2 -1
- package/src/frontend/js/modules/global.js +0 -10
- package/src/frontend/scss/modules/_root.scss +0 -35
- package/src/frontend/ts/modules/global.ts +0 -10
- package/tools/lib/siteData.js +0 -89
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,44 @@ All notable changes to Nibula are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.6.1] - 2026-08-06
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- `src/frontend/scss/pages/prova.scss` was left in the template and shipped with 1.6.0, so every new project started with a stylesheet for a page that doesn't exist — no route, no script, no entry in `pages.json`.
|
|
12
|
+
- Open Graph and Twitter Card tags read `seo.title` and `seo.description` directly, with none of the fallbacks the `<title>` and meta description already had. A page that left those fields empty in `pages.json` published an empty social preview — exactly the case the fallbacks exist for. They now follow the same chain.
|
|
13
|
+
- `data-theme` was missing from the `<html>` tag. It is documented as the hook to target in SCSS for theme variables, so a selector written against it never matched.
|
|
14
|
+
|
|
15
|
+
## [1.6.0] - 2026-08-06
|
|
16
|
+
|
|
17
|
+
### Breaking
|
|
18
|
+
- Per-page settings must move out of `site.json`. Take the block under the `pages`
|
|
19
|
+
key and make it the root object of a new `src/frontend/data/pages.json`, then
|
|
20
|
+
update `base.njk`, `sitemap.njk` and `llms.njk` to read `pages[title]` instead of
|
|
21
|
+
`site.pages[title]`. Without `pages.json` the assistant reports the missing file
|
|
22
|
+
and stops.
|
|
23
|
+
- SCSS and JS import paths change with the `_root`, `_global` and `global` moves.
|
|
24
|
+
Update the `@use` and `@import` lines in your page entry points accordingly.
|
|
25
|
+
- Existing projects keep working until you run `npm install nibula@latest` inside
|
|
26
|
+
them: commands now delegate to the project's own copy, so an old project keeps
|
|
27
|
+
running the version it was built with.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- **Per-page settings moved from `site.json` to a new `pages.json`.** They were nested under a `pages` key in the same file the CLI writes on every page operation; splitting them means a syntax error in one page's record no longer makes the whole site configuration unreadable. Access changes from `{{ site.pages[title] }}` to `{{ pages[title] }}`, and the CLI now reads and writes `pages.json` only.
|
|
31
|
+
- Commands run inside a project now execute the project's own Nibula, taken from its `node_modules`, instead of the globally installed one. `nib cli` typed in a terminal used to run the global copy against local files, so a project scaffolded with an older version could be operated on by a newer CLI expecting a different structure. When the two versions differ, the one being used is reported before the command runs, and the assistant's title shows it as well.
|
|
32
|
+
- `_root.scss` and `_global.scss` moved out of `scss/modules/`. They are not modules — every page imports them and nothing else does — so they now sit at the root of `scss/`.
|
|
33
|
+
- `global.js` and `global.ts` moved out of their `modules/` folders, for the same reason.
|
|
34
|
+
- Import paths updated across existing modules, pages and scaffold templates to match both moves.
|
|
35
|
+
- `base.njk` applies the `url` filter to per-page CSS and JS, to the favicons and to the framework scripts. Without it these paths ignore `pathPrefix`, so every asset 404s on a site published under a subpath — GitHub Pages being the common case.
|
|
36
|
+
- Documentation updated throughout for the new structure and the `site.json` / `pages.json` split.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
- TypeScript page entry points imported modules with a `.js` extension.
|
|
40
|
+
- `logo` was nested inside `legal` in `site.json`, so `{{ site.logo }}` resolved to nothing in the header, the footer, the Open Graph image and the JSON-LD publisher block. It now sits at the root, where the templates already looked for it.
|
|
41
|
+
|
|
42
|
+
### Removed
|
|
43
|
+
- `legal.cookieControls` from `site.json`.
|
|
44
|
+
- Preconnect hint to `cdn.jsdelivr.net` in `base.njk`. The project loads its framework from `node_modules` through passthrough copy, so the hint opened a TCP and TLS connection to a host that was never used.
|
|
45
|
+
|
|
8
46
|
## [1.5.3] - 2026-08-05
|
|
9
47
|
|
|
10
48
|
### Fixed
|
package/README.md
CHANGED
|
@@ -106,3 +106,5 @@ Thanks for giving Nibula a try.
|
|
|
106
106
|
It's a one-person project, so every bit of feedback counts more than you'd think: if something breaks, if the documentation isn't clear, or if a feature is missing, open an [issue](https://github.com/Rhaastrake/Nibula/issues) — even just to tell me what you built with it.
|
|
107
107
|
|
|
108
108
|
If Nibula was useful to you, leaving a ⭐ on the repository is the easiest way to help other people find it.
|
|
109
|
+
|
|
110
|
+
You can support the project by [buying me a coffee](https://paypal.me/rhaastrake) ☕🫡
|
package/bin/create.js
CHANGED
|
@@ -78,7 +78,7 @@ const CREATE_DIRS = [];
|
|
|
78
78
|
|
|
79
79
|
// Backend files that belong to exactly one backend, matched by basename.
|
|
80
80
|
// Everything else (migrations, .htaccess, web.config, README, ...) is shared.
|
|
81
|
-
const NODE_ONLY_FILES = new Set(['package.json'
|
|
81
|
+
const NODE_ONLY_FILES = new Set(['package.json']);
|
|
82
82
|
const PHP_ONLY_FILES = new Set(['composer.json', 'composer.lock']);
|
|
83
83
|
const PHP_ONLY_DIRS = new Set(['vendor']);
|
|
84
84
|
// Runtime artifacts that must never be copied from the template, either way.
|
package/bin/delegate.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { spawnSync } = require('child_process');
|
|
4
|
+
const { color } = require('../tools/lib/colors');
|
|
5
|
+
const { message } = require('../tools/lib/logger');
|
|
6
|
+
|
|
7
|
+
const PACKAGE_NAME = 'nibula';
|
|
8
|
+
const NODE_MODULES = 'node_modules';
|
|
9
|
+
const ENTRY_FILE = path.join('bin', 'nibula.js');
|
|
10
|
+
const PACKAGE_FILE = 'package.json';
|
|
11
|
+
const LOCAL_FLAG = 'NIBULA_LOCAL';
|
|
12
|
+
const LOCAL_FLAG_VALUE = '1';
|
|
13
|
+
|
|
14
|
+
const globalVersion = require(`../${PACKAGE_FILE}`).version;
|
|
15
|
+
|
|
16
|
+
function realPath(target) {
|
|
17
|
+
try {
|
|
18
|
+
return fs.realpathSync(target);
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function readVersion(packageRoot) {
|
|
25
|
+
try {
|
|
26
|
+
return JSON.parse(fs.readFileSync(path.join(packageRoot, PACKAGE_FILE), 'utf8')).version;
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function delegateToLocal(projectRoot) {
|
|
33
|
+
if (process.env[LOCAL_FLAG] === LOCAL_FLAG_VALUE) return;
|
|
34
|
+
|
|
35
|
+
const localRoot = realPath(path.join(projectRoot, NODE_MODULES, PACKAGE_NAME));
|
|
36
|
+
const selfRoot = realPath(path.join(__dirname, '..'));
|
|
37
|
+
|
|
38
|
+
if (!localRoot || localRoot === selfRoot) return;
|
|
39
|
+
|
|
40
|
+
const localEntry = path.join(localRoot, ENTRY_FILE);
|
|
41
|
+
if (!fs.existsSync(localEntry)) return;
|
|
42
|
+
|
|
43
|
+
const localVersion = readVersion(localRoot);
|
|
44
|
+
|
|
45
|
+
if (localVersion && localVersion !== globalVersion) {
|
|
46
|
+
console.log(`${color.dim}${message('cli.usingLocalVersion', { local: localVersion, global: globalVersion })}${color.reset}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const result = spawnSync(process.execPath, [localEntry, ...process.argv.slice(2)], {
|
|
50
|
+
stdio: 'inherit',
|
|
51
|
+
cwd: process.cwd(),
|
|
52
|
+
env: { ...process.env, [LOCAL_FLAG]: LOCAL_FLAG_VALUE },
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
process.exit(result.status ?? 0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { delegateToLocal };
|
package/bin/nibula.js
CHANGED
|
@@ -8,6 +8,7 @@ const { spawnSync } = require('child_process');
|
|
|
8
8
|
const { findProjectRoot, NOT_INSIDE_PROJECT_MESSAGE } = require('../tools/lib/paths');
|
|
9
9
|
const { color } = require('../tools/lib/colors');
|
|
10
10
|
const { message } = require('../tools/lib/logger');
|
|
11
|
+
const { delegateToLocal } = require('./delegate');
|
|
11
12
|
|
|
12
13
|
const pkg = require('../package.json');
|
|
13
14
|
|
|
@@ -48,27 +49,9 @@ function requireOutsideProject() {
|
|
|
48
49
|
process.exit(1);
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
function maybeDelegateToLocal(root) {
|
|
52
|
-
const local = path.join(root, 'node_modules', 'nibula', 'bin', 'nibula.js');
|
|
53
|
-
if (!fs.existsSync(local)) return;
|
|
54
|
-
|
|
55
|
-
let localReal;
|
|
56
|
-
let selfReal;
|
|
57
|
-
try { localReal = fs.realpathSync(local); } catch { return; }
|
|
58
|
-
try { selfReal = fs.realpathSync(__filename); } catch { selfReal = __filename; }
|
|
59
|
-
|
|
60
|
-
if (localReal !== selfReal) {
|
|
61
|
-
const res = spawnSync('node', [localReal, ...process.argv.slice(2)], {
|
|
62
|
-
stdio: 'inherit',
|
|
63
|
-
cwd: process.cwd(),
|
|
64
|
-
});
|
|
65
|
-
process.exit(res.status ?? 0);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
52
|
function enterProject() {
|
|
70
53
|
const root = requireProjectRoot();
|
|
71
|
-
|
|
54
|
+
delegateToLocal(root);
|
|
72
55
|
return root;
|
|
73
56
|
}
|
|
74
57
|
|
|
@@ -198,6 +181,7 @@ function findExistingProject(baseDir, projectName) {
|
|
|
198
181
|
|
|
199
182
|
function usage(currentVersion) {
|
|
200
183
|
console.log(`
|
|
184
|
+
|
|
201
185
|
${color.bold}${color.cyan}Nibula (${currentVersion})${color.reset} ${color.bold}by Michele Garofalo${color.reset}
|
|
202
186
|
|
|
203
187
|
${color.yellow}nib new <project-name>${color.reset} Create your new project
|
|
@@ -206,6 +190,7 @@ ${color.yellow}nib run${color.reset} Start the dev server and b
|
|
|
206
190
|
${color.yellow}nib build${color.reset} Build the site out folder to publish
|
|
207
191
|
${color.yellow}nib clean${color.reset} Remove the output directory
|
|
208
192
|
${color.yellow}nib update${color.reset} Update to the latest version
|
|
193
|
+
|
|
209
194
|
`);
|
|
210
195
|
}
|
|
211
196
|
|
package/docs/Assistant CLI.md
CHANGED
|
@@ -33,7 +33,7 @@ For a page named `my-page`, the following files are created:
|
|
|
33
33
|
| `src/frontend/js/pages/myPage.js` | JS entry point |
|
|
34
34
|
| `src/frontend/routes/my-page.njk` | Nunjucks template |
|
|
35
35
|
|
|
36
|
-
It also adds an `elif` block in `page-components.njk` and a stub entry in `
|
|
36
|
+
It also adds an `elif` block in `page-components.njk` and a stub entry in `pages.json`:
|
|
37
37
|
|
|
38
38
|
```json
|
|
39
39
|
"myPage": {
|
|
@@ -53,11 +53,11 @@ It also adds an `elif` block in `page-components.njk` and a stub entry in `site.
|
|
|
53
53
|
|
|
54
54
|
## Remove page
|
|
55
55
|
|
|
56
|
-
Deletes all source files for the page and cleans up the output directory, `page-components.njk`, and `
|
|
56
|
+
Deletes all source files for the page and cleans up the output directory, `page-components.njk`, and `pages.json`.
|
|
57
57
|
|
|
58
58
|
## Rename page
|
|
59
59
|
|
|
60
|
-
Renames all three source files, updates the `elif` block in `page-components.njk`, and renames the record in `
|
|
60
|
+
Renames all three source files, updates the `elif` block in `page-components.njk`, and renames the record in `pages.json`. Nothing inside the record is touched — your SEO title, description and CDN links stay exactly as you wrote them.
|
|
61
61
|
|
|
62
62
|
## Configure output path
|
|
63
63
|
|
package/docs/Components.md
CHANGED
|
@@ -28,8 +28,8 @@ To render a component inside a page, navigate to `src/frontend/layouts/` and edi
|
|
|
28
28
|
{% include "welcome.njk" %}
|
|
29
29
|
|
|
30
30
|
{% elif title == "examplePage" %}
|
|
31
|
-
{% include "
|
|
32
|
-
{% include "subfolder/
|
|
31
|
+
{% include "example-component-1.njk" %}
|
|
32
|
+
{% include "subfolder/example-component-2.njk" %}
|
|
33
33
|
|
|
34
34
|
{% else %}
|
|
35
35
|
{% include "404/_404.njk" %}
|
|
@@ -50,8 +50,8 @@ A component can include other components. This is useful for breaking complex se
|
|
|
50
50
|
### exampleComponent.njk
|
|
51
51
|
```js
|
|
52
52
|
<section class="hero">
|
|
53
|
-
{% include "
|
|
54
|
-
{% include "
|
|
53
|
+
{% include "hero/hero-title.njk" %}
|
|
54
|
+
{% include "hero/hero-button.njk" %}
|
|
55
55
|
</section>
|
|
56
56
|
```
|
|
57
57
|
|
|
@@ -67,14 +67,23 @@ All values defined in `src/frontend/data/site.json` are globally available in ev
|
|
|
67
67
|
|
|
68
68
|
### site.json <small>(`src/frontend/data/`)</small>
|
|
69
69
|
```json
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
"site_name": "Site name",
|
|
71
|
+
"title": "Site title",
|
|
72
|
+
"description": "Site description",
|
|
73
|
+
"keywords": "keyword1, keyword2, keyword3",
|
|
74
|
+
"domain": "yoursite.com",
|
|
75
|
+
"url": "https://yoursite.com",
|
|
76
|
+
"lang": "en",
|
|
77
|
+
"author": "Name and surname",
|
|
78
|
+
"logo": "/assets/brand/logo.svg",
|
|
79
|
+
"legal": {
|
|
80
|
+
"privacy": "", // This is supposed to contain a link
|
|
81
|
+
"cookie": "", // This is supposed to contain a link
|
|
82
|
+
"terms": "", // This is supposed to contain a link
|
|
83
|
+
"copyright": {
|
|
84
|
+
"year": "2026",
|
|
85
|
+
"text": "All rights reserved."
|
|
76
86
|
}
|
|
77
|
-
}
|
|
78
87
|
```
|
|
79
88
|
|
|
80
89
|
### Usage in any `.njk` file
|
|
@@ -82,4 +91,51 @@ All values defined in `src/frontend/data/site.json` are globally available in ev
|
|
|
82
91
|
<p>{{ site.title }}</p>
|
|
83
92
|
<a href="{{ site.legal.privacy }}">Privacy Policy</a>
|
|
84
93
|
<img src="{{ site.logo }}" alt="{{ site.title }}">
|
|
85
|
-
```
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Your own data files
|
|
97
|
+
|
|
98
|
+
`site.json` isn't special: **any** `.json` file you drop in `src/frontend/data/` becomes a global variable, and the variable takes the name of the file.
|
|
99
|
+
|
|
100
|
+
Create `test.json`, put anything you want inside it, and every key is reachable as `test.yourKey` — no import, no configuration, no restart.
|
|
101
|
+
|
|
102
|
+
### test.json <small>(`src/frontend/data/`)</small>
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"testMessage": "This is a test"
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Usage in any `.njk` file
|
|
110
|
+
```js
|
|
111
|
+
<p>{{ test.testMessage }}</p>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Renders as:
|
|
115
|
+
```html
|
|
116
|
+
<p>This is a test</p>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
That's the whole rule: **file name = variable name, keys inside = what you write after the dot.** `site.json` gives you `site.title` for exactly the same reason.
|
|
120
|
+
|
|
121
|
+
Values can be anything JSON allows, so a list lets you loop instead of copying markup:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"testMessage": "This is a test",
|
|
126
|
+
"links": [
|
|
127
|
+
{ "label": "Home", "url": "/" },
|
|
128
|
+
{ "label": "Contact", "url": "/contact/" }
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
{% for link in test.links %}
|
|
135
|
+
<a href="{{ link.url }}">{{ link.label }}</a>
|
|
136
|
+
{% endfor %}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Subfolders create nested names: `data/shop/products.json` becomes `{{ shop.products }}`.
|
|
140
|
+
|
|
141
|
+
> ⚠️ Don't reuse a name that already exists. Another file named `site.json` in a subfolder overwrites the original one silently, with no error to point you at the cause.
|
package/docs/Creating pages.md
CHANGED
|
@@ -11,16 +11,16 @@ For a page named `my-page`:
|
|
|
11
11
|
|---|---|
|
|
12
12
|
| `src/frontend/routes/my-page.njk` | Template with front matter |
|
|
13
13
|
| `src/frontend/scss/pages/myPage.scss` | Imports framework + modules |
|
|
14
|
-
| `src/frontend/js/pages/myPage.js` | Imports JS modules |
|
|
14
|
+
| `src/frontend/js/pages/myPage.js` | Imports JS/TS modules |
|
|
15
15
|
|
|
16
16
|
## Adding content
|
|
17
17
|
|
|
18
|
-
1. Create a component in `src/frontend/components/` (e.g. `
|
|
18
|
+
1. Create a component in `src/frontend/components/` (e.g. `my-component.njk`)
|
|
19
19
|
2. Include it in `src/frontend/layouts/page-components.njk` inside the generated `elif` block:
|
|
20
20
|
|
|
21
21
|
```njk
|
|
22
22
|
{% elif title == "myPage" %}
|
|
23
|
-
{% include "
|
|
23
|
+
{% include "my-component.njk" ignore missing %}
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
See **Components** DOC file for more info
|
|
@@ -51,15 +51,20 @@ The parent path (`about`) does **not** need to exist as a real page — it's jus
|
|
|
51
51
|
|
|
52
52
|
## SEO
|
|
53
53
|
|
|
54
|
-
The CLI creates a stub entry in `src/frontend/data/
|
|
54
|
+
The CLI creates a stub entry in `src/frontend/data/pages.json`. Fill it in:
|
|
55
55
|
|
|
56
56
|
```json
|
|
57
|
-
"myPage": {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
57
|
+
"myPage": {
|
|
58
|
+
"seo": {
|
|
59
|
+
"title": "My Page",
|
|
60
|
+
"description": "Description",
|
|
61
|
+
"keywords": "",
|
|
62
|
+
"noindex": false,
|
|
63
|
+
"canonical": ""
|
|
64
|
+
},
|
|
65
|
+
"cdn": {
|
|
66
|
+
"css": [],
|
|
67
|
+
"js": []
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
package/docs/Head and SEO.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Head & SEO
|
|
2
2
|
|
|
3
|
-
Global settings live in `src/frontend/data/
|
|
3
|
+
Global settings live in `src/frontend/data/pages.json` and are available everywhere via `{{ pages.* }}`.
|
|
4
4
|
|
|
5
5
|
## Global fields
|
|
6
6
|
|
|
@@ -46,10 +46,10 @@ Global settings live in `src/frontend/data/site.json` and are available everywhe
|
|
|
46
46
|
|
|
47
47
|
## Per-page SEO and CDN
|
|
48
48
|
|
|
49
|
-
Each page is keyed by its camelCase `title` from the front matter:
|
|
49
|
+
Per-page settings live in `src/frontend/data/pages.json`, available via `{{ pages.* }}`. Each page is keyed by its camelCase `title` from the front matter, at the root of the file:
|
|
50
50
|
|
|
51
51
|
```json
|
|
52
|
-
|
|
52
|
+
{
|
|
53
53
|
"examplePage": {
|
|
54
54
|
"seo": {
|
|
55
55
|
"title": "Example Page",
|
|
@@ -113,14 +113,15 @@ See the SCSS docs for how to hook your variables to `data-theme`.
|
|
|
113
113
|
The three favicon tags are hardcoded in `base.njk`. To change the icons, replace the files in `src/frontend/assets/brand/` keeping the same names:
|
|
114
114
|
|
|
115
115
|
```html
|
|
116
|
-
<link rel="icon" type="image/svg+xml" href="/assets/brand/favicon.svg">
|
|
117
|
-
<link rel="icon" type="image/png" sizes="32x32" href="/assets/brand/favicon-32.png">
|
|
118
|
-
<link rel="apple-touch-icon" sizes="180x180" href="/assets/brand/apple-touch-icon.png">
|
|
116
|
+
<link rel="icon" type="image/svg+xml" href="{{ '/assets/brand/favicon.svg' | url }}">
|
|
117
|
+
<link rel="icon" type="image/png" sizes="32x32" href="{{ '/assets/brand/favicon-32.png' | url }}">
|
|
118
|
+
<link rel="apple-touch-icon" sizes="180x180" href="{{ '/assets/brand/apple-touch-icon.png' | url }}">
|
|
119
119
|
```
|
|
120
120
|
|
|
121
121
|
| File | Purpose |
|
|
122
122
|
|---|---|
|
|
123
123
|
| `favicon.svg` | Modern browsers, scales to any size |
|
|
124
|
+
| `favicon-48.png` | Fallback for older browsers and some crawlers |
|
|
124
125
|
| `favicon-32.png` | Fallback for older browsers and some crawlers |
|
|
125
126
|
| `apple-touch-icon.png` | iOS home screen icon — 180×180, must be opaque (iOS renders transparency as black) |
|
|
126
127
|
|
package/docs/Javascript.md
CHANGED
|
@@ -11,7 +11,8 @@ Import only what the page needs.
|
|
|
11
11
|
### examplePage.js <small>(`src/frontend/js/pages/`)</small>
|
|
12
12
|
|
|
13
13
|
```js
|
|
14
|
-
import '../
|
|
14
|
+
import '../global.js';
|
|
15
|
+
|
|
15
16
|
// import { initExampleModule } from '../modules/exampleModule.js';
|
|
16
17
|
|
|
17
18
|
document.addEventListener("DOMContentLoaded", () => {
|
|
@@ -25,9 +26,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
|
25
26
|
|
|
26
27
|
Some code has to run on every page: a header menu, a theme toggle, a cookie banner. Repeating it in every page entry point means you'll eventually forget it on one of them, and the bug only shows up on that single page.
|
|
27
28
|
|
|
28
|
-
That's what `global.js` is for. It lives in `src/frontend/js
|
|
29
|
+
That's what `global.js` is for. It lives in `src/frontend/js/`, every page entry point imports it, and it takes care of running the shared modules.
|
|
29
30
|
|
|
30
|
-
### global.js <small>(`src/frontend/js
|
|
31
|
+
### global.js <small>(`src/frontend/js/`)</small>
|
|
31
32
|
|
|
32
33
|
```js
|
|
33
34
|
// import { initExampleModule } from './exampleModule.js';
|
|
@@ -38,14 +39,15 @@ function initGlobal() {
|
|
|
38
39
|
|
|
39
40
|
// Global logic here
|
|
40
41
|
|
|
41
|
-
// Do not touch
|
|
42
|
+
// Do not touch
|
|
43
|
+
// This instruction starts global.js itself
|
|
42
44
|
document.addEventListener('DOMContentLoaded', initGlobal);
|
|
43
45
|
```
|
|
44
46
|
|
|
45
47
|
Pages import it without curly braces, since there is nothing to export — the module runs on its own as soon as it's part of the bundle:
|
|
46
48
|
|
|
47
49
|
```js
|
|
48
|
-
import '../
|
|
50
|
+
import '../global.js';
|
|
49
51
|
```
|
|
50
52
|
|
|
51
53
|
Note that the import path inside `global.js` is `'./exampleModule.js'` and not `'../modules/exampleModule.js'`: `global.js` already sits in the modules folder, next to the modules it imports.
|
|
@@ -56,9 +58,8 @@ A responsive header has a burger button that opens the navigation on small scree
|
|
|
56
58
|
|
|
57
59
|
Write the behaviour as a normal module:
|
|
58
60
|
|
|
61
|
+
### header.js <small>(`src/frontend/js/modules/`)</small>
|
|
59
62
|
```js
|
|
60
|
-
// src/frontend/js/modules/header.js
|
|
61
|
-
|
|
62
63
|
export function initHeader() {
|
|
63
64
|
const toggle = document.querySelector('.menu-toggle');
|
|
64
65
|
const menu = document.querySelector('.menu');
|
|
@@ -74,13 +75,14 @@ export function initHeader() {
|
|
|
74
75
|
Then wire it up once, in `global.js`:
|
|
75
76
|
|
|
76
77
|
```js
|
|
77
|
-
import { initHeader } from './header.js';
|
|
78
|
+
import { initHeader } from './modules/header.js';
|
|
78
79
|
|
|
79
80
|
function initGlobal() {
|
|
80
81
|
initHeader();
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
// Do not touch
|
|
84
|
+
// Do not touch
|
|
85
|
+
// This instruction starts global.js itself
|
|
84
86
|
document.addEventListener('DOMContentLoaded', initGlobal);
|
|
85
87
|
```
|
|
86
88
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Each page has its own SCSS entry point in `src/frontend/scss/pages/`
|
|
6
6
|
|
|
7
|
-
It must contain `_root.scss` +
|
|
7
|
+
It must contain `_root.scss` + any other module that you need and its own specific css rules
|
|
8
8
|
|
|
9
9
|
`_root.scss` uses `@use` to enable namespaced access (`root.$var`); other modules use `@import` as they don't expose variables.
|
|
10
10
|
|
|
@@ -14,9 +14,9 @@ It must contain `_root.scss` + other modules like `_global.scss` or any other on
|
|
|
14
14
|
// CSS MODULES IMPORTS
|
|
15
15
|
//==========================
|
|
16
16
|
|
|
17
|
-
@use "../
|
|
17
|
+
@use "../root" as root;
|
|
18
18
|
|
|
19
|
-
@import "../
|
|
19
|
+
@import "../global";
|
|
20
20
|
|
|
21
21
|
//==========================
|
|
22
22
|
// PAGE CUSTOM CSS RULES
|
|
@@ -49,7 +49,7 @@ You can create subfolders if you want to refactor the structure, but be sure to
|
|
|
49
49
|
|
|
50
50
|
### _yourModule.scss <small>(`src/frontend/scss/modules/subfolder/`)</small>
|
|
51
51
|
```scss
|
|
52
|
-
@use '
|
|
52
|
+
@use '../../root' as root;
|
|
53
53
|
|
|
54
54
|
body {
|
|
55
55
|
background-color: root.$primary;
|
|
@@ -71,8 +71,6 @@ body {
|
|
|
71
71
|
|
|
72
72
|
| File | Purpose |
|
|
73
73
|
|---|---|
|
|
74
|
-
| `_root.scss` | Global variables (colors, spacing) |
|
|
75
|
-
| `_global.scss` | Site-wide base rules and frameworks |
|
|
76
74
|
| `_typography.scss` | Font rules |
|
|
77
75
|
| `_header.scss` | Header styles |
|
|
78
76
|
| `_footer.scss` | Footer styles |
|
|
@@ -87,12 +85,12 @@ You can choose one or none of them (more than 1 works, but you may get in variou
|
|
|
87
85
|
|
|
88
86
|
To enable/disable them you have to modify 3 files around the project by just commenting them
|
|
89
87
|
|
|
90
|
-
### 1. _global.scss <small>(`src/frontend/scss
|
|
88
|
+
### 1. _global.scss <small>(`src/frontend/scss/`)</small>
|
|
91
89
|
```scss
|
|
92
|
-
@import "
|
|
93
|
-
// @import "
|
|
94
|
-
// @import "
|
|
95
|
-
// @import "
|
|
90
|
+
@import "modules/frameworks/bootstrap";
|
|
91
|
+
// @import "modules/frameworks/bulma";
|
|
92
|
+
// @import "modules/frameworks/foundation";
|
|
93
|
+
// @import "modules/frameworks/uikit";
|
|
96
94
|
```
|
|
97
95
|
|
|
98
96
|
### 2. base.njk <small>(`src/frontend/layouts/`)</small>
|
package/package.json
CHANGED
|
@@ -1,34 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nibula",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "A beginner-friendly static site generator built on Eleventy that stays close to vanilla HTML, CSS and JS — with a page-management CLI, an optional PHP backend, and SEO files generated for you.",
|
|
5
5
|
"keywords": [
|
|
6
|
-
"static-site-generator",
|
|
7
|
-
"ssg",
|
|
8
|
-
"eleventy",
|
|
9
6
|
"11ty",
|
|
7
|
+
"beginner-friendly",
|
|
10
8
|
"boilerplate",
|
|
11
|
-
"
|
|
12
|
-
"
|
|
9
|
+
"bootstrap",
|
|
10
|
+
"bulma",
|
|
13
11
|
"cli",
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"css",
|
|
18
|
-
"scss",
|
|
19
|
-
"sass",
|
|
12
|
+
"deploy-ready",
|
|
13
|
+
"eleventy-template",
|
|
14
|
+
"nginx",
|
|
20
15
|
"njk",
|
|
21
|
-
"
|
|
22
|
-
"components",
|
|
23
|
-
"backend",
|
|
16
|
+
"nodejs",
|
|
24
17
|
"php",
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"seo",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
18
|
+
"rest-api",
|
|
19
|
+
"sass",
|
|
20
|
+
"scaffolder",
|
|
21
|
+
"seo-optimization",
|
|
22
|
+
"ssg",
|
|
23
|
+
"static-site-generator",
|
|
24
|
+
"typescript",
|
|
25
|
+
"uikit"
|
|
32
26
|
],
|
|
33
27
|
"author": "Michele Garofalo",
|
|
34
28
|
"license": "Apache-2.0",
|
|
@@ -40,6 +34,7 @@
|
|
|
40
34
|
"bugs": {
|
|
41
35
|
"url": "https://github.com/Rhaastrake/Nibula/issues"
|
|
42
36
|
},
|
|
37
|
+
"funding": "https://paypal.me/rhaastrake",
|
|
43
38
|
"outputDir": "out",
|
|
44
39
|
"bin": {
|
|
45
40
|
"nib": "bin/nibula.js",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"404": {
|
|
3
|
+
"seo": {
|
|
4
|
+
"title": "404 - Not found",
|
|
5
|
+
"keywords": "",
|
|
6
|
+
"noindex": true,
|
|
7
|
+
"canonical": ""
|
|
8
|
+
},
|
|
9
|
+
"cdn": {
|
|
10
|
+
"css": [],
|
|
11
|
+
"js": []
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"homepage": {
|
|
15
|
+
"seo": {
|
|
16
|
+
"title": "Homepage",
|
|
17
|
+
"description": "Description",
|
|
18
|
+
"keywords": "",
|
|
19
|
+
"noindex": false,
|
|
20
|
+
"canonical": ""
|
|
21
|
+
},
|
|
22
|
+
"cdn": {
|
|
23
|
+
"css": [],
|
|
24
|
+
"js": []
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -8,41 +8,14 @@
|
|
|
8
8
|
"lang": "en",
|
|
9
9
|
"author": "Name and surname",
|
|
10
10
|
"theme": "dark",
|
|
11
|
+
"logo": "/assets/brand/logo.svg",
|
|
11
12
|
"legal": {
|
|
12
13
|
"privacy": "",
|
|
13
14
|
"cookie": "",
|
|
14
|
-
"cookieControls": "",
|
|
15
15
|
"terms": "",
|
|
16
16
|
"copyright": {
|
|
17
17
|
"year": "2026",
|
|
18
18
|
"text": "All rights reserved."
|
|
19
19
|
}
|
|
20
|
-
},
|
|
21
|
-
"pages": {
|
|
22
|
-
"404": {
|
|
23
|
-
"seo": {
|
|
24
|
-
"title": "404 - Not found",
|
|
25
|
-
"keywords": "",
|
|
26
|
-
"noindex": true,
|
|
27
|
-
"canonical": ""
|
|
28
|
-
},
|
|
29
|
-
"cdn": {
|
|
30
|
-
"css": [],
|
|
31
|
-
"js": []
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
"homepage": {
|
|
35
|
-
"seo": {
|
|
36
|
-
"title": "Homepage",
|
|
37
|
-
"description": "Description",
|
|
38
|
-
"keywords": "",
|
|
39
|
-
"noindex": false,
|
|
40
|
-
"canonical": ""
|
|
41
|
-
},
|
|
42
|
-
"cdn": {
|
|
43
|
-
"css": [],
|
|
44
|
-
"js": []
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
20
|
}
|
|
48
21
|
}
|