inline-source
Version:
Inline all flagged js, css, image source files
651 lines (634 loc) • 18.8 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.js
var src_exports = {};
__export(src_exports, {
inlineSource: () => inlineSource
});
module.exports = __toCommonJS(src_exports);
// src/css.js
var import_csso = require("csso");
async function css(source) {
if (source.fileContent && !source.content && source.type == "css") {
source.content = source.compress ? (0, import_csso.minify)(
/** @type { string } */
source.fileContent
).css : source.fileContent;
source.tag = "style";
}
}
// src/context.js
var import_node_fs = __toESM(require("node:fs"), 1);
// src/utils.js
var import_node_url = require("node:url");
var import_node_path = __toESM(require("node:path"), 1);
var ATTRIBUTE_BLACKLIST = [
"href",
"rel",
"src",
"data",
"xmlns",
"xmlns:xlink",
"version",
"baseprofile"
];
var RE_ANY = /<(script|link|img|object)\s?[^>]*?>(?:<\/\1\s?>)?/gm;
var RE_ESCAPE = /[-/\\^$*+?.()|[\]{}]/g;
var RE_NOT_FILEPATH = /[\r\n<>]|^data:/gm;
var RE_QUERY = /\?[^#]*/g;
var RE_REMOTE = /^https?:\/\//;
var RE_FORWARD_SLASH = /\//g;
function escape(str) {
return str.replace(RE_ESCAPE, "\\$&");
}
function getAttributeString(attributes, prefix, strict) {
prefix = String(prefix);
let str = "";
for (const prop in attributes) {
const include = strict ? prop.indexOf(prefix) != 0 && !ATTRIBUTE_BLACKLIST.includes(prop) : prop.indexOf(prefix) != 0;
if (include) {
str += attributes[prop] === true ? ` ${prop}` : ` ${prop}="${attributes[prop]}"`;
}
}
return str;
}
function getFormatFromExtension(extension) {
switch (extension) {
case "js":
return "js";
case "json":
return "json";
case "css":
return "css";
case "gif":
return "gif";
case "png":
return "png";
case "jpeg":
case "jpg":
return "jpeg";
case "svg":
return "svg+xml";
}
return extension;
}
function getPadding(source, html) {
const re = new RegExp(`^([\\t ]+)${escape(source)}`, "gm");
const match = re.exec(html);
return match ? match[1] : "";
}
function getSourcepath(filepath, htmlpath, rootpath) {
let sourcepath = filepath;
if (!sourcepath) {
return ["", ""];
}
if (isRemoteFilepath(sourcepath)) {
const url = new import_node_url.URL(sourcepath);
sourcepath = `./${url.pathname.slice(1).replace(RE_FORWARD_SLASH, "_")}`;
}
sourcepath = sourcepath.replace(RE_QUERY, "");
if (htmlpath && isRelativeFilepath(sourcepath)) {
sourcepath = import_node_path.default.resolve(import_node_path.default.dirname(htmlpath), sourcepath);
} else if (sourcepath.indexOf("/") == 0) {
sourcepath = sourcepath.slice(1);
}
if (sourcepath.includes("#")) {
sourcepath = sourcepath.split("#");
}
return Array.isArray(sourcepath) ? [import_node_path.default.resolve(rootpath, sourcepath[0]), sourcepath[1]] : [import_node_path.default.resolve(rootpath, sourcepath), ""];
}
function getTagRegExp(attribute) {
if (attribute) {
return new RegExp(
"<([a-zA-Z]+)\\b[^>]*?\\s(?:" + attribute + "\\b[^>]*?|" + attribute + "|" + attribute + `=([\\'\\"])(?:true|` + attribute + ")\\2[^>]*?)>(?:<\\/\\1\\s?>)?",
"gm"
);
}
return RE_ANY;
}
function getTypeFromType(type) {
switch (type) {
case "application/javascript":
case "application/x-javascript":
case "application/ecmascript":
case "text/javascript":
case "text/form-script":
case "text/ecmascript":
case "javascript":
case "js":
case "ecmascript":
case "module":
return "js";
case "text/css":
case "css":
return "css";
case "image/png":
case "image/gif":
case "image/jpeg":
case "image/jpg":
case "image/svg+xml":
case "image/svg":
case "png":
case "gif":
case "jpeg":
case "jpg":
case "svg":
case "image":
return "image";
case "application/json":
case "text/json":
case "json":
return "json";
}
return type;
}
function getTypeFromTag(tag) {
switch (tag) {
case "script":
return "js";
case "link":
return "css";
case "img":
case "object":
return "image";
}
return "";
}
function isFilepath(str) {
RE_NOT_FILEPATH.lastIndex = 0;
if (str && typeof str === "string") {
return !RE_NOT_FILEPATH.test(str);
}
return false;
}
function isIgnored(ignore, tag, type, format) {
const formatAlt = format && format.indexOf("+") ? format.split("+")[0] : null;
if (!Array.isArray(ignore)) {
ignore = [ignore];
}
return Boolean(
ignore.includes(tag) || ignore.includes(type) || ignore.includes(format) || formatAlt && ignore.includes(formatAlt)
);
}
function isRelativeFilepath(str) {
if (str) {
return isFilepath(str) && (str.indexOf("./") == 0 || str.indexOf("../") == 0);
}
return false;
}
function isRemoteFilepath(str) {
if (str && typeof str === "string") {
return isFilepath(str) && RE_REMOTE.test(str);
}
return false;
}
function parseAttributes(attributes) {
for (const prop in attributes) {
if (attributes[prop] === "") {
attributes[prop] = true;
}
}
return attributes;
}
function parseProps(attributes, prefix) {
const props = {};
if (typeof prefix === "string") {
prefix += "-";
for (const prop in attributes) {
if (prop.indexOf(prefix) == 0) {
let value = attributes[prop];
if (value === "false") {
value = false;
}
if (value === "true") {
value = true;
}
props[prop.slice(prefix.length)] = value;
}
}
}
return props;
}
// src/inline.js
function inline(source, context) {
if (source.replace) {
context.html = context.html.replace(source.match, () => source.replace);
}
}
// src/js.js
var import_terser = require("terser");
var RE_SCRIPT = /(<)(\/script>)/gi;
async function js(source) {
if (source.fileContent && !source.content && source.type == "js") {
let content;
if (!source.compress) {
content = source.fileContent;
} else {
const compressed = await (0, import_terser.minify)(source.fileContent);
content = /** @type { string } */
compressed.code;
}
if (RE_SCRIPT.test(content)) {
content = content.replace(RE_SCRIPT, "\\x3C$2");
}
source.content = content;
}
}
// src/imgSVG.js
var import_htmlparser2 = require("htmlparser2");
var import_svgo = require("svgo");
var DEFAULT_SVG_ATTR = {
x: "0",
y: "0",
viewBox: "0 0 100 100"
};
var RE_SVG_CONTENT = /<svg[^>]+>([\S\s]*?)<\/\s?svg>/gm;
var RE_SYMBOL = /<symbol\sid=['"]([^'"]+)[\S\s]*?<\/\s?symbol>/gm;
async function imgSVG(source, context, svgoConfig) {
RE_SVG_CONTENT.lastIndex = 0;
const svgContent = RE_SVG_CONTENT.exec(source.fileContent) || source.fileContent;
const defaultAttributes = Array.isArray(svgContent) ? {} : DEFAULT_SVG_ATTR;
let attributes = {};
const parser = new import_htmlparser2.Parser(
new import_htmlparser2.DefaultHandler((err, dom) => {
if (err) {
throw err;
}
dom = dom.filter((item) => item.type == "tag" && item.name == "svg");
if (dom.length) {
attributes = parseAttributes(
/** @type { { attribs: Record<String, string> } } */
dom[0].attribs
);
if ("viewbox" in attributes) {
attributes.viewBox = attributes.viewbox;
delete attributes.viewbox;
}
}
})
);
parser.parseComplete(source.fileContent);
source.content = Array.isArray(svgContent) ? svgContent[1] : svgContent;
source.attributes = Object.assign(
{},
defaultAttributes,
attributes,
source.attributes
);
if ("alt" in source.attributes) {
delete source.attributes.alt;
}
source.tag = "svg";
if (source.filepathAnchor) {
RE_SYMBOL.lastIndex = 0;
const includedIds = source.filepathAnchor.split(",");
let content = source.content;
let match;
while (match = RE_SYMBOL.exec(source.content)) {
if (!includedIds.includes(match[1])) {
content = content.replace(match[0], "");
}
}
source.content = content;
}
if (source.compress) {
const attrs = getAttributeString(
source.attributes,
context.attribute,
false
);
const content = `<svg${attrs}>${source.content}</svg>`;
const result = await (0, import_svgo.optimize)(content, svgoConfig);
if ("data" in result) {
RE_SVG_CONTENT.lastIndex = 0;
const rematch = RE_SVG_CONTENT.exec(result.data);
if (rematch) {
source.content = rematch[1];
} else {
source.replace = result.data;
}
} else {
}
}
}
// src/img.js
var import_svgo2 = require("svgo");
var RE_XML_TAG = /<\?xml.+?\?>\s+/g;
var SVGO_CONFIG = {
plugins: [
"removeDoctype",
"removeXMLProcInst",
"removeComments",
"removeMetadata",
"removeEditorsNSData",
"cleanupAttrs",
"mergeStyles",
"inlineStyles",
"minifyStyles",
// 'cleanupIDs', Prevent removal of unused <symbol> elements
"cleanupNumericValues",
"convertColors",
// 'removeUnknownsAndDefaults', Prevent removal of <image> src attribute
"removeNonInheritableGroupAttrs",
"removeUselessStrokeAndFill",
"removeViewBox",
"cleanupEnableBackground",
"removeHiddenElems",
"removeEmptyText",
"convertShapeToPath",
"convertEllipseToCircle",
"moveElemsAttrsToGroup",
"moveGroupAttrsToElems",
"collapseGroups",
"convertPathData",
"convertTransform",
// 'removeEmptyAttrs', Prevent removal of xlink:href on <image> elements
// 'removeUselessDefs',
"removeEmptyContainers",
"mergePaths",
"removeUnusedNS",
"sortDefsChildren",
"removeTitle",
"removeDesc"
]
};
async function img(source, context) {
if (source.fileContent && !source.content && source.type == "image") {
const attributeType = source.attributes.type;
let strict = !source.errored;
let sourceProp = "src";
let data, encoding;
delete source.attributes.type;
const isIcon = source.attributes.rel == "icon";
if (source.format == "svg+xml" && !isIcon) {
if (!source.svgAsImage) {
await imgSVG(source, context, SVGO_CONFIG);
return;
}
source.tag = "img";
source.content = source.fileContent.replace(RE_XML_TAG, "");
if (source.compress) {
const result = await (0, import_svgo2.optimize)(source.content, SVGO_CONFIG);
if ("data" in result) {
source.content = result.data;
} else {
}
}
data = encodeURIComponent(source.content);
encoding = "charset=utf8";
} else {
data = Buffer.from(source.fileContent).toString("base64");
encoding = "base64";
if (source.tag == "link") {
source.attributes.type = attributeType;
sourceProp = "href";
strict = false;
delete source.attributes.href;
}
}
const src = `data:image/${source.format};${encoding},${data}`;
let attrs = getAttributeString(
source.attributes,
context.attribute,
strict
);
attrs += ` ${sourceProp}="${src}"`;
source.content = src;
source.replace = `<${source.tag}${attrs}/>`;
}
}
// src/load.js
async function load(source, context) {
if (!source.fileContent && source.filepath) {
const encoding = source.type == "image" && source.format != "svg+xml" ? null : "utf8";
try {
source.fileContent = context.fs.readFileSync(source.filepath, encoding);
} catch (err) {
if (!source.isRemote) {
throw err;
}
}
if (source.isRemote) {
const fetch = (await import("node-fetch")).default;
const res = await fetch(
/** @type { string } */
source.sourcepath
);
if (!res.ok) {
throw Error(
res.status === 404 ? "Not found" : `Fetch error: ${res.status}`
);
}
const text = await res.text();
if (context.saveRemote) {
try {
context.fs.writeFileSync(source.filepath, text, "utf8");
} catch (err) {
}
}
source.fileContent = text;
}
}
}
// src/context.js
var import_node_path2 = __toESM(require("node:path"), 1);
// src/wrap.js
var RE_BEGIN_LINE = /^./gm;
function wrap(source, context) {
if (source.content !== null && !source.replace) {
const attrs = getAttributeString(
source.attributes,
context.attribute,
!source.errored
);
const closing = source.tag != "link" ? `</${source.tag}>` : "";
const content = context.pretty ? `
${source.content.replace(RE_BEGIN_LINE, source.padding + "$&")}
${source.padding}` : source.content;
source.replace = `<${source.tag + attrs}>${content}${closing}`;
}
}
// src/context.js
var DEFAULT = {
compress: true,
fs: import_node_fs.default,
html: "",
htmlpath: "",
ignore: [],
pretty: false,
saveRemote: true,
swallowErrors: false,
svgAsImage: false
};
function createContext(options = {}) {
const { attribute = "inline", handlers = [], preHandlers = [] } = options;
const context = Object.assign(
{
attribute,
re: getTagRegExp(attribute),
rootpath: process.cwd(),
sources: [],
stack: [...preHandlers, load, ...handlers, js, css, img, wrap, inline]
},
DEFAULT,
options
);
if (options.rootpath) {
context.rootpath = import_node_path2.default.resolve(options.rootpath);
}
if (options.pretty == true && context.compress == false) {
context.pretty = true;
}
return context;
}
// src/index.js
var import_node_path4 = __toESM(require("node:path"), 1);
// src/parse.js
var import_htmlparser22 = require("htmlparser2");
var import_node_path3 = __toESM(require("node:path"), 1);
var RE_COMMENT = /(<!--[^[i][\S\s]+?--\s?>)/gm;
async function parse(context) {
const html = context.html.replace(RE_COMMENT, "");
let match;
const parser = new import_htmlparser22.Parser(
new import_htmlparser22.DefaultHandler((err, dom) => {
if (err) {
throw err;
}
const parsed = (
/** @type { { attribs: Record<String, string> } } */
dom[0]
);
if (parsed) {
const [matching, tag] = (
/** @type { RegExpExecArray } */
match
);
const attributes = parseAttributes(parsed.attribs);
const props = parseProps(attributes, context.attribute);
const type = getTypeFromType(
/** @type { string } */
attributes.type
) || getTypeFromTag(tag);
const sourcepath = attributes.src || attributes.href || attributes.data;
if (sourcepath === true || tag === "link" && attributes.rel && attributes.rel !== "stylesheet" && attributes.rel !== "icon") {
return;
}
if (sourcepath === void 0 || isFilepath(sourcepath)) {
const filepath = getSourcepath(
/** @type { string } */
sourcepath,
context.htmlpath,
context.rootpath
);
const extension = import_node_path3.default.extname(filepath[0]).slice(1);
const format = getFormatFromExtension(extension);
if (!isIgnored(context.ignore, tag, type, format)) {
context.sources.push({
attributes,
compress: "compress" in props ? (
/** @type { boolean } */
props.compress
) : context.compress,
content: null,
errored: false,
extension,
fileContent: "",
filepath: filepath[0],
filepathAnchor: filepath[1],
format,
isRemote: isRemoteFilepath(sourcepath),
match: matching,
padding: context.pretty ? getPadding(matching, context.html) : "",
parentContext: context,
props,
replace: "",
sourcepath,
stack: context.stack,
svgAsImage: "svgasimage" in props ? (
/** @type { boolean } */
props.svgasimage
) : context.svgAsImage,
tag,
type
});
}
}
}
})
);
while (match = context.re.exec(html)) {
parser.parseComplete(match[0]);
}
}
// src/run.js
var isTest = process.env.NODE_ENV === "test";
async function run(context, sources = [], swallowErrors) {
await Promise.all(
sources.map(async (source) => {
for (const handler of source.stack) {
try {
await handler(source, context);
} catch (err) {
if (!swallowErrors) {
throw err;
}
if (!isTest) {
console.warn(
/** @type { Error } */
err.message
);
}
source.content = "";
source.errored = true;
}
}
})
);
return context.html;
}
// src/index.js
async function inlineSource(htmlpath, options = {}) {
const ctx = createContext(options);
if (isFilepath(htmlpath)) {
ctx.htmlpath = import_node_path4.default.resolve(htmlpath);
ctx.html = ctx.fs.readFileSync(ctx.htmlpath, "utf8");
} else {
ctx.html = htmlpath;
}
await parse(ctx);
if (ctx.sources.length > 0) {
await run(ctx, ctx.sources, ctx.swallowErrors);
}
return ctx.html;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
inlineSource
});
;