img-alt-text-webpack-plugin
Version:
A Webpack plugin that automatically adds alt text to <img> elements in HTML output and injects JavaScript to fetch alt text for dynamically loaded images, enhancing accessibility and SEO.
145 lines (127 loc) • 5.83 kB
JavaScript
const fs = require('fs');
const path = require('path');
const { JSDOM } = require('jsdom');
const { Compilation } = require("webpack");
const { GoogleGenerativeAI } = require("@google/generative-ai");
class ImgAltTextPlugin {
constructor(options = {}) {
this.options = options;
}
apply(compiler) {
const pluginName = "ImgAltTextPlugin";
// observerJS set to true implied output JS file
// jsName sets name of output JS file
// JS file fetches alt text for dynamically loaded images
const { jsName, observerJS } = this.options.jsInject;
if (observerJS) {
compiler.hooks.entryOption.tap(pluginName, (context, entry) => {
const JSfilepath = path.resolve(__dirname, 'src', 'worker.js');
const absolutePath = path.resolve(context, JSfilepath);
if (typeof entry === 'object' && !Array.isArray(entry)) {
// If entry is an object, add a new entry
entry[jsName] = {
import: [absolutePath],
};
} else {
// If entry is an array or any other type, convert to an object and add the new entry
entry = {
...entry,
[jsName]: {
import: [absolutePath],
}
};
}
});
}
// for adding alt text to every img element in output html files
compiler.hooks.compilation.tap(pluginName, (compilation) => {
// process assets before emit event
compilation.hooks.processAssets.tapAsync({
name: pluginName,
stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS,
additionalAssets: true,
},
async (assets, callback) => {
// find JS filename if script tags needs to be injected in output html file
const matchingKey = Object.keys(assets).find(key => key.toLowerCase().includes(jsName.toLowerCase()));
// loop through each output asset to operate on html files
let keysArray = Object.keys(assets);
for (let i = 0; i < keysArray.length; i++) {
let filename = keysArray[i];
if (filename.endsWith('.html')) {
const asset = compilation.assets[filename];
const originalSource = asset.source();
// update html with <img> elements altered to have alt attribute set
const updatedSource = await this.processContent(originalSource, assets, matchingKey, observerJS);
compilation.assets[filename] = {
source: () => updatedSource,
size: () => updatedSource.length
};
}
}
// signal work completion to webpack
callback();
}
);
});
}
async processContent(content, assets, scriptFilename, scriptInjectionFlag) {
// create dom from passed file content
const dom = new JSDOM(content);
const document = dom.window.document;
// injecting script tag into output html file
if (scriptInjectionFlag) {
var script = document.createElement('script');
script.src = `${scriptFilename}`;
script.type = 'text/javascript';
document.body.appendChild(script);
}
// operate on all <img> elements in the content
let images = document.querySelectorAll('img');
images = Array.from(images);
for (let i = 0; i < images.length; i++) {
let img = images[i];
let src = img.getAttribute('src');
if (src) {
const altText = img.getAttribute('alt');
// if alt text is not set or is just space characters
if (!altText || /^\s*$/.test(altText)) {
let source = assets[src].source();
// generate lat text passing the image
let altText = await this.getAltText(source);
// set alt attribute
img.setAttribute('alt', altText);
}
}
}
return document.documentElement.outerHTML;
}
async getAltText(source) {
// get gemini key and prompt from options passed to the plugin
const { key, textPrompt = "Generate alt text for this image that can be inserted in img html element. Keep it concise." } = this.options;
// get google genAI model
const genAI = new GoogleGenerativeAI(key);
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
// create buffer from image source if not already buffer and then
// create base64 string from buffer
if (Buffer.isBuffer(source)) {
source = source.toString("base64");
} else {
source = Buffer.from(source, 'utf-8').toString("base64")
}
// object passed to genAI model representing image information
const imageParts = {
inlineData: {
data: source,
mimeType: "image/png"
},
};
// get alt text back from gemini
const result = await model.generateContent([textPrompt, imageParts]);
const response = await result.response;
const text = response.text();
// return alt text
return text;
}
}
module.exports = ImgAltTextPlugin;