-
-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
234 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
const eleventyImage = require("../"); | ||
|
||
const ATTR_PREFIX = "eleventy:"; | ||
|
||
const ATTR = { | ||
IGNORE: `${ATTR_PREFIX}ignore`, | ||
WIDTHS: `${ATTR_PREFIX}widths`, | ||
FORMATS: `${ATTR_PREFIX}formats`, | ||
OUTPUT: `${ATTR_PREFIX}output`, | ||
}; | ||
|
||
function convertToPosthtmlNode(obj) { | ||
// node.tag | ||
// node.attrs | ||
// node.content | ||
|
||
let node = {}; | ||
let [key] = Object.keys(obj); | ||
node.tag = key; | ||
|
||
if(Array.isArray(obj[key])) { | ||
node.content = obj[key].map(child => { | ||
return convertToPosthtmlNode(child); | ||
}); | ||
} else { | ||
node.attrs = obj[key]; | ||
} | ||
|
||
return node; | ||
} | ||
|
||
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?") | ||
} | ||
|
||
let defaultGlobalAttributes = globalPluginOptions.defaultAttributes; | ||
delete globalPluginOptions.defaultAttributes; | ||
|
||
if(!instanceOptions) { | ||
instanceOptions = {}; | ||
} | ||
|
||
// overrides global widths | ||
if(attributes[ATTR.WIDTHS]) { | ||
if(typeof attributes[ATTR.WIDTHS] === "string") { | ||
instanceOptions.widths = attributes[ATTR.WIDTHS].split(",").map(entry => parseInt(entry, 10)); | ||
delete attributes[ATTR.WIDTHS]; | ||
} | ||
} | ||
|
||
if(attributes[ATTR.FORMATS]) { | ||
if(typeof attributes[ATTR.FORMATS] === "string") { | ||
instanceOptions.formats = attributes[ATTR.FORMATS].split(","); | ||
delete attributes[ATTR.FORMATS]; | ||
} | ||
} | ||
|
||
let options = Object.assign({}, globalPluginOptions, instanceOptions); | ||
let metadata = await eleventyImage(attributes.src, options); | ||
let imageAttributes = Object.assign({}, defaultGlobalAttributes, attributes); | ||
|
||
// You bet we throw an error on missing alt in `imageAttributes` (alt="" works okay) | ||
let obj = await eleventyImage.generateObject(metadata, imageAttributes); | ||
return convertToPosthtmlNode(obj); | ||
}; | ||
|
||
function cleanTag(node) { | ||
// Delete all prefixed attributes | ||
for(let key in node?.attrs) { | ||
if(key.startsWith(ATTR_PREFIX)) { | ||
delete node?.attrs?.[key]; | ||
} | ||
} | ||
} | ||
|
||
function isIgnored(node) { | ||
return node?.attrs && node?.attrs?.[ATTR.IGNORE] !== undefined; | ||
} | ||
|
||
function getOutputDirectory(node) { | ||
return node?.attrs?.[ATTR.OUTPUT]; | ||
} | ||
|
||
module.exports = { | ||
imageAttributesToPosthtmlNode, | ||
cleanTag, | ||
isIgnored, | ||
getOutputDirectory, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
const path = require("path"); | ||
const { imageAttributesToPosthtmlNode, getOutputDirectory, cleanTag, isIgnored } = require("./imageAttributesToPosthtmlNode.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); | ||
} | ||
|
||
function transformTag(context, node, opts) { | ||
let originalSource = node.attrs.src; | ||
let { inputPath, outputPath, url } = context.page; | ||
|
||
node.attrs.src = normalizeImageSource({ | ||
inputPath, | ||
contentDir: opts.eleventyDirectories.input, | ||
}, originalSource); | ||
|
||
let instanceOptions = {}; | ||
|
||
let outputDirectory = getOutputDirectory(node); | ||
if(outputDirectory) { | ||
if(path.isAbsolute(outputDirectory)) { | ||
instanceOptions = { | ||
outputDir: path.join(opts.eleventyDirectories.output, outputDirectory), | ||
urlPath: outputDirectory, | ||
}; | ||
} else { | ||
instanceOptions = { | ||
outputDir: path.join(opts.eleventyDirectories.output, url, outputDirectory), | ||
urlPath: path.join(url, outputDirectory), | ||
}; | ||
} | ||
} else if(opts.urlPath) { | ||
// do nothing, user has specified directories in the plugin options. | ||
} 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/"), | ||
urlPath: "/img/", | ||
}; | ||
} else { | ||
// If original source is a relative one, this colocates images to the template output. | ||
instanceOptions = { | ||
outputDir: path.dirname(outputPath), | ||
urlPath: url, | ||
}; | ||
} | ||
|
||
// returns promise | ||
return imageAttributesToPosthtmlNode(node.attrs, instanceOptions, opts).then(obj => { | ||
// TODO how to assign attributes to `<picture>` only | ||
// Wipe out attrs just in case this is <picture> | ||
node.attrs = {}; | ||
|
||
Object.assign(node, obj); | ||
}); | ||
} | ||
|
||
module.exports = function(eleventyConfig, options, globalOptionsCallback) { | ||
function posthtmlPlugin(context) { | ||
let opts = globalOptionsCallback(); | ||
|
||
return (tree) => { | ||
let promises = []; | ||
tree.match({ tag: 'img' }, (node) => { | ||
if(isIgnored(node)) { | ||
cleanTag(node); | ||
} else { | ||
promises.push(transformTag(context, node, opts)); | ||
} | ||
|
||
return node; | ||
}); | ||
|
||
return Promise.all(promises).then(() => 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 | ||
}); | ||
}; |