UNPKG

inline-source

Version:

Inline all flagged js, css, image source files

651 lines (634 loc) 18.8 kB
"use strict"; 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 });