Skip to content

Instantly share code, notes, and snippets.

@saionaro
Last active June 6, 2020 13:50
Show Gist options
  • Save saionaro/a20915c22d3c8481c4a7c2e6b6a1faa3 to your computer and use it in GitHub Desktop.
Save saionaro/a20915c22d3c8481c4a7c2e6b6a1faa3 to your computer and use it in GitHub Desktop.
The tool for inlining html resources
["yargs:dev", "jsdom:dev"]
const yargs = require("yargs");
const { JSDOM } = require("jsdom");
const fs = require("fs");
const { join, resolve } = require("path");
const { promisify } = require("util");
const readdir = promisify(fs.readdir);
const readfile = promisify(fs.readFile);
const writefile = promisify(fs.writeFile);
const unlink = promisify(fs.unlink);
const HTML_REGEX = /\.html$/;
const ENCODING = "UTF8";
async function findHtmls(dir) {
let contents = [];
try {
contents = await readdir(dir);
} catch (e) {
console.error(e);
}
return contents.filter((filename) => HTML_REGEX.test(filename));
}
async function getContent(path) {
let content = "";
try {
content = await readfile(path, ENCODING);
} catch (e) {
console.error(e);
}
return content;
}
async function writeContent(path, content) {
try {
content = await writefile(path, content);
} catch (e) {
console.error(e);
}
}
/**
*
* @param {object} config
* @param {string} config.directoryPath
* @param {object} config.vDom
* @param {string} config.selector
* @param {string} config.referenceAttr
* @param {string} config.inlineElement
*/
async function inlineResource(config) {
const usedResources = [];
try {
const elements = config.vDom.window.document.querySelectorAll(
config.selector
);
for (const element of elements) {
const resourcePath = join(
config.directoryPath,
element[config.referenceAttr]
);
const virtualElement = config.vDom.window.document.createElement(
config.inlineElement
);
usedResources.push(resourcePath);
const resourceContent = await getContent(resourcePath);
virtualElement.innerHTML = resourceContent;
element.parentElement.insertBefore(virtualElement, element);
element.remove();
}
} catch (e) {
console.error(e);
}
return usedResources;
}
async function cleanupInlinedResources(paths) {
try {
for (const path of paths) {
await unlink(path);
}
} catch (e) {
console.error(e);
}
}
async function performWork({ buildDir, crop = false }) {
const directoryPath = resolve(buildDir);
const htmls = await findHtmls(directoryPath);
const inlinedResources = [];
for (const html of htmls) {
const htmlPath = join(directoryPath, html);
const content = await getContent(htmlPath);
const vDom = new JSDOM(content);
const sharedConfig = {
directoryPath,
vDom,
};
const configs = [
{
...sharedConfig,
selector: `link[rel="stylesheet"]`,
referenceAttr: "href",
inlineElement: "style",
},
{
...sharedConfig,
selector: "script[src]",
referenceAttr: "src",
inlineElement: "script",
},
];
for (const config of configs) {
const usedResources = await inlineResource(config);
inlinedResources.push(...usedResources);
}
let contentFinal = "";
if (crop) {
const styles = vDom.window.document.querySelectorAll("style");
for (const style of styles) {
contentFinal += style.outerHTML;
}
contentFinal += vDom.window.document.body.innerHTML;
} else {
content = vDom.serialize();
}
await writeContent(htmlPath, contentFinal);
}
await cleanupInlinedResources([...new Set(inlinedResources)]);
}
yargs
.usage("Usage: $0 <command> [options]")
.command({
command: "inline <buildDir>",
desc: "Initialize new project",
aliases: ["i"],
builder: (yargs) => {
yargs.positional("buildDir", {
describe: "Build directory",
type: "string",
});
yargs.boolean("crop");
},
handler: performWork,
})
.help().argv;
{
"inline": "node ./scripts/inliner.js inline ./dist --crop"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment