-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
136 lines (132 loc) · 4.25 KB
/
index.js
File metadata and controls
136 lines (132 loc) · 4.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import fs from "fs";
import { unified } from "unified";
import rehypeParse from "rehype-parse";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import { selectAll } from "hast-util-select";
import { visit } from "unist-util-visit";
const inlineNodeContents = (
rootNode,
{ css = true, js = true, images = true, imports = true, svgElements = false }
) => {
if (imports) {
const htmlParser = unified().use(rehypeParse, {
fragment: true,
});
const mdParser = unified().use(remarkParse).use(remarkRehype);
visit(rootNode, (node, index, parent) => {
if (node.tagName === "link" && node.properties.rel.includes("import")) {
const fragmentString = fs.readFileSync(node.properties.href, {
encoding: "utf-8",
});
let fragment;
if (node.properties.type === "text/markdown") {
// markdown import
const mdFragment = mdParser.parse(fragmentString);
fragment = mdParser.runSync(mdFragment);
} else {
// html import
fragment = htmlParser.parse(fragmentString);
}
parent.children.splice(index, 1, ...fragment.children);
}
});
}
if (css) {
const linkElements = selectAll("link", rootNode);
linkElements.forEach((element) => {
if (element.properties.rel.includes("stylesheet")) {
const stylesheetContent = fs.readFileSync(element.properties.href, {
encoding: "utf-8",
});
element.tagName = "style";
// remove previous props
element.properties = {};
element.children = [{ type: "text", value: stylesheetContent }];
}
});
}
if (js) {
const scriptElements = selectAll("script", rootNode);
scriptElements.forEach((element) => {
const scriptLocation = element.properties.src;
if (scriptLocation) {
const scriptContent = fs.readFileSync(scriptLocation, {
encoding: "utf-8",
});
element.properties = {};
element.children = [{ type: "text", value: scriptContent }];
}
});
}
if (images) {
const imgElements = selectAll("img", rootNode);
imgElements.forEach((image) => {
const imgPath = image.properties.src;
if (imgPath.startsWith("data:")) {
// ignore image that's already inlined
return;
}
const fileExt = imgPath.match("\\.([a-zA-Z]+)$")[1];
if (fileExt === undefined || fileExt === null) {
throw new Error("image path without file extension");
}
if (fileExt === "svg" && svgElements) {
const svgContent = fs.readFileSync(image.properties.src, {
encoding: "utf-8",
});
const svgParser = unified().use(rehypeParse, {
fragment: true,
space: "svg",
});
const svgDoc = svgParser.parse(svgContent);
let svgNode;
for (let childNode of svgDoc.children) {
if (childNode.type === "element") {
svgNode = childNode;
break;
}
}
image.tagName = "svg";
image.properties = svgNode.properties;
image.children = svgNode.children;
} else if (fileExt === "svg") {
const imgContent = fs.readFileSync(image.properties.src, "base64");
image.properties.src = `data:image/svg+xml;base64,${imgContent}`;
} else {
const imgContent = fs.readFileSync(image.properties.src, "base64");
image.properties.src = `data:image/${fileExt};base64,${imgContent}`;
}
});
}
return rootNode;
};
/**
* Transformer for unified.js / rehype that inlines assets like css, js and images
*
* Text files are expected to be utf-8 encoded
*
* @param {Object} options
* @param {boolean} options.css - inline CSS stylesheets
* @param {boolean} options.js - inline JS scripts
* @param {boolean} options.images - inline images like png, jpg or svg
* @param {boolean} options.svgElements - inline svgs as <svg> elements instead of <img> elements
* @returns {Object} unified transformer
*/
const inline = ({
css = true,
js = true,
images = true,
imports = true,
svgElements = false,
} = {}) => {
return (rootNode) =>
inlineNodeContents(rootNode, {
css,
js,
images,
imports,
svgElements,
});
};
export default inline;