@stati/core 1.27.2 → 1.29.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 +6 -1
- package/dist/config/errors.d.ts +10 -0
- package/dist/config/errors.d.ts.map +1 -0
- package/dist/config/errors.js +10 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +15 -2
- package/dist/constants.d.ts +26 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +23 -0
- package/dist/core/build.d.ts.map +1 -1
- package/dist/core/build.js +201 -41
- package/dist/core/collections.d.ts +139 -0
- package/dist/core/collections.d.ts.map +1 -0
- package/dist/core/collections.js +342 -0
- package/dist/core/content.d.ts.map +1 -1
- package/dist/core/content.js +2 -1
- package/dist/core/dev.d.ts.map +1 -1
- package/dist/core/dev.js +11 -62
- package/dist/core/expand.d.ts +86 -0
- package/dist/core/expand.d.ts.map +1 -0
- package/dist/core/expand.js +484 -0
- package/dist/core/isg/builder.d.ts +27 -2
- package/dist/core/isg/builder.d.ts.map +1 -1
- package/dist/core/isg/builder.js +83 -11
- package/dist/core/isg/deps.d.ts +9 -0
- package/dist/core/isg/deps.d.ts.map +1 -1
- package/dist/core/isg/deps.js +30 -1
- package/dist/core/isg/hash.d.ts +14 -2
- package/dist/core/isg/hash.d.ts.map +1 -1
- package/dist/core/isg/hash.js +30 -2
- package/dist/core/isg/index.d.ts +4 -2
- package/dist/core/isg/index.d.ts.map +1 -1
- package/dist/core/isg/index.js +4 -2
- package/dist/core/isg/manifest.js +14 -0
- package/dist/core/isg/prune.d.ts +32 -0
- package/dist/core/isg/prune.d.ts.map +1 -0
- package/dist/core/isg/prune.js +113 -0
- package/dist/core/markdown.d.ts +10 -1
- package/dist/core/markdown.d.ts.map +1 -1
- package/dist/core/markdown.js +13 -3
- package/dist/core/navigation.d.ts +4 -2
- package/dist/core/navigation.d.ts.map +1 -1
- package/dist/core/navigation.js +17 -2
- package/dist/core/templates.d.ts +15 -1
- package/dist/core/templates.d.ts.map +1 -1
- package/dist/core/templates.js +121 -126
- package/dist/core/utils/fs.utils.d.ts +29 -1
- package/dist/core/utils/fs.utils.d.ts.map +1 -1
- package/dist/core/utils/fs.utils.js +74 -1
- package/dist/core/utils/index.d.ts +3 -3
- package/dist/core/utils/index.d.ts.map +1 -1
- package/dist/core/utils/index.js +3 -3
- package/dist/core/utils/paths.utils.d.ts +15 -0
- package/dist/core/utils/paths.utils.d.ts.map +1 -1
- package/dist/core/utils/paths.utils.js +20 -0
- package/dist/core/utils/template-discovery.utils.d.ts +25 -1
- package/dist/core/utils/template-discovery.utils.d.ts.map +1 -1
- package/dist/core/utils/template-discovery.utils.js +61 -11
- package/dist/images/config.d.ts +52 -0
- package/dist/images/config.d.ts.map +1 -0
- package/dist/images/config.js +200 -0
- package/dist/images/index.d.ts +15 -0
- package/dist/images/index.d.ts.map +1 -0
- package/dist/images/index.js +11 -0
- package/dist/images/manifest.d.ts +36 -0
- package/dist/images/manifest.d.ts.map +1 -0
- package/dist/images/manifest.js +69 -0
- package/dist/images/markdown-rule.d.ts +26 -0
- package/dist/images/markdown-rule.d.ts.map +1 -0
- package/dist/images/markdown-rule.js +60 -0
- package/dist/images/markup.d.ts +39 -0
- package/dist/images/markup.d.ts.map +1 -0
- package/dist/images/markup.js +72 -0
- package/dist/images/og.d.ts +52 -0
- package/dist/images/og.d.ts.map +1 -0
- package/dist/images/og.js +165 -0
- package/dist/images/plan.d.ts +101 -0
- package/dist/images/plan.d.ts.map +1 -0
- package/dist/images/plan.js +128 -0
- package/dist/images/provider.d.ts +35 -0
- package/dist/images/provider.d.ts.map +1 -0
- package/dist/images/provider.js +70 -0
- package/dist/images/resolve.d.ts +45 -0
- package/dist/images/resolve.d.ts.map +1 -0
- package/dist/images/resolve.js +51 -0
- package/dist/images/runtime.d.ts +258 -0
- package/dist/images/runtime.d.ts.map +1 -0
- package/dist/images/runtime.js +659 -0
- package/dist/images/sources.d.ts +47 -0
- package/dist/images/sources.d.ts.map +1 -0
- package/dist/images/sources.js +97 -0
- package/dist/images/utils/image-probe.utils.d.ts +30 -0
- package/dist/images/utils/image-probe.utils.d.ts.map +1 -0
- package/dist/images/utils/image-probe.utils.js +307 -0
- package/dist/images/utils/index.d.ts +7 -0
- package/dist/images/utils/index.d.ts.map +1 -0
- package/dist/images/utils/index.js +5 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/metrics/noop.d.ts.map +1 -1
- package/dist/metrics/noop.js +3 -0
- package/dist/metrics/recorder.d.ts.map +1 -1
- package/dist/metrics/recorder.js +3 -0
- package/dist/metrics/types.d.ts +8 -0
- package/dist/metrics/types.d.ts.map +1 -1
- package/dist/rss/generator.d.ts.map +1 -1
- package/dist/rss/generator.js +5 -2
- package/dist/search/generator.d.ts +9 -0
- package/dist/search/generator.d.ts.map +1 -1
- package/dist/search/generator.js +20 -0
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.d.ts.map +1 -1
- package/dist/search/index.js +1 -1
- package/dist/seo/auto-inject.d.ts +3 -0
- package/dist/seo/auto-inject.d.ts.map +1 -1
- package/dist/seo/auto-inject.js +1 -0
- package/dist/seo/generator.d.ts +24 -0
- package/dist/seo/generator.d.ts.map +1 -1
- package/dist/seo/generator.js +65 -1
- package/dist/seo/index.d.ts +1 -1
- package/dist/seo/index.d.ts.map +1 -1
- package/dist/seo/index.js +1 -1
- package/dist/types/config.d.ts +60 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/content.d.ts +150 -3
- package/dist/types/content.d.ts.map +1 -1
- package/dist/types/images.d.ts +185 -0
- package/dist/types/images.d.ts.map +1 -0
- package/dist/types/images.js +8 -0
- package/dist/types/index.d.ts +3 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/isg.d.ts +19 -0
- package/dist/types/isg.d.ts.map +1 -1
- package/dist/types/seo.d.ts +9 -0
- package/dist/types/seo.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -183,3 +183,23 @@ export function isPathWithinDirectory(baseDir, targetPath) {
|
|
|
183
183
|
// We also allow exact match (resolvedTarget === resolvedBase) for the root
|
|
184
184
|
return resolvedTarget === resolvedBase || resolvedTarget.startsWith(normalizedBase);
|
|
185
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* The file a page URL is written to, relative to the output directory. The build joins it to
|
|
188
|
+
* the output directory, which also normalizes separators and `.` segments.
|
|
189
|
+
*
|
|
190
|
+
* @param url - The page URL
|
|
191
|
+
* @returns The output file
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```typescript
|
|
195
|
+
* getPageOutputFile('/'); // 'index.html'
|
|
196
|
+
* getPageOutputFile('/blog/'); // '/blog/index.html'
|
|
197
|
+
* getPageOutputFile('/blog/post'); // '/blog/post.html'
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
export function getPageOutputFile(url) {
|
|
201
|
+
if (url === '/') {
|
|
202
|
+
return 'index.html';
|
|
203
|
+
}
|
|
204
|
+
return url.endsWith('/') ? `${url}index.html` : `${url}.html`;
|
|
205
|
+
}
|
|
@@ -1,13 +1,37 @@
|
|
|
1
1
|
import type { StatiConfig, PageModel } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Shared template discovery utilities.
|
|
4
|
+
* Extracted from templates.ts and isg/deps.ts to eliminate duplication.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Whether a page was created by Stati rather than loaded from a Markdown file: a page expanded
|
|
8
|
+
* from a `[param].md` template, or page 2 or later of a paginated listing.
|
|
9
|
+
*
|
|
10
|
+
* Virtual pages are never collection members, never make a folder a collection, and are left
|
|
11
|
+
* out of navigation, search and RSS unless they opt in.
|
|
12
|
+
*
|
|
13
|
+
* @param page - The page to check
|
|
14
|
+
* @returns True for generated pages and for pagination pages after the first
|
|
15
|
+
*/
|
|
16
|
+
export declare function isVirtualPage(page: PageModel): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Finds the first page of a paginated listing, which later pages render like.
|
|
19
|
+
*
|
|
20
|
+
* @param page - A page with `pagination`
|
|
21
|
+
* @param allPages - All pages in the site
|
|
22
|
+
* @returns The listing's first page, or undefined when it is not in the list
|
|
23
|
+
*/
|
|
24
|
+
export declare function getFirstPaginationPage(page: PageModel, allPages: PageModel[]): PageModel | undefined;
|
|
2
25
|
/**
|
|
3
26
|
* Determines if the given page is a collection index page.
|
|
4
27
|
* A collection index page is one whose URL matches a directory path that contains other pages.
|
|
28
|
+
* Generated pages never are; pagination pages after the first are when their first page is.
|
|
5
29
|
*
|
|
6
30
|
* Rendering and ISG dependency tracking must both call this with the same page list,
|
|
7
31
|
* otherwise they can pick different layouts for the same page.
|
|
8
32
|
*
|
|
9
33
|
* Page URLs are read once per `allPages` array, at the first call; see
|
|
10
|
-
* `
|
|
34
|
+
* `pageListCache` for when changes to the pages are picked up.
|
|
11
35
|
*
|
|
12
36
|
* @param page - The page to check
|
|
13
37
|
* @param allPages - All pages in the site (treated as unchanging after the first call)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template-discovery.utils.d.ts","sourceRoot":"","sources":["../../../src/core/utils/template-discovery.utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"template-discovery.utils.d.ts","sourceRoot":"","sources":["../../../src/core/utils/template-discovery.utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAInE;;;GAGG;AAEH;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAEtD;AAiED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,SAAS,EACf,QAAQ,EAAE,SAAS,EAAE,GACpB,SAAS,GAAG,SAAS,CAMvB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,OAAO,CAoBrF;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,WAAW,EACnB,cAAc,CAAC,EAAE,MAAM,EACvB,WAAW,CAAC,EAAE,OAAO,GACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAsDxB;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAkBhE"}
|
|
@@ -2,6 +2,23 @@ import { join, dirname, posix } from 'node:path';
|
|
|
2
2
|
import { pathExists } from './fs.utils.js';
|
|
3
3
|
import { LAYOUT_TEMPLATE } from '../../constants.js';
|
|
4
4
|
import { resolveSrcDir } from './paths.utils.js';
|
|
5
|
+
/**
|
|
6
|
+
* Shared template discovery utilities.
|
|
7
|
+
* Extracted from templates.ts and isg/deps.ts to eliminate duplication.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Whether a page was created by Stati rather than loaded from a Markdown file: a page expanded
|
|
11
|
+
* from a `[param].md` template, or page 2 or later of a paginated listing.
|
|
12
|
+
*
|
|
13
|
+
* Virtual pages are never collection members, never make a folder a collection, and are left
|
|
14
|
+
* out of navigation, search and RSS unless they opt in.
|
|
15
|
+
*
|
|
16
|
+
* @param page - The page to check
|
|
17
|
+
* @returns True for generated pages and for pagination pages after the first
|
|
18
|
+
*/
|
|
19
|
+
export function isVirtualPage(page) {
|
|
20
|
+
return page.generated !== undefined || (page.pagination?.pageNumber ?? 1) > 1;
|
|
21
|
+
}
|
|
5
22
|
/**
|
|
6
23
|
* Directory prefix index per page list, so a build computes it once instead of
|
|
7
24
|
* scanning every page for every page.
|
|
@@ -13,51 +30,84 @@ import { resolveSrcDir } from './paths.utils.js';
|
|
|
13
30
|
* loads a new array and gets a new snapshot. Taking one snapshot keeps rendering and
|
|
14
31
|
* ISG dependency tracking in agreement for the whole build.
|
|
15
32
|
*/
|
|
16
|
-
const
|
|
17
|
-
function
|
|
18
|
-
const cached =
|
|
33
|
+
const pageListCache = new WeakMap();
|
|
34
|
+
function getPageListIndex(allPages) {
|
|
35
|
+
const cached = pageListCache.get(allPages);
|
|
19
36
|
if (cached) {
|
|
20
37
|
return cached;
|
|
21
38
|
}
|
|
22
|
-
const
|
|
23
|
-
|
|
39
|
+
const prefixes = new Map();
|
|
40
|
+
const byUrl = new Map();
|
|
41
|
+
for (const page of allPages) {
|
|
42
|
+
if (!byUrl.has(page.url)) {
|
|
43
|
+
byUrl.set(page.url, page);
|
|
44
|
+
}
|
|
45
|
+
// Generated and pagination pages don't turn the folders they live in into collections
|
|
46
|
+
if (isVirtualPage(page)) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const { url } = page;
|
|
24
50
|
// Every prefix ending at a '/' is a directory this URL lives in (or is, for trailing-slash URLs)
|
|
25
51
|
for (let i = url.indexOf('/'); i !== -1; i = url.indexOf('/', i + 1)) {
|
|
26
52
|
const prefix = url.slice(0, i + 1);
|
|
27
|
-
const existing =
|
|
53
|
+
const existing = prefixes.get(prefix);
|
|
28
54
|
if (existing === undefined) {
|
|
29
|
-
|
|
55
|
+
prefixes.set(prefix, url);
|
|
30
56
|
}
|
|
31
57
|
else if (existing !== null && existing !== url) {
|
|
32
|
-
|
|
58
|
+
prefixes.set(prefix, null);
|
|
33
59
|
}
|
|
34
60
|
}
|
|
35
61
|
}
|
|
36
|
-
|
|
62
|
+
const index = { prefixes, byUrl };
|
|
63
|
+
pageListCache.set(allPages, index);
|
|
37
64
|
return index;
|
|
38
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Finds the first page of a paginated listing, which later pages render like.
|
|
68
|
+
*
|
|
69
|
+
* @param page - A page with `pagination`
|
|
70
|
+
* @param allPages - All pages in the site
|
|
71
|
+
* @returns The listing's first page, or undefined when it is not in the list
|
|
72
|
+
*/
|
|
73
|
+
export function getFirstPaginationPage(page, allPages) {
|
|
74
|
+
const firstUrl = page.pagination?.urls[0];
|
|
75
|
+
if (firstUrl === undefined || firstUrl === page.url) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
return getPageListIndex(allPages).byUrl.get(firstUrl);
|
|
79
|
+
}
|
|
39
80
|
/**
|
|
40
81
|
* Determines if the given page is a collection index page.
|
|
41
82
|
* A collection index page is one whose URL matches a directory path that contains other pages.
|
|
83
|
+
* Generated pages never are; pagination pages after the first are when their first page is.
|
|
42
84
|
*
|
|
43
85
|
* Rendering and ISG dependency tracking must both call this with the same page list,
|
|
44
86
|
* otherwise they can pick different layouts for the same page.
|
|
45
87
|
*
|
|
46
88
|
* Page URLs are read once per `allPages` array, at the first call; see
|
|
47
|
-
* `
|
|
89
|
+
* `pageListCache` for when changes to the pages are picked up.
|
|
48
90
|
*
|
|
49
91
|
* @param page - The page to check
|
|
50
92
|
* @param allPages - All pages in the site (treated as unchanging after the first call)
|
|
51
93
|
* @returns True if the page is a collection index page
|
|
52
94
|
*/
|
|
53
95
|
export function isCollectionIndexPage(page, allPages) {
|
|
96
|
+
if (page.generated) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
// Later pages of a listing render with the layout of its first page
|
|
100
|
+
if ((page.pagination?.pageNumber ?? 1) > 1) {
|
|
101
|
+
const first = getFirstPaginationPage(page, allPages);
|
|
102
|
+
return first !== undefined && !isVirtualPage(first) && isCollectionIndexPage(first, allPages);
|
|
103
|
+
}
|
|
54
104
|
// Root index page is always a collection index
|
|
55
105
|
if (page.url === '/') {
|
|
56
106
|
return true;
|
|
57
107
|
}
|
|
58
108
|
// The page is an index when a different page's URL lives under this page's URL as a directory
|
|
59
109
|
const pageUrlAsDir = page.url.endsWith('/') ? page.url : page.url + '/';
|
|
60
|
-
const containingUrl =
|
|
110
|
+
const containingUrl = getPageListIndex(allPages).prefixes.get(pageUrlAsDir);
|
|
61
111
|
return containingUrl === null || (containingUrl !== undefined && containingUrl !== page.url);
|
|
62
112
|
}
|
|
63
113
|
/**
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation and defaults for the `images` configuration block.
|
|
3
|
+
* @module images/config
|
|
4
|
+
*/
|
|
5
|
+
import type { ImageVariantFormat, OgImageFontConfig, StatiConfig } from '../types/index.js';
|
|
6
|
+
/** Responsive image options with defaults applied. */
|
|
7
|
+
export interface ResolvedResponsiveConfig {
|
|
8
|
+
/** Candidate widths, ascending and unique */
|
|
9
|
+
widths: number[];
|
|
10
|
+
formats: ImageVariantFormat[];
|
|
11
|
+
quality: {
|
|
12
|
+
avif: number;
|
|
13
|
+
webp: number;
|
|
14
|
+
jpeg: number;
|
|
15
|
+
png?: number;
|
|
16
|
+
};
|
|
17
|
+
sizes: string;
|
|
18
|
+
}
|
|
19
|
+
/** Open Graph image options with defaults applied. */
|
|
20
|
+
export interface ResolvedOgConfig {
|
|
21
|
+
template: string;
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
format: 'png' | 'jpeg';
|
|
25
|
+
fonts: OgImageFontConfig[];
|
|
26
|
+
exclude: string[];
|
|
27
|
+
}
|
|
28
|
+
/** The `images` block with defaults applied. */
|
|
29
|
+
export interface ResolvedImagesConfig {
|
|
30
|
+
eagerFirst: boolean;
|
|
31
|
+
/** `null` when responsive images are off */
|
|
32
|
+
responsive: ResolvedResponsiveConfig | null;
|
|
33
|
+
/** `null` when Open Graph images are off */
|
|
34
|
+
og: ResolvedOgConfig | null;
|
|
35
|
+
dev: 'fast' | 'full';
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Validates the `images` configuration block.
|
|
39
|
+
*
|
|
40
|
+
* @param images - The `images` value from the config
|
|
41
|
+
* @throws {ConfigValidationError} When an option has the wrong type or value
|
|
42
|
+
*/
|
|
43
|
+
export declare function validateImagesConfig(images: unknown): void;
|
|
44
|
+
/**
|
|
45
|
+
* Applies defaults to the `images` configuration block.
|
|
46
|
+
* Expects a block that passed {@link validateImagesConfig}.
|
|
47
|
+
*
|
|
48
|
+
* @param images - The `images` value from the config
|
|
49
|
+
* @returns The resolved options, or `null` when images are turned off with `images: false`
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolveImagesConfig(images: StatiConfig['images']): ResolvedImagesConfig | null;
|
|
52
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/images/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAY5F,sDAAsD;AACtD,MAAM,WAAW,wBAAwB;IACvC,6CAA6C;IAC7C,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpE,KAAK,EAAE,MAAM,CAAC;CACf;AAED,sDAAsD;AACtD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;IACvB,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,gDAAgD;AAChD,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,OAAO,CAAC;IACpB,4CAA4C;IAC5C,UAAU,EAAE,wBAAwB,GAAG,IAAI,CAAC;IAC5C,4CAA4C;IAC5C,EAAE,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC5B,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACtB;AA6JD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAqB1D;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,GAAG,oBAAoB,GAAG,IAAI,CA0C9F"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation and defaults for the `images` configuration block.
|
|
3
|
+
* @module images/config
|
|
4
|
+
*/
|
|
5
|
+
import { ConfigValidationError } from '../config/errors.js';
|
|
6
|
+
import { DEFAULT_IMAGE_FORMATS, DEFAULT_IMAGE_QUALITY, DEFAULT_IMAGE_SIZES, DEFAULT_IMAGE_WIDTHS, DEFAULT_OG_HEIGHT, DEFAULT_OG_TEMPLATE, DEFAULT_OG_WIDTH, } from '../constants.js';
|
|
7
|
+
const IMAGES_KEYS = ['eagerFirst', 'responsive', 'og', 'dev'];
|
|
8
|
+
const RESPONSIVE_KEYS = ['widths', 'formats', 'quality', 'sizes'];
|
|
9
|
+
const QUALITY_KEYS = ['avif', 'webp', 'jpeg', 'png'];
|
|
10
|
+
const OG_KEYS = ['template', 'width', 'height', 'format', 'fonts', 'exclude'];
|
|
11
|
+
const FONT_KEYS = ['name', 'path', 'weight', 'style'];
|
|
12
|
+
const VARIANT_FORMATS = ['avif', 'webp'];
|
|
13
|
+
const MAX_IMAGE_WIDTH = 16384;
|
|
14
|
+
const MAX_OG_SIZE = 4096;
|
|
15
|
+
function fail(message) {
|
|
16
|
+
throw new ConfigValidationError(message);
|
|
17
|
+
}
|
|
18
|
+
function describe(value) {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.stringify(value) ?? String(value);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return String(value);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function isPlainObject(value) {
|
|
27
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
28
|
+
}
|
|
29
|
+
function checkKeys(value, allowed, path) {
|
|
30
|
+
for (const key of Object.keys(value)) {
|
|
31
|
+
if (!allowed.includes(key)) {
|
|
32
|
+
fail(`Unknown option ${path}.${key}. Allowed options: ${allowed.join(', ')}.`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function isIntegerInRange(value, min, max) {
|
|
37
|
+
return typeof value === 'number' && Number.isInteger(value) && value >= min && value <= max;
|
|
38
|
+
}
|
|
39
|
+
function validateResponsive(value) {
|
|
40
|
+
if (typeof value === 'boolean')
|
|
41
|
+
return;
|
|
42
|
+
if (!isPlainObject(value)) {
|
|
43
|
+
fail(`images.responsive must be true, false or an object (got ${describe(value)}).`);
|
|
44
|
+
}
|
|
45
|
+
checkKeys(value, RESPONSIVE_KEYS, 'images.responsive');
|
|
46
|
+
if (value.widths !== undefined) {
|
|
47
|
+
if (!Array.isArray(value.widths) ||
|
|
48
|
+
value.widths.length === 0 ||
|
|
49
|
+
!value.widths.every((w) => isIntegerInRange(w, 1, MAX_IMAGE_WIDTH))) {
|
|
50
|
+
fail(`images.responsive.widths must be a non-empty array of whole pixel widths between 1 and ${MAX_IMAGE_WIDTH} (got ${describe(value.widths)}).`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (value.formats !== undefined) {
|
|
54
|
+
if (!Array.isArray(value.formats) ||
|
|
55
|
+
!value.formats.every((f) => typeof f === 'string' && VARIANT_FORMATS.includes(f))) {
|
|
56
|
+
fail(`images.responsive.formats must be an array of 'avif' and 'webp' (got ${describe(value.formats)}).`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (value.quality !== undefined) {
|
|
60
|
+
if (!isPlainObject(value.quality)) {
|
|
61
|
+
fail(`images.responsive.quality must be an object (got ${describe(value.quality)}).`);
|
|
62
|
+
}
|
|
63
|
+
checkKeys(value.quality, QUALITY_KEYS, 'images.responsive.quality');
|
|
64
|
+
for (const [format, quality] of Object.entries(value.quality)) {
|
|
65
|
+
if (quality !== undefined && !isIntegerInRange(quality, 1, 100)) {
|
|
66
|
+
fail(`images.responsive.quality.${format} must be a whole number between 1 and 100 (got ${describe(quality)}).`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (value.sizes !== undefined && (typeof value.sizes !== 'string' || !value.sizes.trim())) {
|
|
71
|
+
fail(`images.responsive.sizes must be a non-empty string (got ${describe(value.sizes)}).`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function validateFont(font, index) {
|
|
75
|
+
const path = `images.og.fonts[${index}]`;
|
|
76
|
+
if (!isPlainObject(font)) {
|
|
77
|
+
fail(`${path} must be an object with name and path (got ${describe(font)}).`);
|
|
78
|
+
}
|
|
79
|
+
checkKeys(font, FONT_KEYS, path);
|
|
80
|
+
if (typeof font.name !== 'string' || !font.name.trim()) {
|
|
81
|
+
fail(`${path}.name must be a non-empty string.`);
|
|
82
|
+
}
|
|
83
|
+
if (typeof font.path !== 'string' || !font.path.trim()) {
|
|
84
|
+
fail(`${path}.path must be a non-empty string.`);
|
|
85
|
+
}
|
|
86
|
+
if (font.weight !== undefined && !isIntegerInRange(font.weight, 100, 900)) {
|
|
87
|
+
fail(`${path}.weight must be a whole number between 100 and 900 (got ${describe(font.weight)}).`);
|
|
88
|
+
}
|
|
89
|
+
if (font.style !== undefined && font.style !== 'normal' && font.style !== 'italic') {
|
|
90
|
+
fail(`${path}.style must be 'normal' or 'italic' (got ${describe(font.style)}).`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function validateOg(value) {
|
|
94
|
+
if (typeof value === 'boolean')
|
|
95
|
+
return;
|
|
96
|
+
if (!isPlainObject(value)) {
|
|
97
|
+
fail(`images.og must be true, false or an object (got ${describe(value)}).`);
|
|
98
|
+
}
|
|
99
|
+
checkKeys(value, OG_KEYS, 'images.og');
|
|
100
|
+
if (value.template !== undefined) {
|
|
101
|
+
if (typeof value.template !== 'string' ||
|
|
102
|
+
!value.template.endsWith('.eta') ||
|
|
103
|
+
/[/\\]/.test(value.template)) {
|
|
104
|
+
fail(`images.og.template must be a file name ending in .eta, such as 'og.eta' (got ${describe(value.template)}).`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
for (const dimension of ['width', 'height']) {
|
|
108
|
+
if (value[dimension] !== undefined && !isIntegerInRange(value[dimension], 1, MAX_OG_SIZE)) {
|
|
109
|
+
fail(`images.og.${dimension} must be a whole number of pixels between 1 and ${MAX_OG_SIZE} (got ${describe(value[dimension])}).`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (value.format !== undefined && value.format !== 'png' && value.format !== 'jpeg') {
|
|
113
|
+
fail(`images.og.format must be 'png' or 'jpeg' (got ${describe(value.format)}).`);
|
|
114
|
+
}
|
|
115
|
+
if (value.fonts !== undefined) {
|
|
116
|
+
if (!Array.isArray(value.fonts)) {
|
|
117
|
+
fail(`images.og.fonts must be an array (got ${describe(value.fonts)}).`);
|
|
118
|
+
}
|
|
119
|
+
value.fonts.forEach(validateFont);
|
|
120
|
+
}
|
|
121
|
+
if (value.exclude !== undefined) {
|
|
122
|
+
if (!Array.isArray(value.exclude) || !value.exclude.every((p) => typeof p === 'string')) {
|
|
123
|
+
fail(`images.og.exclude must be an array of URL glob patterns (got ${describe(value.exclude)}).`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Validates the `images` configuration block.
|
|
129
|
+
*
|
|
130
|
+
* @param images - The `images` value from the config
|
|
131
|
+
* @throws {ConfigValidationError} When an option has the wrong type or value
|
|
132
|
+
*/
|
|
133
|
+
export function validateImagesConfig(images) {
|
|
134
|
+
if (images === undefined || images === false) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (!isPlainObject(images)) {
|
|
138
|
+
fail(`images must be an object or false (got ${describe(images)}).`);
|
|
139
|
+
}
|
|
140
|
+
checkKeys(images, IMAGES_KEYS, 'images');
|
|
141
|
+
if (images.eagerFirst !== undefined && typeof images.eagerFirst !== 'boolean') {
|
|
142
|
+
fail(`images.eagerFirst must be true or false (got ${describe(images.eagerFirst)}).`);
|
|
143
|
+
}
|
|
144
|
+
if (images.dev !== undefined && images.dev !== 'fast' && images.dev !== 'full') {
|
|
145
|
+
fail(`images.dev must be 'fast' or 'full' (got ${describe(images.dev)}).`);
|
|
146
|
+
}
|
|
147
|
+
if (images.responsive !== undefined) {
|
|
148
|
+
validateResponsive(images.responsive);
|
|
149
|
+
}
|
|
150
|
+
if (images.og !== undefined) {
|
|
151
|
+
validateOg(images.og);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Applies defaults to the `images` configuration block.
|
|
156
|
+
* Expects a block that passed {@link validateImagesConfig}.
|
|
157
|
+
*
|
|
158
|
+
* @param images - The `images` value from the config
|
|
159
|
+
* @returns The resolved options, or `null` when images are turned off with `images: false`
|
|
160
|
+
*/
|
|
161
|
+
export function resolveImagesConfig(images) {
|
|
162
|
+
if (images === false) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
const config = images ?? {};
|
|
166
|
+
let responsive = null;
|
|
167
|
+
if (config.responsive) {
|
|
168
|
+
const options = config.responsive === true ? {} : config.responsive;
|
|
169
|
+
const quality = options.quality ?? {};
|
|
170
|
+
responsive = {
|
|
171
|
+
widths: [...new Set(options.widths ?? DEFAULT_IMAGE_WIDTHS)].sort((a, b) => a - b),
|
|
172
|
+
formats: [...new Set(options.formats ?? DEFAULT_IMAGE_FORMATS)],
|
|
173
|
+
quality: {
|
|
174
|
+
avif: quality.avif ?? DEFAULT_IMAGE_QUALITY.avif,
|
|
175
|
+
webp: quality.webp ?? DEFAULT_IMAGE_QUALITY.webp,
|
|
176
|
+
jpeg: quality.jpeg ?? DEFAULT_IMAGE_QUALITY.jpeg,
|
|
177
|
+
...(quality.png !== undefined && { png: quality.png }),
|
|
178
|
+
},
|
|
179
|
+
sizes: options.sizes ?? DEFAULT_IMAGE_SIZES,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
let og = null;
|
|
183
|
+
if (config.og) {
|
|
184
|
+
const options = config.og === true ? {} : config.og;
|
|
185
|
+
og = {
|
|
186
|
+
template: options.template ?? DEFAULT_OG_TEMPLATE,
|
|
187
|
+
width: options.width ?? DEFAULT_OG_WIDTH,
|
|
188
|
+
height: options.height ?? DEFAULT_OG_HEIGHT,
|
|
189
|
+
format: options.format ?? 'png',
|
|
190
|
+
fonts: options.fonts ?? [],
|
|
191
|
+
exclude: options.exclude ?? [],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
eagerFirst: config.eagerFirst ?? true,
|
|
196
|
+
responsive,
|
|
197
|
+
og,
|
|
198
|
+
dev: config.dev ?? 'fast',
|
|
199
|
+
};
|
|
200
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Image pipeline exports
|
|
3
|
+
* Intrinsic sizes, lazy loading and co-located images in core, plus the plumbing for
|
|
4
|
+
* responsive variants and Open Graph images rendered by `@stati/images`.
|
|
5
|
+
*/
|
|
6
|
+
export { resolveImagesConfig, validateImagesConfig } from './config.js';
|
|
7
|
+
export type { ResolvedImagesConfig, ResolvedResponsiveConfig, ResolvedOgConfig } from './config.js';
|
|
8
|
+
export { ImageRuntime, PageImages, validateOgBaseUrl } from './runtime.js';
|
|
9
|
+
export type { StatiImageOptions, SettleResult, ImageRuntimeOptions } from './runtime.js';
|
|
10
|
+
export { installImageRule, selectEagerImage } from './markdown-rule.js';
|
|
11
|
+
export { findOgTemplatePath, clearOgTemplateCache } from './og.js';
|
|
12
|
+
export { loadImageProvider, resolveImagesPackage, missingPackageMessage } from './provider.js';
|
|
13
|
+
export { probeImage } from './utils/index.js';
|
|
14
|
+
export type { ImageProbeResult } from './utils/index.js';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/images/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxE,YAAY,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpG,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAC3E,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACzF,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC/F,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Image pipeline exports
|
|
3
|
+
* Intrinsic sizes, lazy loading and co-located images in core, plus the plumbing for
|
|
4
|
+
* responsive variants and Open Graph images rendered by `@stati/images`.
|
|
5
|
+
*/
|
|
6
|
+
export { resolveImagesConfig, validateImagesConfig } from './config.js';
|
|
7
|
+
export { ImageRuntime, PageImages, validateOgBaseUrl } from './runtime.js';
|
|
8
|
+
export { installImageRule, selectEagerImage } from './markdown-rule.js';
|
|
9
|
+
export { findOgTemplatePath, clearOgTemplateCache } from './og.js';
|
|
10
|
+
export { loadImageProvider, resolveImagesPackage, missingPackageMessage } from './provider.js';
|
|
11
|
+
export { probeImage } from './utils/index.js';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The images manifest, `.stati/images/manifest.json`: which files each image job produced,
|
|
3
|
+
* and cached hashes and probes of source images.
|
|
4
|
+
* @module images/manifest
|
|
5
|
+
*/
|
|
6
|
+
import type { SourceInfo } from './sources.js';
|
|
7
|
+
/**
|
|
8
|
+
* Persisted state of the image pipeline.
|
|
9
|
+
*/
|
|
10
|
+
export interface ImagesManifest {
|
|
11
|
+
version: number;
|
|
12
|
+
/** Cached facts about source images, keyed by absolute path */
|
|
13
|
+
sources: Record<string, SourceInfo>;
|
|
14
|
+
/** Files each job wrote, relative to the output directory, keyed by job key */
|
|
15
|
+
outputs: Record<string, {
|
|
16
|
+
files: string[];
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Creates an empty images manifest.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createEmptyImagesManifest(): ImagesManifest;
|
|
23
|
+
/**
|
|
24
|
+
* Path of the images manifest inside the cache directory.
|
|
25
|
+
*/
|
|
26
|
+
export declare function getImagesManifestPath(cacheDir: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Loads the images manifest. A missing, unreadable or outdated manifest yields an empty one,
|
|
29
|
+
* which only means images are checked again.
|
|
30
|
+
*/
|
|
31
|
+
export declare function loadImagesManifest(cacheDir: string): Promise<ImagesManifest>;
|
|
32
|
+
/**
|
|
33
|
+
* Saves the images manifest.
|
|
34
|
+
*/
|
|
35
|
+
export declare function saveImagesManifest(cacheDir: string, manifest: ImagesManifest): Promise<void>;
|
|
36
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/images/manifest.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAK/C;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACpC,+EAA+E;IAC/E,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CAC9C;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,cAAc,CAE1D;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE9D;AAMD;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAqClF;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAIf"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The images manifest, `.stati/images/manifest.json`: which files each image job produced,
|
|
3
|
+
* and cached hashes and probes of source images.
|
|
4
|
+
* @module images/manifest
|
|
5
|
+
*/
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { ensureDir, readFile, writeFile } from '../core/utils/index.js';
|
|
8
|
+
import { IMAGES_CACHE_SUBDIR, MANIFEST_FILENAME } from '../constants.js';
|
|
9
|
+
/** Bumped when the manifest layout changes; older manifests are discarded. */
|
|
10
|
+
const IMAGES_MANIFEST_VERSION = 1;
|
|
11
|
+
/**
|
|
12
|
+
* Creates an empty images manifest.
|
|
13
|
+
*/
|
|
14
|
+
export function createEmptyImagesManifest() {
|
|
15
|
+
return { version: IMAGES_MANIFEST_VERSION, sources: {}, outputs: {} };
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Path of the images manifest inside the cache directory.
|
|
19
|
+
*/
|
|
20
|
+
export function getImagesManifestPath(cacheDir) {
|
|
21
|
+
return join(cacheDir, IMAGES_CACHE_SUBDIR, MANIFEST_FILENAME);
|
|
22
|
+
}
|
|
23
|
+
function isStringArray(value) {
|
|
24
|
+
return Array.isArray(value) && value.every((item) => typeof item === 'string');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Loads the images manifest. A missing, unreadable or outdated manifest yields an empty one,
|
|
28
|
+
* which only means images are checked again.
|
|
29
|
+
*/
|
|
30
|
+
export async function loadImagesManifest(cacheDir) {
|
|
31
|
+
try {
|
|
32
|
+
const content = await readFile(getImagesManifestPath(cacheDir));
|
|
33
|
+
if (!content)
|
|
34
|
+
return createEmptyImagesManifest();
|
|
35
|
+
const parsed = JSON.parse(content);
|
|
36
|
+
if (parsed?.version !== IMAGES_MANIFEST_VERSION ||
|
|
37
|
+
typeof parsed.sources !== 'object' ||
|
|
38
|
+
typeof parsed.outputs !== 'object') {
|
|
39
|
+
return createEmptyImagesManifest();
|
|
40
|
+
}
|
|
41
|
+
const outputs = {};
|
|
42
|
+
for (const [key, record] of Object.entries(parsed.outputs ?? {})) {
|
|
43
|
+
if (record && isStringArray(record.files)) {
|
|
44
|
+
outputs[key] = { files: record.files };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const sources = {};
|
|
48
|
+
for (const [path, info] of Object.entries(parsed.sources ?? {})) {
|
|
49
|
+
if (info &&
|
|
50
|
+
typeof info.size === 'number' &&
|
|
51
|
+
typeof info.mtimeMs === 'number' &&
|
|
52
|
+
typeof info.hash === 'string') {
|
|
53
|
+
sources[path] = info;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { version: IMAGES_MANIFEST_VERSION, sources, outputs };
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return createEmptyImagesManifest();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Saves the images manifest.
|
|
64
|
+
*/
|
|
65
|
+
export async function saveImagesManifest(cacheDir, manifest) {
|
|
66
|
+
const path = getImagesManifestPath(cacheDir);
|
|
67
|
+
await ensureDir(join(cacheDir, IMAGES_CACHE_SUBDIR));
|
|
68
|
+
await writeFile(path, JSON.stringify(manifest, null, 2), 'utf-8');
|
|
69
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The markdown-it `image` rule that adds dimensions, lazy loading and responsive variants.
|
|
3
|
+
* @module images/markdown-rule
|
|
4
|
+
*/
|
|
5
|
+
import type MarkdownIt from 'markdown-it';
|
|
6
|
+
import type Token from 'markdown-it/lib/token.mjs';
|
|
7
|
+
/** Key of the page's image state in the markdown-it `env`. */
|
|
8
|
+
export declare const IMAGES_ENV_KEY = "statiImages";
|
|
9
|
+
/**
|
|
10
|
+
* Installs the image rule. It holds no per-page state: each render passes its page's image
|
|
11
|
+
* state in `env`, and renders without it fall through to the previous rule. Installed before
|
|
12
|
+
* plugins and `markdown.configure`, so a custom image rule there replaces it.
|
|
13
|
+
*
|
|
14
|
+
* @param md - The markdown-it instance
|
|
15
|
+
*/
|
|
16
|
+
export declare function installImageRule(md: MarkdownIt): void;
|
|
17
|
+
/**
|
|
18
|
+
* Picks the image `eagerFirst` loads eagerly: the document's first image, when it sits in
|
|
19
|
+
* one of the first top-level blocks. A first image further down is not the page's largest
|
|
20
|
+
* contentful paint, so it stays lazy.
|
|
21
|
+
*
|
|
22
|
+
* @param tokens - Parsed block tokens
|
|
23
|
+
* @returns The image token, or undefined
|
|
24
|
+
*/
|
|
25
|
+
export declare function selectEagerImage(tokens: Token[]): Token | undefined;
|
|
26
|
+
//# sourceMappingURL=markdown-rule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown-rule.d.ts","sourceRoot":"","sources":["../../src/images/markdown-rule.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,UAAU,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,KAAK,MAAM,2BAA2B,CAAC;AAGnD,8DAA8D;AAC9D,eAAO,MAAM,cAAc,gBAAgB,CAAC;AAO5C;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,UAAU,GAAG,IAAI,CAwBrD;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,GAAG,SAAS,CAanE"}
|