first
This commit is contained in:
40
node_modules/@11ty/eleventy-img/README.md
generated
vendored
Normal file
40
node_modules/@11ty/eleventy-img/README.md
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
<p align="center"><img src="https://www.11ty.dev/img/logo-github.svg" width="200" height="200" alt="eleventy Logo"></p>
|
||||
|
||||
# eleventy-img
|
||||
|
||||
Requires Node 18+
|
||||
|
||||
Low level utility to perform build-time image transformations for both vector and raster images. Output multiple sizes, save multiple formats, cache remote images locally. Uses the [sharp](https://sharp.pixelplumbing.com/) image processor.
|
||||
|
||||
You maintain full control of your HTML. Use with `<picture>` or `<img>` or CSS `background-image`, or others! Works great to add `width` and `height` to your images!
|
||||
|
||||
## [The full `eleventy-img` documentation is on 11ty.dev](https://www.11ty.dev/docs/plugins/image/).
|
||||
|
||||
* _This is a plugin for the [Eleventy static site generator](https://www.11ty.dev/)._
|
||||
* Find more [Eleventy plugins](https://www.11ty.dev/docs/plugins/).
|
||||
* Please star [Eleventy on GitHub](https://github.com/11ty/eleventy/), follow [@eleven_ty](https://twitter.com/eleven_ty) on Twitter, and support [11ty on Open Collective](https://opencollective.com/11ty)
|
||||
|
||||
[](https://www.npmjs.com/package/@11ty/eleventy-img) [](https://github.com/11ty/eleventy-img/issues)
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
npm install --save-dev @11ty/eleventy-img
|
||||
```
|
||||
|
||||
_[The full `eleventy-img` documentation is on 11ty.dev](https://www.11ty.dev/docs/plugins/image/)._
|
||||
|
||||
## Tests
|
||||
|
||||
```
|
||||
npm run test
|
||||
```
|
||||
|
||||
- We use the [ava JavaScript test runner](https://github.com/avajs/ava) ([Assertions documentation](https://github.com/avajs/ava/blob/master/docs/03-assertions.md))
|
||||
- ℹ️ To keep tests fast, thou shalt try to avoid writing files in tests.
|
||||
|
||||
## Community Roadmap
|
||||
|
||||
- [Top Feature Requests](https://github.com/11ty/eleventy-img/issues?q=label%3Aneeds-votes+sort%3Areactions-%2B1-desc+label%3Aenhancement) (Add your own votes using the 👍 reaction)
|
||||
- [Top Bugs 😱](https://github.com/11ty/eleventy-img/issues?q=is%3Aissue+is%3Aopen+label%3Abug+sort%3Areactions-%2B1-desc) (Add your own votes using the 👍 reaction)
|
||||
- [Newest Bugs 🙀](https://github.com/11ty/eleventy-img/issues?q=is%3Aopen+is%3Aissue+label%3Abug)
|
||||
131
node_modules/@11ty/eleventy-img/eleventy-image.webc
generated
vendored
Normal file
131
node_modules/@11ty/eleventy-img/eleventy-image.webc
generated
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
<!---
|
||||
Supported attribute list:
|
||||
* src (required)
|
||||
* width
|
||||
* formats
|
||||
* url-path
|
||||
* output-dir
|
||||
|
||||
<img
|
||||
webc:is="eleventy-image"
|
||||
src="./src/img/possum-geri.png"
|
||||
alt="The possum is Eleventy’s mascot"
|
||||
width="222, 350"
|
||||
class="some-custom-class"
|
||||
sizes="(min-width: 43.75em) 100px, 15vw">
|
||||
|
||||
Alternative attribute formats:
|
||||
:width="[222, 350]"
|
||||
formats="avif,webp,jpeg"
|
||||
:formats="['avif', 'webp', 'jpeg']"
|
||||
--->
|
||||
<script webc:type="js">
|
||||
const path = require("path");
|
||||
|
||||
// TODO expose this for re-use in a provided shortcode.
|
||||
async function imagePlugin(attributes, globalPluginOptions) {
|
||||
if(!attributes.src) {
|
||||
throw new Error("Missing `src` attribute on <eleventy-image>");
|
||||
}
|
||||
|
||||
// TODO allow remote optimization automatically on full urls
|
||||
|
||||
let imagePackage;
|
||||
let defaultGlobalAttributes;
|
||||
|
||||
if(globalPluginOptions) {
|
||||
defaultGlobalAttributes = globalPluginOptions.defaultAttributes;
|
||||
delete globalPluginOptions.defaultAttributes;
|
||||
|
||||
imagePackage = globalPluginOptions.packages?.image;
|
||||
delete globalPluginOptions.packages;
|
||||
}
|
||||
|
||||
if(!imagePackage) {
|
||||
imagePackage = require("@11ty/eleventy-img");
|
||||
}
|
||||
|
||||
let instanceOptions = {};
|
||||
|
||||
// Note that multiple widths require a `sizes`
|
||||
|
||||
if(attributes.width) {
|
||||
if(typeof attributes.width === "string") {
|
||||
instanceOptions.widths = attributes.width.split(",").map(entry => parseInt(entry, 10));
|
||||
delete attributes.width;
|
||||
} else if(Array.isArray(attributes.width)) {
|
||||
instanceOptions.widths = attributes.width;
|
||||
delete attributes.width;
|
||||
}
|
||||
}
|
||||
|
||||
if(attributes.formats) {
|
||||
if(typeof attributes.formats === "string") {
|
||||
instanceOptions.formats = attributes.formats.split(",").map(entry => entry.trim());
|
||||
delete attributes.formats;
|
||||
} else if(Array.isArray(attributes.formats)) {
|
||||
instanceOptions.formats = attributes.formats;
|
||||
delete attributes.formats;
|
||||
}
|
||||
}
|
||||
|
||||
// These defaults are set only if addPlugin was **not** called:
|
||||
if(!globalPluginOptions) {
|
||||
// Using eleventy.directories global data (Eleventy 2.0.2+)
|
||||
if(eleventy.directories) {
|
||||
instanceOptions.urlPath = "/img/";
|
||||
|
||||
// write to output folder by default
|
||||
instanceOptions.outputDir = path.join(eleventy.directories.output, instanceOptions.urlPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Overrides via attributes (hopefully you don’t need these)
|
||||
if(attributes.urlPath) {
|
||||
instanceOptions.urlPath = attributes.urlPath;
|
||||
delete attributes.urlPath;
|
||||
|
||||
if(eleventy.directories && !attributes.outputDir) {
|
||||
// use output folder if available (Eleventy v2.0.2+)
|
||||
instanceOptions.outputDir = path.join(eleventy.directories.output, instanceOptions.urlPath);
|
||||
}
|
||||
}
|
||||
|
||||
if(attributes.outputDir) {
|
||||
instanceOptions.outputDir = attributes.outputDir;
|
||||
delete attributes.outputDir;
|
||||
}
|
||||
|
||||
let options = Object.assign({}, globalPluginOptions, instanceOptions);
|
||||
|
||||
// see Util.addConfig
|
||||
if(globalPluginOptions.eleventyConfig) {
|
||||
Object.defineProperty(options, "eleventyConfig", {
|
||||
value: globalPluginOptions.eleventyConfig,
|
||||
enumerable: false,
|
||||
});
|
||||
}
|
||||
|
||||
let metadata = await imagePackage(src, options);
|
||||
|
||||
let imageAttributes = Object.assign({}, defaultGlobalAttributes, attributes);
|
||||
|
||||
// You bet we throw an error on missing alt in `imageAttributes` (alt="" works okay)
|
||||
return imagePackage.generateHTML(metadata, imageAttributes);
|
||||
};
|
||||
|
||||
(async () => {
|
||||
let globalPluginOptions;
|
||||
// fetch global options from from addPlugin call
|
||||
if(typeof __private_eleventyImageConfigurationOptions === "function") {
|
||||
globalPluginOptions = __private_eleventyImageConfigurationOptions();
|
||||
}
|
||||
|
||||
if(!("filterPublicAttributes" in webc)) {
|
||||
throw new Error("The <eleventy-image> WebC component requires WebC v0.10.1+");
|
||||
}
|
||||
|
||||
let attributes = webc.filterPublicAttributes(webc.attributes);
|
||||
return imagePlugin(attributes, globalPluginOptions);
|
||||
})();
|
||||
</script>
|
||||
29
node_modules/@11ty/eleventy-img/eslint.config.mjs
generated
vendored
Normal file
29
node_modules/@11ty/eleventy-img/eslint.config.mjs
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
import { defineConfig } from "eslint/config";
|
||||
import pluginJs from "@eslint/js";
|
||||
import pluginStylistic from "@stylistic/eslint-plugin-js";
|
||||
import globals from "globals";
|
||||
|
||||
const GLOB_JS = '**/*.?([cm])js';
|
||||
|
||||
export default defineConfig([
|
||||
{
|
||||
files: [GLOB_JS],
|
||||
plugins: {
|
||||
js: pluginJs,
|
||||
"@stylistic/js": pluginStylistic
|
||||
},
|
||||
extends: [
|
||||
"js/recommended",
|
||||
],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: "module",
|
||||
globals: { ...globals.node },
|
||||
},
|
||||
rules: {
|
||||
"@stylistic/js/indent": ["error", 2],
|
||||
"@stylistic/js/linebreak-style": ["error", "unix"],
|
||||
"@stylistic/js/semi": ["error", "always"],
|
||||
},
|
||||
},
|
||||
]);
|
||||
151
node_modules/@11ty/eleventy-img/img.js
generated
vendored
Normal file
151
node_modules/@11ty/eleventy-img/img.js
generated
vendored
Normal file
@ -0,0 +1,151 @@
|
||||
const {default: PQueue} = require("p-queue");
|
||||
|
||||
const DeferCounter = require("./src/defer-counter.js");
|
||||
const BuildLogger = require("./src/build-logger.js");
|
||||
const Util = require("./src/util.js");
|
||||
const Image = require("./src/image.js");
|
||||
const DirectoryManager = require("./src/directory-manager.js");
|
||||
|
||||
// For exports
|
||||
const getImageSize = require("image-size");
|
||||
const ImagePath = require("./src/image-path.js");
|
||||
|
||||
const debug = require("debug")("Eleventy:Image");
|
||||
|
||||
const GLOBAL_OPTIONS = require("./src/global-options.js").defaults;
|
||||
|
||||
const { memCache, diskCache } = require("./src/caches.js");
|
||||
|
||||
let deferCounter = new DeferCounter();
|
||||
let buildLogger = new BuildLogger();
|
||||
let directoryManager = new DirectoryManager();
|
||||
|
||||
/* Queue */
|
||||
let processingQueue = new PQueue({
|
||||
concurrency: GLOBAL_OPTIONS.concurrency
|
||||
});
|
||||
processingQueue.on("active", () => {
|
||||
debug( `Concurrency: ${processingQueue.concurrency}, Size: ${processingQueue.size}, Pending: ${processingQueue.pending}` );
|
||||
});
|
||||
|
||||
// TODO move this into build-logger.js
|
||||
function setupLogger(eleventyConfig, opts) {
|
||||
if(typeof eleventyConfig?.logger?.logWithOptions !== "function" || Util.isRequested(opts?.generatedVia)) {
|
||||
return;
|
||||
}
|
||||
|
||||
buildLogger.setupOnce(eleventyConfig, () => {
|
||||
// before build
|
||||
deferCounter.resetCount();
|
||||
memCache.resetCount();
|
||||
diskCache.resetCount();
|
||||
}, () => {
|
||||
// after build
|
||||
let [memoryCacheHit] = memCache.getCount();
|
||||
let [diskCacheHit, diskCacheMiss] = diskCache.getCount();
|
||||
// these are unique images, multiple requests to optimize the same image are de-duplicated
|
||||
let deferCount = deferCounter.getCount();
|
||||
|
||||
let cachedCount = memoryCacheHit + diskCacheHit;
|
||||
let optimizedCount = diskCacheMiss + diskCacheHit + memoryCacheHit + deferCount;
|
||||
|
||||
let msg = [];
|
||||
msg.push(`${optimizedCount} ${optimizedCount !== 1 ? "images" : "image"} optimized`);
|
||||
|
||||
if(cachedCount > 0 || deferCount > 0) {
|
||||
let innerMsg = [];
|
||||
if(cachedCount > 0) {
|
||||
innerMsg.push(`${cachedCount} cached`);
|
||||
}
|
||||
if(deferCount > 0) {
|
||||
innerMsg.push(`${deferCount} deferred`);
|
||||
}
|
||||
msg.push(` (${innerMsg.join(", ")})`);
|
||||
}
|
||||
|
||||
if(optimizedCount > 0 || cachedCount > 0 || deferCount > 0) {
|
||||
eleventyConfig?.logger?.logWithOptions({
|
||||
message: msg.join(""),
|
||||
prefix: "[11ty/eleventy-img]",
|
||||
color: "green",
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createImage(src, opts = {}) {
|
||||
let eleventyConfig = opts.eleventyConfig;
|
||||
|
||||
if(opts?.eleventyConfig && {}.propertyIsEnumerable.call(opts, "eleventyConfig")) {
|
||||
delete opts.eleventyConfig;
|
||||
Util.addConfig(eleventyConfig, opts);
|
||||
}
|
||||
|
||||
let img = Image.create(src, opts);
|
||||
|
||||
img.setQueue(processingQueue);
|
||||
img.setBuildLogger(buildLogger);
|
||||
img.setDirectoryManager(directoryManager);
|
||||
|
||||
setupLogger(eleventyConfig, opts);
|
||||
|
||||
if(opts.transformOnRequest) {
|
||||
deferCounter.increment(src);
|
||||
}
|
||||
|
||||
return img;
|
||||
};
|
||||
|
||||
function queueImage(src, opts = {}) {
|
||||
if(src.constructor?.name === "UserConfig") {
|
||||
throw new Error(`Eleventy Image’s default export is not an Eleventy Plugin and cannot be used with \`eleventyConfig.addPlugin()\`. Use the \`eleventyImageTransformPlugin\` named export instead, like this: \`import { eleventyImageTransformPlugin } from '@11ty/eleventy-img';\` or this: \`const { eleventyImageTransformPlugin } = require('@11ty/eleventy-img');\``);
|
||||
}
|
||||
|
||||
let img = createImage(src, opts);
|
||||
return img.queue();
|
||||
}
|
||||
|
||||
// Exports
|
||||
|
||||
module.exports = queueImage;
|
||||
|
||||
Object.defineProperty(module.exports, "concurrency", {
|
||||
get: function() {
|
||||
return processingQueue.concurrency;
|
||||
},
|
||||
set: function(concurrency) {
|
||||
processingQueue.concurrency = concurrency;
|
||||
},
|
||||
});
|
||||
|
||||
module.exports.Util = Util;
|
||||
module.exports.Image = Image;
|
||||
module.exports.ImagePath = ImagePath;
|
||||
module.exports.ImageSize = getImageSize;
|
||||
|
||||
// Backwards compat
|
||||
module.exports.statsSync = Image.statsSync;
|
||||
module.exports.statsByDimensionsSync = Image.statsByDimensionsSync;
|
||||
module.exports.getFormats = Image.getFormatsArray;
|
||||
module.exports.getWidths = Image.getValidWidths;
|
||||
|
||||
module.exports.getHash = function getHash(src, options) {
|
||||
let img = new Image(src, options);
|
||||
return img.getHash();
|
||||
};
|
||||
|
||||
module.exports.setupLogger = setupLogger;
|
||||
|
||||
const generateHTML = require("./src/generate-html.js");
|
||||
module.exports.generateHTML = generateHTML;
|
||||
module.exports.generateObject = generateHTML.generateObject;
|
||||
|
||||
const { eleventyWebcOptionsPlugin } = require("./src/webc-options-plugin.js");
|
||||
module.exports.eleventyImagePlugin = eleventyWebcOptionsPlugin;
|
||||
module.exports.eleventyImageWebcOptionsPlugin = eleventyWebcOptionsPlugin;
|
||||
|
||||
const { eleventyImageTransformPlugin } = require("./src/transform-plugin.js");
|
||||
module.exports.eleventyImageTransformPlugin = eleventyImageTransformPlugin;
|
||||
|
||||
const { eleventyImageOnRequestDuringServePlugin } = require("./src/on-request-during-serve-plugin.js");
|
||||
module.exports.eleventyImageOnRequestDuringServePlugin = eleventyImageOnRequestDuringServePlugin;
|
||||
75
node_modules/@11ty/eleventy-img/package.json
generated
vendored
Normal file
75
node_modules/@11ty/eleventy-img/package.json
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
{
|
||||
"name": "@11ty/eleventy-img",
|
||||
"version": "6.0.4",
|
||||
"description": "Low level utility to perform build-time image transformations.",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"main": "img.js",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"scripts": {
|
||||
"pretest": "eslint img.js src/**.js test/**.js",
|
||||
"test": "ava --no-worker-threads",
|
||||
"watch": "ava --no-worker-threads --watch",
|
||||
"sample": "cd sample && node sample.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/11ty/eleventy-img.git"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/11ty"
|
||||
},
|
||||
"keywords": [
|
||||
"eleventy",
|
||||
"eleventy-utility"
|
||||
],
|
||||
"author": {
|
||||
"name": "Zach Leatherman",
|
||||
"email": "zachleatherman@gmail.com",
|
||||
"url": "https://zachleat.com/"
|
||||
},
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/11ty/eleventy-img/issues"
|
||||
},
|
||||
"homepage": "https://github.com/11ty/eleventy-img#readme",
|
||||
"dependencies": {
|
||||
"@11ty/eleventy-fetch": "^5.1.0",
|
||||
"@11ty/eleventy-utils": "^2.0.7",
|
||||
"brotli-size": "^4.0.0",
|
||||
"debug": "^4.4.0",
|
||||
"entities": "^6.0.0",
|
||||
"image-size": "^1.2.1",
|
||||
"p-queue": "^6.6.2",
|
||||
"sharp": "^0.33.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@11ty/eleventy": "^3.0.0",
|
||||
"@11ty/eleventy-plugin-webc": "^0.11.2",
|
||||
"@eslint/js": "^9.26.0",
|
||||
"@stylistic/eslint-plugin-js": "^4.2.0",
|
||||
"ava": "^6.3.0",
|
||||
"eslint": "^9.26.0",
|
||||
"exifr": "^7.1.3",
|
||||
"globals": "^16.1.0",
|
||||
"pixelmatch": "^5.3.0"
|
||||
},
|
||||
"ava": {
|
||||
"failFast": false,
|
||||
"files": [
|
||||
"./test/*.{js,cjs,mjs}"
|
||||
],
|
||||
"watchMode": {
|
||||
"ignoreChanges": [
|
||||
"./.cache/*",
|
||||
"./img/*",
|
||||
"./test/img/*",
|
||||
"./test/**/generated*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
3
node_modules/@11ty/eleventy-img/src/adapters/brotli-size.browser.js
generated
vendored
Normal file
3
node_modules/@11ty/eleventy-img/src/adapters/brotli-size.browser.js
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
throw new Error("`svgCompressionSize: 'br'` feature is not supported in browser.");
|
||||
};
|
||||
5
node_modules/@11ty/eleventy-img/src/adapters/brotli-size.js
generated
vendored
Normal file
5
node_modules/@11ty/eleventy-img/src/adapters/brotli-size.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
const brotliSize = require("brotli-size");
|
||||
|
||||
module.exports = function(contents) {
|
||||
return brotliSize.sync(contents);
|
||||
};
|
||||
3
node_modules/@11ty/eleventy-img/src/adapters/sharp.browser.js
generated
vendored
Normal file
3
node_modules/@11ty/eleventy-img/src/adapters/sharp.browser.js
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = function() {
|
||||
throw new Error("Sharp is not supported in browser.");
|
||||
};
|
||||
3
node_modules/@11ty/eleventy-img/src/adapters/sharp.js
generated
vendored
Normal file
3
node_modules/@11ty/eleventy-img/src/adapters/sharp.js
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
const sharp = require("sharp");
|
||||
|
||||
module.exports = sharp;
|
||||
66
node_modules/@11ty/eleventy-img/src/build-logger.js
generated
vendored
Normal file
66
node_modules/@11ty/eleventy-img/src/build-logger.js
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
const path = require("node:path");
|
||||
const { TemplatePath } = require("@11ty/eleventy-utils");
|
||||
|
||||
const Util = require("./util.js");
|
||||
|
||||
class BuildLogger {
|
||||
#eleventyConfig;
|
||||
|
||||
constructor() {
|
||||
this.hasAssigned = false;
|
||||
}
|
||||
|
||||
setupOnce(eleventyConfig, beforeCallback, afterCallback) {
|
||||
if(this.hasAssigned) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hasAssigned = true;
|
||||
this.#eleventyConfig = eleventyConfig;
|
||||
|
||||
eleventyConfig.on("eleventy.before", beforeCallback);
|
||||
eleventyConfig.on("eleventy.after", afterCallback);
|
||||
|
||||
eleventyConfig.on("eleventy.reset", () => {
|
||||
this.hasAssigned = false;
|
||||
beforeCallback(); // we run this on reset because the before callback will have disappeared (as the config reset)
|
||||
});
|
||||
}
|
||||
|
||||
getFriendlyImageSource(imageSource) {
|
||||
if(Buffer.isBuffer(imageSource)) {
|
||||
return `<Buffer>`;
|
||||
}
|
||||
|
||||
if(Util.isRemoteUrl(imageSource)) {
|
||||
return imageSource;
|
||||
}
|
||||
if(path.isAbsolute(imageSource)) {
|
||||
// convert back to relative url
|
||||
return TemplatePath.addLeadingDotSlash(path.relative(path.resolve("."), imageSource));
|
||||
}
|
||||
|
||||
return TemplatePath.addLeadingDotSlash(imageSource);
|
||||
}
|
||||
|
||||
log(message, options = {}, logOptions = {}) {
|
||||
if(typeof this.#eleventyConfig?.logger?.logWithOptions !== "function" || options.transformOnRequest) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#eleventyConfig.logger.logWithOptions(Object.assign({
|
||||
message: `${message}${options.generatedVia ? ` (${options.generatedVia})` : ""}`,
|
||||
type: "log",
|
||||
prefix: "[11ty/eleventy-img]"
|
||||
}, logOptions));
|
||||
}
|
||||
|
||||
error(message, options = {}, logOptions = {}) {
|
||||
logOptions.type = "error";
|
||||
logOptions.force = true;
|
||||
|
||||
this.log(message, options, logOptions);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BuildLogger;
|
||||
16
node_modules/@11ty/eleventy-img/src/caches.js
generated
vendored
Normal file
16
node_modules/@11ty/eleventy-img/src/caches.js
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
const MemoryCache = require("./memory-cache.js");
|
||||
const DiskCache = require("./disk-cache.js");
|
||||
const ExistsCache = require("./exists-cache.js");
|
||||
|
||||
let memCache = new MemoryCache();
|
||||
|
||||
let existsCache = new ExistsCache();
|
||||
|
||||
let diskCache = new DiskCache();
|
||||
diskCache.setExistsCache(existsCache);
|
||||
|
||||
module.exports = {
|
||||
memCache,
|
||||
diskCache,
|
||||
existsCache
|
||||
};
|
||||
27
node_modules/@11ty/eleventy-img/src/defer-counter.js
generated
vendored
Normal file
27
node_modules/@11ty/eleventy-img/src/defer-counter.js
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
class DeferCounter {
|
||||
constructor() {
|
||||
this.resetCount();
|
||||
}
|
||||
|
||||
resetCount() {
|
||||
this.deferCount = 0;
|
||||
this.inputs = new Map();
|
||||
}
|
||||
|
||||
getCount() {
|
||||
return this.deferCount;
|
||||
}
|
||||
|
||||
increment(input) {
|
||||
if(input) {
|
||||
if(this.inputs.has(input)) {
|
||||
return;
|
||||
}
|
||||
this.inputs.set(input, true);
|
||||
}
|
||||
|
||||
this.deferCount++;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DeferCounter;
|
||||
29
node_modules/@11ty/eleventy-img/src/directory-manager.js
generated
vendored
Normal file
29
node_modules/@11ty/eleventy-img/src/directory-manager.js
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const debugUtil = require("debug");
|
||||
const debugAssets = debugUtil("Eleventy:Assets");
|
||||
|
||||
class DirectoryManager {
|
||||
#dirs = new Set();
|
||||
|
||||
isCreated(dir) {
|
||||
return this.#dirs.has(dir);
|
||||
}
|
||||
|
||||
create(dir) {
|
||||
if(this.isCreated(dir)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#dirs.add(dir);
|
||||
debugAssets("[11ty/eleventy-img] Creating directory %o", dir);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
createFromFile(filepath) {
|
||||
let dir = path.dirname(filepath);
|
||||
this.create(dir);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DirectoryManager;
|
||||
54
node_modules/@11ty/eleventy-img/src/disk-cache.js
generated
vendored
Normal file
54
node_modules/@11ty/eleventy-img/src/disk-cache.js
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
// const debug = require("debug")("Eleventy:Image");
|
||||
|
||||
class DiskCache {
|
||||
#existsCache;
|
||||
|
||||
constructor() {
|
||||
this.hitCounter = 0;
|
||||
this.missCounter = 0;
|
||||
this.inputs = new Map();
|
||||
}
|
||||
|
||||
setExistsCache(existsCache) {
|
||||
this.#existsCache = existsCache;
|
||||
}
|
||||
|
||||
resetCount() {
|
||||
this.hitCounter = 0;
|
||||
this.missCounter = 0;
|
||||
}
|
||||
|
||||
getCount() {
|
||||
return [this.hitCounter, this.missCounter];
|
||||
}
|
||||
|
||||
isCached(targetFile, sourceInput, incrementCounts = true) {
|
||||
if(!this.#existsCache) {
|
||||
throw new Error("Missing `#existsCache`");
|
||||
}
|
||||
|
||||
// Disk cache runs once per output file, so we only increment counts once per input
|
||||
if(this.inputs.has(sourceInput)) {
|
||||
incrementCounts = false;
|
||||
}
|
||||
|
||||
this.inputs.set(sourceInput, true);
|
||||
|
||||
if(this.#existsCache?.exists(targetFile)) {
|
||||
if(incrementCounts) {
|
||||
this.hitCounter++;
|
||||
}
|
||||
|
||||
// debug("Images re-used (via disk cache): %o", this.hitCounter);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(incrementCounts) {
|
||||
this.missCounter++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DiskCache;
|
||||
40
node_modules/@11ty/eleventy-img/src/exists-cache.js
generated
vendored
Normal file
40
node_modules/@11ty/eleventy-img/src/exists-cache.js
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
const fs = require("node:fs");
|
||||
const Util = require("./util.js");
|
||||
|
||||
// Checks both files and directories
|
||||
class ExistsCache {
|
||||
#exists = new Map();
|
||||
|
||||
constructor() {
|
||||
this.lookupCount = 0;
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.#exists.size;
|
||||
}
|
||||
|
||||
has(path) {
|
||||
return this.#exists.has(path);
|
||||
}
|
||||
|
||||
// Relative paths (to root directory) expected (but not enforced due to perf costs)
|
||||
exists(path) {
|
||||
if(Util.isFullUrl(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.#exists.has(path)) {
|
||||
let exists = fs.existsSync(path);
|
||||
this.lookupCount++;
|
||||
|
||||
// mark for next time
|
||||
this.#exists.set(path, Boolean(exists));
|
||||
|
||||
return exists;
|
||||
}
|
||||
|
||||
return this.#exists.get(path);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ExistsCache;
|
||||
14
node_modules/@11ty/eleventy-img/src/format-hooks/svg.js
generated
vendored
Normal file
14
node_modules/@11ty/eleventy-img/src/format-hooks/svg.js
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
const fs = require("node:fs");
|
||||
const debugUtil = require("debug");
|
||||
const debugAssets = debugUtil("Eleventy:Assets");
|
||||
|
||||
module.exports = async function createSvg(sharpInstance) {
|
||||
let input = sharpInstance.options.input;
|
||||
let svgBuffer = input.buffer;
|
||||
if(svgBuffer) { // remote URL already has buffer
|
||||
return svgBuffer;
|
||||
} else { // local file system
|
||||
debugAssets("[11ty/eleventy-img] Reading %o", input.file);
|
||||
return fs.readFileSync(input.file);
|
||||
}
|
||||
};
|
||||
220
node_modules/@11ty/eleventy-img/src/generate-html.js
generated
vendored
Normal file
220
node_modules/@11ty/eleventy-img/src/generate-html.js
generated
vendored
Normal file
@ -0,0 +1,220 @@
|
||||
const { escapeAttribute } = require("entities");
|
||||
|
||||
const LOWSRC_FORMAT_PREFERENCE = ["jpeg", "png", "gif", "svg", "webp", "avif"];
|
||||
|
||||
const CHILDREN_OBJECT_KEY = "@children";
|
||||
|
||||
function generateSrcset(metadataFormatEntry) {
|
||||
if(!Array.isArray(metadataFormatEntry)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return metadataFormatEntry.map(entry => entry.srcset).join(", ");
|
||||
}
|
||||
|
||||
/*
|
||||
Returns:
|
||||
e.g. { img: { alt: "", src: "" }
|
||||
e.g. { img: { alt: "", src: "", srcset: "", sizes: "" } }
|
||||
e.g. { picture: {
|
||||
class: "",
|
||||
@children: [
|
||||
{ source: { srcset: "", sizes: "" } },
|
||||
{ source: { srcset: "", sizes: "" } },
|
||||
{ img: { alt: "", src: "", srcset: "", sizes: "" } },
|
||||
]
|
||||
}
|
||||
*/
|
||||
function generateObject(metadata, userDefinedImgAttributes = {}, userDefinedPictureAttributes = {}, options = {}) {
|
||||
let htmlOptions = options?.htmlOptions || {};
|
||||
let imgAttributes = Object.assign({}, options?.defaultAttributes, htmlOptions?.imgAttributes, userDefinedImgAttributes);
|
||||
let pictureAttributes = Object.assign({}, htmlOptions?.pictureAttributes, userDefinedPictureAttributes);
|
||||
|
||||
// The attributes.src gets overwritten later on. Save it here to make the error outputs less cryptic.
|
||||
let originalSrc = imgAttributes.src;
|
||||
|
||||
if(imgAttributes.alt === undefined) {
|
||||
// You bet we throw an error on missing alt (alt="" works okay)
|
||||
throw new Error(`Missing \`alt\` attribute on eleventy-img shortcode from: ${originalSrc}`);
|
||||
}
|
||||
|
||||
let formats = Object.keys(metadata);
|
||||
let values = Object.values(metadata);
|
||||
let entryCount = 0;
|
||||
for(let imageFormat of values) {
|
||||
entryCount += imageFormat.length;
|
||||
}
|
||||
|
||||
if(entryCount === 0) {
|
||||
throw new Error("No image results found from `eleventy-img` in generateHTML. Expects a results object similar to: https://www.11ty.dev/docs/plugins/image/#usage.");
|
||||
}
|
||||
|
||||
let lowsrc;
|
||||
let lowsrcFormat;
|
||||
for(let format of LOWSRC_FORMAT_PREFERENCE) {
|
||||
if((format in metadata) && metadata[format].length) {
|
||||
lowsrcFormat = format;
|
||||
lowsrc = metadata[lowsrcFormat];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle if empty intersection between format and LOWSRC_FORMAT_PREFERENCE (e.g. gif)
|
||||
// If there’s only one format in the results, use that
|
||||
if(!lowsrc && formats.length === 1) {
|
||||
lowsrcFormat = formats[0];
|
||||
lowsrc = metadata[lowsrcFormat];
|
||||
}
|
||||
|
||||
if(!lowsrc || !lowsrc.length) {
|
||||
throw new Error(`Could not find the lowest <img> source for responsive markup for ${originalSrc}`);
|
||||
}
|
||||
|
||||
imgAttributes.src = lowsrc[0].url;
|
||||
|
||||
if(htmlOptions.fallback === "largest" || htmlOptions.fallback === undefined) {
|
||||
imgAttributes.width = lowsrc[lowsrc.length - 1].width;
|
||||
imgAttributes.height = lowsrc[lowsrc.length - 1].height;
|
||||
} else if(htmlOptions.fallback === "smallest") {
|
||||
imgAttributes.width = lowsrc[0].width;
|
||||
imgAttributes.height = lowsrc[0].height;
|
||||
} else {
|
||||
throw new Error("Invalid `fallback` option specified. 'largest' and 'smallest' are supported. Received: " + htmlOptions.fallback);
|
||||
}
|
||||
|
||||
let imgAttributesWithoutSizes = Object.assign({}, imgAttributes);
|
||||
delete imgAttributesWithoutSizes.sizes;
|
||||
|
||||
// <img>: one format and one size
|
||||
if(entryCount === 1) {
|
||||
return {
|
||||
img: imgAttributesWithoutSizes
|
||||
};
|
||||
}
|
||||
|
||||
// Per the HTML specification sizes is required srcset is using the `w` unit
|
||||
// https://html.spec.whatwg.org/dev/semantics.html#the-link-element:attr-link-imagesrcset-4
|
||||
// Using the default "100vw" is okay
|
||||
let missingSizesErrorMessage = `Missing \`sizes\` attribute on eleventy-img shortcode from: ${originalSrc}. Workarounds: 1. Use a single output width for this image 2. Use \`loading="lazy"\` (which uses sizes="auto" though browser support currently varies)`;
|
||||
|
||||
// <img srcset>: one format and multiple sizes
|
||||
if(formats.length === 1) { // implied entryCount > 1
|
||||
if(entryCount > 1 && !imgAttributes.sizes) {
|
||||
// Use `sizes="auto"` when using `loading="lazy"` instead of throwing an error.
|
||||
if(imgAttributes.loading === "lazy") {
|
||||
imgAttributes.sizes = "auto";
|
||||
} else {
|
||||
throw new Error(missingSizesErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
let imgAttributesCopy = Object.assign({}, imgAttributesWithoutSizes);
|
||||
imgAttributesCopy.srcset = generateSrcset(lowsrc);
|
||||
imgAttributesCopy.sizes = imgAttributes.sizes;
|
||||
|
||||
return {
|
||||
img: imgAttributesCopy
|
||||
};
|
||||
}
|
||||
|
||||
let children = [];
|
||||
values.filter(imageFormat => {
|
||||
return imageFormat.length > 0 && (lowsrcFormat !== imageFormat[0].format);
|
||||
}).forEach(imageFormat => {
|
||||
if(imageFormat.length > 1 && !imgAttributes.sizes) {
|
||||
if(imgAttributes.loading === "lazy") {
|
||||
imgAttributes.sizes = "auto";
|
||||
} else {
|
||||
throw new Error(missingSizesErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
let sourceAttrs = {
|
||||
type: imageFormat[0].sourceType,
|
||||
srcset: generateSrcset(imageFormat),
|
||||
};
|
||||
|
||||
if(imgAttributes.sizes) {
|
||||
sourceAttrs.sizes = imgAttributes.sizes;
|
||||
}
|
||||
|
||||
children.push({
|
||||
"source": sourceAttrs
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
Add lowsrc as an img, for browsers that don’t support picture or the formats provided in source
|
||||
|
||||
If we have more than one size, we can use srcset and sizes.
|
||||
If the browser doesn't support those attributes, it should ignore them.
|
||||
*/
|
||||
let imgAttributesForPicture = Object.assign({}, imgAttributesWithoutSizes);
|
||||
if (lowsrc.length > 1) {
|
||||
if (!imgAttributes.sizes) {
|
||||
// Per the HTML specification sizes is required srcset is using the `w` unit
|
||||
// https://html.spec.whatwg.org/dev/semantics.html#the-link-element:attr-link-imagesrcset-4
|
||||
// Using the default "100vw" is okay
|
||||
throw new Error(missingSizesErrorMessage);
|
||||
}
|
||||
|
||||
imgAttributesForPicture.srcset = generateSrcset(lowsrc);
|
||||
imgAttributesForPicture.sizes = imgAttributes.sizes;
|
||||
}
|
||||
|
||||
children.push({
|
||||
"img": imgAttributesForPicture
|
||||
});
|
||||
|
||||
return {
|
||||
"picture": {
|
||||
...pictureAttributes,
|
||||
[CHILDREN_OBJECT_KEY]: children,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function mapObjectToHTML(tagName, attrs = {}) {
|
||||
let attrHtml = Object.entries(attrs).map(entry => {
|
||||
let [key, value] = entry;
|
||||
if(key === CHILDREN_OBJECT_KEY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Issue #82
|
||||
if(key === "alt") {
|
||||
return `${key}="${value ? escapeAttribute(value) : ""}"`;
|
||||
}
|
||||
|
||||
return `${key}="${value}"`;
|
||||
}).filter(keyPair => Boolean(keyPair)).join(" ");
|
||||
|
||||
return `<${tagName}${attrHtml ? ` ${attrHtml}` : ""}>`;
|
||||
}
|
||||
|
||||
function generateHTML(metadata, attributes = {}, htmlOptionsOverride = {}) {
|
||||
let htmlOptions = Object.assign({}, metadata?.eleventyImage?.htmlOptions, htmlOptionsOverride);
|
||||
|
||||
let isInline = htmlOptions.whitespaceMode !== "block";
|
||||
let markup = [];
|
||||
|
||||
// htmlOptions.imgAttributes and htmlOptions.pictureAttributes are merged in generateObject
|
||||
let obj = generateObject(metadata, attributes, {}, { htmlOptions });
|
||||
for(let tag in obj) {
|
||||
markup.push(mapObjectToHTML(tag, obj[tag]));
|
||||
|
||||
// <picture>
|
||||
if(Array.isArray(obj[tag]?.[CHILDREN_OBJECT_KEY])) {
|
||||
for(let child of obj[tag][CHILDREN_OBJECT_KEY]) {
|
||||
let childTagName = Object.keys(child)[0];
|
||||
markup.push((!isInline ? " " : "") + mapObjectToHTML(childTagName, child[childTagName]));
|
||||
}
|
||||
|
||||
markup.push(`</${tag}>`);
|
||||
}
|
||||
}
|
||||
return markup.join(!isInline ? "\n" : "");
|
||||
}
|
||||
|
||||
module.exports = generateHTML;
|
||||
module.exports.generateObject = generateObject;
|
||||
121
node_modules/@11ty/eleventy-img/src/global-options.js
generated
vendored
Normal file
121
node_modules/@11ty/eleventy-img/src/global-options.js
generated
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
const path = require("node:path");
|
||||
const os = require("node:os");
|
||||
|
||||
const Util = require("./util.js");
|
||||
const svgHook = require("./format-hooks/svg.js");
|
||||
|
||||
const DEFAULTS = {
|
||||
widths: ["auto"],
|
||||
formats: ["webp", "jpeg"], // "png", "svg", "avif"
|
||||
|
||||
formatFiltering: ["transparent", "animated"],
|
||||
|
||||
// Via https://github.com/11ty/eleventy-img/issues/258
|
||||
concurrency: Math.min(Math.max(8, os.availableParallelism()), 16),
|
||||
|
||||
urlPath: "/img/",
|
||||
outputDir: "img/",
|
||||
|
||||
// true to skip raster formats if SVG input is found
|
||||
// "size" to skip raster formats if larger than SVG input
|
||||
svgShortCircuit: false,
|
||||
svgAllowUpscale: true,
|
||||
svgCompressionSize: "", // "br" to report SVG `size` property in metadata as Brotli compressed
|
||||
// overrideInputFormat: false, // internal, used to force svg output in statsSync et al
|
||||
sharpOptions: {}, // options passed to the Sharp constructor
|
||||
sharpWebpOptions: {}, // options passed to the Sharp webp output method
|
||||
sharpPngOptions: {}, // options passed to the Sharp png output method
|
||||
sharpJpegOptions: {}, // options passed to the Sharp jpeg output method
|
||||
sharpAvifOptions: {}, // options passed to the Sharp avif output method
|
||||
|
||||
formatHooks: {
|
||||
svg: svgHook,
|
||||
},
|
||||
|
||||
cacheDuration: "1d", // deprecated, use cacheOptions.duration
|
||||
|
||||
// disk cache for remote assets
|
||||
cacheOptions: {
|
||||
// duration: "1d",
|
||||
// directory: ".cache",
|
||||
// removeUrlQueryParams: false,
|
||||
// fetchOptions: {},
|
||||
},
|
||||
|
||||
filenameFormat: null,
|
||||
|
||||
// urlFormat allows you to return a full URL to an image including the domain.
|
||||
// Useful when you’re using your own hosted image service (probably via .statsSync or .statsByDimensionsSync)
|
||||
// Note: when you use this, metadata will not include .filename or .outputPath
|
||||
urlFormat: null,
|
||||
|
||||
// If true, skips all image processing, just return stats. Doesn’t read files, doesn’t write files.
|
||||
// Important to note that `dryRun: true` performs image processing and includes a buffer—this does not.
|
||||
// Useful when used with `urlFormat` above.
|
||||
// Better than .statsSync* functions, because this will use the in-memory cache and de-dupe requests. Those will not.
|
||||
statsOnly: false,
|
||||
remoteImageMetadata: {}, // For `statsOnly` remote images, this needs to be populated with { width, height, format? }
|
||||
|
||||
useCache: true, // in-memory and disk cache
|
||||
dryRun: false, // Also returns a buffer instance in the return object. Doesn’t write anything to the file system
|
||||
|
||||
hashLength: 10, // Truncates the hash to this length
|
||||
|
||||
fixOrientation: false, // always rotate images to ensure correct orientation
|
||||
|
||||
// When the original width is smaller than the desired output width, this is the minimum size difference
|
||||
// between the next smallest image width that will generate one extra width in the output.
|
||||
// e.g. when using `widths: [400, 800]`, the source image would need to be at least (400 * 1.25 =) 500px wide
|
||||
// to generate two outputs (400px, 500px). If the source image is less than 500px, only one output will
|
||||
// be generated (400px).
|
||||
// Read more at https://github.com/11ty/eleventy-img/issues/184 and https://github.com/11ty/eleventy-img/pull/190
|
||||
minimumThreshold: 1.25,
|
||||
|
||||
// During --serve mode in Eleventy, this will generate images on request instead of part of the build skipping
|
||||
// writes to the file system and speeding up builds!
|
||||
transformOnRequest: false,
|
||||
|
||||
// operate on Sharp instance manually.
|
||||
transform: undefined,
|
||||
|
||||
// return HTML from generateHTML directly
|
||||
returnType: "object", // or "html"
|
||||
|
||||
// Defaults used when generateHTML is called from a result set
|
||||
htmlOptions: {
|
||||
imgAttributes: {},
|
||||
pictureAttributes: {},
|
||||
|
||||
whitespaceMode: "inline", // "block"
|
||||
|
||||
// the <img> will use the largest dimensions for width/height (when multiple output widths are specified)
|
||||
// see https://github.com/11ty/eleventy-img/issues/63
|
||||
fallback: "largest", // or "smallest"
|
||||
},
|
||||
|
||||
// v5.0.0 Removed `extensions`, option to override output format with new file extension. It wasn’t being used anywhere or documented.
|
||||
// v6.0.0, removed `useCacheValidityInHash: true` see https://github.com/11ty/eleventy-img/issues/146#issuecomment-2555741376
|
||||
};
|
||||
|
||||
function getGlobalOptions(eleventyConfig, options, via) {
|
||||
let directories = eleventyConfig.directories;
|
||||
let globalOptions = Object.assign({
|
||||
packages: {
|
||||
image: require("../"),
|
||||
},
|
||||
outputDir: path.join(directories.output, options.urlPath || ""),
|
||||
failOnError: true,
|
||||
}, options);
|
||||
|
||||
globalOptions.directories = directories;
|
||||
globalOptions.generatedVia = via;
|
||||
|
||||
Util.addConfig(eleventyConfig, globalOptions);
|
||||
|
||||
return globalOptions;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getGlobalOptions,
|
||||
defaults: DEFAULTS,
|
||||
};
|
||||
140
node_modules/@11ty/eleventy-img/src/image-attrs-to-posthtml-node.js
generated
vendored
Normal file
140
node_modules/@11ty/eleventy-img/src/image-attrs-to-posthtml-node.js
generated
vendored
Normal file
@ -0,0 +1,140 @@
|
||||
const eleventyImage = require("../img.js");
|
||||
const Util = require("./util.js");
|
||||
|
||||
const ATTR_PREFIX = "eleventy:";
|
||||
|
||||
const CHILDREN_OBJECT_KEY = "@children";
|
||||
|
||||
const ATTR = {
|
||||
IGNORE: `${ATTR_PREFIX}ignore`,
|
||||
WIDTHS: `${ATTR_PREFIX}widths`,
|
||||
FORMATS: `${ATTR_PREFIX}formats`,
|
||||
OUTPUT: `${ATTR_PREFIX}output`,
|
||||
OPTIONAL: `${ATTR_PREFIX}optional`,
|
||||
PICTURE: `${ATTR_PREFIX}pictureattr:`,
|
||||
};
|
||||
|
||||
function getPictureAttributesFromImgNode(attrs = {}) {
|
||||
let pictureAttrs = {};
|
||||
for(let key in attrs) {
|
||||
// <img eleventy:pictureattr:NAME="VALUE"> hoists to `<picture NAME="VALUE">
|
||||
// e.g. <img eleventy:pictureattr:class="outer"> hoists to <picture class="outer">
|
||||
if(key.startsWith(ATTR.PICTURE)) {
|
||||
pictureAttrs[key.slice(ATTR.PICTURE.length)] = attrs[key];
|
||||
}
|
||||
}
|
||||
return pictureAttrs;
|
||||
}
|
||||
|
||||
function convertToPosthtmlNode(obj) {
|
||||
// node.tag
|
||||
// node.attrs
|
||||
// node.content
|
||||
|
||||
let node = {};
|
||||
let [key] = Object.keys(obj);
|
||||
node.tag = key;
|
||||
|
||||
let children = obj[key]?.[CHILDREN_OBJECT_KEY];
|
||||
let attributes = {};
|
||||
for(let attrKey in obj[key]) {
|
||||
if(attrKey !== CHILDREN_OBJECT_KEY) {
|
||||
attributes[attrKey] = obj[key][attrKey];
|
||||
}
|
||||
}
|
||||
node.attrs = attributes;
|
||||
|
||||
if(Array.isArray(children)) {
|
||||
node.content = obj[key]?.[CHILDREN_OBJECT_KEY]
|
||||
.filter(child => Boolean(child))
|
||||
.map(child => {
|
||||
return convertToPosthtmlNode(child);
|
||||
});
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
function isValidSimpleWidthAttribute(width) {
|
||||
// `width` must be a single integer (not comma separated). Don’t use invalid HTML in width attribute. Use eleventy:widths if you want more complex support
|
||||
return (""+width) == (""+parseInt(width, 10));
|
||||
}
|
||||
|
||||
async function imageAttributesToPosthtmlNode(attributes, instanceOptions, globalPluginOptions) {
|
||||
if(!attributes.src) {
|
||||
throw new Error("Missing `src` attribute for `@11ty/eleventy-img`");
|
||||
}
|
||||
|
||||
if(!globalPluginOptions) {
|
||||
throw new Error("Missing global defaults for `@11ty/eleventy-img`: did you call addPlugin?");
|
||||
}
|
||||
|
||||
if(!instanceOptions) {
|
||||
instanceOptions = {};
|
||||
}
|
||||
|
||||
// overrides global widths
|
||||
if(attributes.width && isValidSimpleWidthAttribute(attributes.width)) {
|
||||
// Support `width` but only single value
|
||||
instanceOptions.widths = [ parseInt(attributes.width, 10) ];
|
||||
} else if(attributes[ATTR.WIDTHS] && typeof attributes[ATTR.WIDTHS] === "string") {
|
||||
instanceOptions.widths = attributes[ATTR.WIDTHS].split(",").map(entry => parseInt(entry, 10));
|
||||
}
|
||||
|
||||
if(attributes[ATTR.FORMATS] && typeof attributes[ATTR.FORMATS] === "string") {
|
||||
instanceOptions.formats = attributes[ATTR.FORMATS].split(",");
|
||||
}
|
||||
|
||||
let options = Object.assign({}, globalPluginOptions, instanceOptions);
|
||||
Util.addConfig(globalPluginOptions.eleventyConfig, options);
|
||||
|
||||
let metadata = await eleventyImage(attributes.src, options);
|
||||
let pictureAttributes = getPictureAttributesFromImgNode(attributes);
|
||||
|
||||
cleanAttrs(attributes);
|
||||
|
||||
// You bet we throw an error on missing alt in `imageAttributes` (alt="" works okay)
|
||||
let obj = await eleventyImage.generateObject(metadata, attributes, pictureAttributes, options);
|
||||
return convertToPosthtmlNode(obj);
|
||||
}
|
||||
|
||||
function cleanAttrs(attrs = {}) {
|
||||
for(let key in attrs) {
|
||||
if(key.startsWith(ATTR_PREFIX)) {
|
||||
delete attrs?.[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function cleanTag(node) {
|
||||
// Delete all prefixed attributes
|
||||
cleanAttrs(node?.attrs);
|
||||
}
|
||||
|
||||
function isIgnored(node) {
|
||||
return node?.attrs && node?.attrs?.[ATTR.IGNORE] !== undefined;
|
||||
}
|
||||
|
||||
function isOptional(node, comparisonValue) {
|
||||
let attrValue = node?.attrs && node?.attrs?.[ATTR.OPTIONAL];
|
||||
if(attrValue !== undefined) {
|
||||
// if comparisonValue is not specified, return true
|
||||
if(comparisonValue === undefined) {
|
||||
return true;
|
||||
}
|
||||
return attrValue === comparisonValue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getOutputDirectory(node) {
|
||||
return node?.attrs?.[ATTR.OUTPUT];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
imageAttributesToPosthtmlNode,
|
||||
cleanTag,
|
||||
isIgnored,
|
||||
isOptional,
|
||||
getOutputDirectory,
|
||||
};
|
||||
30
node_modules/@11ty/eleventy-img/src/image-path.js
generated
vendored
Normal file
30
node_modules/@11ty/eleventy-img/src/image-path.js
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
const path = require("node:path");
|
||||
|
||||
class ImagePath {
|
||||
static filenameFormat(id, src, width, format) { // and options
|
||||
if (width) {
|
||||
return `${id}-${width}.${format}`;
|
||||
}
|
||||
|
||||
return `${id}.${format}`;
|
||||
}
|
||||
|
||||
static getFilename(id, src, width, format, options = {}) {
|
||||
if (typeof options.filenameFormat === "function") {
|
||||
let filename = options.filenameFormat(id, src, width, format, options);
|
||||
// if options.filenameFormat returns falsy, use fallback filename
|
||||
if(filename) {
|
||||
return filename;
|
||||
}
|
||||
}
|
||||
|
||||
return ImagePath.filenameFormat(id, src, width, format, options);
|
||||
}
|
||||
|
||||
static convertFilePathToUrl(dir, filename) {
|
||||
let src = path.join(dir, filename);
|
||||
return src.split(path.sep).join("/");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ImagePath;
|
||||
930
node_modules/@11ty/eleventy-img/src/image.js
generated
vendored
Normal file
930
node_modules/@11ty/eleventy-img/src/image.js
generated
vendored
Normal file
@ -0,0 +1,930 @@
|
||||
const fs = require("node:fs");
|
||||
const fsp = fs.promises;
|
||||
const path = require("node:path");
|
||||
const getImageSize = require("image-size");
|
||||
const debugUtil = require("debug");
|
||||
|
||||
const { createHashSync } = require("@11ty/eleventy-utils");
|
||||
const { Fetch } = require("@11ty/eleventy-fetch");
|
||||
|
||||
const sharp = require("./adapters/sharp.js");
|
||||
const brotliSize = require("./adapters/brotli-size.js");
|
||||
const Util = require("./util.js");
|
||||
const ImagePath = require("./image-path.js");
|
||||
const generateHTML = require("./generate-html.js");
|
||||
|
||||
const GLOBAL_OPTIONS = require("./global-options.js").defaults;
|
||||
const { existsCache, memCache, diskCache } = require("./caches.js");
|
||||
|
||||
const debug = debugUtil("Eleventy:Image");
|
||||
const debugAssets = debugUtil("Eleventy:Assets");
|
||||
|
||||
const MIME_TYPES = {
|
||||
"jpeg": "image/jpeg",
|
||||
"webp": "image/webp",
|
||||
"png": "image/png",
|
||||
"svg": "image/svg+xml",
|
||||
"avif": "image/avif",
|
||||
"gif": "image/gif",
|
||||
};
|
||||
|
||||
const FORMAT_ALIASES = {
|
||||
"jpg": "jpeg",
|
||||
// if you’re working from a mime type input, let’s alias it back to svg
|
||||
"svg+xml": "svg",
|
||||
};
|
||||
|
||||
const ANIMATED_TYPES = [
|
||||
"webp",
|
||||
"gif",
|
||||
];
|
||||
|
||||
const TRANSPARENCY_TYPES = [
|
||||
"avif",
|
||||
"png",
|
||||
"webp",
|
||||
"gif",
|
||||
"svg",
|
||||
];
|
||||
|
||||
const MINIMUM_TRANSPARENCY_TYPES = [
|
||||
"png",
|
||||
"gif",
|
||||
"svg",
|
||||
];
|
||||
|
||||
class Image {
|
||||
#input;
|
||||
#contents = {};
|
||||
#queue;
|
||||
#queuePromise;
|
||||
#buildLogger;
|
||||
#computedHash;
|
||||
#directoryManager;
|
||||
|
||||
constructor(src, options = {}) {
|
||||
if(!src) {
|
||||
throw new Error("`src` is a required argument to the eleventy-img utility (can be a String file path, String URL, or Buffer).");
|
||||
}
|
||||
|
||||
this.src = src;
|
||||
this.isRemoteUrl = typeof src === "string" && Util.isRemoteUrl(src);
|
||||
|
||||
this.rawOptions = options;
|
||||
this.options = Object.assign({}, GLOBAL_OPTIONS, options);
|
||||
|
||||
// Compatible with eleventy-dev-server and Eleventy 3.0.0-alpha.7+ in serve mode.
|
||||
if(this.options.transformOnRequest && !this.options.urlFormat) {
|
||||
this.options.urlFormat = function({ src, width, format }/*, imageOptions*/, options) {
|
||||
return `/.11ty/image/?src=${encodeURIComponent(src)}&width=${width}&format=${format}${options.generatedVia ? `&via=${options.generatedVia}` : ""}`;
|
||||
};
|
||||
|
||||
this.options.statsOnly = true;
|
||||
}
|
||||
|
||||
if(this.isRemoteUrl) {
|
||||
this.cacheOptions = Object.assign({
|
||||
type: "buffer",
|
||||
// deprecated in Eleventy Image, but we already prefer this.cacheOptions.duration automatically
|
||||
duration: this.options.cacheDuration,
|
||||
// Issue #117: re-use eleventy-img dryRun option value for eleventy-fetch dryRun
|
||||
dryRun: this.options.dryRun,
|
||||
}, this.options.cacheOptions);
|
||||
|
||||
// v6.0.0 this now inherits eleventy-fetch option defaults
|
||||
this.assetCache = Fetch(src, this.cacheOptions);
|
||||
}
|
||||
}
|
||||
|
||||
setQueue(queue) {
|
||||
this.#queue = queue;
|
||||
}
|
||||
|
||||
setBuildLogger(buildLogger) {
|
||||
this.#buildLogger = buildLogger;
|
||||
}
|
||||
|
||||
setDirectoryManager(manager) {
|
||||
this.#directoryManager = manager;
|
||||
}
|
||||
|
||||
get directoryManager() {
|
||||
if(!this.#directoryManager) {
|
||||
throw new Error("Missing #directoryManager");
|
||||
}
|
||||
|
||||
return this.#directoryManager;
|
||||
}
|
||||
|
||||
get buildLogger() {
|
||||
if(!this.#buildLogger) {
|
||||
throw new Error("Missing #buildLogger. Call `setBuildLogger`");
|
||||
}
|
||||
return this.#buildLogger;
|
||||
}
|
||||
|
||||
// In memory cache is up front, handles promise de-duping from input (this does not use getHash)
|
||||
// Note: output cache is also in play below (uses getHash)
|
||||
getInMemoryCacheKey() {
|
||||
let opts = Util.getSortedObject(this.options);
|
||||
|
||||
opts.__originalSrc = this.src;
|
||||
|
||||
if(this.isRemoteUrl) {
|
||||
opts.sourceUrl = this.src; // the source url
|
||||
} else if(Buffer.isBuffer(this.src)) {
|
||||
opts.sourceUrl = this.src.toString();
|
||||
opts.__originalSize = this.src.length;
|
||||
} else {
|
||||
// Important: do not cache this
|
||||
opts.__originalSize = fs.statSync(this.src).size;
|
||||
}
|
||||
|
||||
return JSON.stringify(opts, function(key, value) {
|
||||
// allows `transform` functions to be truthy for in-memory key
|
||||
if (typeof value === "function") {
|
||||
return "<fn>" + (value.name || "");
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
getFileContents(overrideLocalFilePath) {
|
||||
if(!overrideLocalFilePath && this.isRemoteUrl) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let src = overrideLocalFilePath || this.src;
|
||||
|
||||
if(!this.#contents[src]) {
|
||||
// perf: check to make sure it’s not a string first
|
||||
if(typeof src !== "string" && Buffer.isBuffer(src)) {
|
||||
this.#contents[src] = src;
|
||||
} else {
|
||||
debugAssets("[11ty/eleventy-img] Reading %o", src);
|
||||
this.#contents[src] = fs.readFileSync(src);
|
||||
}
|
||||
}
|
||||
|
||||
// Always <Buffer>
|
||||
return this.#contents[src];
|
||||
}
|
||||
|
||||
static getValidWidths(originalWidth, widths = [], allowUpscale = false, minimumThreshold = 1) {
|
||||
// replace any falsy values with the original width
|
||||
let valid = widths.map(width => !width || width === 'auto' ? originalWidth : width);
|
||||
|
||||
// Convert strings to numbers, "400" (floats are not allowed in sharp)
|
||||
valid = valid.map(width => parseInt(width, 10));
|
||||
|
||||
// Replace any larger-than-original widths with the original width if upscaling is not allowed.
|
||||
// This ensures that if a larger width has been requested, we're at least providing the closest
|
||||
// non-upscaled image that we can.
|
||||
if (!allowUpscale) {
|
||||
let lastWidthWasBigEnough = true; // first one is always valid
|
||||
valid = valid.sort((a, b) => a - b).map(width => {
|
||||
if(width > originalWidth) {
|
||||
if(lastWidthWasBigEnough) {
|
||||
return originalWidth;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
lastWidthWasBigEnough = originalWidth > Math.floor(width * minimumThreshold);
|
||||
|
||||
return width;
|
||||
}).filter(width => width > 0);
|
||||
}
|
||||
|
||||
// Remove duplicates (e.g., if null happens to coincide with an explicit width
|
||||
// or a user passes in multiple duplicate values, or multiple larger-than-original
|
||||
// widths have resulted in the original width being included multiple times)
|
||||
valid = [...new Set(valid)];
|
||||
|
||||
// sort ascending
|
||||
return valid.sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
static getFormatsArray(formats, autoFormat, svgShortCircuit, isAnimated, hasTransparency) {
|
||||
if(formats && formats.length) {
|
||||
if(typeof formats === "string") {
|
||||
formats = formats.split(",");
|
||||
}
|
||||
|
||||
formats = formats.map(format => {
|
||||
if(autoFormat) {
|
||||
if((!format || format === "auto")) {
|
||||
format = autoFormat;
|
||||
}
|
||||
}
|
||||
|
||||
if(FORMAT_ALIASES[format]) {
|
||||
return FORMAT_ALIASES[format];
|
||||
}
|
||||
return format;
|
||||
});
|
||||
|
||||
if(svgShortCircuit !== "size") {
|
||||
// svg must come first for possible short circuiting
|
||||
formats.sort((a, b) => {
|
||||
if(a === "svg") {
|
||||
return -1;
|
||||
} else if(b === "svg") {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
if(isAnimated) {
|
||||
let validAnimatedFormats = formats.filter(f => ANIMATED_TYPES.includes(f));
|
||||
// override formats if a valid animated format is found, otherwise leave as-is
|
||||
if(validAnimatedFormats.length > 0) {
|
||||
debug("Filtering non-animated formats from output: from %o to %o", formats, validAnimatedFormats);
|
||||
formats = validAnimatedFormats;
|
||||
} else {
|
||||
debug("No animated output formats found for animated image, using original formats (may be a static image): %o", formats);
|
||||
}
|
||||
}
|
||||
|
||||
if(hasTransparency) {
|
||||
let minimumValidTransparencyFormats = formats.filter(f => MINIMUM_TRANSPARENCY_TYPES.includes(f));
|
||||
// override formats if a valid animated format is found, otherwise leave as-is
|
||||
if(minimumValidTransparencyFormats.length > 0) {
|
||||
let validTransparencyFormats = formats.filter(f => TRANSPARENCY_TYPES.includes(f));
|
||||
debug("Filtering non-transparency-friendly formats from output: from %o to %o", formats, validTransparencyFormats);
|
||||
formats = validTransparencyFormats;
|
||||
} else {
|
||||
debug("At least one transparency-friendly output format of %o must be included if the source image has an alpha channel, skipping formatFiltering and using original formats: %o", MINIMUM_TRANSPARENCY_TYPES, formats);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove duplicates (e.g., if null happens to coincide with an explicit format
|
||||
// or a user passes in multiple duplicate values)
|
||||
formats = [...new Set(formats)];
|
||||
|
||||
return formats;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
#transformRawFiles(files = []) {
|
||||
let byType = {};
|
||||
for(let file of files) {
|
||||
if(!byType[file.format]) {
|
||||
byType[file.format] = [];
|
||||
}
|
||||
byType[file.format].push(file);
|
||||
}
|
||||
for(let type in byType) {
|
||||
// sort by width, ascending (for `srcset`)
|
||||
byType[type].sort((a, b) => {
|
||||
return a.width - b.width;
|
||||
});
|
||||
}
|
||||
|
||||
let filterLargeRasterImages = this.options.svgShortCircuit === "size";
|
||||
let svgEntry = byType.svg;
|
||||
let svgSize = svgEntry && svgEntry.length && svgEntry[0].size;
|
||||
|
||||
if(filterLargeRasterImages && svgSize) {
|
||||
for(let type of Object.keys(byType)) {
|
||||
if(type === "svg") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let svgAdded = false;
|
||||
let originalFormatKept = false;
|
||||
byType[type] = byType[type].map(entry => {
|
||||
if(entry.size > svgSize) {
|
||||
if(!svgAdded) {
|
||||
svgAdded = true;
|
||||
// need at least one raster smaller than SVG to do this trick
|
||||
if(originalFormatKept) {
|
||||
return svgEntry[0];
|
||||
}
|
||||
// all rasters are bigger
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
originalFormatKept = true;
|
||||
return entry;
|
||||
}).filter(entry => entry);
|
||||
}
|
||||
}
|
||||
|
||||
return byType;
|
||||
}
|
||||
|
||||
#finalizeResults(results = {}) {
|
||||
// used when results are passed to generate HTML, we maintain some internal metadata about the options used.
|
||||
let imgAttributes = this.options.htmlOptions?.imgAttributes || {};
|
||||
imgAttributes.src = this.src;
|
||||
|
||||
Object.defineProperty(results, "eleventyImage", {
|
||||
enumerable: false,
|
||||
writable: false,
|
||||
value: {
|
||||
htmlOptions: {
|
||||
whitespaceMode: this.options.htmlOptions?.whitespaceMode,
|
||||
imgAttributes,
|
||||
pictureAttributes: this.options.htmlOptions?.pictureAttributes,
|
||||
fallback: this.options.htmlOptions?.fallback,
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
// renamed `return` to `returnType` to match Fetch API in v6.0.0-beta.3
|
||||
if(this.options.returnType === "html" || this.options.return === "html") {
|
||||
return generateHTML(results);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
getSharpOptionsForFormat(format) {
|
||||
if(format === "webp") {
|
||||
return this.options.sharpWebpOptions;
|
||||
} else if(format === "jpeg") {
|
||||
return this.options.sharpJpegOptions;
|
||||
} else if(format === "png") {
|
||||
return this.options.sharpPngOptions;
|
||||
} else if(format === "avif") {
|
||||
return this.options.sharpAvifOptions;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
async getInput() {
|
||||
// internal cache
|
||||
if(!this.#input) {
|
||||
if(this.isRemoteUrl) {
|
||||
// fetch remote image Buffer
|
||||
this.#input = this.assetCache.queue();
|
||||
} else {
|
||||
// not actually a promise, this is sync
|
||||
this.#input = this.getFileContents();
|
||||
}
|
||||
}
|
||||
|
||||
return this.#input;
|
||||
}
|
||||
|
||||
getHash() {
|
||||
if (this.#computedHash) {
|
||||
return this.#computedHash;
|
||||
}
|
||||
|
||||
// debug("Creating hash for %o", this.src);
|
||||
let hashContents = [];
|
||||
|
||||
if(existsCache.exists(this.src)) {
|
||||
let fileContents = this.getFileContents();
|
||||
|
||||
// If the file starts with whitespace or the '<' character, it might be SVG.
|
||||
// Otherwise, skip the expensive buffer.toString() call
|
||||
// (no point in unicode encoding a binary file)
|
||||
let fileContentsPrefix = fileContents?.slice(0, 1)?.toString()?.trim();
|
||||
if (!fileContentsPrefix || fileContentsPrefix[0] == "<") {
|
||||
// remove all newlines for hashing for better cross-OS hash compatibility (Issue #122)
|
||||
let fileContentsStr = fileContents.toString();
|
||||
let firstFour = fileContentsStr.trim().slice(0, 5);
|
||||
if(firstFour === "<svg " || firstFour === "<?xml") {
|
||||
fileContents = fileContentsStr.replace(/\r|\n/g, '');
|
||||
}
|
||||
}
|
||||
|
||||
hashContents.push(fileContents);
|
||||
} else {
|
||||
// probably a remote URL
|
||||
hashContents.push(this.src);
|
||||
|
||||
// `useCacheValidityInHash` was removed in v6.0.0, but we’ll keep this as part of the hash to maintain consistent hashes across versions
|
||||
if(this.isRemoteUrl && this.assetCache && this.cacheOptions) {
|
||||
hashContents.push(`ValidCache:true`);
|
||||
}
|
||||
}
|
||||
|
||||
// We ignore all keys not relevant to the file processing/output (including `widths`, which is a suffix added to the filename)
|
||||
// e.g. `widths: [300]` and `widths: [300, 600]`, with all else being equal the 300px output of each should have the same hash
|
||||
let keysToKeep = [
|
||||
"sharpOptions",
|
||||
"sharpWebpOptions",
|
||||
"sharpPngOptions",
|
||||
"sharpJpegOptions",
|
||||
"sharpAvifOptions"
|
||||
].sort();
|
||||
|
||||
let hashObject = {};
|
||||
// The code currently assumes are keysToKeep are Object literals (see Util.getSortedObject)
|
||||
for(let key of keysToKeep) {
|
||||
if(this.options[key]) {
|
||||
hashObject[key] = Util.getSortedObject(this.options[key]);
|
||||
}
|
||||
}
|
||||
|
||||
hashContents.push(JSON.stringify(hashObject));
|
||||
|
||||
let base64hash = createHashSync(...hashContents);
|
||||
let truncated = base64hash.substring(0, this.options.hashLength);
|
||||
this.#computedHash = truncated;
|
||||
return truncated;
|
||||
}
|
||||
|
||||
getStat(outputFormat, width, height) {
|
||||
let url;
|
||||
let outputFilename;
|
||||
|
||||
if(this.options.urlFormat && typeof this.options.urlFormat === "function") {
|
||||
let hash;
|
||||
if(!this.options.statsOnly) {
|
||||
hash = this.getHash();
|
||||
}
|
||||
|
||||
url = this.options.urlFormat({
|
||||
hash,
|
||||
src: this.src,
|
||||
width,
|
||||
format: outputFormat,
|
||||
}, this.options);
|
||||
} else {
|
||||
let hash = this.getHash();
|
||||
outputFilename = ImagePath.getFilename(hash, this.src, width, outputFormat, this.options);
|
||||
if(Util.isFullUrl(this.options.urlPath)) {
|
||||
url = new URL(outputFilename, this.options.urlPath).toString();
|
||||
} else {
|
||||
url = ImagePath.convertFilePathToUrl(this.options.urlPath, outputFilename);
|
||||
}
|
||||
}
|
||||
|
||||
let statEntry = {
|
||||
format: outputFormat,
|
||||
width: width,
|
||||
height: height,
|
||||
url: url,
|
||||
sourceType: MIME_TYPES[outputFormat],
|
||||
srcset: `${url} ${width}w`,
|
||||
// Not available in stats* functions below
|
||||
// size // only after processing
|
||||
};
|
||||
|
||||
if(outputFilename) {
|
||||
statEntry.filename = outputFilename; // optional
|
||||
statEntry.outputPath = path.join(this.options.outputDir, outputFilename); // optional
|
||||
}
|
||||
|
||||
return statEntry;
|
||||
}
|
||||
|
||||
// https://jdhao.github.io/2019/07/31/image_rotation_exif_info/
|
||||
// Orientations 5 to 8 mean image is rotated ±90º (width/height are flipped)
|
||||
needsRotation(orientation) {
|
||||
// Sharp's metadata API exposes undefined EXIF orientations >8 as 1 (normal) but check anyways
|
||||
return orientation >= 5 && orientation <= 8;
|
||||
}
|
||||
|
||||
isAnimated(metadata) {
|
||||
// sharp options have animated image support enabled
|
||||
if(!this.options?.sharpOptions?.animated) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let isAnimationFriendlyFormat = ANIMATED_TYPES.includes(metadata.format);
|
||||
if(!isAnimationFriendlyFormat) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(metadata?.pages) {
|
||||
// input has multiple pages: https://sharp.pixelplumbing.com/api-input#metadata
|
||||
// this is *unknown* when not called from `resize` (limited metadata available)
|
||||
return metadata?.pages > 1;
|
||||
}
|
||||
|
||||
// Best guess
|
||||
return isAnimationFriendlyFormat;
|
||||
}
|
||||
|
||||
getEntryFormat(metadata) {
|
||||
return metadata.format || this.options.overrideInputFormat;
|
||||
}
|
||||
|
||||
// metadata so far: width, height, format
|
||||
// src is used to calculate the output file names
|
||||
getFullStats(metadata) {
|
||||
let results = [];
|
||||
let isImageAnimated = this.isAnimated(metadata) && Array.isArray(this.options.formatFiltering) && this.options.formatFiltering.includes("animated");
|
||||
let hasAlpha = metadata.hasAlpha && Array.isArray(this.options.formatFiltering) && this.options.formatFiltering.includes("transparent");
|
||||
let entryFormat = this.getEntryFormat(metadata);
|
||||
let outputFormats = Image.getFormatsArray(this.options.formats, entryFormat, this.options.svgShortCircuit, isImageAnimated, hasAlpha);
|
||||
|
||||
if (this.needsRotation(metadata.orientation)) {
|
||||
[metadata.height, metadata.width] = [metadata.width, metadata.height];
|
||||
}
|
||||
|
||||
if(metadata.pageHeight) {
|
||||
// When the { animated: true } option is provided to sharp, animated
|
||||
// image formats like gifs or webp will have an inaccurate `height` value
|
||||
// in their metadata which is actually the height of every single frame added together.
|
||||
// In these cases, the metadata will contain an additional `pageHeight` property which
|
||||
// is the height that the image should be displayed at.
|
||||
metadata.height = metadata.pageHeight;
|
||||
}
|
||||
|
||||
for(let outputFormat of outputFormats) {
|
||||
if(!outputFormat || outputFormat === "auto") {
|
||||
throw new Error("When using statsSync or statsByDimensionsSync, `formats: [null | 'auto']` to use the native image format is not supported.");
|
||||
}
|
||||
|
||||
if(outputFormat === "svg") {
|
||||
if(entryFormat === "svg") {
|
||||
let svgStats = this.getStat("svg", metadata.width, metadata.height);
|
||||
|
||||
// SVG metadata.size is only available with Buffer input (remote urls)
|
||||
if(metadata.size) {
|
||||
// Note this is unfair for comparison with raster formats because its uncompressed (no GZIP, etc)
|
||||
svgStats.size = metadata.size;
|
||||
}
|
||||
results.push(svgStats);
|
||||
|
||||
if(this.options.svgShortCircuit === true) {
|
||||
break;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
debug("Skipping SVG output for %o: received raster input.", this.src);
|
||||
continue;
|
||||
}
|
||||
} else { // not outputting SVG (might still be SVG input though!)
|
||||
let widths = Image.getValidWidths(metadata.width, this.options.widths, metadata.format === "svg" && this.options.svgAllowUpscale, this.options.minimumThreshold);
|
||||
for(let width of widths) {
|
||||
let height = Image.getAspectRatioHeight(metadata, width);
|
||||
|
||||
results.push(this.getStat(outputFormat, width, height));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.#transformRawFiles(results);
|
||||
}
|
||||
|
||||
static getDimensionsFromSharp(sharpInstance, stat) {
|
||||
let dims = {};
|
||||
if(sharpInstance.options.width > -1) {
|
||||
dims.width = sharpInstance.options.width;
|
||||
dims.resized = true;
|
||||
}
|
||||
if(sharpInstance.options.height > -1) {
|
||||
dims.height = sharpInstance.options.height;
|
||||
dims.resized = true;
|
||||
}
|
||||
|
||||
if(dims.width || dims.height) {
|
||||
if(!dims.width) {
|
||||
dims.width = Image.getAspectRatioWidth(stat, dims.height);
|
||||
}
|
||||
if(!dims.height) {
|
||||
dims.height = Image.getAspectRatioHeight(stat, dims.width);
|
||||
}
|
||||
}
|
||||
|
||||
return dims;
|
||||
}
|
||||
|
||||
static getAspectRatioWidth(originalDimensions, newHeight) {
|
||||
return Math.floor(newHeight * originalDimensions.width / originalDimensions.height);
|
||||
}
|
||||
|
||||
static getAspectRatioHeight(originalDimensions, newWidth) {
|
||||
// Warning: if this is a guess via statsByDimensionsSync and that guess is wrong
|
||||
// The aspect ratio will be wrong and any height/widths returned will be wrong!
|
||||
return Math.floor(newWidth * originalDimensions.height / originalDimensions.width);
|
||||
}
|
||||
|
||||
getOutputSize(contents, filePath) {
|
||||
if(contents) {
|
||||
if(this.options.svgCompressionSize === "br") {
|
||||
return brotliSize(contents);
|
||||
}
|
||||
|
||||
if("length" in contents) {
|
||||
return contents.length;
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to looking on local file system
|
||||
if(!filePath) {
|
||||
throw new Error("`filePath` expected.");
|
||||
}
|
||||
|
||||
return fs.statSync(filePath).size;
|
||||
}
|
||||
|
||||
isOutputCached(targetFile, sourceInput) {
|
||||
if(!this.options.useCache) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// last cache was a miss, so we must write to disk
|
||||
if(this.assetCache && !this.assetCache.wasLastFetchCacheHit()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!diskCache.isCached(targetFile, sourceInput, !Util.isRequested(this.options.generatedVia))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// src should be a file path to an image or a buffer
|
||||
async resize(input) {
|
||||
let sharpInputImage = sharp(input, Object.assign({
|
||||
// Deprecated by sharp, use `failOn` option instead
|
||||
// https://github.com/lovell/sharp/blob/1533bf995acda779313fc178d2b9d46791349961/lib/index.d.ts#L915
|
||||
failOnError: false,
|
||||
}, this.options.sharpOptions));
|
||||
|
||||
// Must find the image format from the metadata
|
||||
// File extensions lie or may not be present in the src url!
|
||||
let sharpMetadata = await sharpInputImage.metadata();
|
||||
|
||||
let outputFilePromises = [];
|
||||
|
||||
let fullStats = this.getFullStats(sharpMetadata);
|
||||
|
||||
for(let outputFormat in fullStats) {
|
||||
for(let stat of fullStats[outputFormat]) {
|
||||
if(this.isOutputCached(stat.outputPath, input)) {
|
||||
// Cached images already exist in output
|
||||
let outputFileContents;
|
||||
|
||||
if(this.options.dryRun || outputFormat === "svg" && this.options.svgCompressionSize === "br") {
|
||||
outputFileContents = this.getFileContents(stat.outputPath);
|
||||
}
|
||||
|
||||
if(this.options.dryRun) {
|
||||
stat.buffer = outputFileContents;
|
||||
}
|
||||
|
||||
stat.size = this.getOutputSize(outputFileContents, stat.outputPath);
|
||||
|
||||
outputFilePromises.push(Promise.resolve(stat));
|
||||
continue;
|
||||
}
|
||||
|
||||
let sharpInstance = sharpInputImage.clone();
|
||||
let transform = this.options.transform;
|
||||
let isTransformResize = false;
|
||||
|
||||
if(transform) {
|
||||
if(typeof transform !== "function") {
|
||||
throw new Error("Expected `function` type in `transform` option. Received: " + transform);
|
||||
}
|
||||
|
||||
await transform(sharpInstance);
|
||||
|
||||
// Resized in a transform (maybe for a crop)
|
||||
let dims = Image.getDimensionsFromSharp(sharpInstance, stat);
|
||||
if(dims.resized) {
|
||||
isTransformResize = true;
|
||||
|
||||
// Overwrite current `stat` object with new sizes and file names
|
||||
stat = this.getStat(stat.format, dims.width, dims.height);
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/11ty/eleventy-img/issues/244
|
||||
sharpInstance.keepIccProfile();
|
||||
|
||||
// Output images do not include orientation metadata (https://github.com/11ty/eleventy-img/issues/52)
|
||||
// Use sharp.rotate to bake orientation into the image (https://github.com/lovell/sharp/blob/v0.32.6/docs/api-operation.md#rotate):
|
||||
// > If no angle is provided, it is determined from the EXIF data. Mirroring is supported and may infer the use of a flip operation.
|
||||
// > The use of rotate without an angle will remove the EXIF Orientation tag, if any.
|
||||
if(this.options.fixOrientation || this.needsRotation(sharpMetadata.orientation)) {
|
||||
sharpInstance.rotate();
|
||||
}
|
||||
|
||||
if(!isTransformResize) {
|
||||
if(stat.width < sharpMetadata.width || (this.options.svgAllowUpscale && sharpMetadata.format === "svg")) {
|
||||
let resizeOptions = {
|
||||
width: stat.width
|
||||
};
|
||||
|
||||
if(sharpMetadata.format !== "svg" || !this.options.svgAllowUpscale) {
|
||||
resizeOptions.withoutEnlargement = true;
|
||||
}
|
||||
|
||||
sharpInstance.resize(resizeOptions);
|
||||
}
|
||||
}
|
||||
|
||||
// Format hooks take priority over Sharp processing.
|
||||
// format hooks are only used for SVG out of the box
|
||||
if(this.options.formatHooks && this.options.formatHooks[outputFormat]) {
|
||||
let hookResult = await this.options.formatHooks[outputFormat].call(stat, sharpInstance);
|
||||
if(hookResult) {
|
||||
stat.size = this.getOutputSize(hookResult);
|
||||
|
||||
if(this.options.dryRun) {
|
||||
stat.buffer = Buffer.from(hookResult);
|
||||
|
||||
outputFilePromises.push(Promise.resolve(stat));
|
||||
} else {
|
||||
this.directoryManager.createFromFile(stat.outputPath);
|
||||
|
||||
debugAssets("[11ty/eleventy-img] Writing %o", stat.outputPath);
|
||||
|
||||
outputFilePromises.push(fsp.writeFile(stat.outputPath, hookResult).then(() => stat));
|
||||
}
|
||||
}
|
||||
} else { // not a format hook
|
||||
let sharpFormatOptions = this.getSharpOptionsForFormat(outputFormat);
|
||||
let hasFormatOptions = Object.keys(sharpFormatOptions).length > 0;
|
||||
if(hasFormatOptions || outputFormat && sharpMetadata.format !== outputFormat) {
|
||||
// https://github.com/lovell/sharp/issues/3680
|
||||
// Fix heic regression in sharp 0.33
|
||||
if(outputFormat === "heic" && !sharpFormatOptions.compression) {
|
||||
sharpFormatOptions.compression = "av1";
|
||||
}
|
||||
sharpInstance.toFormat(outputFormat, sharpFormatOptions);
|
||||
}
|
||||
|
||||
if(!this.options.dryRun && stat.outputPath) {
|
||||
// Should never write when dryRun is true
|
||||
this.directoryManager.createFromFile(stat.outputPath);
|
||||
|
||||
debugAssets("[11ty/eleventy-img] Writing %o", stat.outputPath);
|
||||
|
||||
outputFilePromises.push(
|
||||
sharpInstance.toFile(stat.outputPath)
|
||||
.then(info => {
|
||||
stat.size = info.size;
|
||||
return stat;
|
||||
})
|
||||
);
|
||||
} else {
|
||||
outputFilePromises.push(sharpInstance.toBuffer({ resolveWithObject: true }).then(({ data, info }) => {
|
||||
stat.buffer = data;
|
||||
stat.size = info.size;
|
||||
return stat;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if(stat.outputPath) {
|
||||
if(this.options.dryRun) {
|
||||
debug( "Generated %o", stat.url );
|
||||
} else {
|
||||
debug( "Wrote %o", stat.outputPath );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(outputFilePromises).then(files => this.#finalizeResults(this.#transformRawFiles(files)));
|
||||
}
|
||||
|
||||
async getStatsOnly() {
|
||||
if(typeof this.src !== "string" || !this.options.statsOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
let input;
|
||||
if(Util.isRemoteUrl(this.src)) {
|
||||
if(this.rawOptions.remoteImageMetadata?.width && this.rawOptions.remoteImageMetadata?.height) {
|
||||
return this.getFullStats({
|
||||
width: this.rawOptions.remoteImageMetadata.width,
|
||||
height: this.rawOptions.remoteImageMetadata.height,
|
||||
format: this.rawOptions.remoteImageMetadata.format, // only required if you want to use the "auto" format
|
||||
guess: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch remote image to operate on it
|
||||
// `remoteImageMetadata` is no longer required for statsOnly on remote images
|
||||
input = await this.getInput();
|
||||
}
|
||||
|
||||
// Local images
|
||||
try {
|
||||
// Related to https://github.com/11ty/eleventy-img/issues/295
|
||||
let { width, height, type } = getImageSize(input || this.src);
|
||||
|
||||
return this.getFullStats({
|
||||
width,
|
||||
height,
|
||||
format: type // only required if you want to use the "auto" format
|
||||
});
|
||||
} catch(e) {
|
||||
throw new Error(`Eleventy Image error (statsOnly): \`image-size\` on "${this.src}" failed. Original error: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// returns raw Promise
|
||||
queue() {
|
||||
if(!this.#queue) {
|
||||
return Promise.reject(new Error("Missing #queue."));
|
||||
}
|
||||
|
||||
if(this.#queuePromise) {
|
||||
return this.#queuePromise;
|
||||
}
|
||||
|
||||
debug("Processing %o (in-memory cache miss), options: %o", this.src, this.options);
|
||||
|
||||
this.#queuePromise = this.#queue.add(async () => {
|
||||
try {
|
||||
if(typeof this.src === "string" && this.options.statsOnly) {
|
||||
return this.getStatsOnly();
|
||||
}
|
||||
|
||||
this.buildLogger.log(`Processing ${this.buildLogger.getFriendlyImageSource(this.src)}`, this.options);
|
||||
|
||||
let input = await this.getInput();
|
||||
|
||||
return this.resize(input);
|
||||
} catch(e) {
|
||||
this.buildLogger.error(`Error: ${e.message} (via ${this.buildLogger.getFriendlyImageSource(this.src)})`, this.options);
|
||||
|
||||
if(this.options.failOnError) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return this.#queuePromise;
|
||||
}
|
||||
|
||||
// Factory to return from cache if available
|
||||
static create(src, options = {}) {
|
||||
let img = new Image(src, options);
|
||||
|
||||
// use resolved options for this
|
||||
if(!img.options.useCache) {
|
||||
return img;
|
||||
}
|
||||
|
||||
let key = img.getInMemoryCacheKey();
|
||||
let cached = memCache.get(key, !options.transformOnRequest && !Util.isRequested(options.generatedVia));
|
||||
if(cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
memCache.add(key, img);
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
/* `statsSync` doesn’t generate any files, but will tell you where
|
||||
* the asynchronously generated files will end up! This is useful
|
||||
* in synchronous-only template environments where you need the
|
||||
* image URLs synchronously but can’t rely on the files being in
|
||||
* the correct location yet.
|
||||
*
|
||||
* `options.dryRun` is still asynchronous but also doesn’t generate
|
||||
* any files.
|
||||
*/
|
||||
statsSync() {
|
||||
if(this.isRemoteUrl) {
|
||||
throw new Error("`statsSync` is not supported with remote sources. Use `statsByDimensionsSync(src, width, height, options)` instead.");
|
||||
}
|
||||
|
||||
let dimensions = getImageSize(this.src);
|
||||
|
||||
return this.getFullStats({
|
||||
width: dimensions.width,
|
||||
height: dimensions.height,
|
||||
format: dimensions.type,
|
||||
});
|
||||
}
|
||||
|
||||
static statsSync(src, opts) {
|
||||
if(typeof src === "string" && Util.isRemoteUrl(src)) {
|
||||
throw new Error("`statsSync` is not supported with remote sources. Use `statsByDimensionsSync(src, width, height, options)` instead.");
|
||||
}
|
||||
|
||||
let img = Image.create(src, opts);
|
||||
return img.statsSync();
|
||||
}
|
||||
|
||||
statsByDimensionsSync(width, height) {
|
||||
let dimensions = {
|
||||
width,
|
||||
height,
|
||||
guess: true
|
||||
};
|
||||
return this.getFullStats(dimensions);
|
||||
}
|
||||
|
||||
static statsByDimensionsSync(src, width, height, opts) {
|
||||
let img = Image.create(src, opts);
|
||||
return img.statsByDimensionsSync(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Image;
|
||||
54
node_modules/@11ty/eleventy-img/src/memory-cache.js
generated
vendored
Normal file
54
node_modules/@11ty/eleventy-img/src/memory-cache.js
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
const debug = require("debug")("Eleventy:Image");
|
||||
|
||||
class MemoryCache {
|
||||
constructor() {
|
||||
this.cache = {};
|
||||
this.hitCounter = 0;
|
||||
this.missCounter = 0;
|
||||
}
|
||||
|
||||
resetCount() {
|
||||
this.hitCounter = 0;
|
||||
this.missCounter = 0;
|
||||
}
|
||||
|
||||
getCount() {
|
||||
return [this.hitCounter, this.missCounter];
|
||||
}
|
||||
|
||||
add(key, results) {
|
||||
this.cache[key] = {
|
||||
results
|
||||
};
|
||||
|
||||
debug("Unique images processed: %o", this.size());
|
||||
}
|
||||
|
||||
get(key, incrementCounts = false) {
|
||||
if(this.cache[key]) {
|
||||
if(incrementCounts) {
|
||||
this.hitCounter++;
|
||||
}
|
||||
// debug("Images re-used (via in-memory cache): %o", this.hitCounter);
|
||||
|
||||
// may return promise
|
||||
return this.cache[key].results;
|
||||
}
|
||||
|
||||
if(incrementCounts) {
|
||||
this.missCounter++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
has(key) {
|
||||
return key in this.cache;
|
||||
}
|
||||
|
||||
size() {
|
||||
return Object.keys(this.cache).length;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MemoryCache;
|
||||
105
node_modules/@11ty/eleventy-img/src/on-request-during-serve-plugin.js
generated
vendored
Normal file
105
node_modules/@11ty/eleventy-img/src/on-request-during-serve-plugin.js
generated
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
const fs = require("node:fs");
|
||||
const { TemplatePath } = require("@11ty/eleventy-utils");
|
||||
|
||||
const eleventyImage = require("../img.js");
|
||||
const setupLogger = eleventyImage.setupLogger;
|
||||
const Util = require("./util.js");
|
||||
|
||||
const debug = require("debug")("Eleventy:Image");
|
||||
|
||||
function eleventyImageOnRequestDuringServePlugin(eleventyConfig, options = {}) {
|
||||
try {
|
||||
// Throw an error if the application is not using Eleventy 3.0.0-alpha.7 or newer (including prereleases).
|
||||
eleventyConfig.versionCheck(">=3.0.0-alpha.7");
|
||||
} catch(e) {
|
||||
console.log( `[11ty/eleventy-img] Warning: your version of Eleventy is incompatible with the dynamic image rendering plugin (see \`eleventyImageOnRequestDuringServePlugin\`). Any dynamically rendered images will 404 (be missing) during --serve mode but will not affect the standard build output: ${e.message}` );
|
||||
}
|
||||
|
||||
setupLogger(eleventyConfig, {});
|
||||
|
||||
// Eleventy 3.0 or newer only.
|
||||
eleventyConfig.setServerOptions({
|
||||
onRequest: {
|
||||
// TODO work with dev-server’s option for `injectedScriptsFolder`
|
||||
"/.11ty/image/": async function({ url }) {
|
||||
// src could be file path or full url
|
||||
let src = url.searchParams.get("src");
|
||||
let imageFormat = url.searchParams.get("format");
|
||||
let width = parseInt(url.searchParams.get("width"), 10);
|
||||
let via = url.searchParams.get("via");
|
||||
|
||||
let defaultOptions;
|
||||
if(via === "webc") {
|
||||
defaultOptions = eleventyConfig.getFilter("__private_eleventyImageConfigurationOptions")();
|
||||
} else if(via === "transform") {
|
||||
defaultOptions = eleventyConfig.getFilter("__private_eleventyImageTransformConfigurationOptions")();
|
||||
}
|
||||
// if using this plugin directly (not via webc or transform), global default options will need to be passed in to the `addPlugin` call directly
|
||||
|
||||
// Prefer options passed to this plugin, fallback to Transform plugin or WebC options if the image source was generated via those options.
|
||||
let opts = Object.assign({}, defaultOptions, options, {
|
||||
widths: [width || "auto"],
|
||||
formats: [imageFormat || "auto"],
|
||||
|
||||
dryRun: true,
|
||||
cacheOptions: {
|
||||
// We *do* want to write files to .cache for re-use here.
|
||||
dryRun: false
|
||||
},
|
||||
|
||||
transformOnRequest: false, // use the built images so we don’t go in a loop
|
||||
generatedVia: Util.KEYS.requested,
|
||||
});
|
||||
|
||||
Util.addConfig(eleventyConfig, opts);
|
||||
|
||||
debug( `%o transformed on request to %o at %o width.`, src, imageFormat, width );
|
||||
|
||||
try {
|
||||
if(!Util.isFullUrl(src)) {
|
||||
// Image path on file system must be in working directory
|
||||
src = TemplatePath.absolutePath(".", src);
|
||||
|
||||
if(!fs.existsSync(src) || !src.startsWith(TemplatePath.absolutePath("."))) {
|
||||
throw new Error(`Invalid path: ${src}`);
|
||||
}
|
||||
}
|
||||
|
||||
let stats = await eleventyImage(src, opts);
|
||||
|
||||
let format = Object.keys(stats).pop();
|
||||
let stat = stats[format][0];
|
||||
if(!stat) {
|
||||
throw new Error("Invalid image format.");
|
||||
}
|
||||
if(!stat.buffer) {
|
||||
throw new Error("Could not find `buffer` property for image.");
|
||||
}
|
||||
|
||||
return {
|
||||
headers: {
|
||||
// TODO Set cache headers to match eleventy-fetch cache options (though remote fetchs are still written to .cache)
|
||||
"Content-Type": stat.sourceType,
|
||||
},
|
||||
body: stat.buffer,
|
||||
};
|
||||
} catch (error) {
|
||||
debug("Error attempting to transform %o: %O", src, error);
|
||||
|
||||
return {
|
||||
status: 500,
|
||||
headers: {
|
||||
"Content-Type": "image/svg+xml",
|
||||
"x-error-message": error.message
|
||||
},
|
||||
body: `<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="${width}" height="${width}" x="0" y="0" viewBox="0 0 1569.4 2186" xml:space="preserve" aria-hidden="true" focusable="false"><style>.st0{fill:#bbb;stroke:#bbb;stroke-width:28;stroke-miterlimit:10}</style><g><path class="st0" d="M562.2 1410.1c-9 0-13.5-12-13.5-36.1V778.9c0-11.5-2.3-16.9-7-16.2-28.4 7.2-42.7 10.8-43.1 10.8-7.9.7-11.8-7.2-11.8-23.7v-51.7c0-14.3 4.3-22.4 12.9-24.2l142.2-36.6c1.1-.3 2.7-.5 4.8-.5 7.9 0 11.8 8.4 11.8 25.3v712c0 24.1-4.7 36.1-14 36.1l-82.3-.1zM930.5 1411.2c-14.4 0-26.8-1-37.4-3-10.6-2-21.6-6.5-33.1-13.5s-20.9-16.6-28.3-28.8-13.4-29.3-18-51.2-7-47.9-7-78.1V960.4c0-7.2-2-10.8-5.9-10.8h-33.4c-9 0-13.5-8.6-13.5-25.8v-29.1c0-17.6 4.5-26.4 13.5-26.4h33.4c3.9 0 5.9-4.8 5.9-14.5l9.7-209.5c1.1-19 5.7-28.5 14-28.5h53.9c9 0 13.5 9.5 13.5 28.5v209.5c0 9.7 2.1 14.5 6.5 14.5H973c9 0 13.5 8.8 13.5 26.4v29.1c0 17.2-4.5 25.8-13.5 25.8h-68.9c-2.5 0-4.2.6-5.1 1.9-.9 1.2-1.3 4.2-1.3 8.9v277.9c0 20.8 1.3 38.2 4 52s6.6 24 11.8 30.4 10.4 10.8 15.6 12.9c5.2 2.2 11.6 3.2 19.1 3.2h38.2c9.7 0 14.5 6.7 14.5 19.9v32.3c0 14.7-5.2 22.1-15.6 22.1l-54.8.1zM1137.2 1475.8c8.2 0 15.4-6.7 21.5-20.2s9.2-32.6 9.2-57.4c0-5.8-3.6-25.7-10.8-59.8l-105.6-438.9c-.7-5-1.1-9-1.1-11.9 0-12.9 2.7-19.4 8.1-19.4h65.2c5 0 9.1 1.7 12.4 5.1s5.8 10.3 7.5 20.7l70 370.5c1.4 4.3 2.3 6.5 2.7 6.5 1.4 0 2.2-2 2.2-5.9l54.9-369.5c1.4-10.8 3.7-18 6.7-21.8s6.9-5.7 11.6-5.7h45.2c6.1 0 9.2 7 9.2 21 0 3.2-.4 7.4-1.1 12.4l-95.9 499.3c-7.5 41.3-15.8 72.9-24.8 94.8s-19 36.8-30.2 44.7c-11.1 7.9-25.8 12-44.2 12.4h-5.4c-29.1 0-48.8-7.7-59.2-23.2-2.9-3.2-4.3-11.5-4.3-24.8 0-26.6 4.3-39.9 12.9-39.9.7 0 7.2 1.8 19.4 5.4 12.4 3.8 20.3 5.6 23.9 5.6z"/><g><path class="st0" d="M291.2 1411.1c-9 0-13.5-12-13.5-36.1V779.9c0-11.5-2.3-16.9-7-16.2-28.4 7.2-42.7 10.8-43.1 10.8-7.9.7-11.8-7.2-11.8-23.7v-51.7c0-14.3 4.3-22.4 12.9-24.2L371 638.2c1.1-.3 2.7-.5 4.8-.5 7.9 0 11.8 8.4 11.8 25.3v712c0 24.1-4.7 36.1-14 36.1h-82.4z"/></g></g></svg>`,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
eleventyImageOnRequestDuringServePlugin,
|
||||
};
|
||||
210
node_modules/@11ty/eleventy-img/src/transform-plugin.js
generated
vendored
Normal file
210
node_modules/@11ty/eleventy-img/src/transform-plugin.js
generated
vendored
Normal file
@ -0,0 +1,210 @@
|
||||
const path = require("node:path");
|
||||
const Util = require("./util.js");
|
||||
const { imageAttributesToPosthtmlNode, getOutputDirectory, cleanTag, isIgnored, isOptional } = require("./image-attrs-to-posthtml-node.js");
|
||||
const { getGlobalOptions } = require("./global-options.js");
|
||||
const { eleventyImageOnRequestDuringServePlugin } = require("./on-request-during-serve-plugin.js");
|
||||
|
||||
const PLACEHOLDER_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNgYAAAAAMAASsJTYQAAAAASUVORK5CYII=";
|
||||
|
||||
const ATTRS = {
|
||||
ORIGINAL_SOURCE: "eleventy:internal_original_src",
|
||||
};
|
||||
|
||||
function getSrcAttributeValue(sourceNode/*, rootTargetNode*/) {
|
||||
// Debatable TODO: use rootTargetNode (if `picture`) to retrieve a potentially higher quality source from <source srcset>
|
||||
return sourceNode.attrs?.src;
|
||||
}
|
||||
|
||||
function assignAttributes(rootTargetNode, newNode) {
|
||||
// only copy attributes if old and new tag name are the same (picture => picture, img => img)
|
||||
if(rootTargetNode.tag !== newNode.tag) {
|
||||
delete rootTargetNode.attrs;
|
||||
}
|
||||
|
||||
if(!rootTargetNode.attrs) {
|
||||
rootTargetNode.attrs = {};
|
||||
}
|
||||
|
||||
// Copy all new attributes to target
|
||||
if(newNode.attrs) {
|
||||
Object.assign(rootTargetNode.attrs, newNode.attrs);
|
||||
}
|
||||
}
|
||||
|
||||
function getOutputLocations(originalSource, outputDirectoryFromAttribute, pageContext, options) {
|
||||
let projectOutputDirectory = options.directories.output;
|
||||
|
||||
if(outputDirectoryFromAttribute) {
|
||||
if(path.isAbsolute(outputDirectoryFromAttribute)) {
|
||||
return {
|
||||
outputDir: path.join(projectOutputDirectory, outputDirectoryFromAttribute),
|
||||
urlPath: outputDirectoryFromAttribute,
|
||||
};
|
||||
}
|
||||
return {
|
||||
outputDir: path.join(projectOutputDirectory, pageContext.url, outputDirectoryFromAttribute),
|
||||
urlPath: path.join(pageContext.url, outputDirectoryFromAttribute),
|
||||
};
|
||||
}
|
||||
|
||||
if(options.urlPath) {
|
||||
// do nothing, user has specified directories in the plugin options.
|
||||
return {};
|
||||
}
|
||||
|
||||
if(path.isAbsolute(originalSource)) {
|
||||
// if the path is an absolute one (relative to the content directory) write to a global output directory to avoid duplicate writes for identical source images.
|
||||
return {
|
||||
outputDir: path.join(projectOutputDirectory, "/img/"),
|
||||
urlPath: "/img/",
|
||||
};
|
||||
}
|
||||
|
||||
// If original source is a relative one, this colocates images to the template output.
|
||||
let dir = path.dirname(pageContext.outputPath);
|
||||
|
||||
// filename is included in url: ./dir/post.html => /dir/post.html
|
||||
if(pageContext.outputPath.endsWith(pageContext.url)) {
|
||||
// remove file name
|
||||
let split = pageContext.url.split("/");
|
||||
split[split.length - 1] = "";
|
||||
|
||||
return {
|
||||
outputDir: dir,
|
||||
urlPath: split.join("/"),
|
||||
};
|
||||
}
|
||||
|
||||
// filename is not included in url: ./dir/post/index.html => /dir/post/
|
||||
return {
|
||||
outputDir: dir,
|
||||
urlPath: pageContext.url,
|
||||
};
|
||||
}
|
||||
|
||||
function transformTag(context, sourceNode, rootTargetNode, opts) {
|
||||
let originalSource = getSrcAttributeValue(sourceNode, rootTargetNode);
|
||||
|
||||
if(!originalSource) {
|
||||
return sourceNode;
|
||||
}
|
||||
|
||||
let { inputPath } = context.page;
|
||||
|
||||
sourceNode.attrs.src = Util.normalizeImageSource({
|
||||
input: opts.directories.input,
|
||||
inputPath,
|
||||
}, originalSource, {
|
||||
isViaHtml: true, // this reference came from HTML, so we can decode the file name
|
||||
});
|
||||
|
||||
if(sourceNode.attrs.src !== originalSource) {
|
||||
sourceNode.attrs[ATTRS.ORIGINAL_SOURCE] = originalSource;
|
||||
}
|
||||
|
||||
let outputDirectoryFromAttribute = getOutputDirectory(sourceNode);
|
||||
let instanceOptions = getOutputLocations(originalSource, outputDirectoryFromAttribute, context.page, opts);
|
||||
|
||||
// returns promise
|
||||
return imageAttributesToPosthtmlNode(sourceNode.attrs, instanceOptions, opts).then(newNode => {
|
||||
// node.tag
|
||||
// node.attrs
|
||||
// node.content
|
||||
|
||||
assignAttributes(rootTargetNode, newNode);
|
||||
|
||||
rootTargetNode.tag = newNode.tag;
|
||||
rootTargetNode.content = newNode.content;
|
||||
}, (error) => {
|
||||
if(isOptional(sourceNode) || !opts.failOnError) {
|
||||
if(isOptional(sourceNode, "keep")) {
|
||||
// replace with the original source value, no image transformation is taking place
|
||||
if(sourceNode.attrs[ATTRS.ORIGINAL_SOURCE]) {
|
||||
sourceNode.attrs.src = sourceNode.attrs[ATTRS.ORIGINAL_SOURCE];
|
||||
}
|
||||
// leave as-is, likely 404 when a user visits the page
|
||||
} else if(isOptional(sourceNode, "placeholder")) {
|
||||
// transparent png
|
||||
sourceNode.attrs.src = PLACEHOLDER_DATA_URI;
|
||||
} else if(isOptional(sourceNode)) {
|
||||
delete sourceNode.attrs.src;
|
||||
}
|
||||
|
||||
// optional or don’t fail on error
|
||||
cleanTag(sourceNode);
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
function eleventyImageTransformPlugin(eleventyConfig, options = {}) {
|
||||
options = Object.assign({
|
||||
extensions: "html",
|
||||
transformOnRequest: process.env.ELEVENTY_RUN_MODE === "serve",
|
||||
}, options);
|
||||
|
||||
if(options.transformOnRequest !== false) {
|
||||
// Add the on-request plugin automatically (unless opt-out in this plugins options only)
|
||||
eleventyConfig.addPlugin(eleventyImageOnRequestDuringServePlugin);
|
||||
}
|
||||
|
||||
// Notably, global options are not shared automatically with the WebC `eleventyImagePlugin` above.
|
||||
// Devs can pass in the same object to both if they want!
|
||||
let opts = getGlobalOptions(eleventyConfig, options, "transform");
|
||||
|
||||
eleventyConfig.addJavaScriptFunction("__private_eleventyImageTransformConfigurationOptions", () => {
|
||||
return opts;
|
||||
});
|
||||
|
||||
function posthtmlPlugin(context) {
|
||||
return async (tree) => {
|
||||
let promises = [];
|
||||
let match = tree.match;
|
||||
|
||||
tree.match({ tag: 'picture' }, pictureNode => {
|
||||
match.call(pictureNode, { tag: 'img' }, imgNode => {
|
||||
imgNode._insideOfPicture = true;
|
||||
|
||||
if(!isIgnored(imgNode) && !imgNode?.attrs?.src?.startsWith("data:")) {
|
||||
promises.push(transformTag(context, imgNode, pictureNode, opts));
|
||||
}
|
||||
|
||||
return imgNode;
|
||||
});
|
||||
|
||||
return pictureNode;
|
||||
});
|
||||
|
||||
tree.match({ tag: 'img' }, (imgNode) => {
|
||||
if(imgNode._insideOfPicture) {
|
||||
delete imgNode._insideOfPicture;
|
||||
} else if(isIgnored(imgNode) || imgNode?.attrs?.src?.startsWith("data:")) {
|
||||
cleanTag(imgNode);
|
||||
} else {
|
||||
promises.push(transformTag(context, imgNode, imgNode, opts));
|
||||
}
|
||||
|
||||
return imgNode;
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
return tree;
|
||||
};
|
||||
}
|
||||
|
||||
if(!eleventyConfig.htmlTransformer || !("addPosthtmlPlugin" in eleventyConfig.htmlTransformer)) {
|
||||
throw new Error("[@11ty/eleventy-img] `eleventyImageTransformPlugin` is not compatible with this version of Eleventy. You will need to use v3.0.0 or newer.");
|
||||
}
|
||||
|
||||
eleventyConfig.htmlTransformer.addPosthtmlPlugin(options.extensions, posthtmlPlugin, {
|
||||
priority: -1, // we want this to go before <base> or inputpath to url
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
eleventyImageTransformPlugin,
|
||||
};
|
||||
78
node_modules/@11ty/eleventy-img/src/util.js
generated
vendored
Normal file
78
node_modules/@11ty/eleventy-img/src/util.js
generated
vendored
Normal file
@ -0,0 +1,78 @@
|
||||
const path = require("node:path");
|
||||
|
||||
class Util {
|
||||
static KEYS = {
|
||||
requested: "requested"
|
||||
};
|
||||
|
||||
/*
|
||||
* Does not mutate, returns new Object.
|
||||
*/
|
||||
static getSortedObject(unordered) {
|
||||
let keys = Object.keys(unordered).sort();
|
||||
let obj = {};
|
||||
for(let key of keys) {
|
||||
obj[key] = unordered[key];
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
static isRemoteUrl(url) {
|
||||
try {
|
||||
const validUrl = new URL(url);
|
||||
|
||||
if (validUrl.protocol.startsWith("https:") || validUrl.protocol.startsWith("http:")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
} catch(e) {
|
||||
// invalid url OR local path
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static normalizeImageSource({ input, inputPath }, src, options = {}) {
|
||||
let { isViaHtml } = Object.assign({
|
||||
isViaHtml: false
|
||||
}, options);
|
||||
|
||||
if(Util.isFullUrl(src)) {
|
||||
return src;
|
||||
}
|
||||
|
||||
if(isViaHtml) {
|
||||
src = decodeURIComponent(src);
|
||||
}
|
||||
|
||||
if(!path.isAbsolute(src)) {
|
||||
// if the image src is relative, make it relative to the template file (inputPath);
|
||||
let dir = path.dirname(inputPath);
|
||||
return path.join(dir, src);
|
||||
}
|
||||
|
||||
// if the image src is absolute, make it relative to the input/content directory.
|
||||
return path.join(input, src);
|
||||
}
|
||||
|
||||
static isRequested(generatedVia) {
|
||||
return generatedVia === this.KEYS.requested;
|
||||
}
|
||||
|
||||
static addConfig(eleventyConfig, options) {
|
||||
if(!eleventyConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.defineProperty(options, "eleventyConfig", {
|
||||
value: eleventyConfig,
|
||||
enumerable: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Temporary alias for changes made in https://github.com/11ty/eleventy-img/pull/138
|
||||
Util.isFullUrl = Util.isRemoteUrl;
|
||||
|
||||
module.exports = Util;
|
||||
23
node_modules/@11ty/eleventy-img/src/webc-options-plugin.js
generated
vendored
Normal file
23
node_modules/@11ty/eleventy-img/src/webc-options-plugin.js
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
const { getGlobalOptions } = require("./global-options.js");
|
||||
const { eleventyImageOnRequestDuringServePlugin } = require("./on-request-during-serve-plugin.js");
|
||||
|
||||
function eleventyWebcOptionsPlugin(eleventyConfig, options = {}) {
|
||||
options = Object.assign({
|
||||
transformOnRequest: process.env.ELEVENTY_RUN_MODE === "serve",
|
||||
}, options);
|
||||
|
||||
// Notably, global options are not shared automatically with the `eleventyImageTransformPlugin` below.
|
||||
// Devs can pass in the same object to both if they want!
|
||||
eleventyConfig.addJavaScriptFunction("__private_eleventyImageConfigurationOptions", () => {
|
||||
return getGlobalOptions(eleventyConfig, options, "webc");
|
||||
});
|
||||
|
||||
if(options.transformOnRequest !== false) {
|
||||
// Add the on-request plugin automatically (unless opt-out in this plugins options only)
|
||||
eleventyConfig.addPlugin(eleventyImageOnRequestDuringServePlugin);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
eleventyWebcOptionsPlugin,
|
||||
};
|
||||
Reference in New Issue
Block a user