diff --git a/img.js b/img.js
index 97cd4fd..9d4e733 100644
--- a/img.js
+++ b/img.js
@@ -1,25 +1,30 @@
const path = require("path");
const fs = require("fs");
const fsp = fs.promises;
-const { URL } = require("url");
+
const { createHash } = require("crypto");
const {default: PQueue} = require("p-queue");
const getImageSize = require("image-size");
const sharp = require("sharp");
const brotliSize = require("brotli-size");
-const {RemoteAssetCache, queue} = require("@11ty/eleventy-fetch");
+const { RemoteAssetCache, queue } = require("@11ty/eleventy-fetch");
const svgHook = require("./src/format-hooks/svg.js");
const MemoryCache = require("./src/memory-cache.js");
+const DiskCache = require("./src/disk-cache.js");
+const Util = require("./src/util.js");
-const debug = require("debug")("EleventyImg");
+const debug = require("debug")("Eleventy:Image");
-const globalOptions = {
- widths: [null],
+const GLOBAL_OPTIONS = {
+ widths: ["auto"],
formats: ["webp", "jpeg"], // "png", "svg", "avif"
- concurrency: 10,
+
+ concurrency: 20,
+
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,
@@ -31,11 +36,13 @@ const globalOptions = {
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
- extensions: {},
+
formatHooks: {
svg: svgHook,
},
+
cacheDuration: "1d", // deprecated, use cacheOptions.duration
+
// disk cache for remote assets
cacheOptions: {
// duration: "1d",
@@ -75,6 +82,12 @@ const globalOptions = {
// 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,
+
+ // v5 `extensions` was removed (option to override output format with new file extension), it wasn’t being used anywhere or documented
};
const MIME_TYPES = {
@@ -92,49 +105,25 @@ const FORMAT_ALIASES = {
"svg+xml": "svg",
};
-class Util {
- /*
- * 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;
- } catch(e)
-
- {
- // invalid url OR local path
- return false;
- }
- }
-}
-
-// Temporary alias for changes made in https://github.com/11ty/eleventy-img/pull/138
-Util.isFullUrl = Util.isRemoteUrl;
-
class Image {
- constructor(src, options) {
+ 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.options = Object.assign({}, globalOptions, 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*/) {
+ return `/.11ty/image/?src=${encodeURIComponent(src)}&width=${width}&format=${format}`;
+ };
+
+ this.options.statsOnly = true;
+ }
if(this.isRemoteUrl) {
this.cacheOptions = Object.assign({
@@ -177,17 +166,19 @@ class Image {
return false;
}
- // perf: check to make sure it’s not a string first
- if(typeof this.src !== "string" && Buffer.isBuffer(this.src)) {
- this._contents = this.src;
- }
-
- // TODO @zachleat add a smarter cache here (not too aggressive! must handle input file changes)
if(!this._contents) {
- debug("Reading from file system: %o", this.src);
- this._contents = fs.readFileSync(this.src);
+ // perf: check to make sure it’s not a string first
+ if(typeof this.src !== "string" && Buffer.isBuffer(this.src)) {
+ this._contents = this.src;
+ } else {
+ // TODO @zachleat make this aggressively async.
+ // TODO @zachleat add a smarter cache here (not too aggressive! must handle input file changes)
+ // debug("Reading from file system: %o", this.src);
+ this._contents = fs.readFileSync(this.src);
+ }
}
+
return this._contents;
}
@@ -336,28 +327,34 @@ class Image {
return {};
}
- async getInput() {
- if(this.isRemoteUrl) {
- // fetch remote image Buffer
- if(queue) {
- // eleventy-fetch 3.0+ and eleventy-cache-assets 2.0.4+
- return queue(this.src, () => this.assetCache.fetch());
+ // Returns promise
+ getInput() {
+ // internal cache
+ if(!this.inputPromise) {
+ if(this.isRemoteUrl) {
+ // fetch remote image Buffer
+ if(queue) {
+ // eleventy-fetch 3.0+ and eleventy-cache-assets 2.0.4+
+ this.inputPromise = queue(this.src, () => this.assetCache.fetch());
+ } else {
+ // eleventy-cache-assets 2.0.3 and below
+ this.inputPromise = this.assetCache.fetch(this.cacheOptions);
+ }
+ } else {
+ // TODO @zachleat (multiread): read local file contents here and always return a buffer
+ this.inputPromise = Promise.resolve(this.src);
}
-
- // eleventy-cache-assets 2.0.3 and below
- return this.assetCache.fetch(this.cacheOptions);
}
- // TODO @zachleat (multiread): read local file contents here and always return a buffer
- return this.src;
+ return this.inputPromise;
}
getHash() {
if (this.computedHash) {
- debug("Re-using computed hash for %o: %o", this.src, this.computedHash);
return this.computedHash;
}
+ // debug("Creating hash for %o", this.src);
let hash = createHash("sha256");
if(fs.existsSync(this.src)) {
@@ -419,7 +416,7 @@ class Image {
getStat(outputFormat, width, height) {
let url;
let outputFilename;
- let outputExtension = this.options.extensions[outputFormat] || outputFormat;
+
if(this.options.urlFormat && typeof this.options.urlFormat === "function") {
let hash;
if(!this.options.statsOnly) {
@@ -430,11 +427,11 @@ class Image {
hash,
src: this.src,
width,
- format: outputExtension,
+ format: outputFormat,
}, this.options);
} else {
let hash = this.getHash();
- outputFilename = ImagePath.getFilename(hash, this.src, width, outputExtension, this.options);
+ outputFilename = ImagePath.getFilename(hash, this.src, width, outputFormat, this.options);
url = ImagePath.convertFilePathToUrl(this.options.urlPath, outputFilename);
}
@@ -487,6 +484,7 @@ class Image {
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((metadata.format || this.options.overrideInputFormat) === "svg") {
let svgStats = this.getStat("svg", metadata.width, metadata.height);
@@ -536,7 +534,7 @@ class Image {
let fullStats = this.getFullStats(metadata);
for(let outputFormat in fullStats) {
for(let stat of fullStats[outputFormat]) {
- if(this.options.useCache && fs.existsSync(stat.outputPath)){
+ if(this.options.useCache && diskCache.isCached(stat.outputPath)){
// Cached images already exist in output
let contents;
if(this.options.dryRun) {
@@ -638,7 +636,11 @@ class Image {
}
if(stat.outputPath) {
- debug( "Wrote %o", stat.outputPath );
+ if(this.options.dryRun) {
+ debug( "Generated %o", stat.url );
+ } else {
+ debug( "Wrote %o", stat.outputPath );
+ }
}
}
}
@@ -711,10 +713,11 @@ class ImagePath {
/* Size Cache */
let memCache = new MemoryCache();
+let diskCache = new DiskCache();
/* Queue */
let processingQueue = new PQueue({
- concurrency: globalOptions.concurrency
+ concurrency: GLOBAL_OPTIONS.concurrency
});
processingQueue.on("active", () => {
debug( `Concurrency: ${processingQueue.concurrency}, Size: ${processingQueue.size}, Pending: ${processingQueue.pending}` );
@@ -723,8 +726,9 @@ processingQueue.on("active", () => {
function queueImage(src, opts) {
let img = new Image(src, opts);
let key;
+ let resolvedOptions = img.options;
- if(img.options.useCache) {
+ if(resolvedOptions.useCache) {
// we don’t know the output format yet, but this hash is just for the in memory cache
key = img.getInMemoryCacheKey();
let cached = memCache.get(key);
@@ -733,28 +737,32 @@ function queueImage(src, opts) {
}
}
- debug("In-memory cache miss for %o, options: %o", src, opts);
+ debug("Processing %o (in-memory cache miss), options: %o", src, opts);
let promise = (async () => {
- if(typeof src === "string" && opts && opts.statsOnly) {
+ if(typeof src === "string" && resolvedOptions.statsOnly) {
if(Util.isRemoteUrl(src)) {
- if(!opts.remoteImageMetadata || !opts.remoteImageMetadata.width || !opts.remoteImageMetadata.height) {
- throw new Error("When using `statsOnly` and remote images, you must supply a `remoteImageMetadata` object with { width, height, format? }");
+ if(opts.remoteImageMetadata?.width && opts.remoteImageMetadata?.height) {
+ return img.getFullStats({
+ width: opts.remoteImageMetadata.width,
+ height: opts.remoteImageMetadata.height,
+ format: opts.remoteImageMetadata.format, // only required if you want to use the "auto" format
+ guess: true,
+ });
}
- return img.getFullStats({
- width: opts.remoteImageMetadata.width,
- height: opts.remoteImageMetadata.height,
- format: opts.remoteImageMetadata.format, // only required if you want to use the "auto" format
- guess: true,
- });
- } else { // Local images
- let { width, height, type } = getImageSize(src);
- return img.getFullStats({
- width,
- height,
- format: type // only required if you want to use the "auto" format
- });
+
+ // Fetch remote image to operate on it
+ src = await img.getInput();
}
+
+ // Local images
+ let { width, height, type } = getImageSize(src);
+
+ return img.getFullStats({
+ width,
+ height,
+ format: type // only required if you want to use the "auto" format
+ });
}
let input = await img.getInput();
@@ -805,3 +813,6 @@ 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;
diff --git a/package.json b/package.json
index 04df5dd..d07f248 100644
--- a/package.json
+++ b/package.json
@@ -37,25 +37,26 @@
},
"homepage": "https://github.com/11ty/eleventy-img#readme",
"dependencies": {
- "@11ty/eleventy-fetch": "^4.0.0",
+ "@11ty/eleventy-fetch": "^4.0.1",
+ "@11ty/eleventy-utils": "^1.0.2",
"brotli-size": "^4.0.0",
"debug": "^4.3.4",
"entities": "^4.5.0",
"image-size": "^1.1.1",
"p-queue": "^6.6.2",
- "sharp": "^0.33.2"
+ "sharp": "^0.33.3"
},
"devDependencies": {
- "@11ty/eleventy": "^2.0.1",
- "@11ty/eleventy-plugin-webc": "^0.11.1",
- "ava": "^6.1.1",
+ "@11ty/eleventy": "3.0.0-alpha.6",
+ "@11ty/eleventy-plugin-webc": "^0.11.2",
+ "ava": "^6.1.2",
"eslint": "^8.56.0",
"pixelmatch": "^5.3.0"
},
"ava": {
"failFast": false,
"files": [
- "./test/*.js"
+ "./test/*.{js,cjs,mjs}"
],
"watchMode": {
"ignoreChanges": [
diff --git a/src/disk-cache.js b/src/disk-cache.js
new file mode 100644
index 0000000..90d8418
--- /dev/null
+++ b/src/disk-cache.js
@@ -0,0 +1,20 @@
+const fs = require("fs");
+// const debug = require("debug")("Eleventy:Image");
+
+class DiskCache {
+ constructor() {
+ this.hitCounter = 0;
+ }
+
+ isCached(path) {
+ if(fs.existsSync(path)) {
+ this.hitCounter++;
+ // debug("Images re-used (via disk cache): %o", this.hitCounter);
+ return true;
+ }
+
+ return false;
+ }
+}
+
+module.exports = DiskCache;
diff --git a/src/global-options.js b/src/global-options.js
index 4402256..4c9df3e 100644
--- a/src/global-options.js
+++ b/src/global-options.js
@@ -1,14 +1,14 @@
const path = require("path");
-function getGlobalOptions(eleventyDirectories, options) {
+function getGlobalOptions(directories, options) {
let globalOptions = Object.assign({
packages: {
image: require("../"),
},
- outputDir: path.join(eleventyDirectories.output, options.urlPath || ""),
+ outputDir: path.join(directories.output, options.urlPath || ""),
}, options);
- globalOptions.eleventyDirectories = eleventyDirectories;
+ globalOptions.directories = directories;
return globalOptions;
}
diff --git a/src/memory-cache.js b/src/memory-cache.js
index f88e50e..5a58334 100644
--- a/src/memory-cache.js
+++ b/src/memory-cache.js
@@ -1,8 +1,9 @@
-const debug = require("debug")("EleventyImg");
+const debug = require("debug")("Eleventy:Image");
class MemoryCache {
constructor() {
this.cache = {};
+ this.hitCounter = 0;
}
add(key, results) {
@@ -10,19 +11,18 @@ class MemoryCache {
results
};
- debug("Added %o to cache (size: %o)", key, Object.keys(this.cache).length);
+ debug("Unique images processed: %o", Object.keys(this.cache).length);
}
get(key) {
if(this.cache[key]) {
+ this.hitCounter++;
+ // debug("Images re-used (via in-memory cache): %o", this.hitCounter);
+
// may return promise
- // debug("Cache size %o", Object.keys(this.cache).length);
- // debug("Found cached for %o", key);
return this.cache[key].results;
}
- debug("Cache miss for %o", key);
-
return false;
}
}
diff --git a/src/on-request-during-serve-plugin.js b/src/on-request-during-serve-plugin.js
new file mode 100644
index 0000000..fa88bbf
--- /dev/null
+++ b/src/on-request-during-serve-plugin.js
@@ -0,0 +1,95 @@
+const fs = require("fs");
+const { TemplatePath } = require("@11ty/eleventy-utils");
+
+const eleventyImage = require("../img.js");
+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}` );
+ }
+
+ // 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 = decodeURIComponent(url.searchParams.get("src"));
+ let imageFormat = url.searchParams.get("format");
+ let width = url.searchParams.get("width");
+ 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"],
+ transformOnRequest: false, // use the built images so we don’t go in a loop
+
+ dryRun: true,
+ cacheOptions: {
+ // We *do* want to write files to .cache for re-use here.
+ dryRun: false
+ }
+ });
+
+ 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.");
+ }
+
+ 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: ``,
+ };
+ }
+ }
+ }
+ });
+}
+
+module.exports = {
+ eleventyImageOnRequestDuringServePlugin,
+};
diff --git a/src/transform-plugin.js b/src/transform-plugin.js
index b8ef1da..f938a08 100644
--- a/src/transform-plugin.js
+++ b/src/transform-plugin.js
@@ -1,38 +1,16 @@
const path = require("path");
+const Util = require("./util.js");
const { imageAttributesToPosthtmlNode, getOutputDirectory, cleanTag, isIgnored } = require("./image-attrs-to-posthtml-node.js");
const { getGlobalOptions } = require("./global-options.js");
-
-function isFullUrl(url) {
- try {
- new URL(url);
- return true;
- } catch(e) {
- return false;
- }
-}
-
-function normalizeImageSource({ inputPath, contentDir }, src) {
- if(isFullUrl(src)) {
- return 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 content directory.
- return path.join(contentDir, src);
-}
+const { eleventyImageOnRequestDuringServePlugin } = require("./on-request-during-serve-plugin.js");
function transformTag(context, node, opts) {
let originalSource = node.attrs.src;
let { inputPath, outputPath, url } = context.page;
- node.attrs.src = normalizeImageSource({
+ node.attrs.src = Util.normalizeImageSource({
+ input: opts.directories.input,
inputPath,
- contentDir: opts.eleventyDirectories.input,
}, originalSource);
let instanceOptions = {};
@@ -41,12 +19,12 @@ function transformTag(context, node, opts) {
if(outputDirectory) {
if(path.isAbsolute(outputDirectory)) {
instanceOptions = {
- outputDir: path.join(opts.eleventyDirectories.output, outputDirectory),
+ outputDir: path.join(opts.directories.output, outputDirectory),
urlPath: outputDirectory,
};
} else {
instanceOptions = {
- outputDir: path.join(opts.eleventyDirectories.output, url, outputDirectory),
+ outputDir: path.join(opts.directories.output, url, outputDirectory),
urlPath: path.join(url, outputDirectory),
};
}
@@ -55,7 +33,7 @@ function transformTag(context, node, opts) {
} else 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.
instanceOptions = {
- outputDir: path.join(opts.eleventyDirectories.output, "/img/"),
+ outputDir: path.join(opts.directories.output, "/img/"),
urlPath: "/img/",
};
} else {
@@ -79,18 +57,23 @@ function transformTag(context, node, opts) {
function eleventyImageTransformPlugin(eleventyConfig, options = {}) {
options = Object.assign({
extensions: "html",
+ transformOnRequest: process.env.ELEVENTY_RUN_MODE === "serve",
}, options);
- let eleventyDirectories;
- eleventyConfig.on("eleventy.directories", (dirs) => {
- eleventyDirectories = dirs;
+ 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.directories, options);
+
+ eleventyConfig.addJavaScriptFunction("__private_eleventyImageTransformConfigurationOptions", () => {
+ return opts;
});
function posthtmlPlugin(context) {
- // 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(eleventyDirectories, options);
-
return (tree) => {
let promises = [];
tree.match({ tag: 'img' }, (node) => {
diff --git a/src/util.js b/src/util.js
new file mode 100644
index 0000000..7155f86
--- /dev/null
+++ b/src/util.js
@@ -0,0 +1,51 @@
+const path = require("path");
+const { URL } = require("url");
+
+class Util {
+ /*
+ * 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;
+ } catch(e) {
+ // invalid url OR local path
+ return false;
+ }
+ }
+
+ static normalizeImageSource({ input, inputPath }, src) {
+ if(Util.isFullUrl(src)) {
+ return 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);
+ }
+}
+
+// Temporary alias for changes made in https://github.com/11ty/eleventy-img/pull/138
+Util.isFullUrl = Util.isRemoteUrl;
+
+module.exports = Util;
diff --git a/src/webc-options-plugin.js b/src/webc-options-plugin.js
index b3d62e2..0f26045 100644
--- a/src/webc-options-plugin.js
+++ b/src/webc-options-plugin.js
@@ -1,16 +1,21 @@
const { getGlobalOptions } = require("./global-options.js");
+const { eleventyImageOnRequestDuringServePlugin } = require("./on-request-during-serve-plugin.js");
function eleventyWebcOptionsPlugin(eleventyConfig, options = {}) {
- let eleventyDirectories;
- eleventyConfig.on("eleventy.directories", (dirs) => {
- eleventyDirectories = dirs;
- });
+ 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(eleventyDirectories, options);
+ return getGlobalOptions(eleventyConfig.directories, options);
});
+
+ if(options.transformOnRequest !== false) {
+ // Add the on-request plugin automatically (unless opt-out in this plugins options only)
+ eleventyConfig.addPlugin(eleventyImageOnRequestDuringServePlugin);
+ }
}
module.exports = {
diff --git a/test/test-webc.js b/test/test-webc.mjs
similarity index 60%
rename from test/test-webc.js
rename to test/test-webc.mjs
index 992c8f1..431d738 100644
--- a/test/test-webc.js
+++ b/test/test-webc.mjs
@@ -1,5 +1,7 @@
-const test = require("ava");
-const Eleventy = require("@11ty/eleventy");
+import test from "ava";
+import Eleventy from "@11ty/eleventy";
+import eleventyWebcPlugin from "@11ty/eleventy-plugin-webc";
+import { eleventyImagePlugin } from "../img.js";
test("Using ", async t => {
let elev = new Eleventy( "test/webc/simple.webc", "test/webc/_site", {
@@ -37,3 +39,31 @@ test("With url-path", async t => {
t.is(results[0].content, ``);
});
+test("With transform on request during dev mode", async t => {
+ let elev = new Eleventy( "test/webc/simple.webc", "test/webc/_site", {
+ config: eleventyConfig => {
+ // WebC
+ eleventyConfig.addPlugin(eleventyWebcPlugin, {
+ components: [
+ // Add as a global WebC component
+ "eleventy-image.webc",
+ ]
+ });
+
+ // Image plugin
+ eleventyConfig.addPlugin(eleventyImagePlugin, {
+ // Set global default options
+ formats: ["auto"],
+ dryRun: true,
+ transformOnRequest: true,
+
+ defaultAttributes: {
+ loading: "lazy",
+ }
+ });
+ }
+ });
+
+ let results = await elev.toJSON();
+ t.is(results[0].content, ``);
+});
diff --git a/test/transform-test.mjs b/test/transform-test.mjs
new file mode 100644
index 0000000..7b60e5d
--- /dev/null
+++ b/test/transform-test.mjs
@@ -0,0 +1,101 @@
+import test from "ava";
+import Eleventy from "@11ty/eleventy";
+import { eleventyImageTransformPlugin } from "../img.js";
+
+test("Using the transform plugin", async t => {
+ let elev = new Eleventy( "test", "test/_site", {
+ config: eleventyConfig => {
+ eleventyConfig.addTemplate("virtual.html", ``);
+
+ eleventyConfig.addPlugin(eleventyImageTransformPlugin, {
+ dryRun: true // don’t write image files!
+ });
+ }
+ });
+
+ let results = await elev.toJSON();
+ t.is(results[0].content, ``);
+});
+
+
+test("Using the transform plugin (override options)", async t => {
+ let elev = new Eleventy( "test", "test/_site", {
+ config: eleventyConfig => {
+ eleventyConfig.addTemplate("virtual.html", ``);
+
+ eleventyConfig.addPlugin(eleventyImageTransformPlugin, {
+ formats: ["auto"],
+ dryRun: true // don’t write image files!
+ });
+ }
+ });
+
+ let results = await elev.toJSON();
+ t.is(results[0].content, ``);
+});
+
+test("Using the transform plugin with transform on request during dev mode", async t => {
+ let elev = new Eleventy( "test", "test/_site", {
+ config: eleventyConfig => {
+ eleventyConfig.addTemplate("virtual.html", ``);
+
+ eleventyConfig.addPlugin(eleventyImageTransformPlugin, {
+ formats: ["auto"],
+ transformOnRequest: true,
+ dryRun: true, // don’t write image files!
+
+ defaultAttributes: {}
+ });
+ }
+ });
+
+ let results = await elev.toJSON();
+ t.is(results[0].content, ``);
+});
+
+test("Using the transform plugin with transform on request during dev mode (with default attributes)", async t => {
+ let elev = new Eleventy( "test", "test/_site", {
+ config: eleventyConfig => {
+ eleventyConfig.addTemplate("virtual.html", ``);
+
+ eleventyConfig.addPlugin(eleventyImageTransformPlugin, {
+ formats: ["auto"],
+ transformOnRequest: true,
+ dryRun: true, // don’t write image files!
+
+ defaultAttributes: {
+ loading: "lazy",
+ }
+ });
+ }
+ });
+
+ let results = await elev.toJSON();
+ t.is(results[0].content, ``);
+});
+
+
+test("Using the transform plugin with transform on request during dev mode but don’t override existing urlFormat", async t => {
+ let elev = new Eleventy( "test", "test/_site", {
+ config: eleventyConfig => {
+ eleventyConfig.addTemplate("virtual.html", ``);
+
+ eleventyConfig.addPlugin(eleventyImageTransformPlugin, {
+ urlFormat: function(src) {
+ return 'https://example.com/';
+ },
+ formats: ["auto"],
+ transformOnRequest: true,
+ dryRun: true, // don’t write image files!
+
+ defaultAttributes: {
+ loading: "lazy",
+ }
+ });
+ }
+ });
+
+ let results = await elev.toJSON();
+ t.is(results[0].content, ``);
+});
+